From 3833d98755f97a41d8ac294f11c8d72951876f59 Mon Sep 17 00:00:00 2001 From: tonihei Date: Fri, 21 Dec 2018 15:19:24 +0000 Subject: [PATCH 01/38] Improve doc for setKeepContentOnPlayerReset. This also applies when seeking or transitioning to unprepared media, which isn't clear from the current documentation. Issue:#5267 PiperOrigin-RevId: 226486685 --- .../java/com/google/android/exoplayer2/ui/PlayerView.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java index 88eabfed07..83f5b70cbb 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java @@ -679,8 +679,9 @@ public class PlayerView extends FrameLayout { /** * Sets whether the currently displayed video frame or media artwork is kept visible when the * player is reset. A player reset is defined to mean the player being re-prepared with different - * media, {@link Player#stop(boolean)} being called with {@code reset=true}, or the player being - * replaced or cleared by calling {@link #setPlayer(Player)}. + * media, the player transitioning to unprepared media, {@link Player#stop(boolean)} being called + * with {@code reset=true}, or the player being replaced or cleared by calling {@link + * #setPlayer(Player)}. * *

If enabled, the currently displayed video frame or media artwork will be kept visible until * the player set on the view has been successfully prepared with new media and loaded enough of From f79005ab0a8ba0a683d018b5469fe0c3aa76a969 Mon Sep 17 00:00:00 2001 From: tonihei Date: Tue, 11 Dec 2018 15:50:54 +0000 Subject: [PATCH 02/38] Fix manifest uri in SsDownloadHelper. This is the same as in SsMediaSource. PiperOrigin-RevId: 225001911 --- .../source/smoothstreaming/offline/SsDownloadHelper.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 5125beff1c..88830dde6a 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 @@ -25,6 +25,7 @@ import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest; import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifestParser; +import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsUtil; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.ParsingLoadable; import com.google.android.exoplayer2.util.Assertions; @@ -42,7 +43,7 @@ public final class SsDownloadHelper extends DownloadHelper { private @MonotonicNonNull SsManifest manifest; public SsDownloadHelper(Uri uri, DataSource.Factory manifestDataSourceFactory) { - this.uri = uri; + this.uri = SsUtil.fixManifestUri(uri);; this.manifestDataSourceFactory = manifestDataSourceFactory; } From c9cf8e409d4589415da4767d54465e883c8b7dd9 Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Mon, 17 Dec 2018 13:45:40 +0000 Subject: [PATCH 03/38] Update the Cast framework dependency PiperOrigin-RevId: 225812585 --- extensions/cast/build.gradle | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/extensions/cast/build.gradle b/extensions/cast/build.gradle index 30fe10085f..0baa074d4a 100644 --- a/extensions/cast/build.gradle +++ b/extensions/cast/build.gradle @@ -31,7 +31,9 @@ android { } dependencies { - api 'com.google.android.gms:play-services-cast-framework:16.0.3' + api 'com.google.android.gms:play-services-cast-framework:16.1.2' + compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion + compileOnly 'org.checkerframework:checker-compat-qual:' + checkerframeworkVersion implementation project(modulePrefix + 'library-core') implementation project(modulePrefix + 'library-ui') testImplementation project(modulePrefix + 'testutils') From c0cdf3cd2d05afaa5d222d8bf720a5db5cf7556d Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Mon, 17 Dec 2018 13:52:56 +0000 Subject: [PATCH 04/38] Handle failure to get Cast context more gracefully Issue:#4160 Issue:#4743 PiperOrigin-RevId: 225813243 --- .../exoplayer2/castdemo/MainActivity.java | 24 ++++++++++++++- .../cast_context_error_message_layout.xml | 29 +++++++++++++++++++ demos/cast/src/main/res/values/strings.xml | 2 ++ 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 demos/cast/src/main/res/layout/cast_context_error_message_layout.xml diff --git a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/MainActivity.java b/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/MainActivity.java index 30968b8f85..6589685124 100644 --- a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/MainActivity.java +++ b/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/MainActivity.java @@ -41,6 +41,7 @@ import com.google.android.exoplayer2.ui.PlayerControlView; import com.google.android.exoplayer2.ui.PlayerView; import com.google.android.gms.cast.framework.CastButtonFactory; import com.google.android.gms.cast.framework.CastContext; +import com.google.android.gms.dynamite.DynamiteModule; /** * An activity that plays video using {@link SimpleExoPlayer} and {@link CastPlayer}. @@ -61,7 +62,20 @@ public class MainActivity extends AppCompatActivity implements OnClickListener, public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Getting the cast context later than onStart can cause device discovery not to take place. - castContext = CastContext.getSharedInstance(this); + try { + castContext = CastContext.getSharedInstance(this); + } catch (RuntimeException e) { + Throwable cause = e.getCause(); + while (cause != null) { + if (cause instanceof DynamiteModule.LoadingException) { + setContentView(R.layout.cast_context_error_message_layout); + return; + } + cause = cause.getCause(); + } + // Unknown error. We propagate it. + throw e; + } setContentView(R.layout.main_activity); @@ -91,6 +105,10 @@ public class MainActivity extends AppCompatActivity implements OnClickListener, @Override public void onResume() { super.onResume(); + if (castContext == null) { + // There is no Cast context to work with. Do nothing. + return; + } playerManager = PlayerManager.createPlayerManager( /* queuePositionListener= */ this, @@ -104,6 +122,10 @@ public class MainActivity extends AppCompatActivity implements OnClickListener, @Override public void onPause() { super.onPause(); + if (castContext == null) { + // Nothing to release. + return; + } mediaQueueListAdapter.notifyItemRangeRemoved(0, mediaQueueListAdapter.getItemCount()); mediaQueueList.setAdapter(null); playerManager.release(); diff --git a/demos/cast/src/main/res/layout/cast_context_error_message_layout.xml b/demos/cast/src/main/res/layout/cast_context_error_message_layout.xml new file mode 100644 index 0000000000..6d3260de38 --- /dev/null +++ b/demos/cast/src/main/res/layout/cast_context_error_message_layout.xml @@ -0,0 +1,29 @@ + + + + + + + diff --git a/demos/cast/src/main/res/values/strings.xml b/demos/cast/src/main/res/values/strings.xml index 3505c40400..58f5233412 100644 --- a/demos/cast/src/main/res/values/strings.xml +++ b/demos/cast/src/main/res/values/strings.xml @@ -22,4 +22,6 @@ Add samples + Failed to get Cast context. Try updating Google Play Services and restart the app. + From d3f5057d59f26cc50b6baae6de176a2df7f9b596 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Mon, 24 Dec 2018 14:26:01 +0000 Subject: [PATCH 05/38] Handle rectangular rotation projections in Matroska See also https://github.com/Matroska-Org/matroska-specification/issues/269. PiperOrigin-RevId: 226758584 --- .../java/com/google/android/exoplayer2/C.java | 20 +++++++ .../extractor/mkv/MatroskaExtractor.java | 54 +++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/C.java b/library/core/src/main/java/com/google/android/exoplayer2/C.java index fac9818d9e..77d39fe866 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/C.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/C.java @@ -896,6 +896,26 @@ public final class C { */ public static final int COLOR_RANGE_FULL = MediaFormat.COLOR_RANGE_FULL; + /** Video projection types. */ + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + Format.NO_VALUE, + PROJECTION_RECTANGULAR, + PROJECTION_EQUIRECTANGULAR, + PROJECTION_CUBEMAP, + PROJECTION_MESH + }) + public @interface Projection {} + /** Conventional rectangular projection. */ + public static final int PROJECTION_RECTANGULAR = 0; + /** Equirectangular spherical projection. */ + public static final int PROJECTION_EQUIRECTANGULAR = 1; + /** Cube map projection. */ + public static final int PROJECTION_CUBEMAP = 2; + /** 3-D mesh projection. */ + public static final int PROJECTION_MESH = 3; + /** * Priority for media playback. * diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java index 86b750e821..187b9ae443 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java @@ -191,7 +191,11 @@ public final class MatroskaExtractor implements Extractor { private static final int ID_CUE_CLUSTER_POSITION = 0xF1; private static final int ID_LANGUAGE = 0x22B59C; private static final int ID_PROJECTION = 0x7670; + private static final int ID_PROJECTION_TYPE = 0x7671; private static final int ID_PROJECTION_PRIVATE = 0x7672; + private static final int ID_PROJECTION_POSE_YAW = 0x7673; + private static final int ID_PROJECTION_POSE_PITCH = 0x7674; + private static final int ID_PROJECTION_POSE_ROLL = 0x7675; private static final int ID_STEREO_MODE = 0x53B8; private static final int ID_COLOUR = 0x55B0; private static final int ID_COLOUR_RANGE = 0x55B9; @@ -760,6 +764,24 @@ public final class MatroskaExtractor implements Extractor { case ID_MAX_FALL: currentTrack.maxFrameAverageLuminance = (int) value; break; + case ID_PROJECTION_TYPE: + switch ((int) value) { + case 0: + currentTrack.projectionType = C.PROJECTION_RECTANGULAR; + break; + case 1: + currentTrack.projectionType = C.PROJECTION_EQUIRECTANGULAR; + break; + case 2: + currentTrack.projectionType = C.PROJECTION_CUBEMAP; + break; + case 3: + currentTrack.projectionType = C.PROJECTION_MESH; + break; + default: + break; + } + break; default: break; } @@ -803,6 +825,15 @@ public final class MatroskaExtractor implements Extractor { case ID_LUMNINANCE_MIN: currentTrack.minMasteringLuminance = (float) value; break; + case ID_PROJECTION_POSE_YAW: + currentTrack.projectionPoseYaw = (float) value; + break; + case ID_PROJECTION_POSE_PITCH: + currentTrack.projectionPosePitch = (float) value; + break; + case ID_PROJECTION_POSE_ROLL: + currentTrack.projectionPoseRoll = (float) value; + break; default: break; } @@ -1465,6 +1496,7 @@ public final class MatroskaExtractor implements Extractor { case ID_COLOUR_PRIMARIES: case ID_MAX_CLL: case ID_MAX_FALL: + case ID_PROJECTION_TYPE: return TYPE_UNSIGNED_INT; case ID_DOC_TYPE: case ID_NAME: @@ -1491,6 +1523,9 @@ public final class MatroskaExtractor implements Extractor { case ID_WHITE_POINT_CHROMATICITY_Y: case ID_LUMNINANCE_MAX: case ID_LUMNINANCE_MIN: + case ID_PROJECTION_POSE_YAW: + case ID_PROJECTION_POSE_PITCH: + case ID_PROJECTION_POSE_ROLL: return TYPE_FLOAT; default: return TYPE_UNKNOWN; @@ -1631,6 +1666,10 @@ public final class MatroskaExtractor implements Extractor { public int displayWidth = Format.NO_VALUE; public int displayHeight = Format.NO_VALUE; public int displayUnit = DISPLAY_UNIT_PIXELS; + @C.Projection public int projectionType = Format.NO_VALUE; + public float projectionPoseYaw = 0f; + public float projectionPosePitch = 0f; + public float projectionPoseRoll = 0f; public byte[] projectionData = null; @C.StereoMode public int stereoMode = Format.NO_VALUE; @@ -1850,6 +1889,21 @@ public final class MatroskaExtractor implements Extractor { } else if ("htc_video_rotA-270".equals(name)) { rotationDegrees = 270; } + if (projectionType == C.PROJECTION_RECTANGULAR + && Float.compare(projectionPoseYaw, 0f) == 0 + && Float.compare(projectionPosePitch, 0f) == 0) { + // The range of projectionPoseRoll is [-180, 180]. + if (Float.compare(projectionPoseRoll, 0f) == 0) { + rotationDegrees = 0; + } else if (Float.compare(projectionPosePitch, 90f) == 0) { + rotationDegrees = 90; + } else if (Float.compare(projectionPosePitch, -180f) == 0 + || Float.compare(projectionPosePitch, 180f) == 0) { + rotationDegrees = 180; + } else if (Float.compare(projectionPosePitch, -90f) == 0) { + rotationDegrees = 270; + } + } format = Format.createVideoSampleFormat( Integer.toString(trackId), From e448ecdff88ded7304ba7bb308b0fc114e0f3639 Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 2 Jan 2019 11:39:46 +0000 Subject: [PATCH 06/38] Remove stray word PiperOrigin-RevId: 227500707 --- extensions/rtmp/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/rtmp/README.md b/extensions/rtmp/README.md index b222bdabd9..3863dff965 100644 --- a/extensions/rtmp/README.md +++ b/extensions/rtmp/README.md @@ -39,7 +39,7 @@ either instantiated and injected from application code, or obtained from instances of `DataSource.Factory` that are instantiated and injected from application code. -`DefaultDataSource` will automatically use uses the RTMP extension whenever it's +`DefaultDataSource` will automatically use the RTMP extension whenever it's available. Hence if your application is using `DefaultDataSource` or `DefaultDataSourceFactory`, adding support for RTMP streams is as simple as adding a dependency to the RTMP extension as described above. No changes to your From 13638f1c29327bfec7d1e887771bcce8fb2bfd34 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Wed, 2 Jan 2019 15:04:20 +0000 Subject: [PATCH 07/38] Remove AdsLoader listeners on releasing ImaAdsLoader Issue: #4114 PiperOrigin-RevId: 227516509 --- RELEASENOTES.md | 5 +++++ .../com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java | 2 ++ 2 files changed, 7 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 1699f2c09b..b3e3d75ef6 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,5 +1,10 @@ # Release notes # +### 2.9.4 ### + +* IMA extension: Clear ads loader listeners on release + ([#4114](https://github.com/google/ExoPlayer/issues/4114)). + ### 2.9.3 ### * Captions: Support PNG subtitles in SMPTE-TT diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java index 6ca3bfd881..9b4b66125c 100644 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java +++ b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java @@ -597,6 +597,8 @@ public final class ImaAdsLoader adsManager.destroy(); adsManager = null; } + adsLoader.removeAdsLoadedListener(/* adsLoadedListener= */ this); + adsLoader.removeAdErrorListener(/* adErrorListener= */ this); imaPausedContent = false; imaAdState = IMA_AD_STATE_NONE; pendingAdLoadError = null; From 6373554d6a7f94ab8dba9b3f82bda928c7bd4dff Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 2 Jan 2019 15:43:11 +0000 Subject: [PATCH 08/38] Move syncFileDescriptor to use an experimental method PiperOrigin-RevId: 227520168 --- .../upstream/cache/CacheDataSink.java | 45 +++++++------------ .../android/exoplayer2/util/AtomicFile.java | 2 +- 2 files changed, 16 insertions(+), 31 deletions(-) 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..63bc47504b 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 @@ -43,8 +43,8 @@ public final class CacheDataSink implements DataSink { private final Cache cache; private final long maxCacheFileSize; private final int bufferSize; - private final boolean syncFileDescriptor; + private boolean syncFileDescriptor; private DataSpec dataSpec; private File file; private OutputStream outputStream; @@ -64,18 +64,6 @@ public final class CacheDataSink implements DataSink { } - /** - * Constructs a CacheDataSink using the {@link #DEFAULT_BUFFER_SIZE}. - * - * @param cache The cache into which data should be written. - * @param maxCacheFileSize The maximum size of a cache file, in bytes. If the sink is opened for - * a {@link DataSpec} whose size exceeds this value, then the data will be fragmented into - * multiple cache files. - */ - public CacheDataSink(Cache cache, long maxCacheFileSize) { - this(cache, maxCacheFileSize, DEFAULT_BUFFER_SIZE, true); - } - /** * Constructs a CacheDataSink using the {@link #DEFAULT_BUFFER_SIZE}. * @@ -83,10 +71,9 @@ public final class CacheDataSink implements DataSink { * @param maxCacheFileSize The maximum size of a cache file, in bytes. If the sink is opened for a * {@link DataSpec} whose size exceeds this value, then the data will be fragmented into * multiple cache files. - * @param syncFileDescriptor Whether file descriptors are sync'd when closing output streams. */ - public CacheDataSink(Cache cache, long maxCacheFileSize, boolean syncFileDescriptor) { - this(cache, maxCacheFileSize, DEFAULT_BUFFER_SIZE, syncFileDescriptor); + public CacheDataSink(Cache cache, long maxCacheFileSize) { + this(cache, maxCacheFileSize, DEFAULT_BUFFER_SIZE); } /** @@ -98,23 +85,21 @@ public final class CacheDataSink implements DataSink { * value disables buffering. */ public CacheDataSink(Cache cache, long maxCacheFileSize, int bufferSize) { - this(cache, maxCacheFileSize, bufferSize, true); - } - - /** - * @param cache The cache into which data should be written. - * @param maxCacheFileSize The maximum size of a cache file, in bytes. If the sink is opened for a - * {@link DataSpec} whose size exceeds this value, then the data will be fragmented into - * multiple cache files. - * @param bufferSize The buffer size in bytes for writing to a cache file. A zero or negative - * value disables buffering. - * @param syncFileDescriptor Whether file descriptors are sync'd when closing output streams. - */ - public CacheDataSink( - Cache cache, long maxCacheFileSize, int bufferSize, boolean syncFileDescriptor) { this.cache = Assertions.checkNotNull(cache); this.maxCacheFileSize = maxCacheFileSize; this.bufferSize = bufferSize; + syncFileDescriptor = true; + } + + /** + * Sets whether file descriptors are synced when closing output streams. + * + *

This method is experimental, and will be renamed or removed in a future release. It should + * only be called before the renderer is used. + * + * @param syncFileDescriptor Whether file descriptors are synced when closing output streams. + */ + public void experimental_setSyncFileDescriptor(boolean syncFileDescriptor) { this.syncFileDescriptor = syncFileDescriptor; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/AtomicFile.java b/library/core/src/main/java/com/google/android/exoplayer2/util/AtomicFile.java index 4bdee5ceea..2466d5a049 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/AtomicFile.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/AtomicFile.java @@ -29,7 +29,7 @@ import java.io.OutputStream; * has successfully completed. * *

Atomic file guarantees file integrity by ensuring that a file has been completely written and - * sync'd to disk before removing its backup. As long as the backup file exists, the original file + * synced to disk before removing its backup. As long as the backup file exists, the original file * is considered to be invalid (left over from a previous attempt to write the file). * *

Atomic file does not confer any file locking semantics. Do not use this class when the file From f11abbda97b80cfb01625ebb0da36b4d66243173 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Thu, 3 Jan 2019 09:04:10 +0000 Subject: [PATCH 09/38] Fix replacement char check PiperOrigin-RevId: 227646358 --- .../android/exoplayer2/extractor/mp4/MetadataUtil.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/MetadataUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/MetadataUtil.java index 670fe116a6..a4bdf7a268 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/MetadataUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/MetadataUtil.java @@ -103,6 +103,9 @@ import com.google.android.exoplayer2.util.Util; private static final String LANGUAGE_UNDEFINED = "und"; + private static final int TYPE_TOP_BYTE_COPYRIGHT = 0xA9; + private static final int TYPE_TOP_BYTE_REPLACEMENT = 0xFD; // Truncated value of \uFFFD. + private MetadataUtil() {} /** @@ -119,8 +122,7 @@ import com.google.android.exoplayer2.util.Util; int type = ilst.readInt(); int typeTopByte = (type >> 24) & 0xFF; try { - if (typeTopByte == '\u00A9' /* Copyright char */ - || typeTopByte == '\uFFFD' /* Replacement char */) { + if (typeTopByte == TYPE_TOP_BYTE_COPYRIGHT || typeTopByte == TYPE_TOP_BYTE_REPLACEMENT) { int shortType = type & 0x00FFFFFF; if (shortType == SHORT_TYPE_COMMENT) { return parseCommentAttribute(type, ilst); From fc168339035cfe091c75d5c169840be61d34170c Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 3 Jan 2019 09:36:35 +0000 Subject: [PATCH 10/38] Use Handler instead of ExoPlayer messages in ConcatenatingMediaSource ExoPlayer methods must not be called from any thread besides the specified app thread. Therefore we shouldn't use them here. Using a regular Handler instead is fully equivalent. Issue:#5240 PiperOrigin-RevId: 227650489 --- RELEASENOTES.md | 3 + .../source/ConcatenatingMediaSource.java | 97 +++++++++---------- 2 files changed, 47 insertions(+), 53 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index b3e3d75ef6..d92e4174f6 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -4,6 +4,9 @@ * IMA extension: Clear ads loader listeners on release ([#4114](https://github.com/google/ExoPlayer/issues/4114)). +* Fix issue where sending callbacks for playlist changes may cause problems + because of parallel player access + ([#5240](https://github.com/google/ExoPlayer/issues/5240)). ### 2.9.3 ### 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 03ccd56645..4e8cccaa23 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 @@ -16,13 +16,12 @@ package com.google.android.exoplayer2.source; import android.os.Handler; +import android.os.Message; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.Pair; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayer; -import com.google.android.exoplayer2.PlayerMessage; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.source.ConcatenatingMediaSource.MediaSourceHolder; import com.google.android.exoplayer2.source.ShuffleOrder.DefaultShuffleOrder; @@ -45,8 +44,7 @@ import java.util.Map; * during playback. It is valid for the same {@link MediaSource} instance to be present more than * once in the concatenation. Access to this class is thread-safe. */ -public class ConcatenatingMediaSource extends CompositeMediaSource - implements PlayerMessage.Target { +public class ConcatenatingMediaSource extends CompositeMediaSource { private static final int MSG_ADD = 0; private static final int MSG_REMOVE = 1; @@ -68,8 +66,8 @@ public class ConcatenatingMediaSource extends CompositeMediaSource(index, mediaSourceHolders, actionOnCompletion)) - .send(); + if (playbackThreadHandler != null && !mediaSources.isEmpty()) { + playbackThreadHandler + .obtainMessage(MSG_ADD, new MessageData<>(index, mediaSourceHolders, actionOnCompletion)) + .sendToTarget(); } else if (actionOnCompletion != null) { actionOnCompletion.run(); } @@ -328,12 +324,10 @@ public class ConcatenatingMediaSource extends CompositeMediaSource(fromIndex, toIndex, actionOnCompletion)) - .send(); + if (playbackThreadHandler != null) { + playbackThreadHandler + .obtainMessage(MSG_REMOVE, new MessageData<>(fromIndex, toIndex, actionOnCompletion)) + .sendToTarget(); } else if (actionOnCompletion != null) { actionOnCompletion.run(); } @@ -371,12 +365,10 @@ public class ConcatenatingMediaSource extends CompositeMediaSource(currentIndex, newIndex, actionOnCompletion)) - .send(); + if (playbackThreadHandler != null) { + playbackThreadHandler + .obtainMessage(MSG_MOVE, new MessageData<>(currentIndex, newIndex, actionOnCompletion)) + .sendToTarget(); } else if (actionOnCompletion != null) { actionOnCompletion.run(); } @@ -430,8 +422,8 @@ public class ConcatenatingMediaSource extends CompositeMediaSource(/* index= */ 0, shuffleOrder, actionOnCompletion)) - .send(); + playbackThreadHandler + .obtainMessage( + MSG_SET_SHUFFLE_ORDER, + new MessageData<>(/* index= */ 0, shuffleOrder, actionOnCompletion)) + .sendToTarget(); } else { this.shuffleOrder = shuffleOrder.getLength() > 0 ? shuffleOrder.cloneAndClear() : shuffleOrder; @@ -465,8 +457,8 @@ public class ConcatenatingMediaSource extends CompositeMediaSource> addMessage = - (MessageData>) Util.castNonNull(message); + (MessageData>) Util.castNonNull(msg.obj); shuffleOrder = shuffleOrder.cloneAndInsert(addMessage.index, addMessage.customData.size()); addMediaSourcesInternal(addMessage.index, addMessage.customData); scheduleListenerNotification(addMessage.actionOnCompletion); break; case MSG_REMOVE: - MessageData removeMessage = (MessageData) Util.castNonNull(message); + MessageData removeMessage = (MessageData) Util.castNonNull(msg.obj); int fromIndex = removeMessage.index; int toIndex = removeMessage.customData; if (fromIndex == 0 && toIndex == shuffleOrder.getLength()) { @@ -587,7 +577,7 @@ public class ConcatenatingMediaSource extends CompositeMediaSource moveMessage = (MessageData) Util.castNonNull(message); + MessageData moveMessage = (MessageData) Util.castNonNull(msg.obj); shuffleOrder = shuffleOrder.cloneAndRemove(moveMessage.index, moveMessage.index + 1); shuffleOrder = shuffleOrder.cloneAndInsert(moveMessage.customData, 1); moveMediaSourceInternal(moveMessage.index, moveMessage.customData); @@ -595,7 +585,7 @@ public class ConcatenatingMediaSource extends CompositeMediaSource shuffleOrderMessage = - (MessageData) Util.castNonNull(message); + (MessageData) Util.castNonNull(msg.obj); shuffleOrder = shuffleOrderMessage.customData; scheduleListenerNotification(shuffleOrderMessage.actionOnCompletion); break; @@ -603,8 +593,8 @@ public class ConcatenatingMediaSource extends CompositeMediaSource actionsOnCompletion = (List) Util.castNonNull(message); - Handler handler = Assertions.checkNotNull(playerApplicationHandler); + List actionsOnCompletion = (List) Util.castNonNull(msg.obj); + Handler handler = Assertions.checkNotNull(applicationThreadHandler); for (int i = 0; i < actionsOnCompletion.size(); i++) { handler.post(actionsOnCompletion.get(i)); } @@ -612,11 +602,14 @@ public class ConcatenatingMediaSource extends CompositeMediaSource Date: Thu, 3 Jan 2019 12:26:03 +0000 Subject: [PATCH 11/38] Fix bug when calculating EOF position in mp4 sniffing PiperOrigin-RevId: 227668426 --- .../com/google/android/exoplayer2/extractor/mp4/Sniffer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Sniffer.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Sniffer.java index 021c9de654..dce00fa824 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Sniffer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Sniffer.java @@ -114,7 +114,7 @@ import java.io.IOException; // The atom extends to the end of the file. long endPosition = input.getLength(); if (endPosition != C.LENGTH_UNSET) { - atomSize = endPosition - input.getPosition() + headerSize; + atomSize = endPosition - input.getPeekPosition() + headerSize; } } From 61a7750f235b5a3fe457a8fbbbe42421d8e0bf18 Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Thu, 3 Jan 2019 13:29:00 +0000 Subject: [PATCH 12/38] Enable setOutputSurfaceWorkaround for Huawei P10 lite Issue:#5312 PiperOrigin-RevId: 227673949 --- .../android/exoplayer2/video/MediaCodecVideoRenderer.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java index 40b25c2b2e..9d0aae4fcf 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java @@ -1322,7 +1322,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { // https://github.com/google/ExoPlayer/issues/4315, // https://github.com/google/ExoPlayer/issues/4419, // https://github.com/google/ExoPlayer/issues/4460, - // https://github.com/google/ExoPlayer/issues/4468. + // https://github.com/google/ExoPlayer/issues/4468, + // https://github.com/google/ExoPlayer/issues/5312. switch (Util.DEVICE) { case "1601": case "1713": @@ -1378,6 +1379,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { case "HWBLN-H": case "HWCAM-H": case "HWVNS-H": + case "HWWAS-H": case "i9031": case "iball8735_9806": case "Infinix-X572": From 4847889a2f200cc7070cf51fb10fe52ffea57fcb Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Thu, 3 Jan 2019 16:02:48 +0000 Subject: [PATCH 13/38] Prevent IllegalStateException in Mp4 sniffing If a negative value is read, sniffing should just fail. PiperOrigin-RevId: 227689568 --- .../com/google/android/exoplayer2/extractor/mp4/Sniffer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Sniffer.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Sniffer.java index dce00fa824..657a955ab4 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Sniffer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Sniffer.java @@ -109,7 +109,7 @@ import java.io.IOException; headerSize = Atom.LONG_HEADER_SIZE; input.peekFully(buffer.data, Atom.HEADER_SIZE, Atom.LONG_HEADER_SIZE - Atom.HEADER_SIZE); buffer.setLimit(Atom.LONG_HEADER_SIZE); - atomSize = buffer.readUnsignedLongToLong(); + atomSize = buffer.readLong(); } else if (atomSize == Atom.EXTENDS_TO_END_SIZE) { // The atom extends to the end of the file. long endPosition = input.getLength(); From a568fbdd51111f2790c88230d148ed0956912030 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Fri, 4 Jan 2019 08:27:21 +0000 Subject: [PATCH 14/38] Parse frame rate from 'mdta' metadata PiperOrigin-RevId: 227813461 --- .../com/google/android/exoplayer2/Format.java | 31 +++++ .../exoplayer2/extractor/mp4/Atom.java | 5 +- .../exoplayer2/extractor/mp4/AtomParsers.java | 88 ++++++++++++-- .../extractor/mp4/MdtaMetadataEntry.java | 114 ++++++++++++++++++ .../extractor/mp4/MetadataUtil.java | 114 +++++++++++++++--- .../extractor/mp4/Mp4Extractor.java | 69 ++++++----- .../extractor/mp4/MdtaMetadataEntryTest.java | 44 +++++++ 7 files changed, 406 insertions(+), 59 deletions(-) create mode 100644 library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/MdtaMetadataEntry.java create mode 100644 library/core/src/test/java/com/google/android/exoplayer2/extractor/mp4/MdtaMetadataEntryTest.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Format.java b/library/core/src/main/java/com/google/android/exoplayer2/Format.java index 3456fc39a2..d40ae6eccd 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Format.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Format.java @@ -1181,6 +1181,37 @@ public final class Format implements Parcelable { metadata); } + public Format copyWithFrameRate(float frameRate) { + return new Format( + id, + label, + containerMimeType, + sampleMimeType, + codecs, + bitrate, + maxInputSize, + width, + height, + frameRate, + rotationDegrees, + pixelWidthHeightRatio, + projectionData, + stereoMode, + colorInfo, + channelCount, + sampleRate, + pcmEncoding, + encoderDelay, + encoderPadding, + selectionFlags, + language, + accessibilityChannel, + subsampleOffsetUs, + initializationData, + drmInitData, + metadata); + } + public Format copyWithDrmInitData(@Nullable DrmInitData drmInitData) { return new Format( id, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java index f51c97389b..8d78337617 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java @@ -22,8 +22,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -@SuppressWarnings("ConstantField") -/* package*/ abstract class Atom { +@SuppressWarnings({"ConstantField", "ConstantCaseForConstants"}) +/* package */ abstract class Atom { /** * Size of an atom header, in bytes. @@ -130,6 +130,7 @@ import java.util.List; public static final int TYPE_sawb = Util.getIntegerCodeForString("sawb"); public static final int TYPE_udta = Util.getIntegerCodeForString("udta"); public static final int TYPE_meta = Util.getIntegerCodeForString("meta"); + public static final int TYPE_keys = Util.getIntegerCodeForString("keys"); public static final int TYPE_ilst = Util.getIntegerCodeForString("ilst"); public static final int TYPE_mean = Util.getIntegerCodeForString("mean"); public static final int TYPE_name = Util.getIntegerCodeForString("name"); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java index d085156f2b..008a155d1f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java @@ -17,6 +17,7 @@ package com.google.android.exoplayer2.extractor.mp4; import static com.google.android.exoplayer2.util.MimeTypes.getMimeTypeFromMp4ObjectType; +import android.support.annotation.Nullable; import android.util.Pair; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; @@ -39,7 +40,7 @@ import java.util.Collections; import java.util.List; /** Utility methods for parsing MP4 format atom payloads according to ISO 14496-12. */ -@SuppressWarnings("ConstantField") +@SuppressWarnings({"ConstantField", "ConstantCaseForConstants"}) /* package */ final class AtomParsers { private static final String TAG = "AtomParsers"; @@ -51,6 +52,7 @@ import java.util.List; private static final int TYPE_subt = Util.getIntegerCodeForString("subt"); private static final int TYPE_clcp = Util.getIntegerCodeForString("clcp"); private static final int TYPE_meta = Util.getIntegerCodeForString("meta"); + private static final int TYPE_mdta = Util.getIntegerCodeForString("mdta"); /** * The threshold number of samples to trim from the start/end of an audio track when applying an @@ -77,7 +79,7 @@ import java.util.List; DrmInitData drmInitData, boolean ignoreEditLists, boolean isQuickTime) throws ParserException { Atom.ContainerAtom mdia = trak.getContainerAtomOfType(Atom.TYPE_mdia); - int trackType = parseHdlr(mdia.getLeafAtomOfType(Atom.TYPE_hdlr).data); + int trackType = getTrackTypeForHdlr(parseHdlr(mdia.getLeafAtomOfType(Atom.TYPE_hdlr).data)); if (trackType == C.TRACK_TYPE_UNKNOWN) { return null; } @@ -485,6 +487,7 @@ import java.util.List; * @param isQuickTime True for QuickTime media. False otherwise. * @return Parsed metadata, or null. */ + @Nullable public static Metadata parseUdta(Atom.LeafAtom udtaAtom, boolean isQuickTime) { if (isQuickTime) { // Meta boxes are regular boxes rather than full boxes in QuickTime. For now, don't try and @@ -499,14 +502,69 @@ import java.util.List; int atomType = udtaData.readInt(); if (atomType == Atom.TYPE_meta) { udtaData.setPosition(atomPosition); - return parseMetaAtom(udtaData, atomPosition + atomSize); + return parseUdtaMeta(udtaData, atomPosition + atomSize); } - udtaData.skipBytes(atomSize - Atom.HEADER_SIZE); + udtaData.setPosition(atomPosition + atomSize); } return null; } - private static Metadata parseMetaAtom(ParsableByteArray meta, int limit) { + /** + * Parses a metadata meta atom if it contains metadata with handler 'mdta'. + * + * @param meta The metadata atom to decode. + * @return Parsed metadata, or null. + */ + @Nullable + public static Metadata parseMdtaFromMeta(Atom.ContainerAtom meta) { + Atom.LeafAtom hdlrAtom = meta.getLeafAtomOfType(Atom.TYPE_hdlr); + Atom.LeafAtom keysAtom = meta.getLeafAtomOfType(Atom.TYPE_keys); + Atom.LeafAtom ilstAtom = meta.getLeafAtomOfType(Atom.TYPE_ilst); + if (hdlrAtom == null + || keysAtom == null + || ilstAtom == null + || AtomParsers.parseHdlr(hdlrAtom.data) != TYPE_mdta) { + // There isn't enough information to parse the metadata, or the handler type is unexpected. + return null; + } + + // Parse metadata keys. + ParsableByteArray keys = keysAtom.data; + keys.setPosition(Atom.FULL_HEADER_SIZE); + int entryCount = keys.readInt(); + String[] keyNames = new String[entryCount]; + for (int i = 0; i < entryCount; i++) { + int entrySize = keys.readInt(); + keys.skipBytes(4); // keyNamespace + int keySize = entrySize - 8; + keyNames[i] = keys.readString(keySize); + } + + // Parse metadata items. + ParsableByteArray ilst = ilstAtom.data; + ilst.setPosition(Atom.HEADER_SIZE); + ArrayList entries = new ArrayList<>(); + while (ilst.bytesLeft() > Atom.HEADER_SIZE) { + int atomPosition = ilst.getPosition(); + int atomSize = ilst.readInt(); + int keyIndex = ilst.readInt() - 1; + if (keyIndex >= 0 && keyIndex < keyNames.length) { + String key = keyNames[keyIndex]; + Metadata.Entry entry = + MetadataUtil.parseMdtaMetadataEntryFromIlst(ilst, atomPosition + atomSize, key); + if (entry != null) { + entries.add(entry); + } + } else { + Log.w(TAG, "Skipped metadata with unknown key index: " + keyIndex); + } + ilst.setPosition(atomPosition + atomSize); + } + return entries.isEmpty() ? null : new Metadata(entries); + } + + @Nullable + private static Metadata parseUdtaMeta(ParsableByteArray meta, int limit) { meta.skipBytes(Atom.FULL_HEADER_SIZE); while (meta.getPosition() < limit) { int atomPosition = meta.getPosition(); @@ -516,11 +574,12 @@ import java.util.List; meta.setPosition(atomPosition); return parseIlst(meta, atomPosition + atomSize); } - meta.skipBytes(atomSize - Atom.HEADER_SIZE); + meta.setPosition(atomPosition + atomSize); } return null; } + @Nullable private static Metadata parseIlst(ParsableByteArray ilst, int limit) { ilst.skipBytes(Atom.HEADER_SIZE); ArrayList entries = new ArrayList<>(); @@ -610,19 +669,22 @@ import java.util.List; * Parses an hdlr atom. * * @param hdlr The hdlr atom to decode. - * @return The track type. + * @return The handler value. */ private static int parseHdlr(ParsableByteArray hdlr) { hdlr.setPosition(Atom.FULL_HEADER_SIZE + 4); - int trackType = hdlr.readInt(); - if (trackType == TYPE_soun) { + return hdlr.readInt(); + } + + /** Returns the track type for a given handler value. */ + private static int getTrackTypeForHdlr(int hdlr) { + if (hdlr == TYPE_soun) { return C.TRACK_TYPE_AUDIO; - } else if (trackType == TYPE_vide) { + } else if (hdlr == TYPE_vide) { return C.TRACK_TYPE_VIDEO; - } else if (trackType == TYPE_text || trackType == TYPE_sbtl || trackType == TYPE_subt - || trackType == TYPE_clcp) { + } else if (hdlr == TYPE_text || hdlr == TYPE_sbtl || hdlr == TYPE_subt || hdlr == TYPE_clcp) { return C.TRACK_TYPE_TEXT; - } else if (trackType == TYPE_meta) { + } else if (hdlr == TYPE_meta) { return C.TRACK_TYPE_METADATA; } else { return C.TRACK_TYPE_UNKNOWN; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/MdtaMetadataEntry.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/MdtaMetadataEntry.java new file mode 100644 index 0000000000..517a0a51d5 --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/MdtaMetadataEntry.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.extractor.mp4; + +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.Nullable; +import com.google.android.exoplayer2.metadata.Metadata; +import java.util.Arrays; + +/** + * Stores extensible metadata with handler type 'mdta'. See also the QuickTime File Format + * Specification. + */ +public final class MdtaMetadataEntry implements Metadata.Entry { + + /** The metadata key name. */ + public final String key; + /** The payload. The interpretation of the value depends on {@link #typeIndicator}. */ + public final byte[] value; + /** The four byte locale indicator. */ + public final int localeIndicator; + /** The four byte type indicator. */ + public final int typeIndicator; + + /** Creates a new metadata entry for the specified metadata key/value. */ + public MdtaMetadataEntry(String key, byte[] value, int localeIndicator, int typeIndicator) { + this.key = key; + this.value = value; + this.localeIndicator = localeIndicator; + this.typeIndicator = typeIndicator; + } + + private MdtaMetadataEntry(Parcel in) { + key = in.readString(); + value = new byte[in.readInt()]; + in.readByteArray(value); + localeIndicator = in.readInt(); + typeIndicator = in.readInt(); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + MdtaMetadataEntry other = (MdtaMetadataEntry) obj; + return key.equals(other.key) + && Arrays.equals(value, other.value) + && localeIndicator == other.localeIndicator + && typeIndicator == other.typeIndicator; + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + key.hashCode(); + result = 31 * result + Arrays.hashCode(value); + result = 31 * result + localeIndicator; + result = 31 * result + typeIndicator; + return result; + } + + @Override + public String toString() { + return "mdta: key=" + key; + } + + // Parcelable implementation. + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(key); + dest.writeInt(value.length); + dest.writeByteArray(value); + dest.writeInt(localeIndicator); + dest.writeInt(typeIndicator); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + + @Override + public MdtaMetadataEntry createFromParcel(Parcel in) { + return new MdtaMetadataEntry(in); + } + + @Override + public MdtaMetadataEntry[] newArray(int size) { + return new MdtaMetadataEntry[size]; + } + }; +} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/MetadataUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/MetadataUtil.java index a4bdf7a268..02522897ce 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/MetadataUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/MetadataUtil.java @@ -16,6 +16,9 @@ package com.google.android.exoplayer2.extractor.mp4; import android.support.annotation.Nullable; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.extractor.GaplessInfoHolder; import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.id3.ApicFrame; import com.google.android.exoplayer2.metadata.id3.CommentFrame; @@ -25,10 +28,9 @@ import com.google.android.exoplayer2.metadata.id3.TextInformationFrame; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.Util; +import java.nio.ByteBuffer; -/** - * Parses metadata items stored in ilst atoms. - */ +/** Utilities for handling metadata in MP4. */ /* package */ final class MetadataUtil { private static final String TAG = "MetadataUtil"; @@ -106,17 +108,64 @@ import com.google.android.exoplayer2.util.Util; private static final int TYPE_TOP_BYTE_COPYRIGHT = 0xA9; private static final int TYPE_TOP_BYTE_REPLACEMENT = 0xFD; // Truncated value of \uFFFD. + private static final String MDTA_KEY_ANDROID_CAPTURE_FPS = "com.android.capture.fps"; + private static final int MDTA_TYPE_INDICATOR_FLOAT = 23; + private MetadataUtil() {} /** - * Parses a single ilst element from a {@link ParsableByteArray}. The element is read starting - * from the current position of the {@link ParsableByteArray}, and the position is advanced by the - * size of the element. The position is advanced even if the element's type is unrecognized. + * Returns a {@link Format} that is the same as the input format but includes information from the + * specified sources of metadata. + */ + public static Format getFormatWithMetadata( + int trackType, + Format format, + @Nullable Metadata udtaMetadata, + @Nullable Metadata mdtaMetadata, + GaplessInfoHolder gaplessInfoHolder) { + if (trackType == C.TRACK_TYPE_AUDIO) { + if (gaplessInfoHolder.hasGaplessInfo()) { + format = + format.copyWithGaplessInfo( + gaplessInfoHolder.encoderDelay, gaplessInfoHolder.encoderPadding); + } + // We assume all udta metadata is associated with the audio track. + if (udtaMetadata != null) { + format = format.copyWithMetadata(udtaMetadata); + } + } else if (trackType == C.TRACK_TYPE_VIDEO && mdtaMetadata != null) { + // Populate only metadata keys that are known to be specific to video. + for (int i = 0; i < mdtaMetadata.length(); i++) { + Metadata.Entry entry = mdtaMetadata.get(i); + if (entry instanceof MdtaMetadataEntry) { + MdtaMetadataEntry mdtaMetadataEntry = (MdtaMetadataEntry) entry; + if (MDTA_KEY_ANDROID_CAPTURE_FPS.equals(mdtaMetadataEntry.key) + && mdtaMetadataEntry.typeIndicator == MDTA_TYPE_INDICATOR_FLOAT) { + try { + float fps = ByteBuffer.wrap(mdtaMetadataEntry.value).asFloatBuffer().get(); + format = format.copyWithFrameRate(fps); + format = format.copyWithMetadata(new Metadata(mdtaMetadataEntry)); + } catch (NumberFormatException e) { + Log.w(TAG, "Ignoring invalid framerate"); + } + } + } + } + } + return format; + } + + /** + * Parses a single userdata ilst element from a {@link ParsableByteArray}. The element is read + * starting from the current position of the {@link ParsableByteArray}, and the position is + * advanced by the size of the element. The position is advanced even if the element's type is + * unrecognized. * * @param ilst Holds the data to be parsed. * @return The parsed element, or null if the element's type was not recognized. */ - public static @Nullable Metadata.Entry parseIlstElement(ParsableByteArray ilst) { + @Nullable + public static Metadata.Entry parseIlstElement(ParsableByteArray ilst) { int position = ilst.getPosition(); int endPosition = position + ilst.readInt(); int type = ilst.readInt(); @@ -187,7 +236,36 @@ import com.google.android.exoplayer2.util.Util; } } - private static @Nullable TextInformationFrame parseTextAttribute( + /** + * Parses an 'mdta' metadata entry starting at the current position in an ilst box. + * + * @param ilst The ilst box. + * @param endPosition The end position of the entry in the ilst box. + * @param key The mdta metadata entry key for the entry. + * @return The parsed element, or null if the entry wasn't recognized. + */ + @Nullable + public static MdtaMetadataEntry parseMdtaMetadataEntryFromIlst( + ParsableByteArray ilst, int endPosition, String key) { + int atomPosition; + while ((atomPosition = ilst.getPosition()) < endPosition) { + int atomSize = ilst.readInt(); + int atomType = ilst.readInt(); + if (atomType == Atom.TYPE_data) { + int typeIndicator = ilst.readInt(); + int localeIndicator = ilst.readInt(); + int dataSize = atomSize - 16; + byte[] value = new byte[dataSize]; + ilst.readBytes(value, 0, dataSize); + return new MdtaMetadataEntry(key, value, localeIndicator, typeIndicator); + } + ilst.setPosition(atomPosition + atomSize); + } + return null; + } + + @Nullable + private static TextInformationFrame parseTextAttribute( int type, String id, ParsableByteArray data) { int atomSize = data.readInt(); int atomType = data.readInt(); @@ -200,7 +278,8 @@ import com.google.android.exoplayer2.util.Util; return null; } - private static @Nullable CommentFrame parseCommentAttribute(int type, ParsableByteArray data) { + @Nullable + private static CommentFrame parseCommentAttribute(int type, ParsableByteArray data) { int atomSize = data.readInt(); int atomType = data.readInt(); if (atomType == Atom.TYPE_data) { @@ -212,7 +291,8 @@ import com.google.android.exoplayer2.util.Util; return null; } - private static @Nullable Id3Frame parseUint8Attribute( + @Nullable + private static Id3Frame parseUint8Attribute( int type, String id, ParsableByteArray data, @@ -231,7 +311,8 @@ import com.google.android.exoplayer2.util.Util; return null; } - private static @Nullable TextInformationFrame parseIndexAndCountAttribute( + @Nullable + private static TextInformationFrame parseIndexAndCountAttribute( int type, String attributeName, ParsableByteArray data) { int atomSize = data.readInt(); int atomType = data.readInt(); @@ -251,8 +332,8 @@ import com.google.android.exoplayer2.util.Util; return null; } - private static @Nullable TextInformationFrame parseStandardGenreAttribute( - ParsableByteArray data) { + @Nullable + private static TextInformationFrame parseStandardGenreAttribute(ParsableByteArray data) { int genreCode = parseUint8AttributeValue(data); String genreString = (0 < genreCode && genreCode <= STANDARD_GENRES.length) ? STANDARD_GENRES[genreCode - 1] : null; @@ -263,7 +344,8 @@ import com.google.android.exoplayer2.util.Util; return null; } - private static @Nullable ApicFrame parseCoverArt(ParsableByteArray data) { + @Nullable + private static ApicFrame parseCoverArt(ParsableByteArray data) { int atomSize = data.readInt(); int atomType = data.readInt(); if (atomType == Atom.TYPE_data) { @@ -287,8 +369,8 @@ import com.google.android.exoplayer2.util.Util; return null; } - private static @Nullable Id3Frame parseInternalAttribute( - ParsableByteArray data, int endPosition) { + @Nullable + private static Id3Frame parseInternalAttribute(ParsableByteArray data, int endPosition) { String domain = null; String name = null; int dataAtomPosition = -1; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java index 17c82c2c5b..eec48c23cb 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java @@ -75,7 +75,7 @@ public final class Mp4Extractor implements Extractor, SeekMap { private static final int STATE_READING_ATOM_PAYLOAD = 1; private static final int STATE_READING_SAMPLE = 2; - // Brand stored in the ftyp atom for QuickTime media. + /** Brand stored in the ftyp atom for QuickTime media. */ private static final int BRAND_QUICKTIME = Util.getIntegerCodeForString("qt "); /** @@ -377,15 +377,21 @@ public final class Mp4Extractor implements Extractor, SeekMap { long durationUs = C.TIME_UNSET; List tracks = new ArrayList<>(); - Metadata metadata = null; + // Process metadata. + Metadata udtaMetadata = null; GaplessInfoHolder gaplessInfoHolder = new GaplessInfoHolder(); Atom.LeafAtom udta = moov.getLeafAtomOfType(Atom.TYPE_udta); if (udta != null) { - metadata = AtomParsers.parseUdta(udta, isQuickTime); - if (metadata != null) { - gaplessInfoHolder.setFromMetadata(metadata); + udtaMetadata = AtomParsers.parseUdta(udta, isQuickTime); + if (udtaMetadata != null) { + gaplessInfoHolder.setFromMetadata(udtaMetadata); } } + Metadata mdtaMetadata = null; + Atom.ContainerAtom meta = moov.getContainerAtomOfType(Atom.TYPE_meta); + if (meta != null) { + mdtaMetadata = AtomParsers.parseMdtaFromMeta(meta); + } boolean ignoreEditLists = (flags & FLAG_WORKAROUND_IGNORE_EDIT_LISTS) != 0; ArrayList trackSampleTables = @@ -401,15 +407,9 @@ public final class Mp4Extractor implements Extractor, SeekMap { // Allow ten source samples per output sample, like the platform extractor. int maxInputSize = trackSampleTable.maximumSize + 3 * 10; Format format = track.format.copyWithMaxInputSize(maxInputSize); - if (track.type == C.TRACK_TYPE_AUDIO) { - if (gaplessInfoHolder.hasGaplessInfo()) { - format = format.copyWithGaplessInfo(gaplessInfoHolder.encoderDelay, - gaplessInfoHolder.encoderPadding); - } - if (metadata != null) { - format = format.copyWithMetadata(metadata); - } - } + format = + MetadataUtil.getFormatWithMetadata( + track.type, format, udtaMetadata, mdtaMetadata, gaplessInfoHolder); mp4Track.trackOutput.format(format); durationUs = @@ -716,24 +716,37 @@ public final class Mp4Extractor implements Extractor, SeekMap { return false; } - /** - * Returns whether the extractor should decode a leaf atom with type {@code atom}. - */ + /** Returns whether the extractor should decode a leaf atom with type {@code atom}. */ private static boolean shouldParseLeafAtom(int atom) { - return atom == Atom.TYPE_mdhd || atom == Atom.TYPE_mvhd || atom == Atom.TYPE_hdlr - || atom == Atom.TYPE_stsd || atom == Atom.TYPE_stts || atom == Atom.TYPE_stss - || atom == Atom.TYPE_ctts || atom == Atom.TYPE_elst || atom == Atom.TYPE_stsc - || atom == Atom.TYPE_stsz || atom == Atom.TYPE_stz2 || atom == Atom.TYPE_stco - || atom == Atom.TYPE_co64 || atom == Atom.TYPE_tkhd || atom == Atom.TYPE_ftyp - || atom == Atom.TYPE_udta; + return atom == Atom.TYPE_mdhd + || atom == Atom.TYPE_mvhd + || atom == Atom.TYPE_hdlr + || atom == Atom.TYPE_stsd + || atom == Atom.TYPE_stts + || atom == Atom.TYPE_stss + || atom == Atom.TYPE_ctts + || atom == Atom.TYPE_elst + || atom == Atom.TYPE_stsc + || atom == Atom.TYPE_stsz + || atom == Atom.TYPE_stz2 + || atom == Atom.TYPE_stco + || atom == Atom.TYPE_co64 + || atom == Atom.TYPE_tkhd + || atom == Atom.TYPE_ftyp + || atom == Atom.TYPE_udta + || atom == Atom.TYPE_keys + || atom == Atom.TYPE_ilst; } - /** - * Returns whether the extractor should decode a container atom with type {@code atom}. - */ + /** Returns whether the extractor should decode a container atom with type {@code atom}. */ private static boolean shouldParseContainerAtom(int atom) { - return atom == Atom.TYPE_moov || atom == Atom.TYPE_trak || atom == Atom.TYPE_mdia - || atom == Atom.TYPE_minf || atom == Atom.TYPE_stbl || atom == Atom.TYPE_edts; + return atom == Atom.TYPE_moov + || atom == Atom.TYPE_trak + || atom == Atom.TYPE_mdia + || atom == Atom.TYPE_minf + || atom == Atom.TYPE_stbl + || atom == Atom.TYPE_edts + || atom == Atom.TYPE_meta; } private static final class Mp4Track { diff --git a/library/core/src/test/java/com/google/android/exoplayer2/extractor/mp4/MdtaMetadataEntryTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/mp4/MdtaMetadataEntryTest.java new file mode 100644 index 0000000000..2f81836540 --- /dev/null +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/mp4/MdtaMetadataEntryTest.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.extractor.mp4; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.Parcel; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +/** Test for {@link MdtaMetadataEntry}. */ +@RunWith(RobolectricTestRunner.class) +public final class MdtaMetadataEntryTest { + + @Test + public void testParcelable() { + MdtaMetadataEntry mdtaMetadataEntryToParcel = + new MdtaMetadataEntry("test", new byte[] {1, 2}, 3, 4); + + Parcel parcel = Parcel.obtain(); + mdtaMetadataEntryToParcel.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + + MdtaMetadataEntry mdtaMetadataEntryFromParcel = + MdtaMetadataEntry.CREATOR.createFromParcel(parcel); + assertThat(mdtaMetadataEntryFromParcel).isEqualTo(mdtaMetadataEntryToParcel); + + parcel.recycle(); + } +} From 189e3c31be13ca83d8f0f935d374c11099953420 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Fri, 4 Jan 2019 09:51:01 +0000 Subject: [PATCH 15/38] Fix nullness issue PiperOrigin-RevId: 227822937 --- .../android/exoplayer2/extractor/mp4/MdtaMetadataEntry.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/MdtaMetadataEntry.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/MdtaMetadataEntry.java index 517a0a51d5..b458a8f0f4 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/MdtaMetadataEntry.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/MdtaMetadataEntry.java @@ -19,6 +19,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.support.annotation.Nullable; import com.google.android.exoplayer2.metadata.Metadata; +import com.google.android.exoplayer2.util.Util; import java.util.Arrays; /** @@ -45,7 +46,7 @@ public final class MdtaMetadataEntry implements Metadata.Entry { } private MdtaMetadataEntry(Parcel in) { - key = in.readString(); + key = Util.castNonNull(in.readString()); value = new byte[in.readInt()]; in.readByteArray(value); localeIndicator = in.readInt(); From 99bc13221c7448c9c2cb736e8b6c495c5a27db3f Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Fri, 4 Jan 2019 15:07:13 +0000 Subject: [PATCH 16/38] Treat AVERROR_INVALIDDATA as non-fatal Also configure the FFmpeg context to ignore errors as far as possible (this appears to have an effect only for certain decoders). Issue: #5293 PiperOrigin-RevId: 227851397 --- RELEASENOTES.md | 2 ++ .../exoplayer2/ext/ffmpeg/FfmpegDecoder.java | 14 ++++++++++++-- extensions/ffmpeg/src/main/jni/ffmpeg_jni.cc | 10 ++++++++-- .../audio/SimpleDecoderAudioRenderer.java | 5 ++++- 4 files changed, 26 insertions(+), 5 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index d92e4174f6..15e61d98ad 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -4,6 +4,8 @@ * IMA extension: Clear ads loader listeners on release ([#4114](https://github.com/google/ExoPlayer/issues/4114)). +* FFmpeg extension: Treat invalid data errors as non-fatal to match the behavior + of MediaCodec ([#5293](https://github.com/google/ExoPlayer/issues/5293)). * Fix issue where sending callbacks for playlist changes may cause problems because of parallel player access ([#5240](https://github.com/google/ExoPlayer/issues/5240)). diff --git a/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegDecoder.java b/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegDecoder.java index 6f3c623f3f..c5b76002fa 100644 --- a/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegDecoder.java +++ b/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegDecoder.java @@ -37,6 +37,10 @@ import java.util.List; private static final int OUTPUT_BUFFER_SIZE_16BIT = 65536; private static final int OUTPUT_BUFFER_SIZE_32BIT = OUTPUT_BUFFER_SIZE_16BIT * 2; + // Error codes matching ffmpeg_jni.cc. + private static final int DECODER_ERROR_INVALID_DATA = -1; + private static final int DECODER_ERROR_OTHER = -2; + private final String codecName; private final @Nullable byte[] extraData; private final @C.Encoding int encoding; @@ -106,8 +110,14 @@ import java.util.List; int inputSize = inputData.limit(); ByteBuffer outputData = outputBuffer.init(inputBuffer.timeUs, outputBufferSize); int result = ffmpegDecode(nativeContext, inputData, inputSize, outputData, outputBufferSize); - if (result < 0) { - return new FfmpegDecoderException("Error decoding (see logcat). Code: " + result); + if (result == DECODER_ERROR_INVALID_DATA) { + // Treat invalid data errors as non-fatal to match the behavior of MediaCodec. No output will + // be produced for this buffer, so mark it as decode-only to ensure that the audio sink's + // position is reset when more audio is produced. + outputBuffer.setFlags(C.BUFFER_FLAG_DECODE_ONLY); + return null; + } else if (result == DECODER_ERROR_OTHER) { + return new FfmpegDecoderException("Error decoding (see logcat)."); } if (!hasOutputFormat) { channelCount = ffmpegGetChannelCount(nativeContext); diff --git a/extensions/ffmpeg/src/main/jni/ffmpeg_jni.cc b/extensions/ffmpeg/src/main/jni/ffmpeg_jni.cc index 87579ebb9a..dcd4560e4a 100644 --- a/extensions/ffmpeg/src/main/jni/ffmpeg_jni.cc +++ b/extensions/ffmpeg/src/main/jni/ffmpeg_jni.cc @@ -63,6 +63,10 @@ static const AVSampleFormat OUTPUT_FORMAT_PCM_16BIT = AV_SAMPLE_FMT_S16; // Output format corresponding to AudioFormat.ENCODING_PCM_FLOAT. static const AVSampleFormat OUTPUT_FORMAT_PCM_FLOAT = AV_SAMPLE_FMT_FLT; +// Error codes matching FfmpegDecoder.java. +static const int DECODER_ERROR_INVALID_DATA = -1; +static const int DECODER_ERROR_OTHER = -2; + /** * Returns the AVCodec with the specified name, or NULL if it is not available. */ @@ -79,7 +83,7 @@ AVCodecContext *createContext(JNIEnv *env, AVCodec *codec, jbyteArray extraData, /** * Decodes the packet into the output buffer, returning the number of bytes - * written, or a negative value in the case of an error. + * written, or a negative DECODER_ERROR constant value in the case of an error. */ int decodePacket(AVCodecContext *context, AVPacket *packet, uint8_t *outputBuffer, int outputSize); @@ -238,6 +242,7 @@ AVCodecContext *createContext(JNIEnv *env, AVCodec *codec, jbyteArray extraData, context->channels = rawChannelCount; context->channel_layout = av_get_default_channel_layout(rawChannelCount); } + context->err_recognition = AV_EF_IGNORE_ERR; int result = avcodec_open2(context, codec, NULL); if (result < 0) { logError("avcodec_open2", result); @@ -254,7 +259,8 @@ int decodePacket(AVCodecContext *context, AVPacket *packet, result = avcodec_send_packet(context, packet); if (result) { logError("avcodec_send_packet", result); - return result; + return result == AVERROR_INVALIDDATA ? DECODER_ERROR_INVALID_DATA + : DECODER_ERROR_OTHER; } // Dequeue output data until it runs out. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java index 9b6be57e4c..bd63765953 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java @@ -366,7 +366,10 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements if (outputBuffer == null) { return false; } - decoderCounters.skippedOutputBufferCount += outputBuffer.skippedOutputBufferCount; + if (outputBuffer.skippedOutputBufferCount > 0) { + decoderCounters.skippedOutputBufferCount += outputBuffer.skippedOutputBufferCount; + audioSink.handleDiscontinuity(); + } } if (outputBuffer.isEndOfStream()) { From ff9a40f0d76130c56b9eca7ec7cd7a00c8e6dd1d Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Fri, 4 Jan 2019 16:42:40 +0000 Subject: [PATCH 17/38] Update FakeAd overrides These are part of published IMA SDK 3.10.2. PiperOrigin-RevId: 227861713 --- .../java/com/google/android/exoplayer2/ext/ima/FakeAd.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakeAd.java b/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakeAd.java index b626a08780..59dfc6473c 100644 --- a/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakeAd.java +++ b/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakeAd.java @@ -64,14 +64,17 @@ import java.util.Set; }; } + @Override public int getVastMediaWidth() { throw new UnsupportedOperationException(); } + @Override public int getVastMediaHeight() { throw new UnsupportedOperationException(); } + @Override public int getVastMediaBitrate() { throw new UnsupportedOperationException(); } From 32bad6915851b39a48d5385b1268a87d87d7ff20 Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Mon, 7 Jan 2019 12:36:59 +0000 Subject: [PATCH 18/38] Increase search size in mp4 sniffing once moov has been found Issue:#5320 PiperOrigin-RevId: 228142567 --- .../exoplayer2/extractor/mp4/Sniffer.java | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Sniffer.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Sniffer.java index 657a955ab4..a1c90bf1f2 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Sniffer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Sniffer.java @@ -27,9 +27,7 @@ import java.io.IOException; */ /* package */ final class Sniffer { - /** - * The maximum number of bytes to peek when sniffing. - */ + /** The maximum number of bytes to peek when sniffing. */ private static final int SEARCH_LENGTH = 4 * 1024; private static final int[] COMPATIBLE_BRANDS = new int[] { @@ -112,12 +110,16 @@ import java.io.IOException; atomSize = buffer.readLong(); } else if (atomSize == Atom.EXTENDS_TO_END_SIZE) { // The atom extends to the end of the file. - long endPosition = input.getLength(); - if (endPosition != C.LENGTH_UNSET) { - atomSize = endPosition - input.getPeekPosition() + headerSize; + long fileEndPosition = input.getLength(); + if (fileEndPosition != C.LENGTH_UNSET) { + atomSize = fileEndPosition - input.getPeekPosition() + headerSize; } } + if (inputLength != C.LENGTH_UNSET && bytesSearched + atomSize > inputLength) { + // The file is invalid because the atom extends past the end of the file. + return false; + } if (atomSize < headerSize) { // The file is invalid because the atom size is too small for its header. return false; @@ -125,6 +127,13 @@ import java.io.IOException; bytesSearched += headerSize; if (atomType == Atom.TYPE_moov) { + // We have seen the moov atom. We increase the search size to make sure we don't miss an + // mvex atom because the moov's size exceeds the search length. + bytesToSearch += (int) atomSize; + if (inputLength != C.LENGTH_UNSET && bytesToSearch > inputLength) { + // Make sure we don't exceed the file size. + bytesToSearch = (int) inputLength; + } // Check for an mvex atom inside the moov atom to identify whether the file is fragmented. continue; } From 93f4a19f07939bb3cdf606f69142f8de527735f7 Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Mon, 7 Jan 2019 14:34:31 +0000 Subject: [PATCH 19/38] Expand check for muxed audio media tags to include uris that match variants Issue:#5313 PiperOrigin-RevId: 228155222 --- .../hls/playlist/HlsPlaylistParser.java | 16 +++++++++++++++- .../playlist/HlsMasterPlaylistParserTest.java | 19 +++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java index 65f4796187..242711431c 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java @@ -360,7 +360,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser variants, String mediaTagUri) { + if (mediaTagUri == null) { + return true; + } + // The URI attribute is defined, but it may match the uri of a variant. + for (int i = 0; i < variants.size(); i++) { + if (mediaTagUri.equals(variants.get(i).url)) { + return true; + } + } + return false; + } + private static class LineIterator { private final BufferedReader reader; diff --git a/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java b/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java index d03049efb3..9701171ce9 100644 --- a/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java +++ b/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java @@ -134,6 +134,17 @@ public class HlsMasterPlaylistParserTest { + "#EXT-X-STREAM-INF:BANDWIDTH=65000,CODECS=\"{$codecs}\"\n" + "http://example.com/{$tricky}\n"; + private static final String PLAYLIST_WITH_MULTIPLE_MUXED_MEDIA_TAGS = + "#EXTM3U\n" + + "#EXT-X-VERSION:3\n" + + "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"a\",NAME=\"audio_0\",DEFAULT=YES,URI=\"0/0.m3u8\"\n" + + "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"b\",NAME=\"audio_0\",DEFAULT=YES,URI=\"1/1.m3u8\"\n" + + "#EXT-X-STREAM-INF:BANDWIDTH=140800,CODECS=\"mp4a.40.2\",AUDIO=\"a\"\n" + + "0/0.m3u8\n" + + "\n" + + "#EXT-X-STREAM-INF:BANDWIDTH=281600,CODECS=\"mp4a.40.2\",AUDIO=\"b\"\n" + + "1/1.m3u8\n"; + @Test public void testParseMasterPlaylist() throws IOException { HlsMasterPlaylist masterPlaylist = parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_SIMPLE); @@ -271,6 +282,14 @@ public class HlsMasterPlaylistParserTest { assertThat(variant.url).isEqualTo("http://example.com/This/{$nested}/reference/shouldnt/work"); } + @Test + public void testMultipleMuxedMediaTags() throws IOException { + HlsMasterPlaylist playlistWithMultipleMuxedMediaTags = + parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_MULTIPLE_MUXED_MEDIA_TAGS); + assertThat(playlistWithMultipleMuxedMediaTags.variants).hasSize(2); + assertThat(playlistWithMultipleMuxedMediaTags.audios).isEmpty(); + } + private static HlsMasterPlaylist parseMasterPlaylist(String uri, String playlistString) throws IOException { Uri playlistUri = Uri.parse(uri); From 80645056d29ce5b48801bca3f6b64f7284d70b95 Mon Sep 17 00:00:00 2001 From: olly Date: Tue, 8 Jan 2019 09:36:30 +0000 Subject: [PATCH 20/38] Doc fix. PiperOrigin-RevId: 228296962 --- .../google/android/exoplayer2/upstream/DataSource.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSource.java index c759499577..ab22e18358 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DataSource.java @@ -64,11 +64,11 @@ public interface DataSource { long open(DataSpec dataSpec) throws IOException; /** - * Reads up to {@code length} bytes of data and stores them into {@code buffer}, starting at + * Reads up to {@code readLength} bytes of data and stores them into {@code buffer}, starting at * index {@code offset}. - *

- * If {@code length} is zero then 0 is returned. Otherwise, if no data is available because the - * end of the opened range has been reached, then {@link C#RESULT_END_OF_INPUT} is returned. + * + *

If {@code readLength} is zero then 0 is returned. Otherwise, if no data is available because + * the end of the opened range has been reached, then {@link C#RESULT_END_OF_INPUT} is returned. * Otherwise, the call will block until at least one byte of data has been read and the number of * bytes read is returned. * From e760965595f914a56dcea7697c40c24110f7914e Mon Sep 17 00:00:00 2001 From: tonihei Date: Wed, 9 Jan 2019 10:24:32 +0000 Subject: [PATCH 21/38] Update README and dev guide with Java 8 config for Kotlin. Setting the target conpatibility only seems to work for Java. Added the equivalent Kotlin config options to the docs. Issue:#5276 PiperOrigin-RevId: 228482496 --- README.md | 40 +++++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 37967dd527..03f16bd655 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,8 @@ repository and depend on the modules locally. ### From JCenter ### +#### 1. Add repositories #### + The easiest way to get started using ExoPlayer is to add it as a gradle dependency. You need to make sure you have the Google and JCenter repositories included in the `build.gradle` file in the root of your project: @@ -38,6 +40,8 @@ repositories { } ``` +#### 2. Add ExoPlayer module dependencies #### + Next add a dependency in the `build.gradle` file of your app module. The following will add a dependency to the full library: @@ -45,15 +49,7 @@ following will add a dependency to the full library: implementation 'com.google.android.exoplayer:exoplayer:2.X.X' ``` -where `2.X.X` is your preferred version. If not enabled already, you also need -to turn on Java 8 support in all `build.gradle` files depending on ExoPlayer, by -adding the following to the `android` section: - -```gradle -compileOptions { - targetCompatibility JavaVersion.VERSION_1_8 -} -``` +where `2.X.X` is your preferred version. As an alternative to the full library, you can depend on only the library modules that you actually need. For example the following will add dependencies @@ -87,6 +83,32 @@ JCenter can be found on [Bintray][]. [extensions directory]: https://github.com/google/ExoPlayer/tree/release-v2/extensions/ [Bintray]: https://bintray.com/google/exoplayer +#### 3. Turn on Java 8 support #### + +If not enabled already, you also need to turn on Java 8 support in all +`build.gradle` files depending on ExoPlayer, by adding the following to the +`android` section: + +```gradle +compileOptions { + targetCompatibility JavaVersion.VERSION_1_8 +} +``` + +Note that if you want to use Java 8 features in your own code, the following +additional options need to be set: + +```gradle +// For Java compilers: +compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 +} +// For Kotlin compilers: +kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8 +} +``` + ### Locally ### Cloning the repository and depending on the modules locally is required when From 81ca7fe090d3b22ca3b28bc3c5bae09c17c94541 Mon Sep 17 00:00:00 2001 From: tonihei Date: Wed, 9 Jan 2019 17:23:30 +0000 Subject: [PATCH 22/38] Add missing call to timeline.getWindow. The window object is used without being filled with data. This used to work well for most cases as the same live stream is sending regular updates and the first update is almost never used if it's not the first item in a playlist. It causes problems when the first timeline update of a live stream is actually used for playback (e.g. when the live stream is lazily prepared in a playlist and played first). PiperOrigin-RevId: 228530232 --- .../android/exoplayer2/source/ConcatenatingMediaSource.java | 1 + 1 file changed, 1 insertion(+) 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 4e8cccaa23..72ecd2c767 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 @@ -709,6 +709,7 @@ public class ConcatenatingMediaSource extends CompositeMediaSource Date: Mon, 14 Jan 2019 10:05:17 +0000 Subject: [PATCH 23/38] =?UTF-8?q?=EF=BF=BCBlacklist=20OMX.SEC.mp3.dec=20fo?= =?UTF-8?q?r=20more=20devices?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue: #4519 PiperOrigin-RevId: 229145790 --- .../google/android/exoplayer2/mediacodec/MediaCodecUtil.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java index 4d971d461e..9ae50179c3 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java @@ -326,7 +326,9 @@ public final class MediaCodecUtil { || Util.MODEL.startsWith("SM-G350") || Util.MODEL.startsWith("SM-G386") || Util.MODEL.startsWith("SM-T231") - || Util.MODEL.startsWith("SM-T530"))) { + || Util.MODEL.startsWith("SM-T530") + || Util.MODEL.startsWith("SCH-I535") + || Util.MODEL.startsWith("SPH-L710"))) { return false; } if ("OMX.brcm.audio.mp3.decoder".equals(name) From 66ca43ed1dca3011ce6efdf00703f14f8360948b Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 14 Jan 2019 14:02:46 +0000 Subject: [PATCH 24/38] Don't forget isSeekable in ExtractorMediaSource. We currently forget whether a source is seekable at re-preparation. This was implemented intentionally this way under the assumption that we really can't seek until we have loaded the seek map again. However, seek operations are only allowed after a media period is prepared. So there is no harm in remembering whether a source is seekable. This problem currently prevents reusing ClippingMediaSources with ExtractorMediaSource and a non-zero start clip position. Issue: #5351 PiperOrigin-RevId: 229169441 --- RELEASENOTES.md | 3 +++ .../google/android/exoplayer2/source/ExtractorMediaSource.java | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 15e61d98ad..cfeb994247 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -9,6 +9,9 @@ * Fix issue where sending callbacks for playlist changes may cause problems because of parallel player access ([#5240](https://github.com/google/ExoPlayer/issues/5240)). +* Fix issue with reusing a `ClippingMediaSource` with an inner + `ExtractorMediaSource` and a non-zero start position + ([#5351](https://github.com/google/ExoPlayer/issues/5351)). ### 2.9.3 ### 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 085b5dba71..07733436cb 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 @@ -370,7 +370,7 @@ public final class ExtractorMediaSource extends BaseMediaSource boolean isTopLevelSource, @Nullable TransferListener mediaTransferListener) { transferListener = mediaTransferListener; - notifySourceInfoRefreshed(timelineDurationUs, /* isSeekable= */ false); + notifySourceInfoRefreshed(timelineDurationUs, timelineIsSeekable); } @Override From 546af063d63de9b7b94470fb7b914b58ced9d5ef Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 14 Jan 2019 23:02:03 +0000 Subject: [PATCH 25/38] Fix DRM protected SmoothStreaming with subtitles Issue: #5378 PiperOrigin-RevId: 229261658 --- RELEASENOTES.md | 2 + .../smoothstreaming/DefaultSsChunkSource.java | 11 +++-- .../source/smoothstreaming/SsChunkSource.java | 3 -- .../source/smoothstreaming/SsMediaPeriod.java | 18 +------- .../smoothstreaming/manifest/SsManifest.java | 5 ++- .../manifest/SsManifestParser.java | 43 ++++++++++++++++++- .../src/test/assets/sample_ismc_1 | 2 +- .../src/test/assets/sample_ismc_2 | 2 +- 8 files changed, 55 insertions(+), 31 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index cfeb994247..14cd740850 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -4,6 +4,8 @@ * IMA extension: Clear ads loader listeners on release ([#4114](https://github.com/google/ExoPlayer/issues/4114)). +* SmoothStreaming: Fix support for subtitles in DRM protected streams + ([#5378](https://github.com/google/ExoPlayer/issues/5378)). * FFmpeg extension: Treat invalid data errors as non-fatal to match the behavior of MediaCodec ([#5293](https://github.com/google/ExoPlayer/issues/5293)). * Fix issue where sending callbacks for playlist changes may cause problems diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java index 9ac376efad..7c76dba749 100644 --- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java +++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java @@ -61,14 +61,13 @@ public class DefaultSsChunkSource implements SsChunkSource { SsManifest manifest, int elementIndex, TrackSelection trackSelection, - TrackEncryptionBox[] trackEncryptionBoxes, @Nullable TransferListener transferListener) { DataSource dataSource = dataSourceFactory.createDataSource(); if (transferListener != null) { dataSource.addTransferListener(transferListener); } - return new DefaultSsChunkSource(manifestLoaderErrorThrower, manifest, elementIndex, - trackSelection, dataSource, trackEncryptionBoxes); + return new DefaultSsChunkSource( + manifestLoaderErrorThrower, manifest, elementIndex, trackSelection, dataSource); } } @@ -90,15 +89,13 @@ public class DefaultSsChunkSource implements SsChunkSource { * @param streamElementIndex The index of the stream element in the manifest. * @param trackSelection The track selection. * @param dataSource A {@link DataSource} suitable for loading the media data. - * @param trackEncryptionBoxes Track encryption boxes for the stream. */ public DefaultSsChunkSource( LoaderErrorThrower manifestLoaderErrorThrower, SsManifest manifest, int streamElementIndex, TrackSelection trackSelection, - DataSource dataSource, - TrackEncryptionBox[] trackEncryptionBoxes) { + DataSource dataSource) { this.manifestLoaderErrorThrower = manifestLoaderErrorThrower; this.manifest = manifest; this.streamElementIndex = streamElementIndex; @@ -110,6 +107,8 @@ public class DefaultSsChunkSource implements SsChunkSource { for (int i = 0; i < extractorWrappers.length; i++) { int manifestTrackIndex = trackSelection.getIndexInTrackGroup(i); Format format = streamElement.formats[manifestTrackIndex]; + TrackEncryptionBox[] trackEncryptionBoxes = + format.drmInitData != null ? manifest.protectionElement.trackEncryptionBoxes : null; int nalUnitLengthFieldLength = streamElement.type == C.TRACK_TYPE_VIDEO ? 4 : 0; Track track = new Track(manifestTrackIndex, streamElement.type, streamElement.timescale, C.TIME_UNSET, manifest.durationUs, format, Track.TRANSFORMATION_NONE, diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsChunkSource.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsChunkSource.java index f333a6f92c..4940f1592f 100644 --- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsChunkSource.java +++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsChunkSource.java @@ -16,7 +16,6 @@ package com.google.android.exoplayer2.source.smoothstreaming; import android.support.annotation.Nullable; -import com.google.android.exoplayer2.extractor.mp4.TrackEncryptionBox; import com.google.android.exoplayer2.source.chunk.ChunkSource; import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest; import com.google.android.exoplayer2.trackselection.TrackSelection; @@ -38,7 +37,6 @@ public interface SsChunkSource extends ChunkSource { * @param manifest The initial manifest. * @param streamElementIndex The index of the corresponding stream element in the manifest. * @param trackSelection The track selection. - * @param trackEncryptionBoxes Track encryption boxes for the stream. * @param transferListener The transfer listener which should be informed of any data transfers. * May be null if no listener is available. * @return The created {@link SsChunkSource}. @@ -48,7 +46,6 @@ public interface SsChunkSource extends ChunkSource { SsManifest manifest, int streamElementIndex, TrackSelection trackSelection, - TrackEncryptionBox[] trackEncryptionBoxes, @Nullable TransferListener transferListener); } diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java index 14b54bc471..d3518c0a35 100644 --- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java +++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java @@ -29,7 +29,6 @@ import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.chunk.ChunkSampleStream; import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest; -import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest.ProtectionElement; import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy; @@ -44,8 +43,6 @@ import java.util.ArrayList; /* package */ final class SsMediaPeriod implements MediaPeriod, SequenceableLoader.Callback> { - private static final int INITIALIZATION_VECTOR_SIZE = 8; - private final SsChunkSource.Factory chunkSourceFactory; private final @Nullable TransferListener transferListener; private final LoaderErrorThrower manifestLoaderErrorThrower; @@ -53,7 +50,6 @@ import java.util.ArrayList; private final EventDispatcher eventDispatcher; private final Allocator allocator; private final TrackGroupArray trackGroups; - private final TrackEncryptionBox[] trackEncryptionBoxes; private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory; private @Nullable Callback callback; @@ -71,6 +67,7 @@ import java.util.ArrayList; EventDispatcher eventDispatcher, LoaderErrorThrower manifestLoaderErrorThrower, Allocator allocator) { + this.manifest = manifest; this.chunkSourceFactory = chunkSourceFactory; this.transferListener = transferListener; this.manifestLoaderErrorThrower = manifestLoaderErrorThrower; @@ -78,18 +75,7 @@ import java.util.ArrayList; this.eventDispatcher = eventDispatcher; this.allocator = allocator; this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory; - trackGroups = buildTrackGroups(manifest); - ProtectionElement protectionElement = manifest.protectionElement; - if (protectionElement != null) { - byte[] keyId = getProtectionElementKeyId(protectionElement.data); - // We assume pattern encryption does not apply. - trackEncryptionBoxes = new TrackEncryptionBox[] { - new TrackEncryptionBox(true, null, INITIALIZATION_VECTOR_SIZE, keyId, 0, 0, null)}; - } else { - trackEncryptionBoxes = null; - } - this.manifest = manifest; sampleStreams = newSampleStreamArray(0); compositeSequenceableLoader = compositeSequenceableLoaderFactory.createCompositeSequenceableLoader(sampleStreams); @@ -229,7 +215,6 @@ import java.util.ArrayList; manifest, streamElementIndex, selection, - trackEncryptionBoxes, transferListener); return new ChunkSampleStream<>( manifest.streamElements[streamElementIndex].type, @@ -277,5 +262,4 @@ import java.util.ArrayList; data[firstPosition] = data[secondPosition]; data[secondPosition] = temp; } - } diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifest.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifest.java index 2c508f0fde..cfb772a86b 100644 --- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifest.java +++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifest.java @@ -18,6 +18,7 @@ package com.google.android.exoplayer2.source.smoothstreaming.manifest; import android.net.Uri; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.extractor.mp4.TrackEncryptionBox; import com.google.android.exoplayer2.offline.FilterableManifest; import com.google.android.exoplayer2.offline.StreamKey; import com.google.android.exoplayer2.util.Assertions; @@ -41,10 +42,12 @@ public class SsManifest implements FilterableManifest { public final UUID uuid; public final byte[] data; + public final TrackEncryptionBox[] trackEncryptionBoxes; - public ProtectionElement(UUID uuid, byte[] data) { + public ProtectionElement(UUID uuid, byte[] data, TrackEncryptionBox[] trackEncryptionBoxes) { this.uuid = uuid; this.data = data; + this.trackEncryptionBoxes = trackEncryptionBoxes; } } diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParser.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParser.java index 3d5ade403a..4c1c6ee0cc 100644 --- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParser.java +++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestParser.java @@ -25,6 +25,7 @@ import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.drm.DrmInitData; import com.google.android.exoplayer2.drm.DrmInitData.SchemeData; import com.google.android.exoplayer2.extractor.mp4.PsshAtomUtil; +import com.google.android.exoplayer2.extractor.mp4.TrackEncryptionBox; import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest.ProtectionElement; import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest.StreamElement; import com.google.android.exoplayer2.upstream.ParsingLoadable; @@ -397,9 +398,10 @@ public class SsManifestParser implements ParsingLoadable.Parser { public static final String TAG = "Protection"; public static final String TAG_PROTECTION_HEADER = "ProtectionHeader"; - public static final String KEY_SYSTEM_ID = "SystemID"; + private static final int INITIALIZATION_VECTOR_SIZE = 8; + private boolean inProtectionHeader; private UUID uuid; private byte[] initData; @@ -439,7 +441,44 @@ public class SsManifestParser implements ParsingLoadable.Parser { @Override public Object build() { - return new ProtectionElement(uuid, PsshAtomUtil.buildPsshAtom(uuid, initData)); + return new ProtectionElement( + uuid, PsshAtomUtil.buildPsshAtom(uuid, initData), buildTrackEncryptionBoxes(initData)); + } + + private static TrackEncryptionBox[] buildTrackEncryptionBoxes(byte[] initData) { + return new TrackEncryptionBox[] { + new TrackEncryptionBox( + /* isEncrypted= */ true, + /* schemeType= */ null, + INITIALIZATION_VECTOR_SIZE, + getProtectionElementKeyId(initData), + /* defaultEncryptedBlocks= */ 0, + /* defaultClearBlocks= */ 0, + /* defaultInitializationVector= */ null) + }; + } + + private static byte[] getProtectionElementKeyId(byte[] initData) { + StringBuilder initDataStringBuilder = new StringBuilder(); + for (int i = 0; i < initData.length; i += 2) { + initDataStringBuilder.append((char) initData[i]); + } + String initDataString = initDataStringBuilder.toString(); + String keyIdString = + initDataString.substring( + initDataString.indexOf("") + 5, initDataString.indexOf("")); + byte[] keyId = Base64.decode(keyIdString, Base64.DEFAULT); + swap(keyId, 0, 3); + swap(keyId, 1, 2); + swap(keyId, 4, 5); + swap(keyId, 6, 7); + return keyId; + } + + private static void swap(byte[] data, int firstPosition, int secondPosition) { + byte temp = data[firstPosition]; + data[firstPosition] = data[secondPosition]; + data[secondPosition] = temp; } private static String stripCurlyBraces(String uuidString) { diff --git a/library/smoothstreaming/src/test/assets/sample_ismc_1 b/library/smoothstreaming/src/test/assets/sample_ismc_1 index 25a37d65b4..1d279d0a67 100644 --- a/library/smoothstreaming/src/test/assets/sample_ismc_1 +++ b/library/smoothstreaming/src/test/assets/sample_ismc_1 @@ -3,7 +3,7 @@ Duration="2300000000" TimeScale="10000000"> - + fgMAAAEAAQB0AzwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4AQgBhAFUATQBPAEcAYwBzAGgAVQBDAEQAZAB3ADMANABZAGMAawBmAFoAQQA9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgBnADcATgBhAFIARABJAEkATwA5ADAAPQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APABMAEEAXwBVAFIATAA+AGgAdAB0AHAAcwA6AC8ALwBUAC0ATwBOAEwASQBOAEUALgBEAFUATQBNAFkALQBTAEUAUgBWAEUAUgAvAEEAcgB0AGUAbQBpAHMATABpAGMAZQBuAHMAZQBTAGUAcgB2AGUAcgAvAFAAbABhAHkAUgBlAGEAZAB5AE0AYQBuAGEAZwBlAHIALgBhAHMAbQB4ADwALwBMAEEAXwBVAFIATAA+ADwAQwBVAFMAVABPAE0AQQBUAFQAUgBJAEIAVQBUAEUAUwA+ADwAQwBJAEQAPgAxADcANQA4ADIANgA8AC8AQwBJAEQAPgA8AEkASQBTAF8ARABSAE0AXwBWAEUAUgBTAEkATwBOAD4ANwAuADEALgAxADUANgA1AC4ANAA8AC8ASQBJAFMAXwBEAFIATQBfAFYARQBSAFMASQBPAE4APgA8AC8AQwBVAFMAVABPAE0AQQBUAFQAUgBJAEIAVQBUAEUAUwA+ADwALwBEAEEAVABBAD4APAAvAFcAUgBNAEgARQBBAEQARQBSAD4A diff --git a/library/smoothstreaming/src/test/assets/sample_ismc_2 b/library/smoothstreaming/src/test/assets/sample_ismc_2 index 5875a18183..7f2a53036f 100644 --- a/library/smoothstreaming/src/test/assets/sample_ismc_2 +++ b/library/smoothstreaming/src/test/assets/sample_ismc_2 @@ -3,7 +3,7 @@ Duration="2300000000" TimeScale="10000000"> - + fgMAAAEAAQB0AzwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4AQgBhAFUATQBPAEcAYwBzAGgAVQBDAEQAZAB3ADMANABZAGMAawBmAFoAQQA9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgBnADcATgBhAFIARABJAEkATwA5ADAAPQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APABMAEEAXwBVAFIATAA+AGgAdAB0AHAAcwA6AC8ALwBUAC0ATwBOAEwASQBOAEUALgBEAFUATQBNAFkALQBTAEUAUgBWAEUAUgAvAEEAcgB0AGUAbQBpAHMATABpAGMAZQBuAHMAZQBTAGUAcgB2AGUAcgAvAFAAbABhAHkAUgBlAGEAZAB5AE0AYQBuAGEAZwBlAHIALgBhAHMAbQB4ADwALwBMAEEAXwBVAFIATAA+ADwAQwBVAFMAVABPAE0AQQBUAFQAUgBJAEIAVQBUAEUAUwA+ADwAQwBJAEQAPgAxADcANQA4ADIANgA8AC8AQwBJAEQAPgA8AEkASQBTAF8ARABSAE0AXwBWAEUAUgBTAEkATwBOAD4ANwAuADEALgAxADUANgA1AC4ANAA8AC8ASQBJAFMAXwBEAFIATQBfAFYARQBSAFMASQBPAE4APgA8AC8AQwBVAFMAVABPAE0AQQBUAFQAUgBJAEIAVQBUAEUAUwA+ADwALwBEAEEAVABBAD4APAAvAFcAUgBNAEgARQBBAEQARQBSAD4A From e1b55e6d278913341370ac7a54cfc77ceb80df3d Mon Sep 17 00:00:00 2001 From: tonihei Date: Tue, 15 Jan 2019 14:18:29 +0000 Subject: [PATCH 26/38] Add buffer flag for last sample to improve buffered position calculation. The buffered position is currently based on the mimimum queued timestamp of all AV tracks. If the tracks have unequal lengths, one track continues loading without bounds as the "buffered position" will always stay at the shorter track's duration. This change adds an optional buffer flag to mark the last sample of the stream. This is set in the Mp4Extractor only so far. ExtractorMediaSource uses this flag to ignore AV streams in the buffered duration calculation if they already finished loading. Issue:#3670 PiperOrigin-RevId: 229359899 --- RELEASENOTES.md | 2 ++ .../java/com/google/android/exoplayer2/C.java | 7 +++-- .../extractor/mp4/TrackSampleTable.java | 3 +++ .../source/ExtractorMediaPeriod.java | 7 ++--- .../source/SampleMetadataQueue.java | 26 ++++++++++++++----- .../exoplayer2/source/SampleQueue.java | 9 +++++++ .../src/test/assets/mp4/sample.mp4.0.dump | 4 +-- .../src/test/assets/mp4/sample.mp4.1.dump | 4 +-- .../src/test/assets/mp4/sample.mp4.2.dump | 4 +-- .../src/test/assets/mp4/sample.mp4.3.dump | 4 +-- 10 files changed, 51 insertions(+), 19 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 14cd740850..f6ecfb962e 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -14,6 +14,8 @@ * Fix issue with reusing a `ClippingMediaSource` with an inner `ExtractorMediaSource` and a non-zero start position ([#5351](https://github.com/google/ExoPlayer/issues/5351)). +* Fix issue where uneven track durations in MP4 streams can cause OOM problems + ([#3670](https://github.com/google/ExoPlayer/issues/3670)). ### 2.9.3 ### diff --git a/library/core/src/main/java/com/google/android/exoplayer2/C.java b/library/core/src/main/java/com/google/android/exoplayer2/C.java index 77d39fe866..8810b51000 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/C.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/C.java @@ -460,8 +460,8 @@ public final class C { /** * Flags which can apply to a buffer containing a media sample. Possible flag values are {@link - * #BUFFER_FLAG_KEY_FRAME}, {@link #BUFFER_FLAG_END_OF_STREAM}, {@link #BUFFER_FLAG_ENCRYPTED} and - * {@link #BUFFER_FLAG_DECODE_ONLY}. + * #BUFFER_FLAG_KEY_FRAME}, {@link #BUFFER_FLAG_END_OF_STREAM}, {@link #BUFFER_FLAG_LAST_SAMPLE}, + * {@link #BUFFER_FLAG_ENCRYPTED} and {@link #BUFFER_FLAG_DECODE_ONLY}. */ @Documented @Retention(RetentionPolicy.SOURCE) @@ -470,6 +470,7 @@ public final class C { value = { BUFFER_FLAG_KEY_FRAME, BUFFER_FLAG_END_OF_STREAM, + BUFFER_FLAG_LAST_SAMPLE, BUFFER_FLAG_ENCRYPTED, BUFFER_FLAG_DECODE_ONLY }) @@ -482,6 +483,8 @@ public final class C { * Flag for empty buffers that signal that the end of the stream was reached. */ public static final int BUFFER_FLAG_END_OF_STREAM = MediaCodec.BUFFER_FLAG_END_OF_STREAM; + /** Indicates that a buffer is known to contain the last media sample of the stream. */ + public static final int BUFFER_FLAG_LAST_SAMPLE = 1 << 29; // 0x20000000 /** Indicates that a buffer is (at least partially) encrypted. */ public static final int BUFFER_FLAG_ENCRYPTED = 1 << 30; // 0x40000000 /** Indicates that a buffer should be decoded but not rendered. */ diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackSampleTable.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackSampleTable.java index 56851fc1e0..59ea386335 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackSampleTable.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackSampleTable.java @@ -64,6 +64,9 @@ import com.google.android.exoplayer2.util.Util; this.flags = flags; this.durationUs = durationUs; sampleCount = offsets.length; + if (flags.length > 0) { + flags[flags.length - 1] |= C.BUFFER_FLAG_LAST_SAMPLE; + } } /** 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 31daf65d38..823901af2a 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 @@ -346,18 +346,19 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; } else if (isPendingReset()) { return pendingResetPositionUs; } - long largestQueuedTimestampUs; + long largestQueuedTimestampUs = C.TIME_UNSET; if (haveAudioVideoTracks) { // Ignore non-AV tracks, which may be sparse or poorly interleaved. largestQueuedTimestampUs = Long.MAX_VALUE; int trackCount = sampleQueues.length; for (int i = 0; i < trackCount; i++) { - if (trackIsAudioVideoFlags[i]) { + if (trackIsAudioVideoFlags[i] && !sampleQueues[i].isLastSampleQueued()) { largestQueuedTimestampUs = Math.min(largestQueuedTimestampUs, sampleQueues[i].getLargestQueuedTimestampUs()); } } - } else { + } + if (largestQueuedTimestampUs == C.TIME_UNSET) { largestQueuedTimestampUs = getLargestQueuedTimestampUs(); } return largestQueuedTimestampUs == Long.MIN_VALUE ? lastSeekPositionUs diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/SampleMetadataQueue.java b/library/core/src/main/java/com/google/android/exoplayer2/source/SampleMetadataQueue.java index e5b950cf2e..ab5c5e57d9 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/SampleMetadataQueue.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/SampleMetadataQueue.java @@ -57,6 +57,7 @@ import com.google.android.exoplayer2.util.Util; private long largestDiscardedTimestampUs; private long largestQueuedTimestampUs; + private boolean isLastSampleQueued; private boolean upstreamKeyframeRequired; private boolean upstreamFormatRequired; private Format upstreamFormat; @@ -93,6 +94,7 @@ import com.google.android.exoplayer2.util.Util; upstreamKeyframeRequired = true; largestDiscardedTimestampUs = Long.MIN_VALUE; largestQueuedTimestampUs = Long.MIN_VALUE; + isLastSampleQueued = false; if (resetUpstreamFormat) { upstreamFormat = null; upstreamFormatRequired = true; @@ -118,6 +120,7 @@ import com.google.android.exoplayer2.util.Util; Assertions.checkArgument(0 <= discardCount && discardCount <= (length - readPosition)); length -= discardCount; largestQueuedTimestampUs = Math.max(largestDiscardedTimestampUs, getLargestTimestamp(length)); + isLastSampleQueued = discardCount == 0 && isLastSampleQueued; if (length == 0) { return 0; } else { @@ -186,6 +189,19 @@ import com.google.android.exoplayer2.util.Util; return largestQueuedTimestampUs; } + /** + * Returns whether the last sample of the stream has knowingly been queued. A return value of + * {@code false} means that the last sample had not been queued or that it's unknown whether the + * last sample has been queued. + * + *

Samples that were discarded by calling {@link #discardUpstreamSamples(int)} are not + * considered as having been queued. Samples that were dequeued from the front of the queue are + * considered as having been queued. + */ + public synchronized boolean isLastSampleQueued() { + return isLastSampleQueued; + } + /** Returns the timestamp of the first sample, or {@link Long#MIN_VALUE} if the queue is empty. */ public synchronized long getFirstTimestampUs() { return length == 0 ? Long.MIN_VALUE : timesUs[relativeFirstIndex]; @@ -224,7 +240,7 @@ import com.google.android.exoplayer2.util.Util; boolean formatRequired, boolean loadingFinished, Format downstreamFormat, SampleExtrasHolder extrasHolder) { if (!hasNextSample()) { - if (loadingFinished) { + if (loadingFinished || isLastSampleQueued) { buffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM); return C.RESULT_BUFFER_READ; } else if (upstreamFormat != null @@ -388,7 +404,9 @@ import com.google.android.exoplayer2.util.Util; upstreamKeyframeRequired = false; } Assertions.checkState(!upstreamFormatRequired); - commitSampleTimestamp(timeUs); + + isLastSampleQueued = (sampleFlags & C.BUFFER_FLAG_LAST_SAMPLE) != 0; + largestQueuedTimestampUs = Math.max(largestQueuedTimestampUs, timeUs); int relativeEndIndex = getRelativeIndex(length); timesUs[relativeEndIndex] = timeUs; @@ -439,10 +457,6 @@ import com.google.android.exoplayer2.util.Util; } } - public synchronized void commitSampleTimestamp(long timeUs) { - largestQueuedTimestampUs = Math.max(largestQueuedTimestampUs, timeUs); - } - /** * Attempts to discard samples from the end of the queue to allow samples starting from the * specified timestamp to be spliced in. Samples will not be discarded prior to the read position. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/SampleQueue.java b/library/core/src/main/java/com/google/android/exoplayer2/source/SampleQueue.java index ecc720c656..0886e79d21 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/SampleQueue.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/SampleQueue.java @@ -224,6 +224,15 @@ public class SampleQueue implements TrackOutput { return metadataQueue.getLargestQueuedTimestampUs(); } + /** + * Returns whether the last sample of the stream has knowingly been queued. A return value of + * {@code false} means that the last sample had not been queued or that it's unknown whether the + * last sample has been queued. + */ + public boolean isLastSampleQueued() { + return metadataQueue.isLastSampleQueued(); + } + /** Returns the timestamp of the first sample, or {@link Long#MIN_VALUE} if the queue is empty. */ public long getFirstTimestampUs() { return metadataQueue.getFirstTimestampUs(); diff --git a/library/core/src/test/assets/mp4/sample.mp4.0.dump b/library/core/src/test/assets/mp4/sample.mp4.0.dump index efc804d48b..b05d8250ab 100644 --- a/library/core/src/test/assets/mp4/sample.mp4.0.dump +++ b/library/core/src/test/assets/mp4/sample.mp4.0.dump @@ -147,7 +147,7 @@ track 0: data = length 530, hash C98BC6A8 sample 29: time = 934266 - flags = 0 + flags = 536870912 data = length 568, hash 4FE5C8EA track 1: format: @@ -352,6 +352,6 @@ track 1: data = length 229, hash FFF98DF0 sample 44: time = 1065678 - flags = 1 + flags = 536870913 data = length 6, hash 31B22286 tracksEnded = true diff --git a/library/core/src/test/assets/mp4/sample.mp4.1.dump b/library/core/src/test/assets/mp4/sample.mp4.1.dump index 10104b5e81..84d86f8ccf 100644 --- a/library/core/src/test/assets/mp4/sample.mp4.1.dump +++ b/library/core/src/test/assets/mp4/sample.mp4.1.dump @@ -147,7 +147,7 @@ track 0: data = length 530, hash C98BC6A8 sample 29: time = 934266 - flags = 0 + flags = 536870912 data = length 568, hash 4FE5C8EA track 1: format: @@ -304,6 +304,6 @@ track 1: data = length 229, hash FFF98DF0 sample 32: time = 1065678 - flags = 1 + flags = 536870913 data = length 6, hash 31B22286 tracksEnded = true diff --git a/library/core/src/test/assets/mp4/sample.mp4.2.dump b/library/core/src/test/assets/mp4/sample.mp4.2.dump index 8af96be673..9bbe8caa01 100644 --- a/library/core/src/test/assets/mp4/sample.mp4.2.dump +++ b/library/core/src/test/assets/mp4/sample.mp4.2.dump @@ -147,7 +147,7 @@ track 0: data = length 530, hash C98BC6A8 sample 29: time = 934266 - flags = 0 + flags = 536870912 data = length 568, hash 4FE5C8EA track 1: format: @@ -244,6 +244,6 @@ track 1: data = length 229, hash FFF98DF0 sample 17: time = 1065678 - flags = 1 + flags = 536870913 data = length 6, hash 31B22286 tracksEnded = true diff --git a/library/core/src/test/assets/mp4/sample.mp4.3.dump b/library/core/src/test/assets/mp4/sample.mp4.3.dump index f1259661ed..f210f277b3 100644 --- a/library/core/src/test/assets/mp4/sample.mp4.3.dump +++ b/library/core/src/test/assets/mp4/sample.mp4.3.dump @@ -147,7 +147,7 @@ track 0: data = length 530, hash C98BC6A8 sample 29: time = 934266 - flags = 0 + flags = 536870912 data = length 568, hash 4FE5C8EA track 1: format: @@ -184,6 +184,6 @@ track 1: data = length 229, hash FFF98DF0 sample 2: time = 1065678 - flags = 1 + flags = 536870913 data = length 6, hash 31B22286 tracksEnded = true From 516e02c696c13d95802fa91f843d73cce50d7d9d Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Tue, 15 Jan 2019 14:32:15 +0000 Subject: [PATCH 27/38] Fix release branch --- .../source/smoothstreaming/manifest/SsManifestTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/smoothstreaming/src/test/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestTest.java b/library/smoothstreaming/src/test/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestTest.java index dc8d6754f5..b692d94c18 100644 --- a/library/smoothstreaming/src/test/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestTest.java +++ b/library/smoothstreaming/src/test/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsManifestTest.java @@ -19,6 +19,7 @@ import static com.google.common.truth.Truth.assertThat; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.extractor.mp4.TrackEncryptionBox; import com.google.android.exoplayer2.offline.StreamKey; import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest.ProtectionElement; import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest.StreamElement; @@ -36,7 +37,7 @@ import org.robolectric.RobolectricTestRunner; public class SsManifestTest { private static final ProtectionElement DUMMY_PROTECTION_ELEMENT = - new ProtectionElement(C.WIDEVINE_UUID, new byte[] {0, 1, 2}); + new ProtectionElement(C.WIDEVINE_UUID, new byte[0], new TrackEncryptionBox[0]); @Test public void testCopy() throws Exception { From 84574d9c111a7843795b825fd64fb07ff9ea022f Mon Sep 17 00:00:00 2001 From: olly Date: Tue, 15 Jan 2019 15:02:38 +0000 Subject: [PATCH 28/38] Bump version for 2.9.4 release PiperOrigin-RevId: 229364563 --- constants.gradle | 4 ++-- .../com/google/android/exoplayer2/ExoPlayerLibraryInfo.java | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/constants.gradle b/constants.gradle index ac801d2d3b..716ddbadba 100644 --- a/constants.gradle +++ b/constants.gradle @@ -13,8 +13,8 @@ // limitations under the License. project.ext { // ExoPlayer version and version code. - releaseVersion = '2.9.3' - releaseVersionCode = 2009003 + releaseVersion = '2.9.4' + releaseVersionCode = 2009004 // Important: ExoPlayer specifies a minSdkVersion of 14 because various // components provided by the library may be of use on older devices. // However, please note that the core media playback functionality provided diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java index 792f6cf651..36723c5d73 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java @@ -29,11 +29,11 @@ public final class ExoPlayerLibraryInfo { /** The version of the library expressed as a string, for example "1.2.3". */ // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa. - public static final String VERSION = "2.9.3"; + public static final String VERSION = "2.9.4"; /** The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}. */ // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. - public static final String VERSION_SLASHY = "ExoPlayerLib/2.9.3"; + public static final String VERSION_SLASHY = "ExoPlayerLib/2.9.4"; /** * The version of the library expressed as an integer, for example 1002003. @@ -43,7 +43,7 @@ public final class ExoPlayerLibraryInfo { * integer version 123045006 (123-045-006). */ // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. - public static final int VERSION_INT = 2009003; + public static final int VERSION_INT = 2009004; /** * Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions} From 4f1f4fbf49e2cb1dc7743a5d272664ca8d509248 Mon Sep 17 00:00:00 2001 From: tonihei Date: Tue, 15 Jan 2019 15:07:47 +0000 Subject: [PATCH 29/38] Fix typo. PiperOrigin-RevId: 229365333 --- .../exoplayer2/source/dash/DefaultDashChunkSource.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java index 5e20fb769c..3e51009f20 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java @@ -457,10 +457,10 @@ public class DefaultDashChunkSource implements DashChunkSource { } private ArrayList getRepresentations() { - List manifestAdapationSets = manifest.getPeriod(periodIndex).adaptationSets; + List manifestAdaptationSets = manifest.getPeriod(periodIndex).adaptationSets; ArrayList representations = new ArrayList<>(); for (int adaptationSetIndex : adaptationSetIndices) { - representations.addAll(manifestAdapationSets.get(adaptationSetIndex).representations); + representations.addAll(manifestAdaptationSets.get(adaptationSetIndex).representations); } return representations; } From 4483639f9a710717e9463c793175422f55b90462 Mon Sep 17 00:00:00 2001 From: tonihei Date: Tue, 15 Jan 2019 15:08:12 +0000 Subject: [PATCH 30/38] Fix bug where missing switch adaptation set causes multiple identical track groups. When the extra adaptation set of a switch group isn't defined in the manifest, we currently assume it's the first adaptation group. This either leads to wrong grouping or duplicate track groups. Such a case may easily happen if the manifest is filtered such that only one of the switch adaptation sets will be present in the manifest. PiperOrigin-RevId: 229365379 --- .../source/dash/DashMediaPeriod.java | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java index 5c9a933508..ea5193eae1 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java @@ -46,6 +46,7 @@ import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy; import com.google.android.exoplayer2.upstream.LoaderErrorThrower; import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.util.MimeTypes; +import com.google.android.exoplayer2.util.Util; import java.io.IOException; import java.lang.annotation.Documented; import java.lang.annotation.Retention; @@ -452,13 +453,22 @@ import java.util.List; if (adaptationSetSwitchingProperty == null) { groupedAdaptationSetIndices[groupCount++] = new int[] {i}; } else { - String[] extraAdaptationSetIds = adaptationSetSwitchingProperty.value.split(","); + String[] extraAdaptationSetIds = Util.split(adaptationSetSwitchingProperty.value, ","); int[] adaptationSetIndices = new int[1 + extraAdaptationSetIds.length]; adaptationSetIndices[0] = i; + int outputIndex = 1; for (int j = 0; j < extraAdaptationSetIds.length; j++) { - int extraIndex = idToIndexMap.get(Integer.parseInt(extraAdaptationSetIds[j])); - adaptationSetUsedFlags[extraIndex] = true; - adaptationSetIndices[1 + j] = extraIndex; + int extraIndex = + idToIndexMap.get( + Integer.parseInt(extraAdaptationSetIds[j]), /* valueIfKeyNotFound= */ -1); + if (extraIndex != -1) { + adaptationSetUsedFlags[extraIndex] = true; + adaptationSetIndices[outputIndex] = extraIndex; + outputIndex++; + } + } + if (outputIndex < adaptationSetIndices.length) { + adaptationSetIndices = Arrays.copyOf(adaptationSetIndices, outputIndex); } groupedAdaptationSetIndices[groupCount++] = adaptationSetIndices; } From 8bf1267495a414aa5873d67657a23f3e730ff66b Mon Sep 17 00:00:00 2001 From: bachinger Date: Wed, 16 Jan 2019 11:05:44 +0000 Subject: [PATCH 31/38] allow developers to set the subText of the notifcation Issue: #5344 PiperOrigin-RevId: 229527963 --- RELEASENOTES.md | 2 ++ .../exoplayer2/ui/PlayerNotificationManager.java | 13 +++++++++++++ 2 files changed, 15 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index f6ecfb962e..7fb10727cc 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -16,6 +16,8 @@ ([#5351](https://github.com/google/ExoPlayer/issues/5351)). * Fix issue where uneven track durations in MP4 streams can cause OOM problems ([#3670](https://github.com/google/ExoPlayer/issues/3670)). +* Add the sub text to the MediaDescriptionAdapter of the + PlayerNotificationManager. ### 2.9.3 ### diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java index 47025d9bba..7cbe52d404 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java @@ -125,6 +125,18 @@ public class PlayerNotificationManager { @Nullable String getCurrentContentText(Player player); + /** + * Gets the content sub text for the current media item. + * + *

See {@link NotificationCompat.Builder#setSubText(CharSequence)}. + * + * @param player The {@link Player} for which a notification is being built. + */ + @Nullable + default String getCurrentSubText(Player player) { + return null; + } + /** * Gets the large icon for the current media item. * @@ -832,6 +844,7 @@ public class PlayerNotificationManager { // Set media specific notification properties from MediaDescriptionAdapter. builder.setContentTitle(mediaDescriptionAdapter.getCurrentContentTitle(player)); builder.setContentText(mediaDescriptionAdapter.getCurrentContentText(player)); + builder.setSubText(mediaDescriptionAdapter.getCurrentSubText(player)); if (largeIcon == null) { largeIcon = mediaDescriptionAdapter.getCurrentLargeIcon( From 9911c11191e7df82998eaac377debb33724ac9c0 Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 17 Jan 2019 17:00:11 +0000 Subject: [PATCH 32/38] Add start position to MediaSource.createPeriod. That's the same position set in MediaPeriod.prepare (where it may be removed in the future). Having the position at an earlier point is necessary to fix an issue with lazy preparation in ConcatenatingMediaSource where the prepare position was assumed to be known but MediaPeriod.prepare hasn't been called yet. Issue:#5350 PiperOrigin-RevId: 229756637 --- RELEASENOTES.md | 8 +++-- .../exoplayer2/ext/ima/ImaAdsMediaSource.java | 4 +-- .../android/exoplayer2/MediaPeriodHolder.java | 2 +- .../source/ClippingMediaSource.java | 4 +-- .../source/ConcatenatingMediaSource.java | 8 +++-- .../source/DeferredMediaPeriod.java | 36 ++++++++++--------- .../source/ExtractorMediaSource.java | 2 +- .../exoplayer2/source/LoopingMediaSource.java | 7 ++-- .../exoplayer2/source/MediaSource.java | 7 ++-- .../exoplayer2/source/MergingMediaSource.java | 4 +-- .../source/SingleSampleMediaSource.java | 2 +- .../exoplayer2/source/ads/AdsMediaSource.java | 8 +++-- .../source/dash/DashMediaSource.java | 3 +- .../exoplayer2/source/hls/HlsMediaSource.java | 2 +- .../source/smoothstreaming/SsMediaSource.java | 2 +- .../exoplayer2/testutil/FakeMediaSource.java | 2 +- .../testutil/MediaSourceTestRunner.java | 19 ++++++++-- 17 files changed, 74 insertions(+), 46 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 7fb10727cc..28d7fb5597 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -16,8 +16,12 @@ ([#5351](https://github.com/google/ExoPlayer/issues/5351)). * Fix issue where uneven track durations in MP4 streams can cause OOM problems ([#3670](https://github.com/google/ExoPlayer/issues/3670)). -* Add the sub text to the MediaDescriptionAdapter of the - PlayerNotificationManager. +* Add `startPositionUs` to `MediaSource.createPeriod`. This fixes an issue where + using lazy preparation in `ConcatenatingMediaSource` with an + `ExtractorMediaSource` overrides initial seek positions + ([#5350](https://github.com/google/ExoPlayer/issues/5350)). +* Add subtext to the `MediaDescriptionAdapter` of the + `PlayerNotificationManager`. ### 2.9.3 ### 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 85042c4354..0978ee401c 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 @@ -97,8 +97,8 @@ public final class ImaAdsMediaSource extends BaseMediaSource implements SourceIn } @Override - public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) { - return adsMediaSource.createPeriod(id, allocator); + public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) { + return adsMediaSource.createPeriod(id, allocator, startPositionUs); } @Override diff --git a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java index 5925c8f383..8e51740c56 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/MediaPeriodHolder.java @@ -79,7 +79,7 @@ import com.google.android.exoplayer2.util.Log; this.info = info; sampleStreams = new SampleStream[rendererCapabilities.length]; mayRetainStreamFlags = new boolean[rendererCapabilities.length]; - MediaPeriod mediaPeriod = mediaSource.createPeriod(info.id, allocator); + MediaPeriod mediaPeriod = mediaSource.createPeriod(info.id, allocator, info.startPositionUs); if (info.id.endPositionUs != C.TIME_END_OF_SOURCE) { mediaPeriod = new ClippingMediaPeriod( 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 3ed18049bf..fce1c4b877 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 @@ -240,10 +240,10 @@ public final class ClippingMediaSource extends CompositeMediaSource { } @Override - public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) { + public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) { ClippingMediaPeriod mediaPeriod = new ClippingMediaPeriod( - mediaSource.createPeriod(id, allocator), + mediaSource.createPeriod(id, allocator, startPositionUs), enableInitialDiscontinuity, periodStartUs, periodEndUs); 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 72ecd2c767..c93afdb249 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 @@ -476,7 +476,8 @@ public class ConcatenatingMediaSource extends CompositeMediaSource { } @Override - public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) { + public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) { if (loopCount == Integer.MAX_VALUE) { - return childSource.createPeriod(id, allocator); + return childSource.createPeriod(id, allocator, startPositionUs); } Object childPeriodUid = LoopingTimeline.getChildPeriodUidFromConcatenatedUid(id.periodUid); MediaPeriodId childMediaPeriodId = id.copyWithPeriodUid(childPeriodUid); childMediaPeriodIdToMediaPeriodId.put(childMediaPeriodId, id); - MediaPeriod mediaPeriod = childSource.createPeriod(childMediaPeriodId, allocator); + MediaPeriod mediaPeriod = + childSource.createPeriod(childMediaPeriodId, allocator, startPositionUs); mediaPeriodToChildMediaPeriodId.put(mediaPeriod, childMediaPeriodId); return mediaPeriod; } 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 74449ba16b..801737faef 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 @@ -35,8 +35,8 @@ import java.io.IOException; * on the {@link SourceInfoRefreshListener}s passed to {@link #prepareSource(ExoPlayer, * boolean, SourceInfoRefreshListener, TransferListener)}. *

  • To provide {@link MediaPeriod} instances for the periods in its timeline. MediaPeriods are - * obtained by calling {@link #createPeriod(MediaPeriodId, Allocator)}, and provide a way for - * the player to load and read the media. + * obtained by calling {@link #createPeriod(MediaPeriodId, Allocator, long)}, and provide a + * way for the player to load and read the media. * * * All methods are called on the player's internal playback thread, as described in the {@link @@ -274,9 +274,10 @@ public interface MediaSource { * * @param id The identifier of the period. * @param allocator An {@link Allocator} from which to obtain media buffer allocations. + * @param startPositionUs The expected start position, in microseconds. * @return A new {@link MediaPeriod}. */ - MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator); + MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs); /** * Releases the period. 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 573e97cb13..cc7202f9b2 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 @@ -124,13 +124,13 @@ public final class MergingMediaSource extends CompositeMediaSource { } @Override - public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) { + public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) { MediaPeriod[] periods = new MediaPeriod[mediaSources.length]; int periodIndex = timelines[0].getIndexOfPeriod(id.periodUid); for (int i = 0; i < periods.length; i++) { MediaPeriodId childMediaPeriodId = id.copyWithPeriodUid(timelines[i].getUidOfPeriod(periodIndex)); - periods[i] = mediaSources[i].createPeriod(childMediaPeriodId, allocator); + periods[i] = mediaSources[i].createPeriod(childMediaPeriodId, allocator, startPositionUs); } return new MergingMediaPeriod(compositeSequenceableLoaderFactory, periods); } 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 66097970c7..046672bb77 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 @@ -318,7 +318,7 @@ public final class SingleSampleMediaSource extends BaseMediaSource { } @Override - public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) { + public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) { return new SingleSampleMediaPeriod( dataSpec, dataSourceFactory, 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 19ddbd2c54..4bf661ddc0 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 @@ -341,7 +341,7 @@ public final class AdsMediaSource extends CompositeMediaSource { } @Override - public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) { + public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) { if (adPlaybackState.adGroupCount > 0 && id.isAd()) { int adGroupIndex = id.adGroupIndex; int adIndexInAdGroup = id.adIndexInAdGroup; @@ -360,7 +360,8 @@ public final class AdsMediaSource extends CompositeMediaSource { prepareChildSource(id, adMediaSource); } MediaSource mediaSource = adGroupMediaSources[adGroupIndex][adIndexInAdGroup]; - DeferredMediaPeriod deferredMediaPeriod = new DeferredMediaPeriod(mediaSource, id, allocator); + DeferredMediaPeriod deferredMediaPeriod = + new DeferredMediaPeriod(mediaSource, id, allocator, startPositionUs); deferredMediaPeriod.setPrepareErrorListener( new AdPrepareErrorListener(adUri, adGroupIndex, adIndexInAdGroup)); List mediaPeriods = deferredMediaPeriodByAdMediaSource.get(mediaSource); @@ -376,7 +377,8 @@ public final class AdsMediaSource extends CompositeMediaSource { } return deferredMediaPeriod; } else { - DeferredMediaPeriod mediaPeriod = new DeferredMediaPeriod(contentMediaSource, id, allocator); + DeferredMediaPeriod mediaPeriod = + new DeferredMediaPeriod(contentMediaSource, id, allocator, startPositionUs); mediaPeriod.createPeriod(id); return mediaPeriod; } 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 c8de8f02b1..c65bfceb39 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 @@ -635,7 +635,8 @@ public final class DashMediaSource extends BaseMediaSource { } @Override - public MediaPeriod createPeriod(MediaPeriodId periodId, Allocator allocator) { + public MediaPeriod createPeriod( + MediaPeriodId periodId, Allocator allocator, long startPositionUs) { int periodIndex = (Integer) periodId.periodUid - firstPeriodId; EventDispatcher periodEventDispatcher = createEventDispatcher(periodId, manifest.getPeriod(periodIndex).startMs); 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 a9b0c579ac..2afd041631 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 @@ -412,7 +412,7 @@ public final class HlsMediaSource extends BaseMediaSource } @Override - public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) { + public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) { EventDispatcher eventDispatcher = createEventDispatcher(id); return new HlsMediaPeriod( extractorFactory, 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 103a52a55a..d025f8fa3a 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 @@ -533,7 +533,7 @@ public final class SsMediaSource extends BaseMediaSource } @Override - public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) { + public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) { EventDispatcher eventDispatcher = createEventDispatcher(id); SsMediaPeriod period = new SsMediaPeriod( 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 1f0c0c1a40..999372b90a 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 @@ -116,7 +116,7 @@ public class FakeMediaSource extends BaseMediaSource { } @Override - public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) { + public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) { assertThat(preparedSource).isTrue(); assertThat(releasedSource).isFalse(); int periodIndex = timeline.getIndexOfPeriod(id.periodUid); diff --git a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java index 70e7669dfb..e6fb5bc5f3 100644 --- a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java +++ b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java @@ -142,15 +142,28 @@ public class MediaSourceTestRunner { } /** - * Calls {@link MediaSource#createPeriod(MediaSource.MediaPeriodId, Allocator)} on the playback - * thread, asserting that a non-null {@link MediaPeriod} is returned. + * Calls {@link MediaSource#createPeriod(MediaSource.MediaPeriodId, Allocator, long)} with a zero + * start position on the playback thread, asserting that a non-null {@link MediaPeriod} is + * returned. * * @param periodId The id of the period to create. * @return The created {@link MediaPeriod}. */ public MediaPeriod createPeriod(final MediaPeriodId periodId) { + return createPeriod(periodId, /* startPositionUs= */ 0); + } + + /** + * Calls {@link MediaSource#createPeriod(MediaSource.MediaPeriodId, Allocator, long)} on the + * playback thread, asserting that a non-null {@link MediaPeriod} is returned. + * + * @param periodId The id of the period to create. + * @return The created {@link MediaPeriod}. + */ + public MediaPeriod createPeriod(final MediaPeriodId periodId, long startPositionUs) { final MediaPeriod[] holder = new MediaPeriod[1]; - runOnPlaybackThread(() -> holder[0] = mediaSource.createPeriod(periodId, allocator)); + runOnPlaybackThread( + () -> holder[0] = mediaSource.createPeriod(periodId, allocator, startPositionUs)); assertThat(holder[0]).isNotNull(); return holder[0]; } From 29376b3dd2c820bd3c3497edd7ff583d9b0f530d Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 17 Jan 2019 17:08:52 +0000 Subject: [PATCH 33/38] Add missing @Nullable to SimpleExoPlayer fields and methods. Issue:#5402 PiperOrigin-RevId: 229758525 --- .../android/exoplayer2/SimpleExoPlayer.java | 43 +++++++++---------- .../exoplayer2/ui/DebugTextViewHelper.java | 31 ++++++++++--- 2 files changed, 44 insertions(+), 30 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java index fe52cc7e8c..eba02b0c0a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java @@ -94,25 +94,25 @@ public class SimpleExoPlayer extends BasePlayer private final AudioFocusManager audioFocusManager; - private Format videoFormat; - private Format audioFormat; + @Nullable private Format videoFormat; + @Nullable private Format audioFormat; - private Surface surface; + @Nullable private Surface surface; private boolean ownsSurface; private @C.VideoScalingMode int videoScalingMode; - private SurfaceHolder surfaceHolder; - private TextureView textureView; + @Nullable private SurfaceHolder surfaceHolder; + @Nullable private TextureView textureView; private int surfaceWidth; private int surfaceHeight; - private DecoderCounters videoDecoderCounters; - private DecoderCounters audioDecoderCounters; + @Nullable private DecoderCounters videoDecoderCounters; + @Nullable private DecoderCounters audioDecoderCounters; private int audioSessionId; private AudioAttributes audioAttributes; private float audioVolume; - private MediaSource mediaSource; + @Nullable private MediaSource mediaSource; private List currentCues; - private VideoFrameMetadataListener videoFrameMetadataListener; - private CameraMotionListener cameraMotionListener; + @Nullable private VideoFrameMetadataListener videoFrameMetadataListener; + @Nullable private CameraMotionListener cameraMotionListener; private boolean hasNotifiedFullWrongThreadWarning; /** @@ -558,30 +558,26 @@ public class SimpleExoPlayer extends BasePlayer setPlaybackParameters(playbackParameters); } - /** - * Returns the video format currently being played, or null if no video is being played. - */ + /** Returns the video format currently being played, or null if no video is being played. */ + @Nullable public Format getVideoFormat() { return videoFormat; } - /** - * Returns the audio format currently being played, or null if no audio is being played. - */ + /** Returns the audio format currently being played, or null if no audio is being played. */ + @Nullable public Format getAudioFormat() { return audioFormat; } - /** - * Returns {@link DecoderCounters} for video, or null if no video is being played. - */ + /** Returns {@link DecoderCounters} for video, or null if no video is being played. */ + @Nullable public DecoderCounters getVideoDecoderCounters() { return videoDecoderCounters; } - /** - * Returns {@link DecoderCounters} for audio, or null if no audio is being played. - */ + /** Returns {@link DecoderCounters} for audio, or null if no audio is being played. */ + @Nullable public DecoderCounters getAudioDecoderCounters() { return audioDecoderCounters; } @@ -1048,7 +1044,8 @@ public class SimpleExoPlayer extends BasePlayer } @Override - public @Nullable Object getCurrentManifest() { + @Nullable + public Object getCurrentManifest() { verifyApplicationThread(); return player.getCurrentManifest(); } diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/DebugTextViewHelper.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/DebugTextViewHelper.java index 8c7c507f92..da2081db31 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/DebugTextViewHelper.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/DebugTextViewHelper.java @@ -137,23 +137,40 @@ public class DebugTextViewHelper implements Player.EventListener, Runnable { /** Returns a string containing video debugging information. */ protected String getVideoString() { Format format = player.getVideoFormat(); - if (format == null) { + DecoderCounters decoderCounters = player.getVideoDecoderCounters(); + if (format == null || decoderCounters == null) { return ""; } - return "\n" + format.sampleMimeType + "(id:" + format.id + " r:" + format.width + "x" - + format.height + getPixelAspectRatioString(format.pixelWidthHeightRatio) - + getDecoderCountersBufferCountString(player.getVideoDecoderCounters()) + ")"; + return "\n" + + format.sampleMimeType + + "(id:" + + format.id + + " r:" + + format.width + + "x" + + format.height + + getPixelAspectRatioString(format.pixelWidthHeightRatio) + + getDecoderCountersBufferCountString(decoderCounters) + + ")"; } /** Returns a string containing audio debugging information. */ protected String getAudioString() { Format format = player.getAudioFormat(); - if (format == null) { + DecoderCounters decoderCounters = player.getAudioDecoderCounters(); + if (format == null || decoderCounters == null) { return ""; } - return "\n" + format.sampleMimeType + "(id:" + format.id + " hz:" + format.sampleRate + " ch:" + return "\n" + + format.sampleMimeType + + "(id:" + + format.id + + " hz:" + + format.sampleRate + + " ch:" + format.channelCount - + getDecoderCountersBufferCountString(player.getAudioDecoderCounters()) + ")"; + + getDecoderCountersBufferCountString(decoderCounters) + + ")"; } private static String getDecoderCountersBufferCountString(DecoderCounters counters) { From 563d3c20ab32a8f4bdf46d8f9e6dcd62fb6aaccf Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 21 Jan 2019 12:15:30 +0000 Subject: [PATCH 34/38] Add max video size workaround for Amlogic decoder. The Amlogic awesome decoder reduces the video size of interlaced videos by half if the internal configuration isn't force reset with new maximum input size values. The product of these new values must exceed 1920x1088 to force the reset. Issue:#5003 PiperOrigin-RevId: 230206675 --- RELEASENOTES.md | 14 ++++++++------ .../video/MediaCodecVideoRenderer.java | 16 ++++++++++++++++ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 28d7fb5597..31129f3b71 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -8,6 +8,14 @@ ([#5378](https://github.com/google/ExoPlayer/issues/5378)). * FFmpeg extension: Treat invalid data errors as non-fatal to match the behavior of MediaCodec ([#5293](https://github.com/google/ExoPlayer/issues/5293)). +* Add `startPositionUs` to `MediaSource.createPeriod`. This fixes an issue where + using lazy preparation in `ConcatenatingMediaSource` with an + `ExtractorMediaSource` overrides initial seek positions + ([#5350](https://github.com/google/ExoPlayer/issues/5350)). +* Add subtext to the `MediaDescriptionAdapter` of the + `PlayerNotificationManager`. +* Add workaround for video quality problems with Amlogic decoders + ([#5003](https://github.com/google/ExoPlayer/issues/5003)). * Fix issue where sending callbacks for playlist changes may cause problems because of parallel player access ([#5240](https://github.com/google/ExoPlayer/issues/5240)). @@ -16,12 +24,6 @@ ([#5351](https://github.com/google/ExoPlayer/issues/5351)). * Fix issue where uneven track durations in MP4 streams can cause OOM problems ([#3670](https://github.com/google/ExoPlayer/issues/3670)). -* Add `startPositionUs` to `MediaSource.createPeriod`. This fixes an issue where - using lazy preparation in `ConcatenatingMediaSource` with an - `ExtractorMediaSource` overrides initial seek positions - ([#5350](https://github.com/google/ExoPlayer/issues/5350)). -* Add subtext to the `MediaDescriptionAdapter` of the - `PlayerNotificationManager`. ### 2.9.3 ### diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java index 9d0aae4fcf..388aa29ce9 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java @@ -1087,6 +1087,10 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { throws DecoderQueryException { int maxWidth = format.width; int maxHeight = format.height; + if (codecNeedsMaxVideoSizeResetWorkaround(codecInfo.name)) { + maxWidth = Math.max(maxWidth, 1920); + maxHeight = Math.max(maxHeight, 1089); + } int maxInputSize = getMaxInputSize(codecInfo, format); if (streamFormats.length == 1) { // The single entry in streamFormats must correspond to the format for which the codec is @@ -1274,6 +1278,18 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { return "NVIDIA".equals(Util.MANUFACTURER); } + /** + * Returns whether the codec is known to have problems with the configuration for interlaced + * content and needs minimum values for the maximum video size to force reset the configuration. + * + *

    See https://github.com/google/ExoPlayer/issues/5003. + * + * @param name The name of the codec. + */ + private static boolean codecNeedsMaxVideoSizeResetWorkaround(String name) { + return "OMX.amlogic.avc.decoder.awesome".equals(name) && Util.SDK_INT <= 25; + } + /* * TODO: * From 95bef2d9b138fefb48ed0efd0cd51c17a63dd130 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Fri, 18 Jan 2019 16:21:57 +0000 Subject: [PATCH 35/38] Upgrade to GVR SDK 1.190.0 Change the dependency to the new monolithic GVR SDK target. PiperOrigin-RevId: 229931549 --- RELEASENOTES.md | 9 +-------- extensions/gvr/build.gradle | 3 ++- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 31129f3b71..10f14c3d7b 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -8,14 +8,7 @@ ([#5378](https://github.com/google/ExoPlayer/issues/5378)). * FFmpeg extension: Treat invalid data errors as non-fatal to match the behavior of MediaCodec ([#5293](https://github.com/google/ExoPlayer/issues/5293)). -* Add `startPositionUs` to `MediaSource.createPeriod`. This fixes an issue where - using lazy preparation in `ConcatenatingMediaSource` with an - `ExtractorMediaSource` overrides initial seek positions - ([#5350](https://github.com/google/ExoPlayer/issues/5350)). -* Add subtext to the `MediaDescriptionAdapter` of the - `PlayerNotificationManager`. -* Add workaround for video quality problems with Amlogic decoders - ([#5003](https://github.com/google/ExoPlayer/issues/5003)). +* GVR extension: upgrade GVR SDK dependency to 1.190.0. * Fix issue where sending callbacks for playlist changes may cause problems because of parallel player access ([#5240](https://github.com/google/ExoPlayer/issues/5240)). diff --git a/extensions/gvr/build.gradle b/extensions/gvr/build.gradle index af973e1345..234f551896 100644 --- a/extensions/gvr/build.gradle +++ b/extensions/gvr/build.gradle @@ -32,7 +32,8 @@ android { dependencies { implementation project(modulePrefix + 'library-core') implementation 'com.android.support:support-annotations:' + supportLibraryVersion - implementation 'com.google.vr:sdk-audio:1.80.0' + api 'com.google.vr:sdk-base:1.190.0' + compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion } ext { From 289f63c6505a95049a955fc294ac189e44453686 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Mon, 21 Jan 2019 13:23:52 +0000 Subject: [PATCH 36/38] Fix GVR dependency PiperOrigin-RevId: 230213842 From 0b49d002f109b2c85538f896e27def12236c1ae2 Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 21 Jan 2019 13:52:14 +0000 Subject: [PATCH 37/38] Use loading period event time for fatal load errors. ExoPlaybackExceptions of type SOURCE are always associated with the loading period and thus we can use the event time for the loading period in onPlayerError. Renderer and unexpected exceptions are still associated with the currently playing period. Issue:#5407 PiperOrigin-RevId: 230216253 --- RELEASENOTES.md | 3 +++ .../android/exoplayer2/analytics/AnalyticsCollector.java | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 10f14c3d7b..1594e63b8d 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -9,6 +9,9 @@ * FFmpeg extension: Treat invalid data errors as non-fatal to match the behavior of MediaCodec ([#5293](https://github.com/google/ExoPlayer/issues/5293)). * GVR extension: upgrade GVR SDK dependency to 1.190.0. +* Associate fatal player errors of type SOURCE with the loading source in + `AnalyticsListener.EventTime` + ([#5407](https://github.com/google/ExoPlayer/issues/5407)). * Fix issue where sending callbacks for playlist changes may cause problems because of parallel player access ([#5240](https://github.com/google/ExoPlayer/issues/5240)). diff --git a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java index 113add612a..55031e2d12 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java @@ -488,7 +488,10 @@ public class AnalyticsCollector @Override public final void onPlayerError(ExoPlaybackException error) { - EventTime eventTime = generatePlayingMediaPeriodEventTime(); + EventTime eventTime = + error.type == ExoPlaybackException.TYPE_SOURCE + ? generateLoadingMediaPeriodEventTime() + : generatePlayingMediaPeriodEventTime(); for (AnalyticsListener listener : listeners) { listener.onPlayerError(eventTime, error); } From 200c877d712a15456ffa8129299658090f3f99a6 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Mon, 21 Jan 2019 14:21:29 +0000 Subject: [PATCH 38/38] Fix release notes --- RELEASENOTES.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 1594e63b8d..68794310bd 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -12,6 +12,14 @@ * Associate fatal player errors of type SOURCE with the loading source in `AnalyticsListener.EventTime` ([#5407](https://github.com/google/ExoPlayer/issues/5407)). +* Add `startPositionUs` to `MediaSource.createPeriod`. This fixes an issue where + using lazy preparation in `ConcatenatingMediaSource` with an + `ExtractorMediaSource` overrides initial seek positions + ([#5350](https://github.com/google/ExoPlayer/issues/5350)). +* Add subtext to the `MediaDescriptionAdapter` of the + `PlayerNotificationManager`. +* Add workaround for video quality problems with Amlogic decoders + ([#5003](https://github.com/google/ExoPlayer/issues/5003)). * Fix issue where sending callbacks for playlist changes may cause problems because of parallel player access ([#5240](https://github.com/google/ExoPlayer/issues/5240)).