Skip to content

Commit

Permalink
Add support for Vimeo over HLS (#63)
Browse files Browse the repository at this point in the history
* Add support for Vimeo HLS

* Simplify Vimeo HLS url resolution
  • Loading branch information
freyacodes authored Jan 2, 2024
1 parent c363f95 commit cb2b849
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ public HttpInterface getHttpInterface() {
return httpInterfaceManager.getInterface();
}

HttpInterfaceManager getHttpInterfaceManager() {
return httpInterfaceManager;
}

@Override
public void configureRequests(Function<RequestConfig, RequestConfig> configurator) {
httpInterfaceManager.configureRequests(configurator);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.sedmelluq.discord.lavaplayer.source.vimeo;

import com.sedmelluq.discord.lavaplayer.container.mpeg.MpegAudioTrack;
import com.sedmelluq.discord.lavaplayer.container.playlists.ExtendedM3uParser;
import com.sedmelluq.discord.lavaplayer.container.playlists.HlsStreamTrack;
import com.sedmelluq.discord.lavaplayer.source.AudioSourceManager;
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
import com.sedmelluq.discord.lavaplayer.tools.JsonBrowser;
Expand All @@ -20,6 +22,7 @@
import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.Objects;

import static com.sedmelluq.discord.lavaplayer.tools.FriendlyException.Severity.SUSPICIOUS;

Expand All @@ -44,26 +47,53 @@ public VimeoAudioTrack(AudioTrackInfo trackInfo, VimeoAudioSourceManager sourceM
@Override
public void process(LocalAudioTrackExecutor localExecutor) throws Exception {
try (HttpInterface httpInterface = sourceManager.getHttpInterface()) {
String playbackUrl = loadPlaybackUrl(httpInterface);

log.debug("Starting Vimeo track from URL: {}", playbackUrl);

try (PersistentHttpStream stream = new PersistentHttpStream(httpInterface, new URI(playbackUrl), null)) {
processDelegate(new MpegAudioTrack(trackInfo, stream), localExecutor);
PlaybackSource playbackSource = getPlaybackSource(httpInterface);

log.debug("Starting Vimeo track. HLS: {}, URL: {}", playbackSource.isHls, playbackSource.url);

if (playbackSource.isHls) {
processDelegate(new HlsStreamTrack(
trackInfo,
extractHlsAudioPlaylistUrl(httpInterface, playbackSource.url),
sourceManager.getHttpInterfaceManager(),
true
), localExecutor);
} else {
try (PersistentHttpStream stream = new PersistentHttpStream(httpInterface, new URI(playbackSource.url), null)) {
processDelegate(new MpegAudioTrack(trackInfo, stream), localExecutor);
}
}
}
}

private String loadPlaybackUrl(HttpInterface httpInterface) throws IOException {
private PlaybackSource getPlaybackSource(HttpInterface httpInterface) throws IOException {
JsonBrowser config = loadPlayerConfig(httpInterface);
if (config == null) {
throw new FriendlyException("Track information not present on the page.", SUSPICIOUS, null);
}

String trackConfigUrl = config.get("player").get("config_url").text();
JsonBrowser trackConfig = loadTrackConfig(httpInterface, trackConfigUrl);
JsonBrowser files = trackConfig.get("request").get("files");

if (!files.get("progressive").values().isEmpty()) {
String url = files.get("progressive").index(0).get("url").text();
return new PlaybackSource(url, false);
} else {
JsonBrowser hls = files.get("hls");
String defaultCdn = hls.get("default_cdn").text();
return new PlaybackSource(hls.get("cdns").get(defaultCdn).get("url").text(), true);
}
}

private static class PlaybackSource {
public String url;
public boolean isHls;

return trackConfig.get("request").get("files").get("progressive").index(0).get("url").text();
public PlaybackSource(String url, boolean isHls) {
this.url = url;
this.isHls = isHls;
}
}

private JsonBrowser loadPlayerConfig(HttpInterface httpInterface) throws IOException {
Expand Down Expand Up @@ -92,6 +122,43 @@ private JsonBrowser loadTrackConfig(HttpInterface httpInterface, String trackAcc
}
}

protected String resolveRelativeUrl(String baseUrl, String url) {
while (url.startsWith("../")) {
url = url.substring(3);
baseUrl = baseUrl.substring(0, baseUrl.lastIndexOf('/'));
}

return baseUrl + ((url.startsWith("/")) ? url : "/" + url);
}

/** Vimeo HLS uses separate audio and video. This extracts the audio playlist URL from EXT-X-MEDIA */
private String extractHlsAudioPlaylistUrl(HttpInterface httpInterface, String videoPlaylistUrl) throws IOException {
String url = null;
try (CloseableHttpResponse response = httpInterface.execute(new HttpGet(videoPlaylistUrl))) {
int statusCode = response.getStatusLine().getStatusCode();

if (!HttpClientTools.isSuccessWithContent(statusCode)) {
throw new FriendlyException("Server responded with an error.", SUSPICIOUS,
new IllegalStateException("Response code for track access info is " + statusCode));
}

String bodyString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
for (String rawLine : bodyString.split("\n")) {
ExtendedM3uParser.Line line = ExtendedM3uParser.parseLine(rawLine);
if (Objects.equals(line.directiveName, "EXT-X-MEDIA")
&& Objects.equals(line.directiveArguments.get("TYPE"), "AUDIO")) {
url = line.directiveArguments.get("URI");
break;
}
}
}

if (url == null) throw new FriendlyException("Failed to find audio playlist URL.", SUSPICIOUS,
new IllegalStateException("Valid audio directive was not found"));

return resolveRelativeUrl(videoPlaylistUrl.substring(0, videoPlaylistUrl.lastIndexOf('/')), url);
}

@Override
protected AudioTrack makeShallowClone() {
return new VimeoAudioTrack(trackInfo, sourceManager);
Expand Down

0 comments on commit cb2b849

Please sign in to comment.