Skip to content

Commit cb2b849

Browse files
authored
Add support for Vimeo over HLS (#63)
* Add support for Vimeo HLS * Simplify Vimeo HLS url resolution
1 parent c363f95 commit cb2b849

File tree

2 files changed

+79
-8
lines changed

2 files changed

+79
-8
lines changed

main/src/main/java/com/sedmelluq/discord/lavaplayer/source/vimeo/VimeoAudioSourceManager.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@ public HttpInterface getHttpInterface() {
9292
return httpInterfaceManager.getInterface();
9393
}
9494

95+
HttpInterfaceManager getHttpInterfaceManager() {
96+
return httpInterfaceManager;
97+
}
98+
9599
@Override
96100
public void configureRequests(Function<RequestConfig, RequestConfig> configurator) {
97101
httpInterfaceManager.configureRequests(configurator);

main/src/main/java/com/sedmelluq/discord/lavaplayer/source/vimeo/VimeoAudioTrack.java

Lines changed: 75 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.sedmelluq.discord.lavaplayer.source.vimeo;
22

33
import 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;
46
import com.sedmelluq.discord.lavaplayer.source.AudioSourceManager;
57
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
68
import com.sedmelluq.discord.lavaplayer.tools.JsonBrowser;
@@ -20,6 +22,7 @@
2022
import java.io.IOException;
2123
import java.net.URI;
2224
import java.nio.charset.StandardCharsets;
25+
import java.util.Objects;
2326

2427
import 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

Comments
 (0)