private AudioPlaylist loadPlaylistWithId(String playlistId, String selectedVideoId) { log.debug("Starting to load playlist with ID {}", playlistId); try (HttpInterface httpInterface = getHttpInterface()) { try (CloseableHttpResponse response = httpInterface.execute(new HttpGet("https://www.youtube.com/playlist?list=" + playlistId))) { int statusCode = response.getStatusLine().getStatusCode(); if (statusCode != 200) { throw new IOException("Invalid status code for playlist response: " + statusCode); } Document document = Jsoup.parse(response.getEntity().getContent(), CHARSET, ""); return buildPlaylist(httpInterface, document, selectedVideoId); } } catch (Exception e) { throw ExceptionTools.wrapUnfriendlyExceptions(e); } }
@Override public void process(LocalAudioTrackExecutor localExecutor) throws Exception { try (HttpInterface httpInterface = sourceManager.getHttpInterface()) { FormatWithUrl format = loadBestFormatWithUrl(httpInterface); log.debug("Starting track from URL: {}", format.signedUrl); if (trackInfo.isStream) { processStream(localExecutor, format); } else { processStatic(localExecutor, httpInterface, format); } } }
private AudioItem loadAnonymous(String videoIds) { try (HttpInterface httpInterface = getHttpInterface()) { try (CloseableHttpResponse response = httpInterface.execute(new HttpGet("https://www.youtube.com/watch_videos?video_ids=" + videoIds))) { int statusCode = response.getStatusLine().getStatusCode(); HttpClientContext context = httpInterface.getContext(); if (statusCode != 200) { throw new IOException("Invalid status code for playlist response: " + statusCode); } // youtube currently transforms watch_video links into a link with a video id and a list id. // because thats what happens, we can simply re-process with the redirected link List<URI> redirects = context.getRedirectLocations(); if (redirects != null && !redirects.isEmpty()) { return loadNonSearch(redirects.get(0).toString()); } else { throw new FriendlyException("Unable to process youtube watch_videos link", SUSPICIOUS, new IllegalStateException("Expected youtube to redirect watch_videos link to a watch?v={id}&list={list_id} link, but it did not redirect at all")); } } } catch (Exception e) { throw ExceptionTools.wrapUnfriendlyExceptions(e); } }
/** * Loads tracks from mix in parallel into a playlist entry. * * @param mixId ID of the mix * @param selectedVideoId Selected track, {@link AudioPlaylist#getSelectedTrack()} will return this. * @return Playlist of the tracks in the mix. */ public AudioItem loadMixWithId(String mixId, String selectedVideoId) { List<String> videoIds = new ArrayList<>(); try (HttpInterface httpInterface = sourceManager.getHttpInterface()) { String mixUrl = "https://www.youtube.com/watch?v=" + selectedVideoId + "&list=" + mixId; try (CloseableHttpResponse response = httpInterface.execute(new HttpGet(mixUrl))) { int statusCode = response.getStatusLine().getStatusCode(); if (statusCode != 200) { throw new IOException("Invalid status code for mix response: " + statusCode); } Document document = Jsoup.parse(response.getEntity().getContent(), StandardCharsets.UTF_8.name(), ""); extractVideoIdsFromMix(document, videoIds); if (videoIds.isEmpty() && !document.select("#player-unavailable").isEmpty()) { return AudioReference.NO_TRACK; } } } catch (IOException e) { throw new FriendlyException("Could not read mix page.", SUSPICIOUS, e); } if (videoIds.isEmpty()) { throw new FriendlyException("Could not find tracks from mix.", SUSPICIOUS, null); } return loadTracksAsynchronously(videoIds, selectedVideoId); }
private void processStream(LocalAudioTrackExecutor localExecutor, FormatWithUrl format) throws Exception { if (MIME_AUDIO_WEBM.equals(format.details.getType().getMimeType())) { throw new FriendlyException("YouTube WebM streams are currently not supported.", COMMON, null); } else { try (HttpInterface streamingInterface = sourceManager.getHttpInterface()) { processDelegate(new YoutubeMpegStreamAudioTrack(trackInfo, streamingInterface, format.signedUrl), localExecutor); } } }
/** * Loads a single track from video ID. * * @param videoId ID of the YouTube video. * @param mustExist True if it should throw an exception on missing track, otherwise returns AudioReference.NO_TRACK. * @return Loaded YouTube track. */ public AudioItem loadTrackWithVideoId(String videoId, boolean mustExist) { try (HttpInterface httpInterface = getHttpInterface()) { JsonBrowser info = getTrackInfoFromMainPage(httpInterface, videoId, mustExist); if (info == null) { return AudioReference.NO_TRACK; } JsonBrowser args = info.get("args"); if ("fail".equals(args.get("status").text())) { throw new FriendlyException(args.get("reason").text(), COMMON, null); } boolean isStream = "1".equals(args.get("live_playback").text()); long duration = isStream ? Long.MAX_VALUE : args.get("length_seconds").as(Long.class) * 1000; return buildTrackObject(videoId, args.get("title").text(), args.get("author").text(), isStream, duration); } catch (Exception e) { throw ExceptionTools.wrapUnfriendlyExceptions("Loading information for a YouTube track failed.", FAULT, e); } }