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 retry system for expired Youtube ciphers (403) #631

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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 @@ -6,8 +6,6 @@
import com.sedmelluq.discord.lavaplayer.tools.JsonBrowser;
import com.sedmelluq.discord.lavaplayer.tools.io.HttpClientTools;
import com.sedmelluq.discord.lavaplayer.tools.io.HttpInterface;
import java.io.IOException;
import java.net.URLEncoder;
import org.apache.http.NameValuePair;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
Expand All @@ -18,6 +16,9 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.URLEncoder;

import static com.sedmelluq.discord.lavaplayer.source.youtube.YoutubeTrackJsonData.fromEmbedParts;
import static com.sedmelluq.discord.lavaplayer.tools.ExceptionTools.throwWithDebugInfo;
import static com.sedmelluq.discord.lavaplayer.tools.FriendlyException.Severity.COMMON;
Expand Down Expand Up @@ -297,13 +298,25 @@ protected YoutubeTrackJsonData augmentWithPlayerScript(
}
}

protected static class CachedPlayerScript {
public final String playerScriptUrl;
public final long timestamp;
public CachedPlayerScript getCachedPlayerScript() {
return cachedPlayerScript;
}

public void clearCache() {
cachedPlayerScript = null;
}

public static class CachedPlayerScript {
private final String playerScriptUrl;
private final long timestamp;

public CachedPlayerScript(String playerScriptUrl, long timestamp) {
this.playerScriptUrl = playerScriptUrl;
this.timestamp = timestamp;
}

public String getPlayerScriptUrl() {
return playerScriptUrl;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

import com.sedmelluq.discord.lavaplayer.tools.io.HttpClientTools;
import com.sedmelluq.discord.lavaplayer.tools.io.HttpInterface;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URIBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
Expand All @@ -18,12 +25,6 @@
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URIBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Handles parsing and caching of signature ciphers
Expand Down Expand Up @@ -235,6 +236,10 @@ private YoutubeSignatureCipher extractTokensFromScript(String script, String sou
return cipherKey;
}

public void clearCache(String cipherScriptUrl) {
cipherCache.remove(cipherScriptUrl);
}

private static String extractDollarEscapedFirstGroup(Pattern pattern, String text) {
Matcher matcher = pattern.matcher(text);
return matcher.find() ? matcher.group(1).replace("$", "\\$") : null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
import com.sedmelluq.discord.lavaplayer.format.AudioDataFormat;
import com.sedmelluq.discord.lavaplayer.player.AudioConfiguration;
import com.sedmelluq.discord.lavaplayer.player.AudioPlayerOptions;
import com.sedmelluq.discord.lavaplayer.source.youtube.DefaultYoutubeTrackDetailsLoader;
import com.sedmelluq.discord.lavaplayer.source.youtube.YoutubeAudioSourceManager;
import com.sedmelluq.discord.lavaplayer.source.youtube.YoutubeSignatureCipherManager;
import com.sedmelluq.discord.lavaplayer.tools.ExceptionTools;
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
import com.sedmelluq.discord.lavaplayer.track.AudioTrackState;
Expand Down Expand Up @@ -30,6 +33,7 @@
*/
public class LocalAudioTrackExecutor implements AudioTrackExecutor {
private static final Logger log = LoggerFactory.getLogger(LocalAudioTrackExecutor.class);
private static final long RETRY_COOLDOWN = 5000L;

private final InternalAudioTrack audioTrack;
private final AudioProcessingContext processingContext;
Expand All @@ -45,6 +49,7 @@ public class LocalAudioTrackExecutor implements AudioTrackExecutor {
private long externalSeekPosition = -1;
private boolean interruptibleForSeek = false;
private volatile Throwable trackException;
private volatile long lastRetry = -1;

/**
* @param audioTrack The audio track that this executor executes
Expand Down Expand Up @@ -89,7 +94,6 @@ public AudioFrameBuffer getAudioBuffer() {

@Override
public void execute(TrackStateListener listener) {
InterruptedException interrupt = null;

if (Thread.interrupted()) {
log.debug("Cleared a stray interrupt.");
Expand All @@ -99,44 +103,78 @@ public void execute(TrackStateListener listener) {
log.debug("Starting to play track {} locally with listener {}", audioTrack.getInfo().identifier, listener);

state.set(AudioTrackState.LOADING);
attemptProcess(listener);

try {
audioTrack.process(this);
} else {
log.warn("Tried to start an already playing track {}", audioTrack.getIdentifier());
}
}

log.debug("Playing track {} finished or was stopped.", audioTrack.getIdentifier());
} catch (Throwable e) {
// Temporarily clear the interrupted status so it would not disrupt listener methods.
interrupt = findInterrupt(e);
private void attemptProcess(TrackStateListener listener) {

if (interrupt != null && checkStopped()) {
log.debug("Track {} was interrupted outside of execution loop.", audioTrack.getIdentifier());
} else {
frameBuffer.setTerminateOnEmpty();
InterruptedException interrupt = null;

FriendlyException exception = ExceptionTools.wrapUnfriendlyExceptions("Something broke when playing the track.", FAULT, e);
ExceptionTools.log(log, exception, "playback of " + audioTrack.getIdentifier());
try {
audioTrack.process(this);

trackException = exception;
listener.onTrackException(audioTrack, exception);
log.debug("Playing track {} finished or was stopped.", audioTrack.getIdentifier());
} catch (Throwable e) {

ExceptionTools.rethrowErrors(e);
}
} finally {
synchronized (actionSynchronizer) {
interrupt = interrupt != null ? interrupt : findInterrupt(null);
// Check for 403, attempt to clear cipher cache and retry if no retries in the past 5 seconds.
if (e.getMessage().contains("Not success status code: 403")
&& (lastRetry == -1 || lastRetry + RETRY_COOLDOWN <= System.currentTimeMillis())
&& audioTrack.getSourceManager() instanceof YoutubeAudioSourceManager) {

playingThread.compareAndSet(Thread.currentThread(), null);
YoutubeAudioSourceManager sourceManager = (YoutubeAudioSourceManager) audioTrack.getSourceManager();

markerTracker.trigger(ENDED);
state.set(AudioTrackState.FINISHED);
}
if (sourceManager.getTrackDetailsLoader() instanceof DefaultYoutubeTrackDetailsLoader) {
log.debug("Detected 403, clearing cipher cache and retrying.");
lastRetry = System.currentTimeMillis();

if (interrupt != null) {
Thread.currentThread().interrupt();
DefaultYoutubeTrackDetailsLoader trackDetailsLoader = (DefaultYoutubeTrackDetailsLoader) sourceManager.getTrackDetailsLoader();
DefaultYoutubeTrackDetailsLoader.CachedPlayerScript cachedScript = trackDetailsLoader.getCachedPlayerScript();

// Clear cached scripts and ciphers.
if (cachedScript != null) {
((YoutubeSignatureCipherManager) sourceManager.getSignatureResolver()).clearCache(cachedScript.getPlayerScriptUrl());
trackDetailsLoader.clearCache();
}

// Attempt to process again.
attemptProcess(listener);
return;
}
}
} else {
log.warn("Tried to start an already playing track {}", audioTrack.getIdentifier());

// Temporarily clear the interrupted status so it would not disrupt listener methods.
interrupt = findInterrupt(e);

if (interrupt != null && checkStopped()) {
log.debug("Track {} was interrupted outside of execution loop.", audioTrack.getIdentifier());
} else {
frameBuffer.setTerminateOnEmpty();

FriendlyException exception = ExceptionTools.wrapUnfriendlyExceptions("Something broke when playing the track.", FAULT, e);
ExceptionTools.log(log, exception, "playback of " + audioTrack.getIdentifier());

trackException = exception;
listener.onTrackException(audioTrack, exception);

ExceptionTools.rethrowErrors(e);
}
} finally {
synchronized (actionSynchronizer) {
interrupt = interrupt != null ? interrupt : findInterrupt(null);

playingThread.compareAndSet(Thread.currentThread(), null);

markerTracker.trigger(ENDED);
state.set(AudioTrackState.FINISHED);
}

if (interrupt != null) {
Thread.currentThread().interrupt();
}
}
}

Expand Down