2
2
3
3
import com .sedmelluq .discord .lavaplayer .player .AudioPlayerManager ;
4
4
import com .sedmelluq .discord .lavaplayer .source .AudioSourceManager ;
5
- import com .sedmelluq .discord .lavaplayer .tools .DataFormatTools ;
6
- import com .sedmelluq .discord .lavaplayer .tools .ExceptionTools ;
7
- import com .sedmelluq .discord .lavaplayer .tools .FriendlyException ;
8
- import com .sedmelluq .discord .lavaplayer .tools .JsonBrowser ;
5
+ import com .sedmelluq .discord .lavaplayer .tools .*;
9
6
import com .sedmelluq .discord .lavaplayer .tools .io .HttpClientTools ;
10
7
import com .sedmelluq .discord .lavaplayer .tools .io .HttpConfigurable ;
11
8
import com .sedmelluq .discord .lavaplayer .tools .io .HttpInterface ;
19
16
import org .apache .http .client .config .RequestConfig ;
20
17
import org .apache .http .client .methods .CloseableHttpResponse ;
21
18
import org .apache .http .client .methods .HttpGet ;
19
+ import org .apache .http .client .methods .HttpUriRequest ;
20
+ import org .apache .http .client .utils .URIBuilder ;
22
21
import org .apache .http .impl .client .HttpClientBuilder ;
23
22
24
23
import java .io .DataInput ;
25
24
import java .io .DataOutput ;
26
25
import java .io .IOException ;
26
+ import java .net .URISyntaxException ;
27
27
import java .nio .charset .StandardCharsets ;
28
28
import java .util .function .Consumer ;
29
29
import java .util .function .Function ;
30
+ import java .util .regex .Matcher ;
30
31
import java .util .regex .Pattern ;
31
32
32
33
import static com .sedmelluq .discord .lavaplayer .tools .FriendlyException .Severity .SUSPICIOUS ;
35
36
* Audio source manager which detects Vimeo tracks by URL.
36
37
*/
37
38
public class VimeoAudioSourceManager implements AudioSourceManager , HttpConfigurable {
38
- private static final String TRACK_URL_REGEX = "^https://vimeo.com/[0-9]+(?:\\ ?.*|)$" ;
39
+ private static final String TRACK_URL_REGEX = "^https? ://vimeo.com/( [0-9]+) (?:\\ ?.*|)$" ;
39
40
private static final Pattern trackUrlPattern = Pattern .compile (TRACK_URL_REGEX );
40
41
41
42
private final HttpInterfaceManager httpInterfaceManager ;
@@ -54,13 +55,15 @@ public String getSourceName() {
54
55
55
56
@ Override
56
57
public AudioItem loadItem (AudioPlayerManager manager , AudioReference reference ) {
57
- if (!trackUrlPattern .matcher (reference .identifier ).matches ()) {
58
+ Matcher trackUrl = trackUrlPattern .matcher (reference .identifier );
59
+
60
+ if (!trackUrl .matches ()) {
58
61
return null ;
59
62
}
60
63
61
64
try (HttpInterface httpInterface = httpInterfaceManager .getInterface ()) {
62
- return loadFromTrackPage (httpInterface , reference . identifier );
63
- } catch (IOException e ) {
65
+ return loadVideoFromApi (httpInterface , trackUrl . group ( 1 ) );
66
+ } catch (IOException | URISyntaxException e ) {
64
67
throw new FriendlyException ("Loading Vimeo track information failed." , SUSPICIOUS , e );
65
68
}
66
69
}
@@ -85,6 +88,10 @@ public void shutdown() {
85
88
ExceptionTools .closeWithWarnings (httpInterfaceManager );
86
89
}
87
90
91
+ public HttpInterfaceManager getHttpInterfaceManager () {
92
+ return httpInterfaceManager ;
93
+ }
94
+
88
95
/**
89
96
* @return Get an HTTP interface for a playing track.
90
97
*/
@@ -143,4 +150,85 @@ private AudioTrack loadTrackFromPageContent(String trackUrl, String content) thr
143
150
trackUrl
144
151
), this );
145
152
}
153
+
154
+ private AudioTrack loadVideoFromApi (HttpInterface httpInterface , String videoId ) throws IOException , URISyntaxException {
155
+ JsonBrowser videoData = getVideoFromApi (httpInterface , videoId );
156
+
157
+ AudioTrackInfo info = new AudioTrackInfo (
158
+ videoData .get ("name" ).text (),
159
+ videoData .get ("uploader" ).get ("name" ).textOrDefault ("Unknown artist" ),
160
+ Units .secondsToMillis (videoData .get ("duration" ).asLong (Units .DURATION_SEC_UNKNOWN )),
161
+ videoId ,
162
+ false ,
163
+ "https://vimeo.com/" + videoId
164
+ );
165
+
166
+ return new VimeoAudioTrack (info , this );
167
+ }
168
+
169
+ public JsonBrowser getVideoFromApi (HttpInterface httpInterface , String videoId ) throws IOException , URISyntaxException {
170
+ String jwt = getApiJwt (httpInterface );
171
+
172
+ URIBuilder builder = new URIBuilder ("https://api.vimeo.com/videos/" + videoId );
173
+ // adding `play` to the fields achieves the same thing as requesting the config_url, but with one less request.
174
+ // maybe we should consider using that instead? Need to figure out what the difference is, if any.
175
+ builder .setParameter ("fields" , "config_url,name,uploader.name,duration,pictures" );
176
+
177
+ HttpUriRequest request = new HttpGet (builder .build ());
178
+ request .setHeader ("Authorization" , "jwt " + jwt );
179
+ request .setHeader ("Accept" , "application/json" );
180
+
181
+ try (CloseableHttpResponse response = httpInterface .execute (request )) {
182
+ HttpClientTools .assertSuccessWithContent (response , "fetch video api" );
183
+ return JsonBrowser .parse (response .getEntity ().getContent ());
184
+ }
185
+ }
186
+
187
+ public PlaybackFormat getPlaybackFormat (HttpInterface httpInterface , String configUrl ) throws IOException {
188
+ try (CloseableHttpResponse response = httpInterface .execute (new HttpGet (configUrl ))) {
189
+ HttpClientTools .assertSuccessWithContent (response , "fetch playback formats" );
190
+
191
+ JsonBrowser json = JsonBrowser .parse (response .getEntity ().getContent ());
192
+
193
+ // {"dash", "hls", "progressive"}
194
+ // N.B. opus is referenced in some of the URLs, but I don't see any formats offering opus audio codec.
195
+ // Might be a gradual rollout so this may need revisiting.
196
+ JsonBrowser files = json .get ("request" ).get ("files" );
197
+
198
+ if (!files .get ("progressive" ).isNull ()) {
199
+ JsonBrowser progressive = files .get ("progressive" ).index (0 );
200
+
201
+ if (!progressive .isNull ()) {
202
+ return new PlaybackFormat (progressive .get ("url" ).text (), false );
203
+ }
204
+ }
205
+
206
+ if (!files .get ("hls" ).isNull ()) {
207
+ JsonBrowser hls = files .get ("hls" );
208
+ // ["akfire_interconnect_quic", "fastly_skyfire"]
209
+ JsonBrowser cdns = hls .get ("cdns" );
210
+ return new PlaybackFormat (cdns .get (hls .get ("default_cdn" ).text ()).get ("url" ).text (), true );
211
+ }
212
+
213
+ throw new RuntimeException ("No supported formats" );
214
+ }
215
+ }
216
+
217
+ private String getApiJwt (HttpInterface httpInterface ) throws IOException {
218
+ try (CloseableHttpResponse response = httpInterface .execute (new HttpGet ("https://vimeo.com/_next/viewer" ))) {
219
+ HttpClientTools .assertSuccessWithContent (response , "fetch jwt" );
220
+ JsonBrowser json = JsonBrowser .parse (response .getEntity ().getContent ());
221
+ return json .get ("jwt" ).text ();
222
+ }
223
+ }
224
+
225
+ public static class PlaybackFormat {
226
+ public final String url ;
227
+ public final boolean isHls ;
228
+
229
+ public PlaybackFormat (String url , boolean isHls ) {
230
+ this .url = url ;
231
+ this .isHls = isHls ;
232
+ }
233
+ }
146
234
}
0 commit comments