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 8742a6344e..e93a53b713 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 @@ public final class Cea608Decoder extends CeaDecoder { 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 final class Cea608Decoder extends CeaDecoder { 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 400061d019..d9c13e07b9 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 final class ImaAdsMediaSource extends BaseMediaSource implements SourceIn 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 3916d41b61..90047f9741 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 final class ClippingMediaSource extends CompositeMediaSource { } } + @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 1f3b01182a..f6fd497f3d 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 class ConcatenatingMediaSource extends CompositeMediaSource { } } + @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 d8335131f9..8a7f861893 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 @@ public interface MediaSource { */ 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 ecb4b10c6a..bfe0800d35 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 final class MergingMediaSource extends CompositeMediaSource { } } + @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 1ac6207454..57a0440b06 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 final class SingleSampleMediaSource extends BaseMediaSource { 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 @@ public final class SingleSampleMediaSource extends BaseMediaSource { 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 final class SingleSampleMediaSource extends BaseMediaSource { ((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 7fc0f22bf3..0944cc2ff2 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 final class AdsMediaSource extends CompositeMediaSource { 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 b04fcf7247..9068f62d37 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 final class DashMediaSource extends BaseMediaSource { 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 a075dacf3a..1af68dc2ec 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 final class HlsMediaSource extends BaseMediaSource ((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 a756b7f4f1..9b2001bb48 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 final class SsMediaSource extends BaseMediaSource 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 2fca4f42c7..f00274dbf8 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 class FakeMediaSource extends BaseMediaSource { 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 d9c13e07b9..85042c4354 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 final class ImaAdsMediaSource extends BaseMediaSource implements SourceIn adUiViewGroup, eventHandler, eventListener); } + @Override + @Nullable + public Object getTag() { + return adsMediaSource.getTag(); + } + @Override public void prepareSourceInternal( final ExoPlayer player, @@ -100,12 +106,6 @@ public final class ImaAdsMediaSource extends BaseMediaSource implements SourceIn 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 90047f9741..1dbb41dfb0 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 final class ClippingMediaSource extends CompositeMediaSource { window = new Timeline.Window(); } + @Override + @Nullable + public Object getTag() { + return mediaSource.getTag(); + } + @Override public void prepareSourceInternal( ExoPlayer player, @@ -224,12 +230,6 @@ public final class ClippingMediaSource extends CompositeMediaSource { } } - @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 f6fd497f3d..26667e641f 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 class ConcatenatingMediaSource extends CompositeMediaSource { mediaPeriodToChildMediaPeriodId = new HashMap<>(); } + @Override + @Nullable + public Object getTag() { + return childSource.getTag(); + } + @Override public void prepareSourceInternal( ExoPlayer player, @@ -95,12 +101,6 @@ public final class LoopingMediaSource extends CompositeMediaSource { } } - @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 8a7f861893..acd5dd3e2e 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 interface MediaSource { */ 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 @@ public interface MediaSource { */ 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 bfe0800d35..573e97cb13 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 final class MergingMediaSource extends CompositeMediaSource { 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 final class MergingMediaSource extends CompositeMediaSource { } } - @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 57a0440b06..a8f0c0b678 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 @@ public final class SingleSampleMediaSource extends BaseMediaSource { // MediaSource implementation. + @Override + @Nullable + public Object getTag() { + return tag; + } + @Override public void prepareSourceInternal( ExoPlayer player, @@ -329,12 +335,6 @@ public final class SingleSampleMediaSource extends BaseMediaSource { ((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 0944cc2ff2..19ddbd2c54 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 final class AdsMediaSource extends CompositeMediaSource { adsLoader.setSupportedContentTypes(adMediaSourceFactory.getSupportedTypes()); } + @Override + @Nullable + public Object getTag() { + return contentMediaSource.getTag(); + } + @Override public void prepareSourceInternal( final ExoPlayer player, @@ -387,12 +393,6 @@ public final class AdsMediaSource extends CompositeMediaSource { 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 9068f62d37..1f08a43731 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 final class DashMediaSource extends BaseMediaSource { // MediaSource implementation. + @Override + @Nullable + public Object getTag() { + return tag; + } + @Override public void prepareSourceInternal( ExoPlayer player, @@ -658,12 +664,6 @@ public final class DashMediaSource extends BaseMediaSource { 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 1af68dc2ec..a9b0c579ac 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 @@ public final class HlsMediaSource extends BaseMediaSource this.tag = tag; } + @Override + @Nullable + public Object getTag() { + return tag; + } + @Override public void prepareSourceInternal( ExoPlayer player, @@ -425,12 +431,6 @@ public final class HlsMediaSource extends BaseMediaSource ((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 9b2001bb48..103a52a55a 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 @@ public final class SsMediaSource extends BaseMediaSource // MediaSource implementation. + @Override + @Nullable + public Object getTag() { + return tag; + } + @Override public void prepareSourceInternal( ExoPlayer player, @@ -549,12 +555,6 @@ public final class SsMediaSource extends BaseMediaSource 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 f00274dbf8..7291b83ff6 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 class FakeMediaSource extends BaseMediaSource { 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 class FakeMediaSource extends BaseMediaSource { 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 acd5dd3e2e..a96893ea26 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 interface MediaSource { /** * 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 905619c6f0..044bd8cc8a 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 abstract class DownloadHelper { 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 abstract class DownloadHelper { * 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 abstract class DownloadHelper { }.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 abstract class DownloadHelper { * @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 abstract class DownloadHelper { * @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 6c1ceafd93..70587694c4 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.Collections; 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; + super(DownloadAction.TYPE_PROGRESSIVE, uri, customCacheKey); } @Override - protected void prepareInternal() { - // Do nothing. + protected Void loadManifest(Uri uri) { + return null; } @Override - public int getPeriodCount() { - return 1; + protected TrackGroupArray[] getTrackGroupArrays(Void manifest) { + return new TrackGroupArray[] {TrackGroupArray.EMPTY}; } @Override - public TrackGroupArray getTrackGroups(int periodIndex) { - return TrackGroupArray.EMPTY; - } - - @Override - public DownloadAction getDownloadAction(@Nullable byte[] data, List trackKeys) { - return DownloadAction.createDownloadAction( - DownloadAction.TYPE_PROGRESSIVE, - uri, - /* keys= */ Collections.emptyList(), - customCacheKey, - data); - } - - @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 9c6a24d1b2..f4e43f4641 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.DashManifestParser; 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; + return ParsingLoadable.load(dataSource, new DashManifestParser(), uri, C.DATA_TYPE_MANIFEST); } @Override - public int getPeriodCount() { - Assertions.checkNotNull(manifest); - return manifest.getPeriodCount(); - } - - @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 d4cbd8b638..c6ebe8e294 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.io.IOException; 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 final class HlsDownloadHelper extends DownloadHelper { 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 @@ public final class HlsDownloadHelper extends DownloadHelper { } 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 d7083400cd..154ac30ac6 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.SsManifest; 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; + return ParsingLoadable.load(dataSource, new SsManifestParser(), uri, C.DATA_TYPE_MANIFEST); } @Override - public int getPeriodCount() { - Assertions.checkNotNull(manifest); - return 1; - } - - @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 7c3c1481fc..eff7bc8de2 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.decoder.DecoderCounters; 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 @@ public interface AudioRendererEventListener { * 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 617211afb7..7d78ba03c7 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.decoder.DecoderCounters; 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 @@ public interface VideoRendererEventListener { * 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 @@ public interface VideoRendererEventListener { * 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 @@ public interface VideoRendererEventListener { * @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 d7bd90055e..ec11b5b7ed 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 49209023e6..cb154eed30 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 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; private DataSpec dataSpec; private long length; + @SuppressWarnings("method.invocation.invalid") public ExtractingLoadable( Uri uri, DataSource dataSource, @@ -864,7 +865,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; 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 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; 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 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; // 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 1ac6207454..218bc84b11 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 @@ public final class SingleSampleMediaSource extends BaseMediaSource { 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 4a4cc021f4..c33c7c823f 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 cdcb3787fa..48e03a0083 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 final class ParsingLoadable implements Loadable { * @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 8d310015f8..e9c3379280 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 final class CacheDataSink implements DataSink { @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 1a44fb3144..fd4937ef86 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 @@ public final class CacheUtil { 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 83126ce34a..9182074eb9 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 @@ public final class CacheDataSourceTest { } 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 9d6fbe37e7..664532d3ff 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 final class CacheAsserts { * @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 c5645bedf0..4a76c80d64 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 @@ public final class DownloadManager { */ @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 final class DownloadManager { state = STATE_STARTED; targetState = STATE_COMPLETED; downloadManager.onTaskStateChange(this); + downloader = downloaderFactory.createDownloader(action); thread = new Thread(this); thread.start(); } @@ -648,11 +649,7 @@ public final class DownloadManager { 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 final class DownloadManager { 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 e55d2a1baf..1b32abff60 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 abstract class SegmentDownloader> impleme 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 abstract class SegmentDownloader> impleme */ 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 abstract class SegmentDownloader> impleme @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 abstract class SegmentDownloader> impleme // Ignore exceptions when removing. } finally { // Always attempt to remove the manifest. - removeDataSpec(new DataSpec(manifestUri)); + removeDataSpec(manifestDataSpec); } } @@ -190,11 +190,11 @@ public abstract class SegmentDownloader> impleme * 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 @@ public abstract class SegmentDownloader> impleme // 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 @@ public abstract class SegmentDownloader> impleme 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 48e03a0083..b41f1aa09f 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 final class ParsingLoadable implements Loadable { 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 68120d6177..5dad468724 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.DashUtil; 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 final class DashDownloader extends SegmentDownloader { } @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 @@ public final class DashDownloader extends SegmentDownloader { 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 85f41df359..a0f64f298e 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 final class HlsDownloader extends SegmentDownloader { } @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 @@ public final class HlsDownloader extends SegmentDownloader { 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 84ef251e5f..18820ca49c 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 final class SsDownloader extends SegmentDownloader { } @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 9c2f426de5..7bea5de8ba 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.C; 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 final class Util { 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 {