diff --git a/youtubejextractor/build.gradle b/youtubejextractor/build.gradle index d616c08..cefadda 100644 --- a/youtubejextractor/build.gradle +++ b/youtubejextractor/build.gradle @@ -5,8 +5,8 @@ android { defaultConfig { minSdkVersion 19 targetSdkVersion 29 - versionCode 4 - versionName "0.2.9" + versionCode 5 + versionName "0.3.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } lintOptions { diff --git a/youtubejextractor/src/androidTest/java/com/github/kotvertolet/youtubejextractor/ExtractionTests.java b/youtubejextractor/src/androidTest/java/com/github/kotvertolet/youtubejextractor/ExtractionTests.java index b22ed08..0a303ff 100644 --- a/youtubejextractor/src/androidTest/java/com/github/kotvertolet/youtubejextractor/ExtractionTests.java +++ b/youtubejextractor/src/androidTest/java/com/github/kotvertolet/youtubejextractor/ExtractionTests.java @@ -84,38 +84,73 @@ public void checkLiveStream() throws YoutubeRequestException, ExtractionExceptio checkIfStreamsWork(videoData); } + @Test + public void checkLiveStreamWithoutAdaptiveStreams() throws YoutubeRequestException, ExtractionException { + videoData = youtubeJExtractor.extract("up0fWFqgC6g"); + assertTrue(videoData.getVideoDetails().isLiveContent()); + assertNotNull(videoData.getStreamingData().getDashManifestUrl()); + assertNotNull(videoData.getStreamingData().getHlsManifestUrl()); + assertEquals(0, videoData.getStreamingData().getAdaptiveAudioStreams().size()); + assertEquals(0, videoData.getStreamingData().getAdaptiveVideoStreams().size()); + } + @Test public void checkMuxedStreamNonEncrypted() throws YoutubeRequestException, ExtractionException { videoData = youtubeJExtractor.extract("8QyDmvuts9s"); checkIfStreamsWork(videoData); } - private void checkIfStreamsWork(YoutubeVideoData videoData) throws YoutubeRequestException { - String streamErrorMask = "Stream wasn't processed correctly, stream details:\\n %s"; - Response responseBody; - if (videoData.getVideoDetails().isLiveContent()) { - responseBody = youtubeSiteNetwork.getStream(videoData.getStreamingData().getDashManifestUrl()); - assertNotNull(responseBody); - assertTrue(responseBody.isSuccessful()); - responseBody = youtubeSiteNetwork.getStream(videoData.getStreamingData().getHlsManifestUrl()); - assertNotNull(responseBody); - assertTrue(responseBody.isSuccessful()); - } else { - for (AdaptiveVideoStream adaptiveVideoStream : videoData.getStreamingData().getAdaptiveVideoStreams()) { - responseBody = youtubeSiteNetwork.getStream(adaptiveVideoStream.getUrl()); - assertThat(String.format(streamErrorMask, adaptiveVideoStream.toString()), responseBody, is(not(nullValue()))); - assertThat(String.format(streamErrorMask, adaptiveVideoStream.toString()), responseBody.isSuccessful(), is(true)); + @Test + public void checkCallbackBasedExtractionSuccessful() { + youtubeJExtractor.extract("iIKxyDRjecU", new JExtractorCallback() { + @Override + public void onSuccess(YoutubeVideoData videoData) { + checkIfStreamsWork(videoData); } - for (AdaptiveAudioStream adaptiveAudioStream : videoData.getStreamingData().getAdaptiveAudioStreams()) { - responseBody = youtubeSiteNetwork.getStream(adaptiveAudioStream.getUrl()); - assertThat(String.format(streamErrorMask, adaptiveAudioStream.toString()), responseBody, is(not(nullValue()))); - assertThat(String.format(streamErrorMask, adaptiveAudioStream.toString()), responseBody.isSuccessful(), is(true)); + + @Override + public void onNetworkException(YoutubeRequestException e) { + fail("Network exception occurred"); + } + + @Override + public void onError(Exception exception) { + fail("Extraction exception occurred"); } - for (MuxedStream muxedStream : videoData.getStreamingData().getMuxedStreams()) { - responseBody = youtubeSiteNetwork.getStream(muxedStream.getUrl()); - assertThat(String.format(streamErrorMask, muxedStream.toString()), responseBody, is(not(nullValue()))); - assertThat(String.format(streamErrorMask, muxedStream.toString()), responseBody.isSuccessful(), is(true)); + }); + } + + private void checkIfStreamsWork(YoutubeVideoData videoData) { + String streamErrorMask = "Stream wasn't processed correctly, stream details:\\n %s"; + Response responseBody; + try { + if (videoData.getVideoDetails().isLiveContent()) { + responseBody = youtubeSiteNetwork.getStream(videoData.getStreamingData().getDashManifestUrl()); + assertNotNull(responseBody); + assertTrue(responseBody.isSuccessful()); + responseBody = youtubeSiteNetwork.getStream(videoData.getStreamingData().getHlsManifestUrl()); + assertNotNull(responseBody); + assertTrue(responseBody.isSuccessful()); + } else { + for (AdaptiveVideoStream adaptiveVideoStream : videoData.getStreamingData().getAdaptiveVideoStreams()) { + responseBody = youtubeSiteNetwork.getStream(adaptiveVideoStream.getUrl()); + assertThat(String.format(streamErrorMask, adaptiveVideoStream.toString()), responseBody, is(not(nullValue()))); + assertThat(String.format(streamErrorMask, adaptiveVideoStream.toString()), responseBody.isSuccessful(), is(true)); + } + for (AdaptiveAudioStream adaptiveAudioStream : videoData.getStreamingData().getAdaptiveAudioStreams()) { + responseBody = youtubeSiteNetwork.getStream(adaptiveAudioStream.getUrl()); + assertThat(String.format(streamErrorMask, adaptiveAudioStream.toString()), responseBody, is(not(nullValue()))); + assertThat(String.format(streamErrorMask, adaptiveAudioStream.toString()), responseBody.isSuccessful(), is(true)); + } + for (MuxedStream muxedStream : videoData.getStreamingData().getMuxedStreams()) { + responseBody = youtubeSiteNetwork.getStream(muxedStream.getUrl()); + assertThat(String.format(streamErrorMask, muxedStream.toString()), responseBody, is(not(nullValue()))); + assertThat(String.format(streamErrorMask, muxedStream.toString()), responseBody.isSuccessful(), is(true)); + } } } + catch (YoutubeRequestException e) { + fail(e.getMessage()); + } } } \ No newline at end of file diff --git a/youtubejextractor/src/main/java/com/github/kotvertolet/youtubejextractor/JExtractorCallback.java b/youtubejextractor/src/main/java/com/github/kotvertolet/youtubejextractor/JExtractorCallback.java new file mode 100644 index 0000000..3e9067e --- /dev/null +++ b/youtubejextractor/src/main/java/com/github/kotvertolet/youtubejextractor/JExtractorCallback.java @@ -0,0 +1,13 @@ +package com.github.kotvertolet.youtubejextractor; + +import com.github.kotvertolet.youtubejextractor.exception.YoutubeRequestException; +import com.github.kotvertolet.youtubejextractor.models.youtube.videoData.YoutubeVideoData; + +public interface JExtractorCallback { + + void onSuccess(YoutubeVideoData videoData); + + void onNetworkException(YoutubeRequestException e); + + void onError(Exception exception); +} diff --git a/youtubejextractor/src/main/java/com/github/kotvertolet/youtubejextractor/YoutubeJExtractor.java b/youtubejextractor/src/main/java/com/github/kotvertolet/youtubejextractor/YoutubeJExtractor.java index 9a34166..1fddbc0 100644 --- a/youtubejextractor/src/main/java/com/github/kotvertolet/youtubejextractor/YoutubeJExtractor.java +++ b/youtubejextractor/src/main/java/com/github/kotvertolet/youtubejextractor/YoutubeJExtractor.java @@ -72,18 +72,39 @@ public YoutubeJExtractor(OkHttpClient client) { public YoutubeVideoData extract(String videoId) throws ExtractionException, YoutubeRequestException { try { LogI(TAG, "Extracting video data from youtube page"); - PlayerResponse playerResponse = extractYoutubeVideoData(videoId); - if (areStreamsAreEncrypted(playerResponse)) { - LogI(TAG, "Streams are encrypted, decrypting"); - decryptYoutubeStreams(playerResponse); - } - else LogI(TAG, "Streams are not encrypted"); + PlayerResponse playerResponse = extractAndPrepareVideoData(videoId); return new YoutubeVideoData(playerResponse.getVideoDetails(), playerResponse.getRawStreamingData()); } catch (SignatureDecryptionException e) { throw new ExtractionException(e); } } + public void extract(String videoId, JExtractorCallback callback) { + try { + PlayerResponse playerResponse = extractAndPrepareVideoData(videoId); + callback.onSuccess(new YoutubeVideoData(playerResponse.getVideoDetails(), playerResponse.getRawStreamingData())); + } + catch (SignatureDecryptionException | ExtractionException e) { + callback.onError(e); + } + catch (YoutubeRequestException e) { + callback.onNetworkException(e); + } + + } + + private PlayerResponse extractAndPrepareVideoData(String videoId) throws ExtractionException, YoutubeRequestException, SignatureDecryptionException { + LogI(TAG, "Extracting video data from youtube page"); + PlayerResponse playerResponse = extractYoutubeVideoData(videoId); + if (checkIfStreamsAreEncrypted(playerResponse)) { + LogI(TAG, "Streams are encrypted, decrypting"); + decryptYoutubeStreams(playerResponse); + } + else LogI(TAG, "Streams are not encrypted"); + return playerResponse; + } + + private PlayerResponse extractYoutubeVideoData(String videoId) throws ExtractionException, YoutubeRequestException { PlayerResponse playerResponse; try { @@ -150,7 +171,7 @@ private String getVideoInfoForAgeRestrictedVideo(String videoId) throws Extracti } } - private boolean areStreamsAreEncrypted(PlayerResponse playerResponse) throws ExtractionException { + private boolean checkIfStreamsAreEncrypted(PlayerResponse playerResponse) throws ExtractionException { // Even if a single stream is encrypted it means they all are RawStreamingData rawStreamingData = playerResponse.getRawStreamingData(); if (rawStreamingData != null) { diff --git a/youtubejextractor/src/main/java/com/github/kotvertolet/youtubejextractor/models/youtube/videoData/StreamingData.java b/youtubejextractor/src/main/java/com/github/kotvertolet/youtubejextractor/models/youtube/videoData/StreamingData.java index b4d42de..7c24e61 100644 --- a/youtubejextractor/src/main/java/com/github/kotvertolet/youtubejextractor/models/youtube/videoData/StreamingData.java +++ b/youtubejextractor/src/main/java/com/github/kotvertolet/youtubejextractor/models/youtube/videoData/StreamingData.java @@ -41,9 +41,9 @@ public StreamingData[] newArray(int size) { @SerializedName("probeUrl") private String probeUrl; @Expose - private List adaptiveAudioStreams; + private List adaptiveAudioStreams = new ArrayList<>(); @Expose - private List adaptiveVideoStreams; + private List adaptiveVideoStreams = new ArrayList<>(); public StreamingData() { } @@ -54,7 +54,10 @@ public StreamingData(RawStreamingData rawStreamingData) { this.hlsManifestUrl = rawStreamingData.getHlsManifestUrl(); this.probeUrl = rawStreamingData.getProbeUrl(); this.muxedStreams = rawStreamingData.getMuxedStreams(); - sortAdaptiveStreamsByType(rawStreamingData.getAdaptiveStreams()); + List adaptiveStreams = rawStreamingData.getAdaptiveStreams(); + if (adaptiveStreams != null && adaptiveStreams.size() > 0) { + sortAdaptiveStreamsByType(adaptiveStreams); + } } public StreamingData(String expiresInSeconds, String dashManifestUrl, String hlsManifestUrl, List adaptiveAudioStreams, List adaptiveVideoStreams) {