Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Vimeo over HLS #63

Merged
merged 2 commits into from
Jan 2, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
}
}

return trackConfig.get("request").get("files").get("progressive").index(0).get("url").text();
private static class PlaybackSource {
public String url;
public boolean isHls;

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,48 @@ private JsonBrowser loadTrackConfig(HttpInterface httpInterface, String trackAcc
}
}

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

for (int i = 0; i < upTraversals; i++) {
baseUrl = baseUrl.substring(0, baseUrl.lastIndexOf('/'));
freyacodes marked this conversation as resolved.
Show resolved Hide resolved
}

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