From d1e49f20744d2beab7b41bc110e1e53a8de0a73f Mon Sep 17 00:00:00 2001 From: Zsolt Matyas Date: Wed, 7 Nov 2018 14:07:09 -0800 Subject: [PATCH 01/10] CEA608: PAINT-ON Mode must keep the last shown captions on the screen [Problem] PAINT-ON mode is not implemented. From the compliance tests: * RDC command has no effect except to select paint-on style. * Next data are written directly to the display upon receipt. * If other captioning is already on the screen, the four-row limit is still in effect. [Solution] It is a rare use case, we do not support overriding characters in existing cueBuilders as PAINT-ON would require. But several compliance tests check if the screen is cleared when the mode switch happens. We must keep the old captions when switching to PAINT-ON mode [Test] - Live Over-the-Air content, beginning of commercials often uses PAINT-ON mode --- .../exoplayer2/text/cea/Cea608Decoder.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java index 8742a6344e5..e93a53b7131 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java @@ -559,8 +559,19 @@ private void setCaptionMode(int captionMode) { int oldCaptionMode = this.captionMode; this.captionMode = captionMode; - // Clear the working memory. + // Clear the cues and cueBuilders except for Paint-on mode. Paint-on mode may modify characters + // already on the screen. This feature is not fully supported, but we need to keep the previous + // screen content shown + if (captionMode == CC_MODE_PAINT_ON) { + // update the Mode member of all existing cueBuilders even if we are mid-row + for (CueBuilder builder : cueBuilders) { + builder.setCaptionMode(captionMode); + } + return; + } + resetCueBuilders(); + if (oldCaptionMode == CC_MODE_PAINT_ON || captionMode == CC_MODE_ROLL_UP || captionMode == CC_MODE_UNKNOWN) { // When switching from paint-on or to roll-up or unknown, we also need to clear the caption. @@ -653,6 +664,10 @@ public CueBuilder(int captionMode, int captionRowCount) { setCaptionRowCount(captionRowCount); } + public void setCaptionMode(int captionMode) { + this.captionMode = captionMode; + } + public void reset(int captionMode) { this.captionMode = captionMode; cueStyles.clear(); From 8a359bb1fb6d63f80a98e497f2c778eee568a8d7 Mon Sep 17 00:00:00 2001 From: GiuseppePiscopo Date: Mon, 3 Dec 2018 15:17:36 +0100 Subject: [PATCH 02/10] feat(MediaSource): client code can get the tag of a MediaSource --- .../exoplayer2/ext/ima/ImaAdsMediaSource.java | 6 ++++++ .../exoplayer2/source/ClippingMediaSource.java | 6 ++++++ .../exoplayer2/source/ConcatenatingMediaSource.java | 12 ++++++++++++ .../exoplayer2/source/ExtractorMediaSource.java | 6 ++++++ .../exoplayer2/source/LoopingMediaSource.java | 6 ++++++ .../android/exoplayer2/source/MediaSource.java | 5 +++++ .../exoplayer2/source/MergingMediaSource.java | 6 ++++++ .../exoplayer2/source/SingleSampleMediaSource.java | 8 ++++++++ .../exoplayer2/source/ads/AdsMediaSource.java | 6 ++++++ .../exoplayer2/source/dash/DashMediaSource.java | 6 ++++++ .../exoplayer2/source/hls/HlsMediaSource.java | 6 ++++++ .../source/smoothstreaming/SsMediaSource.java | 6 ++++++ .../android/exoplayer2/testutil/FakeMediaSource.java | 8 ++++++++ 13 files changed, 87 insertions(+) diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsMediaSource.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsMediaSource.java index 400061d0190..d9c13e07b97 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsMediaSource.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsMediaSource.java @@ -100,6 +100,12 @@ public void releasePeriod(MediaPeriod mediaPeriod) { adsMediaSource.releasePeriod(mediaPeriod); } + @Override + @Nullable + public Object getTag() { + return adsMediaSource.getTag(); + } + @Override public void releaseSourceInternal() { adsMediaSource.releaseSource(/* listener= */ this); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java index 3916d41b61f..90047f97414 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java @@ -224,6 +224,12 @@ public void releasePeriod(MediaPeriod mediaPeriod) { } } + @Override + @Nullable + public Object getTag() { + return mediaSource.getTag(); + } + @Override public void releaseSourceInternal() { super.releaseSourceInternal(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java index 1f3b01182a4..f6fd497f3da 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java @@ -508,6 +508,12 @@ public final void releasePeriod(MediaPeriod mediaPeriod) { maybeReleaseChildSource(holder); } + @Override + @Nullable + public Object getTag() { + return null; + } + @Override public final void releaseSourceInternal() { super.releaseSourceInternal(); @@ -1069,6 +1075,12 @@ protected void prepareSourceInternal( // Do nothing. } + @Override + @Nullable + public Object getTag() { + return null; + } + @Override protected void releaseSourceInternal() { // Do nothing. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java index 66af3a7e62b..c669aaa5749 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java @@ -395,6 +395,12 @@ public void releasePeriod(MediaPeriod mediaPeriod) { ((ExtractorMediaPeriod) mediaPeriod).release(); } + @Override + @Nullable + public Object getTag() { + return tag; + } + @Override public void releaseSourceInternal() { // Do nothing. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java index 78a1e75d90f..fd012277a7d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java @@ -95,6 +95,12 @@ public void releasePeriod(MediaPeriod mediaPeriod) { } } + @Override + @Nullable + public Object getTag() { + return childSource.getTag(); + } + @Override protected void onChildSourceInfoRefreshed( Void id, MediaSource mediaSource, Timeline timeline, @Nullable Object manifest) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java index d8335131f96..8a7f8618931 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java @@ -275,6 +275,11 @@ void prepareSource( */ void releasePeriod(MediaPeriod mediaPeriod); + /** + * Returns the tag set on media source, or null when none was set. + */ + Object getTag(); + /** * Removes a listener for timeline and/or manifest updates and releases the source if no longer * required. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java index ecb4b10c6ac..bfe0800d35f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java @@ -137,6 +137,12 @@ public void releasePeriod(MediaPeriod mediaPeriod) { } } + @Override + @Nullable + public Object getTag() { + return mediaSources.length > 0 ? mediaSources[0].getTag() : null; + } + @Override public void releaseSourceInternal() { super.releaseSourceInternal(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java index 1ac62074540..57a0440b06a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java @@ -185,6 +185,7 @@ public SingleSampleMediaSource createMediaSource( private final LoadErrorHandlingPolicy loadErrorHandlingPolicy; private final boolean treatLoadErrorsAsEndOfStream; private final Timeline timeline; + private final @Nullable Object tag; private @Nullable TransferListener transferListener; @@ -287,6 +288,7 @@ private SingleSampleMediaSource( this.durationUs = durationUs; this.loadErrorHandlingPolicy = loadErrorHandlingPolicy; this.treatLoadErrorsAsEndOfStream = treatLoadErrorsAsEndOfStream; + this.tag = tag; dataSpec = new DataSpec(uri, DataSpec.FLAG_ALLOW_GZIP | DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH); timeline = @@ -327,6 +329,12 @@ public void releasePeriod(MediaPeriod mediaPeriod) { ((SingleSampleMediaPeriod) mediaPeriod).release(); } + @Override + @Nullable + public Object getTag() { + return tag; + } + @Override public void releaseSourceInternal() { // Do nothing. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java index 7fc0f22bf39..0944cc2ff23 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java @@ -387,6 +387,12 @@ public void releasePeriod(MediaPeriod mediaPeriod) { deferredMediaPeriod.releasePeriod(); } + @Override + @Nullable + public Object getTag() { + return contentMediaSource.getTag(); + } + @Override public void releaseSourceInternal() { super.releaseSourceInternal(); diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java index b04fcf7247a..9068f62d37e 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java @@ -658,6 +658,12 @@ public void releasePeriod(MediaPeriod mediaPeriod) { periodsById.remove(dashMediaPeriod.id); } + @Override + @Nullable + public Object getTag() { + return tag; + } + @Override public void releaseSourceInternal() { manifestLoadPending = false; diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java index a075dacf3ae..1af68dc2ec4 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java @@ -425,6 +425,12 @@ public void releasePeriod(MediaPeriod mediaPeriod) { ((HlsMediaPeriod) mediaPeriod).release(); } + @Override + @Nullable + public Object getTag() { + return tag; + } + @Override public void releaseSourceInternal() { playlistTracker.stop(); diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java index a756b7f4f1b..9b2001bb480 100644 --- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java +++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java @@ -549,6 +549,12 @@ public void releasePeriod(MediaPeriod period) { mediaPeriods.remove(period); } + @Override + @Nullable + public Object getTag() { + return tag; + } + @Override public void releaseSourceInternal() { manifest = sideloadedManifest ? manifest : null; diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSource.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSource.java index 2fca4f42c72..f00274dbf8b 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSource.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSource.java @@ -133,6 +133,14 @@ public void releasePeriod(MediaPeriod mediaPeriod) { fakeMediaPeriod.release(); } + @Override + @Nullable + public Object getTag() { + boolean hasTimeline = timeline != null && !timeline.isEmpty(); + + return hasTimeline ? timeline.getWindow(0, new Timeline.Window()).tag : null; + } + @Override public void releaseSourceInternal() { assertThat(preparedSource).isTrue(); From b278b02816c9b188eb90fc97d00c05557848bb5b Mon Sep 17 00:00:00 2001 From: GiuseppePiscopo Date: Mon, 3 Dec 2018 18:21:37 +0100 Subject: [PATCH 03/10] chore(MediaSource): move getTag after removeEventListener --- .../exoplayer2/ext/ima/ImaAdsMediaSource.java | 12 ++++++------ .../exoplayer2/source/ClippingMediaSource.java | 12 ++++++------ .../source/ConcatenatingMediaSource.java | 12 ++++++------ .../exoplayer2/source/ExtractorMediaSource.java | 12 ++++++------ .../exoplayer2/source/LoopingMediaSource.java | 12 ++++++------ .../android/exoplayer2/source/MediaSource.java | 10 +++++----- .../exoplayer2/source/MergingMediaSource.java | 12 ++++++------ .../source/SingleSampleMediaSource.java | 12 ++++++------ .../exoplayer2/source/ads/AdsMediaSource.java | 12 ++++++------ .../exoplayer2/source/dash/DashMediaSource.java | 12 ++++++------ .../exoplayer2/source/hls/HlsMediaSource.java | 12 ++++++------ .../source/smoothstreaming/SsMediaSource.java | 12 ++++++------ .../exoplayer2/testutil/FakeMediaSource.java | 16 ++++++++-------- 13 files changed, 79 insertions(+), 79 deletions(-) diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsMediaSource.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsMediaSource.java index d9c13e07b97..85042c43549 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsMediaSource.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsMediaSource.java @@ -76,6 +76,12 @@ public ImaAdsMediaSource( adUiViewGroup, eventHandler, eventListener); } + @Override + @Nullable + public Object getTag() { + return adsMediaSource.getTag(); + } + @Override public void prepareSourceInternal( final ExoPlayer player, @@ -100,12 +106,6 @@ public void releasePeriod(MediaPeriod mediaPeriod) { adsMediaSource.releasePeriod(mediaPeriod); } - @Override - @Nullable - public Object getTag() { - return adsMediaSource.getTag(); - } - @Override public void releaseSourceInternal() { adsMediaSource.releaseSource(/* listener= */ this); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java index 90047f97414..1dbb41dfb08 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java @@ -186,6 +186,12 @@ public ClippingMediaSource( window = new Timeline.Window(); } + @Override + @Nullable + public Object getTag() { + return mediaSource.getTag(); + } + @Override public void prepareSourceInternal( ExoPlayer player, @@ -224,12 +230,6 @@ public void releasePeriod(MediaPeriod mediaPeriod) { } } - @Override - @Nullable - public Object getTag() { - return mediaSource.getTag(); - } - @Override public void releaseSourceInternal() { super.releaseSourceInternal(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java index f6fd497f3da..26667e641f7 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java @@ -453,6 +453,12 @@ public final synchronized void setShuffleOrder( } } + @Override + @Nullable + public Object getTag() { + return null; + } + @Override public final synchronized void prepareSourceInternal( ExoPlayer player, @@ -508,12 +514,6 @@ public final void releasePeriod(MediaPeriod mediaPeriod) { maybeReleaseChildSource(holder); } - @Override - @Nullable - public Object getTag() { - return null; - } - @Override public final void releaseSourceInternal() { super.releaseSourceInternal(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java index c669aaa5749..085b5dba717 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java @@ -358,6 +358,12 @@ private ExtractorMediaSource( this.tag = tag; } + @Override + @Nullable + public Object getTag() { + return tag; + } + @Override public void prepareSourceInternal( ExoPlayer player, @@ -395,12 +401,6 @@ public void releasePeriod(MediaPeriod mediaPeriod) { ((ExtractorMediaPeriod) mediaPeriod).release(); } - @Override - @Nullable - public Object getTag() { - return tag; - } - @Override public void releaseSourceInternal() { // Do nothing. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java index fd012277a7d..cac15d5ed93 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java @@ -64,6 +64,12 @@ public LoopingMediaSource(MediaSource childSource, int loopCount) { mediaPeriodToChildMediaPeriodId = new HashMap<>(); } + @Override + @Nullable + public Object getTag() { + return childSource.getTag(); + } + @Override public void prepareSourceInternal( ExoPlayer player, @@ -95,12 +101,6 @@ public void releasePeriod(MediaPeriod mediaPeriod) { } } - @Override - @Nullable - public Object getTag() { - return childSource.getTag(); - } - @Override protected void onChildSourceInfoRefreshed( Void id, MediaSource mediaSource, Timeline timeline, @Nullable Object manifest) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java index 8a7f8618931..acd5dd3e2eb 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java @@ -219,6 +219,11 @@ public int hashCode() { */ void removeEventListener(MediaSourceEventListener eventListener); + /** + * Returns the tag set on the media source, or null when none was set. + */ + @Nullable Object getTag(); + /** * Starts source preparation if not yet started, and adds a listener for timeline and/or manifest * updates. @@ -275,11 +280,6 @@ void prepareSource( */ void releasePeriod(MediaPeriod mediaPeriod); - /** - * Returns the tag set on media source, or null when none was set. - */ - Object getTag(); - /** * Removes a listener for timeline and/or manifest updates and releases the source if no longer * required. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java index bfe0800d35f..573e97cb13c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java @@ -98,6 +98,12 @@ public MergingMediaSource(CompositeSequenceableLoaderFactory compositeSequenceab timelines = new Timeline[mediaSources.length]; } + @Override + @Nullable + public Object getTag() { + return mediaSources.length > 0 ? mediaSources[0].getTag() : null; + } + @Override public void prepareSourceInternal( ExoPlayer player, @@ -137,12 +143,6 @@ public void releasePeriod(MediaPeriod mediaPeriod) { } } - @Override - @Nullable - public Object getTag() { - return mediaSources.length > 0 ? mediaSources[0].getTag() : null; - } - @Override public void releaseSourceInternal() { super.releaseSourceInternal(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java index 57a0440b06a..a8f0c0b6783 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java @@ -297,6 +297,12 @@ private SingleSampleMediaSource( // MediaSource implementation. + @Override + @Nullable + public Object getTag() { + return tag; + } + @Override public void prepareSourceInternal( ExoPlayer player, @@ -329,12 +335,6 @@ public void releasePeriod(MediaPeriod mediaPeriod) { ((SingleSampleMediaPeriod) mediaPeriod).release(); } - @Override - @Nullable - public Object getTag() { - return tag; - } - @Override public void releaseSourceInternal() { // Do nothing. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java index 0944cc2ff23..19ddbd2c542 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java @@ -319,6 +319,12 @@ public AdsMediaSource( adsLoader.setSupportedContentTypes(adMediaSourceFactory.getSupportedTypes()); } + @Override + @Nullable + public Object getTag() { + return contentMediaSource.getTag(); + } + @Override public void prepareSourceInternal( final ExoPlayer player, @@ -387,12 +393,6 @@ public void releasePeriod(MediaPeriod mediaPeriod) { deferredMediaPeriod.releasePeriod(); } - @Override - @Nullable - public Object getTag() { - return contentMediaSource.getTag(); - } - @Override public void releaseSourceInternal() { super.releaseSourceInternal(); diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java index 9068f62d37e..1f08a437311 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java @@ -607,6 +607,12 @@ public void replaceManifestUri(Uri manifestUri) { // MediaSource implementation. + @Override + @Nullable + public Object getTag() { + return tag; + } + @Override public void prepareSourceInternal( ExoPlayer player, @@ -658,12 +664,6 @@ public void releasePeriod(MediaPeriod mediaPeriod) { periodsById.remove(dashMediaPeriod.id); } - @Override - @Nullable - public Object getTag() { - return tag; - } - @Override public void releaseSourceInternal() { manifestLoadPending = false; diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java index 1af68dc2ec4..a9b0c579ac6 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java @@ -390,6 +390,12 @@ private HlsMediaSource( this.tag = tag; } + @Override + @Nullable + public Object getTag() { + return tag; + } + @Override public void prepareSourceInternal( ExoPlayer player, @@ -425,12 +431,6 @@ public void releasePeriod(MediaPeriod mediaPeriod) { ((HlsMediaPeriod) mediaPeriod).release(); } - @Override - @Nullable - public Object getTag() { - return tag; - } - @Override public void releaseSourceInternal() { playlistTracker.stop(); diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java index 9b2001bb480..103a52a55a0 100644 --- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java +++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java @@ -503,6 +503,12 @@ private SsMediaSource( // MediaSource implementation. + @Override + @Nullable + public Object getTag() { + return tag; + } + @Override public void prepareSourceInternal( ExoPlayer player, @@ -549,12 +555,6 @@ public void releasePeriod(MediaPeriod period) { mediaPeriods.remove(period); } - @Override - @Nullable - public Object getTag() { - return tag; - } - @Override public void releaseSourceInternal() { manifest = sideloadedManifest ? manifest : null; diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSource.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSource.java index f00274dbf8b..7291b83ff60 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSource.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSource.java @@ -88,6 +88,14 @@ public FakeMediaSource(@Nullable Timeline timeline, Object manifest, this.trackGroupArray = trackGroupArray; } + @Override + @Nullable + public Object getTag() { + boolean hasTimeline = timeline != null && !timeline.isEmpty(); + + return hasTimeline ? timeline.getWindow(0, new Timeline.Window()).tag : null; + } + @Override public synchronized void prepareSourceInternal( ExoPlayer player, @@ -133,14 +141,6 @@ public void releasePeriod(MediaPeriod mediaPeriod) { fakeMediaPeriod.release(); } - @Override - @Nullable - public Object getTag() { - boolean hasTimeline = timeline != null && !timeline.isEmpty(); - - return hasTimeline ? timeline.getWindow(0, new Timeline.Window()).tag : null; - } - @Override public void releaseSourceInternal() { assertThat(preparedSource).isTrue(); From a11a8716ef0560fcd2d714a5d74978d036c99694 Mon Sep 17 00:00:00 2001 From: GiuseppePiscopo Date: Mon, 3 Dec 2018 18:24:38 +0100 Subject: [PATCH 04/10] feat(MediaSource): provide getTag default implementation --- .../com/google/android/exoplayer2/source/MediaSource.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java index acd5dd3e2eb..a96893ea26d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java @@ -222,7 +222,9 @@ public int hashCode() { /** * Returns the tag set on the media source, or null when none was set. */ - @Nullable Object getTag(); + @Nullable default Object getTag() { + return null; + } /** * Starts source preparation if not yet started, and adds a listener for timeline and/or manifest From 8a566fb330918eb029a05fe8b558f4045358f15b Mon Sep 17 00:00:00 2001 From: tonihei Date: Tue, 4 Dec 2018 14:16:20 +0000 Subject: [PATCH 05/10] Converge DownloadHelper implementations. Moving most of the logic to the base DownloaderHelper helps to implement track selection for downloading in a single place instead of multiple places. PiperOrigin-RevId: 223964869 --- .../exoplayer2/offline/DownloadHelper.java | 89 ++++++++++++++++--- .../offline/ProgressiveDownloadHelper.java | 35 ++------ .../dash/offline/DashDownloadHelper.java | 68 +++++--------- .../source/hls/offline/HlsDownloadHelper.java | 61 ++++--------- .../offline/SsDownloadHelper.java | 44 ++------- 5 files changed, 126 insertions(+), 171 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadHelper.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadHelper.java index 905619c6f01..044bd8cc8ad 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadHelper.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadHelper.java @@ -15,15 +15,21 @@ */ package com.google.android.exoplayer2.offline; +import android.net.Uri; import android.os.Handler; import android.os.Looper; import android.support.annotation.Nullable; import com.google.android.exoplayer2.source.TrackGroupArray; +import com.google.android.exoplayer2.util.Assertions; import java.io.IOException; import java.util.List; -/** A helper for initializing and removing downloads. */ -public abstract class DownloadHelper { +/** + * A helper for initializing and removing downloads. + * + * @param The manifest type. + */ +public abstract class DownloadHelper { /** A callback to be notified when the {@link DownloadHelper} is prepared. */ public interface Callback { @@ -44,6 +50,26 @@ public interface Callback { void onPrepareError(DownloadHelper helper, IOException e); } + private final String downloadType; + private final Uri uri; + @Nullable private final String cacheKey; + + @Nullable private T manifest; + @Nullable private TrackGroupArray[] trackGroupArrays; + + /** + * Create download helper. + * + * @param downloadType A download type. This value will be used as {@link DownloadAction#type}. + * @param uri A {@link Uri}. + * @param cacheKey An optional cache key. + */ + public DownloadHelper(String downloadType, Uri uri, @Nullable String cacheKey) { + this.downloadType = downloadType; + this.uri = uri; + this.cacheKey = cacheKey; + } + /** * Initializes the helper for starting a download. * @@ -51,14 +77,15 @@ public interface Callback { * will be invoked on the calling thread unless that thread does not have an associated {@link * Looper}, in which case it will be called on the application's main thread. */ - public void prepare(final Callback callback) { + public final void prepare(final Callback callback) { final Handler handler = new Handler(Looper.myLooper() != null ? Looper.myLooper() : Looper.getMainLooper()); new Thread() { @Override public void run() { try { - prepareInternal(); + manifest = loadManifest(uri); + trackGroupArrays = getTrackGroupArrays(manifest); handler.post(() -> callback.onPrepared(DownloadHelper.this)); } catch (final IOException e) { handler.post(() -> callback.onPrepareError(DownloadHelper.this, e)); @@ -67,18 +94,20 @@ public void run() { }.start(); } - /** - * Called on a background thread during preparation. - * - * @throws IOException If preparation fails. - */ - protected abstract void prepareInternal() throws IOException; + /** Returns the manifest. Must not be called until after preparation completes. */ + public final T getManifest() { + Assertions.checkNotNull(manifest); + return manifest; + } /** * Returns the number of periods for which media is available. Must not be called until after * preparation completes. */ - public abstract int getPeriodCount(); + public final int getPeriodCount() { + Assertions.checkNotNull(trackGroupArrays); + return trackGroupArrays.length; + } /** * Returns the track groups for the given period. Must not be called until after preparation @@ -88,7 +117,10 @@ public void run() { * @return The track groups for the period. May be {@link TrackGroupArray#EMPTY} for single stream * content. */ - public abstract TrackGroupArray getTrackGroups(int periodIndex); + public final TrackGroupArray getTrackGroups(int periodIndex) { + Assertions.checkNotNull(trackGroupArrays); + return trackGroupArrays[periodIndex]; + } /** * Builds a {@link DownloadAction} for downloading the specified tracks. Must not be called until @@ -98,12 +130,41 @@ public void run() { * @param trackKeys The selected tracks. If empty, all streams will be downloaded. * @return The built {@link DownloadAction}. */ - public abstract DownloadAction getDownloadAction(@Nullable byte[] data, List trackKeys); + public final DownloadAction getDownloadAction(@Nullable byte[] data, List trackKeys) { + return DownloadAction.createDownloadAction( + downloadType, uri, toStreamKeys(trackKeys), cacheKey, data); + } /** * Builds a {@link DownloadAction} for removing the media. May be called in any state. * * @return The built {@link DownloadAction}. */ - public abstract DownloadAction getRemoveAction(); + public final DownloadAction getRemoveAction() { + return DownloadAction.createRemoveAction(downloadType, uri, cacheKey); + } + + /** + * Loads the manifest. This method is called on a background thread. + * + * @param uri The manifest uri. + * @throws IOException If loading fails. + */ + protected abstract T loadManifest(Uri uri) throws IOException; + + /** + * Returns the track group arrays for each period in the manifest. + * + * @param manifest The manifest. + * @return An array of {@link TrackGroupArray}s. One for each period in the manifest. + */ + protected abstract TrackGroupArray[] getTrackGroupArrays(T manifest); + + /** + * Converts a list of {@link TrackKey track keys} to {@link StreamKey stream keys}. + * + * @param trackKeys A list of track keys. + * @return A corresponding list of stream keys. + */ + protected abstract List toStreamKeys(List trackKeys); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/ProgressiveDownloadHelper.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/ProgressiveDownloadHelper.java index 6c1ceafd935..70587694c40 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/ProgressiveDownloadHelper.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/ProgressiveDownloadHelper.java @@ -22,47 +22,28 @@ import java.util.List; /** A {@link DownloadHelper} for progressive streams. */ -public final class ProgressiveDownloadHelper extends DownloadHelper { - - private final Uri uri; - private final @Nullable String customCacheKey; +public final class ProgressiveDownloadHelper extends DownloadHelper { public ProgressiveDownloadHelper(Uri uri) { this(uri, null); } public ProgressiveDownloadHelper(Uri uri, @Nullable String customCacheKey) { - this.uri = uri; - this.customCacheKey = customCacheKey; - } - - @Override - protected void prepareInternal() { - // Do nothing. - } - - @Override - public int getPeriodCount() { - return 1; + super(DownloadAction.TYPE_PROGRESSIVE, uri, customCacheKey); } @Override - public TrackGroupArray getTrackGroups(int periodIndex) { - return TrackGroupArray.EMPTY; + protected Void loadManifest(Uri uri) { + return null; } @Override - public DownloadAction getDownloadAction(@Nullable byte[] data, List trackKeys) { - return DownloadAction.createDownloadAction( - DownloadAction.TYPE_PROGRESSIVE, - uri, - /* keys= */ Collections.emptyList(), - customCacheKey, - data); + protected TrackGroupArray[] getTrackGroupArrays(Void manifest) { + return new TrackGroupArray[] {TrackGroupArray.EMPTY}; } @Override - public DownloadAction getRemoveAction() { - return DownloadAction.createRemoveAction(DownloadAction.TYPE_PROGRESSIVE, uri, customCacheKey); + protected List toStreamKeys(List trackKeys) { + return Collections.emptyList(); } } diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/offline/DashDownloadHelper.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/offline/DashDownloadHelper.java index 9c6a24d1b2a..f4e43f4641b 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/offline/DashDownloadHelper.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/offline/DashDownloadHelper.java @@ -16,7 +16,6 @@ package com.google.android.exoplayer2.source.dash.offline; import android.net.Uri; -import android.support.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.offline.DownloadAction; @@ -31,74 +30,49 @@ import com.google.android.exoplayer2.source.dash.manifest.Representation; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.ParsingLoadable; -import com.google.android.exoplayer2.util.Assertions; import java.io.IOException; import java.util.ArrayList; import java.util.List; -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** A {@link DownloadHelper} for DASH streams. */ -public final class DashDownloadHelper extends DownloadHelper { +public final class DashDownloadHelper extends DownloadHelper { - private final Uri uri; private final DataSource.Factory manifestDataSourceFactory; - private @MonotonicNonNull DashManifest manifest; - public DashDownloadHelper(Uri uri, DataSource.Factory manifestDataSourceFactory) { - this.uri = uri; + super(DownloadAction.TYPE_DASH, uri, /* cacheKey= */ null); this.manifestDataSourceFactory = manifestDataSourceFactory; } @Override - protected void prepareInternal() throws IOException { + protected DashManifest loadManifest(Uri uri) throws IOException { DataSource dataSource = manifestDataSourceFactory.createDataSource(); - manifest = - ParsingLoadable.load(dataSource, new DashManifestParser(), uri, C.DATA_TYPE_MANIFEST); - } - - /** Returns the DASH manifest. Must not be called until after preparation completes. */ - public DashManifest getManifest() { - Assertions.checkNotNull(manifest); - return manifest; - } - - @Override - public int getPeriodCount() { - Assertions.checkNotNull(manifest); - return manifest.getPeriodCount(); + return ParsingLoadable.load(dataSource, new DashManifestParser(), uri, C.DATA_TYPE_MANIFEST); } @Override - public TrackGroupArray getTrackGroups(int periodIndex) { - Assertions.checkNotNull(manifest); - List adaptationSets = manifest.getPeriod(periodIndex).adaptationSets; - TrackGroup[] trackGroups = new TrackGroup[adaptationSets.size()]; - for (int i = 0; i < trackGroups.length; i++) { - List representations = adaptationSets.get(i).representations; - Format[] formats = new Format[representations.size()]; - int representationsCount = representations.size(); - for (int j = 0; j < representationsCount; j++) { - formats[j] = representations.get(j).format; + public TrackGroupArray[] getTrackGroupArrays(DashManifest manifest) { + int periodCount = manifest.getPeriodCount(); + TrackGroupArray[] trackGroupArrays = new TrackGroupArray[periodCount]; + for (int periodIndex = 0; periodIndex < periodCount; periodIndex++) { + List adaptationSets = manifest.getPeriod(periodIndex).adaptationSets; + TrackGroup[] trackGroups = new TrackGroup[adaptationSets.size()]; + for (int i = 0; i < trackGroups.length; i++) { + List representations = adaptationSets.get(i).representations; + Format[] formats = new Format[representations.size()]; + int representationsCount = representations.size(); + for (int j = 0; j < representationsCount; j++) { + formats[j] = representations.get(j).format; + } + trackGroups[i] = new TrackGroup(formats); } - trackGroups[i] = new TrackGroup(formats); + trackGroupArrays[periodIndex] = new TrackGroupArray(trackGroups); } - return new TrackGroupArray(trackGroups); + return trackGroupArrays; } @Override - public DownloadAction getDownloadAction(@Nullable byte[] data, List trackKeys) { - return DownloadAction.createDownloadAction( - DownloadAction.TYPE_DASH, uri, toStreamKeys(trackKeys), /* customCacheKey= */ null, data); - } - - @Override - public DownloadAction getRemoveAction() { - return DownloadAction.createRemoveAction( - DownloadAction.TYPE_DASH, uri, /* customCacheKey= */ null); - } - - private static List toStreamKeys(List trackKeys) { + protected List toStreamKeys(List trackKeys) { List streamKeys = new ArrayList<>(trackKeys.size()); for (int i = 0; i < trackKeys.size(); i++) { TrackKey trackKey = trackKeys.get(i); diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloadHelper.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloadHelper.java index d4cbd8b6387..c6ebe8e294b 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloadHelper.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloadHelper.java @@ -16,7 +16,6 @@ package com.google.android.exoplayer2.source.hls.offline; import android.net.Uri; -import android.support.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.offline.DownloadAction; @@ -36,46 +35,31 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** A {@link DownloadHelper} for HLS streams. */ -public final class HlsDownloadHelper extends DownloadHelper { +public final class HlsDownloadHelper extends DownloadHelper { - private final Uri uri; private final DataSource.Factory manifestDataSourceFactory; - private @MonotonicNonNull HlsPlaylist playlist; private int[] renditionGroups; public HlsDownloadHelper(Uri uri, DataSource.Factory manifestDataSourceFactory) { - this.uri = uri; + super(DownloadAction.TYPE_HLS, uri, /* cacheKey= */ null); this.manifestDataSourceFactory = manifestDataSourceFactory; } @Override - protected void prepareInternal() throws IOException { + protected HlsPlaylist loadManifest(Uri uri) throws IOException { DataSource dataSource = manifestDataSourceFactory.createDataSource(); - playlist = ParsingLoadable.load(dataSource, new HlsPlaylistParser(), uri, C.DATA_TYPE_MANIFEST); - } - - /** Returns the HLS playlist. Must not be called until after preparation completes. */ - public HlsPlaylist getPlaylist() { - Assertions.checkNotNull(playlist); - return playlist; + return ParsingLoadable.load(dataSource, new HlsPlaylistParser(), uri, C.DATA_TYPE_MANIFEST); } @Override - public int getPeriodCount() { - Assertions.checkNotNull(playlist); - return 1; - } - - @Override - public TrackGroupArray getTrackGroups(int periodIndex) { + protected TrackGroupArray[] getTrackGroupArrays(HlsPlaylist playlist) { Assertions.checkNotNull(playlist); if (playlist instanceof HlsMediaPlaylist) { renditionGroups = new int[0]; - return TrackGroupArray.EMPTY; + return new TrackGroupArray[] {TrackGroupArray.EMPTY}; } // TODO: Generate track groups as in playback. Reverse the mapping in getDownloadAction. HlsMasterPlaylist masterPlaylist = (HlsMasterPlaylist) playlist; @@ -94,24 +78,18 @@ public TrackGroupArray getTrackGroups(int periodIndex) { renditionGroups[trackGroupIndex] = HlsMasterPlaylist.GROUP_INDEX_SUBTITLE; trackGroups[trackGroupIndex++] = new TrackGroup(toFormats(masterPlaylist.subtitles)); } - return new TrackGroupArray(Arrays.copyOf(trackGroups, trackGroupIndex)); + return new TrackGroupArray[] {new TrackGroupArray(Arrays.copyOf(trackGroups, trackGroupIndex))}; } @Override - public DownloadAction getDownloadAction(@Nullable byte[] data, List trackKeys) { - Assertions.checkNotNull(renditionGroups); - return DownloadAction.createDownloadAction( - DownloadAction.TYPE_HLS, - uri, - toStreamKeys(trackKeys, renditionGroups), - /* customCacheKey= */ null, - data); - } - - @Override - public DownloadAction getRemoveAction() { - return DownloadAction.createRemoveAction( - DownloadAction.TYPE_HLS, uri, /* customCacheKey= */ null); + protected List toStreamKeys(List trackKeys) { + List representationKeys = new ArrayList<>(trackKeys.size()); + for (int i = 0; i < trackKeys.size(); i++) { + TrackKey trackKey = trackKeys.get(i); + representationKeys.add( + new StreamKey(renditionGroups[trackKey.groupIndex], trackKey.trackIndex)); + } + return representationKeys; } private static Format[] toFormats(List hlsUrls) { @@ -121,13 +99,4 @@ private static Format[] toFormats(List hlsUrls) { } return formats; } - - private static List toStreamKeys(List trackKeys, int[] groups) { - List representationKeys = new ArrayList<>(trackKeys.size()); - for (int i = 0; i < trackKeys.size(); i++) { - TrackKey trackKey = trackKeys.get(i); - representationKeys.add(new StreamKey(groups[trackKey.groupIndex], trackKey.trackIndex)); - } - return representationKeys; - } } diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloadHelper.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloadHelper.java index d7083400cd8..154ac30ac64 100644 --- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloadHelper.java +++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloadHelper.java @@ -16,7 +16,6 @@ package com.google.android.exoplayer2.source.smoothstreaming.offline; import android.net.Uri; -import android.support.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.offline.DownloadAction; import com.google.android.exoplayer2.offline.DownloadHelper; @@ -28,67 +27,38 @@ import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifestParser; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.ParsingLoadable; -import com.google.android.exoplayer2.util.Assertions; import java.io.IOException; import java.util.ArrayList; import java.util.List; -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** A {@link DownloadHelper} for SmoothStreaming streams. */ -public final class SsDownloadHelper extends DownloadHelper { +public final class SsDownloadHelper extends DownloadHelper { - private final Uri uri; private final DataSource.Factory manifestDataSourceFactory; - private @MonotonicNonNull SsManifest manifest; - public SsDownloadHelper(Uri uri, DataSource.Factory manifestDataSourceFactory) { - this.uri = uri; + super(DownloadAction.TYPE_SS, uri, /* cacheKey= */ null); this.manifestDataSourceFactory = manifestDataSourceFactory; } @Override - protected void prepareInternal() throws IOException { + protected SsManifest loadManifest(Uri uri) throws IOException { DataSource dataSource = manifestDataSourceFactory.createDataSource(); - manifest = ParsingLoadable.load(dataSource, new SsManifestParser(), uri, C.DATA_TYPE_MANIFEST); - } - - /** Returns the SmoothStreaming manifest. Must not be called until after preparation completes. */ - public SsManifest getManifest() { - Assertions.checkNotNull(manifest); - return manifest; - } - - @Override - public int getPeriodCount() { - Assertions.checkNotNull(manifest); - return 1; + return ParsingLoadable.load(dataSource, new SsManifestParser(), uri, C.DATA_TYPE_MANIFEST); } @Override - public TrackGroupArray getTrackGroups(int periodIndex) { - Assertions.checkNotNull(manifest); + protected TrackGroupArray[] getTrackGroupArrays(SsManifest manifest) { SsManifest.StreamElement[] streamElements = manifest.streamElements; TrackGroup[] trackGroups = new TrackGroup[streamElements.length]; for (int i = 0; i < streamElements.length; i++) { trackGroups[i] = new TrackGroup(streamElements[i].formats); } - return new TrackGroupArray(trackGroups); + return new TrackGroupArray[] {new TrackGroupArray(trackGroups)}; } @Override - public DownloadAction getDownloadAction(@Nullable byte[] data, List trackKeys) { - return DownloadAction.createDownloadAction( - DownloadAction.TYPE_SS, uri, toStreamKeys(trackKeys), /* customCacheKey= */ null, data); - } - - @Override - public DownloadAction getRemoveAction() { - return DownloadAction.createRemoveAction( - DownloadAction.TYPE_SS, uri, /* customCacheKey= */ null); - } - - private static List toStreamKeys(List trackKeys) { + protected List toStreamKeys(List trackKeys) { List representationKeys = new ArrayList<>(trackKeys.size()); for (int i = 0; i < trackKeys.size(); i++) { TrackKey trackKey = trackKeys.get(i); From 976a21f1392d92746f8c2f8377372b4dc00f709b Mon Sep 17 00:00:00 2001 From: tonihei Date: Tue, 4 Dec 2018 17:30:08 +0000 Subject: [PATCH 06/10] Add no-op defaults to Video(Audio)RendererEventListener. This is in line with how Player.EventListener and AnalyticsListener methods are defined and helps to only implement the callbacks needed. PiperOrigin-RevId: 223991262 --- .../audio/AudioRendererEventListener.java | 18 ++++++----- .../video/VideoRendererEventListener.java | 32 +++++++++---------- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioRendererEventListener.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioRendererEventListener.java index 7c3c1481fc4..eff7bc8de29 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioRendererEventListener.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioRendererEventListener.java @@ -25,7 +25,8 @@ import com.google.android.exoplayer2.util.Assertions; /** - * Listener of audio {@link Renderer} events. + * Listener of audio {@link Renderer} events. All methods have no-op default implementations to + * allow selective overrides. */ public interface AudioRendererEventListener { @@ -35,14 +36,14 @@ public interface AudioRendererEventListener { * @param counters {@link DecoderCounters} that will be updated by the renderer for as long as it * remains enabled. */ - void onAudioEnabled(DecoderCounters counters); + default void onAudioEnabled(DecoderCounters counters) {} /** * Called when the audio session is set. * * @param audioSessionId The audio session id. */ - void onAudioSessionId(int audioSessionId); + default void onAudioSessionId(int audioSessionId) {} /** * Called when a decoder is created. @@ -52,15 +53,15 @@ public interface AudioRendererEventListener { * finished. * @param initializationDurationMs The time taken to initialize the decoder in milliseconds. */ - void onAudioDecoderInitialized(String decoderName, long initializedTimestampMs, - long initializationDurationMs); + default void onAudioDecoderInitialized( + String decoderName, long initializedTimestampMs, long initializationDurationMs) {} /** * Called when the format of the media being consumed by the renderer changes. * * @param format The new format. */ - void onAudioInputFormatChanged(Format format); + default void onAudioInputFormatChanged(Format format) {} /** * Called when an {@link AudioSink} underrun occurs. @@ -71,14 +72,15 @@ void onAudioDecoderInitialized(String decoderName, long initializedTimestampMs, * as the buffered media can have a variable bitrate so the duration may be unknown. * @param elapsedSinceLastFeedMs The time since the {@link AudioSink} was last fed data. */ - void onAudioSinkUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs); + default void onAudioSinkUnderrun( + int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {} /** * Called when the renderer is disabled. * * @param counters {@link DecoderCounters} that were updated by the renderer. */ - void onAudioDisabled(DecoderCounters counters); + default void onAudioDisabled(DecoderCounters counters) {} /** * Dispatches events to a {@link AudioRendererEventListener}. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/VideoRendererEventListener.java b/library/core/src/main/java/com/google/android/exoplayer2/video/VideoRendererEventListener.java index 617211afb72..7d78ba03c7f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/VideoRendererEventListener.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/VideoRendererEventListener.java @@ -26,7 +26,8 @@ import com.google.android.exoplayer2.util.Assertions; /** - * Listener of video {@link Renderer} events. + * Listener of video {@link Renderer} events. All methods have no-op default implementations to + * allow selective overrides. */ public interface VideoRendererEventListener { @@ -36,7 +37,7 @@ public interface VideoRendererEventListener { * @param counters {@link DecoderCounters} that will be updated by the renderer for as long as it * remains enabled. */ - void onVideoEnabled(DecoderCounters counters); + default void onVideoEnabled(DecoderCounters counters) {} /** * Called when a decoder is created. @@ -46,15 +47,15 @@ public interface VideoRendererEventListener { * finished. * @param initializationDurationMs The time taken to initialize the decoder in milliseconds. */ - void onVideoDecoderInitialized(String decoderName, long initializedTimestampMs, - long initializationDurationMs); + default void onVideoDecoderInitialized( + String decoderName, long initializedTimestampMs, long initializationDurationMs) {} /** * Called when the format of the media being consumed by the renderer changes. * * @param format The new format. */ - void onVideoInputFormatChanged(Format format); + default void onVideoInputFormatChanged(Format format) {} /** * Called to report the number of frames dropped by the renderer. Dropped frames are reported @@ -62,12 +63,11 @@ void onVideoDecoderInitialized(String decoderName, long initializedTimestampMs, * reaches a specified threshold whilst the renderer is started. * * @param count The number of dropped frames. - * @param elapsedMs The duration in milliseconds over which the frames were dropped. This - * duration is timed from when the renderer was started or from when dropped frames were - * last reported (whichever was more recent), and not from when the first of the reported - * drops occurred. + * @param elapsedMs The duration in milliseconds over which the frames were dropped. This duration + * is timed from when the renderer was started or from when dropped frames were last reported + * (whichever was more recent), and not from when the first of the reported drops occurred. */ - void onDroppedFrames(int count, long elapsedMs); + default void onDroppedFrames(int count, long elapsedMs) {} /** * Called before a frame is rendered for the first time since setting the surface, and each time @@ -82,12 +82,12 @@ void onVideoDecoderInitialized(String decoderName, long initializedTimestampMs, * this is not possible. Applications that use {@link TextureView} can apply the rotation by * calling {@link TextureView#setTransform}. Applications that do not expect to encounter * rotated videos can safely ignore this parameter. - * @param pixelWidthHeightRatio The width to height ratio of each pixel. For the normal case - * of square pixels this will be equal to 1.0. Different values are indicative of anamorphic + * @param pixelWidthHeightRatio The width to height ratio of each pixel. For the normal case of + * square pixels this will be equal to 1.0. Different values are indicative of anamorphic * content. */ - void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, - float pixelWidthHeightRatio); + default void onVideoSizeChanged( + int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {} /** * Called when a frame is rendered for the first time since setting the surface, and when a frame @@ -96,14 +96,14 @@ void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, * @param surface The {@link Surface} to which a first frame has been rendered, or {@code null} if * the renderer renders to something that isn't a {@link Surface}. */ - void onRenderedFirstFrame(@Nullable Surface surface); + default void onRenderedFirstFrame(@Nullable Surface surface) {} /** * Called when the renderer is disabled. * * @param counters {@link DecoderCounters} that were updated by the renderer. */ - void onVideoDisabled(DecoderCounters counters); + default void onVideoDisabled(DecoderCounters counters) {} /** * Dispatches events to a {@link VideoRendererEventListener}. From 5bbe3ae7d6f65db9e0e97076da84ead7fe5f907e Mon Sep 17 00:00:00 2001 From: olly Date: Tue, 4 Dec 2018 17:45:50 +0000 Subject: [PATCH 07/10] Cache data with unknown length by default We currently default to not caching data if the content length cannot be resolved once the DataSource has been open. The reason for this is to avoid caching progressive live streams. By doing this we were accidentally not caching in other places where caching is possible, such as DASH/SS/HLS segments during playback if the server doesn't include a Content-Length header. Also HLS encryption key chunks, which were very unlikely to be cached during playback because we explicitly set FLAG_ALLOW_GZIP (which normally stops content length from resolving) without setting FLAG_ALLOW_CACHING_UNKNOWN_LENGTH. It seems like a good idea to flip the default at this point, and explicitly disable caching in the one case where we want that to happen. PiperOrigin-RevId: 223994110 --- RELEASENOTES.md | 3 ++ .../source/ExtractorMediaPeriod.java | 16 ++++++++-- .../source/SingleSampleMediaSource.java | 3 +- .../android/exoplayer2/upstream/DataSpec.java | 29 +++++++++---------- .../exoplayer2/upstream/ParsingLoadable.java | 6 +--- .../upstream/cache/CacheDataSink.java | 2 +- .../exoplayer2/upstream/cache/CacheUtil.java | 2 +- .../upstream/cache/CacheDataSourceTest.java | 3 +- .../exoplayer2/testutil/CacheAsserts.java | 2 +- 9 files changed, 36 insertions(+), 30 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index d7bd90055ea..ec11b5b7ed3 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -9,6 +9,9 @@ ([#3314](https://github.com/google/ExoPlayer/issues/3314)). * Do not retry failed loads whose error is `FileNotFoundException`. * Prevent Cea608Decoder from generating Subtitles with null Cues list +* Caching: Cache data with unknown length by default. The previous flag to opt in + to this behavior (`DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH`) has been + replaced with an opt out flag (`DataSpec.FLAG_DONT_CACHE_IF_LENGTH_UNKNOWN`). ### 2.9.2 ### diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java index 49209023e64..cb154eed303 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java @@ -850,6 +850,7 @@ public int skipData(long positionUs) { private DataSpec dataSpec; private long length; + @SuppressWarnings("method.invocation.invalid") public ExtractingLoadable( Uri uri, DataSource dataSource, @@ -864,7 +865,7 @@ public ExtractingLoadable( this.positionHolder = new PositionHolder(); this.pendingExtractorSeek = true; this.length = C.LENGTH_UNSET; - dataSpec = new DataSpec(uri, positionHolder.position, C.LENGTH_UNSET, customCacheKey); + dataSpec = buildDataSpec(/* position= */ 0); } // Loadable implementation. @@ -881,7 +882,7 @@ public void load() throws IOException, InterruptedException { ExtractorInput input = null; try { long position = positionHolder.position; - dataSpec = new DataSpec(uri, position, C.LENGTH_UNSET, customCacheKey); + dataSpec = buildDataSpec(position); length = dataSource.open(dataSpec); if (length != C.LENGTH_UNSET) { length += position; @@ -915,6 +916,17 @@ public void load() throws IOException, InterruptedException { // Internal methods. + private DataSpec buildDataSpec(long position) { + // Disable caching if the content length cannot be resolved, since this is indicative of a + // progressive live stream. + return new DataSpec( + uri, + position, + C.LENGTH_UNSET, + customCacheKey, + DataSpec.FLAG_DONT_CACHE_IF_LENGTH_UNKNOWN); + } + private void setLoadPosition(long position, long timeUs) { positionHolder.position = position; seekTimeUs = timeUs; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java index 1ac62074540..218bc84b118 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java @@ -287,8 +287,7 @@ private SingleSampleMediaSource( this.durationUs = durationUs; this.loadErrorHandlingPolicy = loadErrorHandlingPolicy; this.treatLoadErrorsAsEndOfStream = treatLoadErrorsAsEndOfStream; - dataSpec = - new DataSpec(uri, DataSpec.FLAG_ALLOW_GZIP | DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH); + dataSpec = new DataSpec(uri, DataSpec.FLAG_ALLOW_GZIP); timeline = new SinglePeriodTimeline(durationUs, /* isSeekable= */ true, /* isDynamic= */ false, tag); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java index 4a4cc021f40..c33c7c823f7 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSpec.java @@ -32,32 +32,29 @@ public final class DataSpec { /** * The flags that apply to any request for data. Possible flag values are {@link #FLAG_ALLOW_GZIP} - * and {@link #FLAG_ALLOW_CACHING_UNKNOWN_LENGTH}. + * and {@link #FLAG_DONT_CACHE_IF_LENGTH_UNKNOWN}. */ @Documented @Retention(RetentionPolicy.SOURCE) @IntDef( flag = true, - value = {FLAG_ALLOW_GZIP, FLAG_ALLOW_CACHING_UNKNOWN_LENGTH}) + value = {FLAG_ALLOW_GZIP, FLAG_DONT_CACHE_IF_LENGTH_UNKNOWN}) public @interface Flags {} /** - * Permits an underlying network stack to request that the server use gzip compression. - *

- * Should not typically be set if the data being requested is already compressed (e.g. most audio - * and video requests). May be set when requesting other data. - *

- * When a {@link DataSource} is used to request data with this flag set, and if the - * {@link DataSource} does make a network request, then the value returned from - * {@link DataSource#open(DataSpec)} will typically be {@link C#LENGTH_UNSET}. The data read from - * {@link DataSource#read(byte[], int, int)} will be the decompressed data. + * Allows an underlying network stack to request that the server use gzip compression. + * + *

Should not typically be set if the data being requested is already compressed (e.g. most + * audio and video requests). May be set when requesting other data. + * + *

When a {@link DataSource} is used to request data with this flag set, and if the {@link + * DataSource} does make a network request, then the value returned from {@link + * DataSource#open(DataSpec)} will typically be {@link C#LENGTH_UNSET}. The data read from {@link + * DataSource#read(byte[], int, int)} will be the decompressed data. */ public static final int FLAG_ALLOW_GZIP = 1; - /** - * Permits content to be cached even if its length can not be resolved. Typically this's the case - * for progressive live streams and when {@link #FLAG_ALLOW_GZIP} is used. - */ - public static final int FLAG_ALLOW_CACHING_UNKNOWN_LENGTH = 1 << 1; // 2 + /** Prevents caching if the length cannot be resolved when the {@link DataSource} is opened. */ + public static final int FLAG_DONT_CACHE_IF_LENGTH_UNKNOWN = 1 << 1; // 2 /** * The set of HTTP methods that are supported by ExoPlayer {@link HttpDataSource}s. One of {@link diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/ParsingLoadable.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/ParsingLoadable.java index cdcb3787fa6..48e03a0083d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/ParsingLoadable.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/ParsingLoadable.java @@ -91,11 +91,7 @@ public static T load(DataSource dataSource, Parser parser, Uri * @param parser Parses the object from the response. */ public ParsingLoadable(DataSource dataSource, Uri uri, int type, Parser parser) { - this( - dataSource, - new DataSpec(uri, DataSpec.FLAG_ALLOW_GZIP | DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH), - type, - parser); + this(dataSource, new DataSpec(uri, DataSpec.FLAG_ALLOW_GZIP), type, parser); } /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSink.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSink.java index 8d310015f80..e9c33792805 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSink.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSink.java @@ -121,7 +121,7 @@ public CacheDataSink( @Override public void open(DataSpec dataSpec) throws CacheDataSinkException { if (dataSpec.length == C.LENGTH_UNSET - && !dataSpec.isFlagSet(DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH)) { + && dataSpec.isFlagSet(DataSpec.FLAG_DONT_CACHE_IF_LENGTH_UNKNOWN)) { this.dataSpec = null; return; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheUtil.java index 1a44fb31448..fd4937ef861 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheUtil.java @@ -268,7 +268,7 @@ private static long readAndDiscard( dataSpec.position + absoluteStreamPosition - dataSpec.absoluteStreamPosition, C.LENGTH_UNSET, dataSpec.key, - dataSpec.flags | DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH); + dataSpec.flags); long resolvedLength = dataSource.open(dataSpec); if (counters.contentLength == C.LENGTH_UNSET && resolvedLength != C.LENGTH_UNSET) { counters.contentLength = dataSpec.absoluteStreamPosition + resolvedLength; diff --git a/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java b/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java index 83126ce34ad..9182074eb96 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java @@ -602,7 +602,6 @@ private DataSpec buildDataSpec(long position, long length) { } private DataSpec buildDataSpec(long position, long length, @Nullable String key) { - return new DataSpec( - testDataUri, position, length, key, DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH); + return new DataSpec(testDataUri, position, length, key); } } diff --git a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/CacheAsserts.java b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/CacheAsserts.java index 9d6fbe37e75..664532d3ff6 100644 --- a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/CacheAsserts.java +++ b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/CacheAsserts.java @@ -83,7 +83,7 @@ public static void assertCachedData(Cache cache, FakeDataSet fakeDataSet, Uri... * @throws IOException If an error occurred reading from the Cache. */ public static void assertDataCached(Cache cache, Uri uri, byte[] expected) throws IOException { - DataSpec dataSpec = new DataSpec(uri, DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH); + DataSpec dataSpec = new DataSpec(uri); assertDataCached(cache, dataSpec, expected); } From f8b85739b13eb423e092fd8a0f7078527cf2f42b Mon Sep 17 00:00:00 2001 From: olly Date: Tue, 4 Dec 2018 22:38:01 +0000 Subject: [PATCH 08/10] Fix race condition that could cause downloader not to be canceled PiperOrigin-RevId: 224048465 --- .../android/exoplayer2/offline/DownloadManager.java | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadManager.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadManager.java index c5645bedf01..4a76c80d642 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadManager.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadManager.java @@ -565,7 +565,7 @@ private static final class Task implements Runnable { */ @TargetState private volatile int targetState; - @MonotonicNonNull private volatile Downloader downloader; + @MonotonicNonNull private Downloader downloader; @MonotonicNonNull private Thread thread; @MonotonicNonNull private Throwable error; @@ -624,6 +624,7 @@ public void start() { state = STATE_STARTED; targetState = STATE_COMPLETED; downloadManager.onTaskStateChange(this); + downloader = downloaderFactory.createDownloader(action); thread = new Thread(this); thread.start(); } @@ -648,11 +649,7 @@ public void stop() { private void stopDownloadThread(@TargetState int targetState) { this.targetState = targetState; - // TODO: The possibility of downloader being null here may prevent the download thread from - // stopping in a timely way. Fix this. - if (downloader != null) { - downloader.cancel(); - } + Assertions.checkNotNull(downloader).cancel(); Assertions.checkNotNull(thread).interrupt(); } @@ -675,7 +672,6 @@ public void run() { logd("Task is started", this); Throwable error = null; try { - downloader = downloaderFactory.createDownloader(action); if (action.isRemoveAction) { downloader.remove(); } else { From 22a8aa311bf37936e4e4d51b3ab40863ea5d885d Mon Sep 17 00:00:00 2001 From: olly Date: Tue, 4 Dec 2018 23:28:11 +0000 Subject: [PATCH 09/10] Clean up requesting non-media segments in downloader implementations - Enable GZIP for media playlist + encryption key chunk requests in HLS, as we do during playback - Pass around DataSpecs rather than Uris. This will be needed for if we add manifest cacheKey support (which seems like a good idea for completeness, if nothing else) PiperOrigin-RevId: 224057139 --- .../exoplayer2/offline/SegmentDownloader.java | 22 ++++-- .../exoplayer2/upstream/ParsingLoadable.java | 18 +++++ .../source/dash/offline/DashDownloader.java | 10 ++- .../source/hls/offline/HlsDownloader.java | 74 ++++++++++--------- .../smoothstreaming/offline/SsDownloader.java | 4 +- 5 files changed, 81 insertions(+), 47 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloader.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloader.java index e55d2a1baff..1b32abff606 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/SegmentDownloader.java @@ -62,7 +62,7 @@ public int compareTo(@NonNull Segment other) { private static final int BUFFER_SIZE_BYTES = 128 * 1024; - private final Uri manifestUri; + private final DataSpec manifestDataSpec; private final Cache cache; private final CacheDataSource dataSource; private final CacheDataSource offlineDataSource; @@ -84,7 +84,7 @@ public int compareTo(@NonNull Segment other) { */ public SegmentDownloader( Uri manifestUri, List streamKeys, DownloaderConstructorHelper constructorHelper) { - this.manifestUri = manifestUri; + this.manifestDataSpec = getCompressibleDataSpec(manifestUri); this.streamKeys = new ArrayList<>(streamKeys); this.cache = constructorHelper.getCache(); this.dataSource = constructorHelper.createCacheDataSource(); @@ -171,7 +171,7 @@ public final float getDownloadPercentage() { @Override public final void remove() throws InterruptedException { try { - M manifest = getManifest(offlineDataSource, manifestUri); + M manifest = getManifest(offlineDataSource, manifestDataSpec); List segments = getSegments(offlineDataSource, manifest, true); for (int i = 0; i < segments.size(); i++) { removeDataSpec(segments.get(i).dataSpec); @@ -180,7 +180,7 @@ public final void remove() throws InterruptedException { // Ignore exceptions when removing. } finally { // Always attempt to remove the manifest. - removeDataSpec(new DataSpec(manifestUri)); + removeDataSpec(manifestDataSpec); } } @@ -190,11 +190,11 @@ public final void remove() throws InterruptedException { * Loads and parses the manifest. * * @param dataSource The {@link DataSource} through which to load. - * @param uri The manifest uri. + * @param dataSpec The manifest {@link DataSpec}. * @return The manifest. * @throws IOException If an error occurs reading data. */ - protected abstract M getManifest(DataSource dataSource, Uri uri) throws IOException; + protected abstract M getManifest(DataSource dataSource, DataSpec dataSpec) throws IOException; /** * Returns a list of all downloadable {@link Segment}s for a given manifest. @@ -217,7 +217,7 @@ protected abstract List getSegments( // Writes to downloadedSegments and downloadedBytes are safe. See the comment on download(). @SuppressWarnings("NonAtomicVolatileUpdate") private List initDownload() throws IOException, InterruptedException { - M manifest = getManifest(dataSource, manifestUri); + M manifest = getManifest(dataSource, manifestDataSpec); if (!streamKeys.isEmpty()) { manifest = manifest.copy(streamKeys); } @@ -252,4 +252,12 @@ private void removeDataSpec(DataSpec dataSpec) { CacheUtil.remove(dataSpec, cache, cacheKeyFactory); } + protected static DataSpec getCompressibleDataSpec(Uri uri) { + return new DataSpec( + uri, + /* absoluteStreamPosition= */ 0, + /* length= */ C.LENGTH_UNSET, + /* key= */ null, + /* flags= */ DataSpec.FLAG_ALLOW_GZIP); + } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/ParsingLoadable.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/ParsingLoadable.java index 48e03a0083d..b41f1aa09f9 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/ParsingLoadable.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/ParsingLoadable.java @@ -69,6 +69,24 @@ public static T load(DataSource dataSource, Parser parser, Uri return Assertions.checkNotNull(loadable.getResult()); } + /** + * Loads a single parsable object. + * + * @param dataSource The {@link DataSource} through which the object should be read. + * @param parser The {@link Parser} to parse the object from the response. + * @param dataSpec The {@link DataSpec} of the object to read. + * @param type The type of the data. One of the {@link C}{@code DATA_TYPE_*} constants. + * @return The parsed object + * @throws IOException Thrown if there is an error while loading or parsing. + */ + public static T load( + DataSource dataSource, Parser parser, DataSpec dataSpec, int type) + throws IOException { + ParsingLoadable loadable = new ParsingLoadable<>(dataSource, dataSpec, type, parser); + loadable.load(); + return Assertions.checkNotNull(loadable.getResult()); + } + /** * The {@link DataSpec} that defines the data to be loaded. */ diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/offline/DashDownloader.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/offline/DashDownloader.java index 68120d61772..5dad4687241 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/offline/DashDownloader.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/offline/DashDownloader.java @@ -28,11 +28,13 @@ import com.google.android.exoplayer2.source.dash.DashWrappingSegmentIndex; import com.google.android.exoplayer2.source.dash.manifest.AdaptationSet; import com.google.android.exoplayer2.source.dash.manifest.DashManifest; +import com.google.android.exoplayer2.source.dash.manifest.DashManifestParser; import com.google.android.exoplayer2.source.dash.manifest.Period; import com.google.android.exoplayer2.source.dash.manifest.RangedUri; import com.google.android.exoplayer2.source.dash.manifest.Representation; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSpec; +import com.google.android.exoplayer2.upstream.ParsingLoadable; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -73,8 +75,9 @@ public DashDownloader( } @Override - protected DashManifest getManifest(DataSource dataSource, Uri uri) throws IOException { - return DashUtil.loadManifest(dataSource, uri); + protected DashManifest getManifest(DataSource dataSource, DataSpec dataSpec) throws IOException { + return ParsingLoadable.load( + dataSource, new DashManifestParser(), dataSpec, C.DATA_TYPE_MANIFEST); } @Override @@ -121,8 +124,7 @@ private static void addSegmentsForAdaptationSet( if (!allowIncompleteList) { throw e; } - // Loading failed, but generating an incomplete segment list is allowed. Advance to the next - // representation. + // Generating an incomplete segment list is allowed. Advance to the next representation. continue; } diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloader.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloader.java index 85f41df3598..a0f64f298e7 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloader.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloader.java @@ -71,35 +71,37 @@ public HlsDownloader( } @Override - protected HlsPlaylist getManifest(DataSource dataSource, Uri uri) throws IOException { - return loadManifest(dataSource, uri); + protected HlsPlaylist getManifest(DataSource dataSource, DataSpec dataSpec) throws IOException { + return loadManifest(dataSource, dataSpec); } @Override protected List getSegments( DataSource dataSource, HlsPlaylist playlist, boolean allowIncompleteList) throws IOException { - ArrayList mediaPlaylistUris = new ArrayList<>(); + String baseUri = playlist.baseUri; + + ArrayList mediaPlaylistDataSpecs = new ArrayList<>(); if (playlist instanceof HlsMasterPlaylist) { HlsMasterPlaylist masterPlaylist = (HlsMasterPlaylist) playlist; - addResolvedUris(masterPlaylist.baseUri, masterPlaylist.variants, mediaPlaylistUris); - addResolvedUris(masterPlaylist.baseUri, masterPlaylist.audios, mediaPlaylistUris); - addResolvedUris(masterPlaylist.baseUri, masterPlaylist.subtitles, mediaPlaylistUris); + addMediaPlaylistDataSpecs(baseUri, masterPlaylist.variants, mediaPlaylistDataSpecs); + addMediaPlaylistDataSpecs(baseUri, masterPlaylist.audios, mediaPlaylistDataSpecs); + addMediaPlaylistDataSpecs(baseUri, masterPlaylist.subtitles, mediaPlaylistDataSpecs); } else { - mediaPlaylistUris.add(Uri.parse(playlist.baseUri)); + mediaPlaylistDataSpecs.add(SegmentDownloader.getCompressibleDataSpec(Uri.parse(baseUri))); } - ArrayList segments = new ArrayList<>(); + ArrayList segments = new ArrayList<>(); HashSet seenEncryptionKeyUris = new HashSet<>(); - for (Uri mediaPlaylistUri : mediaPlaylistUris) { + for (DataSpec mediaPlaylistDataSpec : mediaPlaylistDataSpecs) { + segments.add(new Segment(/* startTimeUs= */ 0, mediaPlaylistDataSpec)); HlsMediaPlaylist mediaPlaylist; try { - mediaPlaylist = (HlsMediaPlaylist) loadManifest(dataSource, mediaPlaylistUri); - segments.add(new Segment(mediaPlaylist.startTimeUs, new DataSpec(mediaPlaylistUri))); + mediaPlaylist = (HlsMediaPlaylist) loadManifest(dataSource, mediaPlaylistDataSpec); } catch (IOException e) { if (!allowIncompleteList) { throw e; } - segments.add(new Segment(0, new DataSpec(mediaPlaylistUri))); + // Generating an incomplete segment list is allowed. Advance to the next media playlist. continue; } HlsMediaPlaylist.Segment lastInitSegment = null; @@ -109,39 +111,43 @@ protected List getSegments( HlsMediaPlaylist.Segment initSegment = segment.initializationSegment; if (initSegment != null && initSegment != lastInitSegment) { lastInitSegment = initSegment; - addSegment(segments, mediaPlaylist, initSegment, seenEncryptionKeyUris); + addSegment(mediaPlaylist, initSegment, seenEncryptionKeyUris, segments); } - addSegment(segments, mediaPlaylist, segment, seenEncryptionKeyUris); + addSegment(mediaPlaylist, segment, seenEncryptionKeyUris, segments); } } return segments; } - private static HlsPlaylist loadManifest(DataSource dataSource, Uri uri) throws IOException { - return ParsingLoadable.load(dataSource, new HlsPlaylistParser(), uri, C.DATA_TYPE_MANIFEST); + private void addMediaPlaylistDataSpecs(String baseUri, List urls, List out) { + for (int i = 0; i < urls.size(); i++) { + Uri playlistUri = UriUtil.resolveToUri(baseUri, urls.get(i).url); + out.add(SegmentDownloader.getCompressibleDataSpec(playlistUri)); + } } - private static void addSegment( - ArrayList segments, + private static HlsPlaylist loadManifest(DataSource dataSource, DataSpec dataSpec) + throws IOException { + return ParsingLoadable.load( + dataSource, new HlsPlaylistParser(), dataSpec, C.DATA_TYPE_MANIFEST); + } + + private void addSegment( HlsMediaPlaylist mediaPlaylist, - HlsMediaPlaylist.Segment hlsSegment, - HashSet seenEncryptionKeyUris) { - long startTimeUs = mediaPlaylist.startTimeUs + hlsSegment.relativeStartTimeUs; - if (hlsSegment.fullSegmentEncryptionKeyUri != null) { - Uri keyUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, - hlsSegment.fullSegmentEncryptionKeyUri); + HlsMediaPlaylist.Segment segment, + HashSet seenEncryptionKeyUris, + ArrayList out) { + String baseUri = mediaPlaylist.baseUri; + long startTimeUs = mediaPlaylist.startTimeUs + segment.relativeStartTimeUs; + if (segment.fullSegmentEncryptionKeyUri != null) { + Uri keyUri = UriUtil.resolveToUri(baseUri, segment.fullSegmentEncryptionKeyUri); if (seenEncryptionKeyUris.add(keyUri)) { - segments.add(new Segment(startTimeUs, new DataSpec(keyUri))); + out.add(new Segment(startTimeUs, SegmentDownloader.getCompressibleDataSpec(keyUri))); } } - Uri resolvedUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, hlsSegment.url); - segments.add(new Segment(startTimeUs, - new DataSpec(resolvedUri, hlsSegment.byterangeOffset, hlsSegment.byterangeLength, null))); - } - - private static void addResolvedUris(String baseUri, List urls, List out) { - for (int i = 0; i < urls.size(); i++) { - out.add(UriUtil.resolveToUri(baseUri, urls.get(i).url)); - } + Uri segmentUri = UriUtil.resolveToUri(baseUri, segment.url); + DataSpec dataSpec = + new DataSpec(segmentUri, segment.byterangeOffset, segment.byterangeLength, /* key= */ null); + out.add(new Segment(startTimeUs, dataSpec)); } } diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloader.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloader.java index 84ef251e5f5..18820ca49cb 100644 --- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloader.java +++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloader.java @@ -68,8 +68,8 @@ public SsDownloader( } @Override - protected SsManifest getManifest(DataSource dataSource, Uri uri) throws IOException { - return ParsingLoadable.load(dataSource, new SsManifestParser(), uri, C.DATA_TYPE_MANIFEST); + protected SsManifest getManifest(DataSource dataSource, DataSpec dataSpec) throws IOException { + return ParsingLoadable.load(dataSource, new SsManifestParser(), dataSpec, C.DATA_TYPE_MANIFEST); } @Override From b993367a3b9e5c1d1b0a74795bc1249d3c97c85c Mon Sep 17 00:00:00 2001 From: tonihei Date: Wed, 5 Dec 2018 09:48:23 +0000 Subject: [PATCH 10/10] Add util method to extract renderer capabilities. This instantiates the renderers and extract the capabilities. None of the known renderes incurs any overhead during instantiation. PiperOrigin-RevId: 224118511 --- .../google/android/exoplayer2/util/Util.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java b/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java index 9c2f426de58..7bea5de8bab 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java @@ -48,8 +48,15 @@ import com.google.android.exoplayer2.ExoPlayerLibraryInfo; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.ParserException; +import com.google.android.exoplayer2.Renderer; +import com.google.android.exoplayer2.RendererCapabilities; +import com.google.android.exoplayer2.RenderersFactory; import com.google.android.exoplayer2.SeekParameters; +import com.google.android.exoplayer2.audio.AudioRendererEventListener; +import com.google.android.exoplayer2.drm.DrmSessionManager; +import com.google.android.exoplayer2.drm.FrameworkMediaCrypto; import com.google.android.exoplayer2.upstream.DataSource; +import com.google.android.exoplayer2.video.VideoRendererEventListener; import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.File; @@ -1842,6 +1849,32 @@ public static Point getPhysicalDisplaySize(Context context, Display display) { return displaySize; } + /** + * Extract renderer capabilities for the renderers created by the provided renderers factory. + * + * @param renderersFactory A {@link RenderersFactory}. + * @param drmSessionManager An optional {@link DrmSessionManager} used by the renderers. + * @return The {@link RendererCapabilities} for each renderer created by the {@code + * renderersFactory}. + */ + public static RendererCapabilities[] getRendererCapabilities( + RenderersFactory renderersFactory, + @Nullable DrmSessionManager drmSessionManager) { + Renderer[] renderers = + renderersFactory.createRenderers( + new Handler(), + new VideoRendererEventListener() {}, + new AudioRendererEventListener() {}, + (cues) -> {}, + (metadata) -> {}, + drmSessionManager); + RendererCapabilities[] capabilities = new RendererCapabilities[renderers.length]; + for (int i = 0; i < renderers.length; i++) { + capabilities[i] = renderers[i].getCapabilities(); + } + return capabilities; + } + @Nullable private static String getSystemProperty(String name) { try {