11package com .sedmelluq .discord .lavaplayer .source .vimeo ;
22
33import com .sedmelluq .discord .lavaplayer .container .mpeg .MpegAudioTrack ;
4+ import com .sedmelluq .discord .lavaplayer .container .playlists .ExtendedM3uParser ;
5+ import com .sedmelluq .discord .lavaplayer .container .playlists .HlsStreamTrack ;
46import com .sedmelluq .discord .lavaplayer .source .AudioSourceManager ;
57import com .sedmelluq .discord .lavaplayer .tools .FriendlyException ;
68import com .sedmelluq .discord .lavaplayer .tools .JsonBrowser ;
2022import java .io .IOException ;
2123import java .net .URI ;
2224import java .nio .charset .StandardCharsets ;
25+ import java .util .Objects ;
2326
2427import static com .sedmelluq .discord .lavaplayer .tools .FriendlyException .Severity .SUSPICIOUS ;
2528
@@ -44,26 +47,53 @@ public VimeoAudioTrack(AudioTrackInfo trackInfo, VimeoAudioSourceManager sourceM
4447 @ Override
4548 public void process (LocalAudioTrackExecutor localExecutor ) throws Exception {
4649 try (HttpInterface httpInterface = sourceManager .getHttpInterface ()) {
47- String playbackUrl = loadPlaybackUrl (httpInterface );
48-
49- log .debug ("Starting Vimeo track from URL: {}" , playbackUrl );
50-
51- try (PersistentHttpStream stream = new PersistentHttpStream (httpInterface , new URI (playbackUrl ), null )) {
52- processDelegate (new MpegAudioTrack (trackInfo , stream ), localExecutor );
50+ PlaybackSource playbackSource = getPlaybackSource (httpInterface );
51+
52+ log .debug ("Starting Vimeo track. HLS: {}, URL: {}" , playbackSource .isHls , playbackSource .url );
53+
54+ if (playbackSource .isHls ) {
55+ processDelegate (new HlsStreamTrack (
56+ trackInfo ,
57+ extractHlsAudioPlaylistUrl (httpInterface , playbackSource .url ),
58+ sourceManager .getHttpInterfaceManager (),
59+ true
60+ ), localExecutor );
61+ } else {
62+ try (PersistentHttpStream stream = new PersistentHttpStream (httpInterface , new URI (playbackSource .url ), null )) {
63+ processDelegate (new MpegAudioTrack (trackInfo , stream ), localExecutor );
64+ }
5365 }
5466 }
5567 }
5668
57- private String loadPlaybackUrl (HttpInterface httpInterface ) throws IOException {
69+ private PlaybackSource getPlaybackSource (HttpInterface httpInterface ) throws IOException {
5870 JsonBrowser config = loadPlayerConfig (httpInterface );
5971 if (config == null ) {
6072 throw new FriendlyException ("Track information not present on the page." , SUSPICIOUS , null );
6173 }
6274
6375 String trackConfigUrl = config .get ("player" ).get ("config_url" ).text ();
6476 JsonBrowser trackConfig = loadTrackConfig (httpInterface , trackConfigUrl );
77+ JsonBrowser files = trackConfig .get ("request" ).get ("files" );
78+
79+ if (!files .get ("progressive" ).values ().isEmpty ()) {
80+ String url = files .get ("progressive" ).index (0 ).get ("url" ).text ();
81+ return new PlaybackSource (url , false );
82+ } else {
83+ JsonBrowser hls = files .get ("hls" );
84+ String defaultCdn = hls .get ("default_cdn" ).text ();
85+ return new PlaybackSource (hls .get ("cdns" ).get (defaultCdn ).get ("url" ).text (), true );
86+ }
87+ }
88+
89+ private static class PlaybackSource {
90+ public String url ;
91+ public boolean isHls ;
6592
66- return trackConfig .get ("request" ).get ("files" ).get ("progressive" ).index (0 ).get ("url" ).text ();
93+ public PlaybackSource (String url , boolean isHls ) {
94+ this .url = url ;
95+ this .isHls = isHls ;
96+ }
6797 }
6898
6999 private JsonBrowser loadPlayerConfig (HttpInterface httpInterface ) throws IOException {
@@ -92,6 +122,43 @@ private JsonBrowser loadTrackConfig(HttpInterface httpInterface, String trackAcc
92122 }
93123 }
94124
125+ protected String resolveRelativeUrl (String baseUrl , String url ) {
126+ while (url .startsWith ("../" )) {
127+ url = url .substring (3 );
128+ baseUrl = baseUrl .substring (0 , baseUrl .lastIndexOf ('/' ));
129+ }
130+
131+ return baseUrl + ((url .startsWith ("/" )) ? url : "/" + url );
132+ }
133+
134+ /** Vimeo HLS uses separate audio and video. This extracts the audio playlist URL from EXT-X-MEDIA */
135+ private String extractHlsAudioPlaylistUrl (HttpInterface httpInterface , String videoPlaylistUrl ) throws IOException {
136+ String url = null ;
137+ try (CloseableHttpResponse response = httpInterface .execute (new HttpGet (videoPlaylistUrl ))) {
138+ int statusCode = response .getStatusLine ().getStatusCode ();
139+
140+ if (!HttpClientTools .isSuccessWithContent (statusCode )) {
141+ throw new FriendlyException ("Server responded with an error." , SUSPICIOUS ,
142+ new IllegalStateException ("Response code for track access info is " + statusCode ));
143+ }
144+
145+ String bodyString = IOUtils .toString (response .getEntity ().getContent (), StandardCharsets .UTF_8 );
146+ for (String rawLine : bodyString .split ("\n " )) {
147+ ExtendedM3uParser .Line line = ExtendedM3uParser .parseLine (rawLine );
148+ if (Objects .equals (line .directiveName , "EXT-X-MEDIA" )
149+ && Objects .equals (line .directiveArguments .get ("TYPE" ), "AUDIO" )) {
150+ url = line .directiveArguments .get ("URI" );
151+ break ;
152+ }
153+ }
154+ }
155+
156+ if (url == null ) throw new FriendlyException ("Failed to find audio playlist URL." , SUSPICIOUS ,
157+ new IllegalStateException ("Valid audio directive was not found" ));
158+
159+ return resolveRelativeUrl (videoPlaylistUrl .substring (0 , videoPlaylistUrl .lastIndexOf ('/' )), url );
160+ }
161+
95162 @ Override
96163 protected AudioTrack makeShallowClone () {
97164 return new VimeoAudioTrack (trackInfo , sourceManager );
0 commit comments