From b2831d8559713b757feb3f15a2c828db0e5ecd7c Mon Sep 17 00:00:00 2001 From: rohks Date: Mon, 27 Jun 2022 11:31:10 +0000 Subject: [PATCH] Add timestamp to `Metadata` `MetadataRenderer` is updated to output `Metadata` with its presentation time, in microseconds. PiperOrigin-RevId: 457444718 --- .../java/androidx/media3/common/Metadata.java | 43 ++++++++++++++++--- .../androidx/media3/common/text/CueGroup.java | 2 +- .../androidx/media3/common/MetadataTest.java | 5 ++- .../exoplayer/metadata/MetadataRenderer.java | 26 +++++++---- .../test/assets/playbackdumps/dash/emsg.dump | 3 ++ .../playbackdumps/ts/sample_ait.ts.dump | 3 ++ .../playbackdumps/ts/sample_scte35.ts.dump | 3 ++ .../ts/sample_with_id3.adts.dump | 2 + .../utils/robolectric/PlaybackOutput.java | 1 + 9 files changed, 73 insertions(+), 15 deletions(-) diff --git a/libraries/common/src/main/java/androidx/media3/common/Metadata.java b/libraries/common/src/main/java/androidx/media3/common/Metadata.java index 1af08378f7..eadf456c18 100644 --- a/libraries/common/src/main/java/androidx/media3/common/Metadata.java +++ b/libraries/common/src/main/java/androidx/media3/common/Metadata.java @@ -20,6 +20,7 @@ import android.os.Parcelable; import androidx.annotation.Nullable; import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.Util; +import com.google.common.primitives.Longs; import java.util.Arrays; import java.util.List; @@ -61,11 +62,28 @@ public final class Metadata implements Parcelable { } private final Entry[] entries; + /** + * The presentation time of the metadata, in microseconds. + * + *

This time is an offset from the start of the current {@link Timeline.Period}. + * + *

This time is {@link C#TIME_UNSET} when not known or undefined. + */ + public final long presentationTimeUs; /** * @param entries The metadata entries. */ public Metadata(Entry... entries) { + this(/* presentationTimeUs= */ C.TIME_UNSET, entries); + } + + /** + * @param presentationTimeUs The presentation time for the metadata entries. + * @param entries The metadata entries. + */ + public Metadata(long presentationTimeUs, Entry... entries) { + this.presentationTimeUs = presentationTimeUs; this.entries = entries; } @@ -73,7 +91,15 @@ public final class Metadata implements Parcelable { * @param entries The metadata entries. */ public Metadata(List entries) { - this.entries = entries.toArray(new Entry[0]); + this(entries.toArray(new Entry[0])); + } + + /** + * @param presentationTimeUs The presentation time for the metadata entries. + * @param entries The metadata entries. + */ + public Metadata(long presentationTimeUs, List entries) { + this(presentationTimeUs, entries.toArray(new Entry[0])); } /* package */ Metadata(Parcel in) { @@ -81,6 +107,7 @@ public final class Metadata implements Parcelable { for (int i = 0; i < entries.length; i++) { entries[i] = in.readParcelable(Entry.class.getClassLoader()); } + presentationTimeUs = in.readLong(); } /** Returns the number of metadata entries. */ @@ -123,7 +150,8 @@ public final class Metadata implements Parcelable { if (entriesToAppend.length == 0) { return this; } - return new Metadata(Util.nullSafeArrayConcatenation(entries, entriesToAppend)); + return new Metadata( + presentationTimeUs, Util.nullSafeArrayConcatenation(entries, entriesToAppend)); } @Override @@ -135,17 +163,21 @@ public final class Metadata implements Parcelable { return false; } Metadata other = (Metadata) obj; - return Arrays.equals(entries, other.entries); + return Arrays.equals(entries, other.entries) && presentationTimeUs == other.presentationTimeUs; } @Override public int hashCode() { - return Arrays.hashCode(entries); + int result = Arrays.hashCode(entries); + result = 31 * result + Longs.hashCode(presentationTimeUs); + return result; } @Override public String toString() { - return "entries=" + Arrays.toString(entries); + return "entries=" + + Arrays.toString(entries) + + (presentationTimeUs == C.TIME_UNSET ? "" : ", presentationTimeUs=" + presentationTimeUs); } // Parcelable implementation. @@ -161,6 +193,7 @@ public final class Metadata implements Parcelable { for (Entry entry : entries) { dest.writeParcelable(entry, 0); } + dest.writeLong(presentationTimeUs); } public static final Parcelable.Creator CREATOR = diff --git a/libraries/common/src/main/java/androidx/media3/common/text/CueGroup.java b/libraries/common/src/main/java/androidx/media3/common/text/CueGroup.java index 3d1f1bec20..67e58eb065 100644 --- a/libraries/common/src/main/java/androidx/media3/common/text/CueGroup.java +++ b/libraries/common/src/main/java/androidx/media3/common/text/CueGroup.java @@ -47,7 +47,7 @@ public final class CueGroup implements Bundleable { /** * The presentation time of the {@link #cues}, in microseconds. * - *

This time is an offset from the start of the current {@link Timeline.Period} + *

This time is an offset from the start of the current {@link Timeline.Period}. */ @UnstableApi public final long presentationTimeUs; diff --git a/libraries/common/src/test/java/androidx/media3/common/MetadataTest.java b/libraries/common/src/test/java/androidx/media3/common/MetadataTest.java index 31ad83244a..0aa869aaad 100644 --- a/libraries/common/src/test/java/androidx/media3/common/MetadataTest.java +++ b/libraries/common/src/test/java/androidx/media3/common/MetadataTest.java @@ -30,7 +30,10 @@ public class MetadataTest { @Test public void parcelable() { Metadata metadataToParcel = - new Metadata(new FakeMetadataEntry("id1"), new FakeMetadataEntry("id2")); + new Metadata( + /* presentationTimeUs= */ 1_230_000, + new FakeMetadataEntry("id1"), + new FakeMetadataEntry("id2")); Parcel parcel = Parcel.obtain(); metadataToParcel.writeToParcel(parcel, 0); diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/metadata/MetadataRenderer.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/metadata/MetadataRenderer.java index 4aca3b8164..0c7b16f8f3 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/metadata/MetadataRenderer.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/metadata/MetadataRenderer.java @@ -15,6 +15,7 @@ */ package androidx.media3.exoplayer.metadata; +import static androidx.media3.common.util.Assertions.checkState; import static androidx.media3.common.util.Util.castNonNull; import android.os.Handler; @@ -36,6 +37,7 @@ import androidx.media3.extractor.metadata.MetadataDecoder; import androidx.media3.extractor.metadata.MetadataInputBuffer; import java.util.ArrayList; import java.util.List; +import org.checkerframework.dataflow.qual.SideEffectFree; /** A renderer for metadata. */ @UnstableApi @@ -53,8 +55,8 @@ public final class MetadataRenderer extends BaseRenderer implements Callback { private boolean inputStreamEnded; private boolean outputStreamEnded; private long subsampleOffsetUs; - private long pendingMetadataTimestampUs; @Nullable private Metadata pendingMetadata; + private long outputStreamOffsetUs; /** * @param output The output. @@ -85,7 +87,7 @@ public final class MetadataRenderer extends BaseRenderer implements Callback { outputLooper == null ? null : Util.createHandler(outputLooper, /* callback= */ this); this.decoderFactory = Assertions.checkNotNull(decoderFactory); buffer = new MetadataInputBuffer(); - pendingMetadataTimestampUs = C.TIME_UNSET; + outputStreamOffsetUs = C.TIME_UNSET; } @Override @@ -106,12 +108,12 @@ public final class MetadataRenderer extends BaseRenderer implements Callback { @Override protected void onStreamChanged(Format[] formats, long startPositionUs, long offsetUs) { decoder = decoderFactory.createDecoder(formats[0]); + outputStreamOffsetUs = offsetUs; } @Override protected void onPositionReset(long positionUs, boolean joining) { pendingMetadata = null; - pendingMetadataTimestampUs = C.TIME_UNSET; inputStreamEnded = false; outputStreamEnded = false; } @@ -158,8 +160,8 @@ public final class MetadataRenderer extends BaseRenderer implements Callback { @Override protected void onDisabled() { pendingMetadata = null; - pendingMetadataTimestampUs = C.TIME_UNSET; decoder = null; + outputStreamOffsetUs = C.TIME_UNSET; } @Override @@ -200,9 +202,9 @@ public final class MetadataRenderer extends BaseRenderer implements Callback { List entries = new ArrayList<>(metadata.length()); decodeWrappedMetadata(metadata, entries); if (!entries.isEmpty()) { - Metadata expandedMetadata = new Metadata(entries); + Metadata expandedMetadata = + new Metadata(getPresentationTimeUs(buffer.timeUs), entries); pendingMetadata = expandedMetadata; - pendingMetadataTimestampUs = buffer.timeUs; } } } @@ -214,10 +216,10 @@ public final class MetadataRenderer extends BaseRenderer implements Callback { private boolean outputMetadata(long positionUs) { boolean didOutput = false; - if (pendingMetadata != null && pendingMetadataTimestampUs <= positionUs) { + if (pendingMetadata != null + && pendingMetadata.presentationTimeUs <= getPresentationTimeUs(positionUs)) { invokeRenderer(pendingMetadata); pendingMetadata = null; - pendingMetadataTimestampUs = C.TIME_UNSET; didOutput = true; } if (inputStreamEnded && pendingMetadata == null) { @@ -237,4 +239,12 @@ public final class MetadataRenderer extends BaseRenderer implements Callback { private void invokeRendererInternal(Metadata metadata) { output.onMetadata(metadata); } + + @SideEffectFree + private long getPresentationTimeUs(long positionUs) { + checkState(positionUs != C.TIME_UNSET); + checkState(outputStreamOffsetUs != C.TIME_UNSET); + + return positionUs - outputStreamOffsetUs; + } } diff --git a/libraries/test_data/src/test/assets/playbackdumps/dash/emsg.dump b/libraries/test_data/src/test/assets/playbackdumps/dash/emsg.dump index 4a3c7b2e0e..2bc2f0c2b9 100644 --- a/libraries/test_data/src/test/assets/playbackdumps/dash/emsg.dump +++ b/libraries/test_data/src/test/assets/playbackdumps/dash/emsg.dump @@ -94,8 +94,11 @@ MediaCodecAdapter (exotest.audio.aac): buffers[91] = length 0, hash 1 MetadataOutput: Metadata[0]: + presentationTimeUs = 100000 entry[0] = EMSG: scheme=urn:mpeg:dash:event:callback:2015, id=0, durationMs=1000, value=1 Metadata[1]: + presentationTimeUs = 100000 entry[0] = EMSG: scheme=urn:mpeg:dash:event:callback:2015, id=1, durationMs=1000, value=1 Metadata[2]: + presentationTimeUs = 1000000 entry[0] = EMSG: scheme=urn:mpeg:dash:event:callback:2015, id=2, durationMs=1000, value=1 diff --git a/libraries/test_data/src/test/assets/playbackdumps/ts/sample_ait.ts.dump b/libraries/test_data/src/test/assets/playbackdumps/ts/sample_ait.ts.dump index 75a33b8c24..c94ca1b2c6 100644 --- a/libraries/test_data/src/test/assets/playbackdumps/ts/sample_ait.ts.dump +++ b/libraries/test_data/src/test/assets/playbackdumps/ts/sample_ait.ts.dump @@ -22,11 +22,14 @@ MediaCodecAdapter (exotest.audio.eac3): buffers[19] = length 0, hash 1 MetadataOutput: Metadata[0]: + presentationTimeUs = 0 entry[0] = Ait(controlCode=1,url=http://static-cdn.arte.tv/redbutton/index_fr.html) entry[1] = Ait(controlCode=2,url=http://www.arte.tv/hbbtvv2/index.html?lang=fr_FR&page=PLUS7) Metadata[1]: + presentationTimeUs = 192000 entry[0] = Ait(controlCode=1,url=http://static-cdn.arte.tv/redbutton/index_fr.html) entry[1] = Ait(controlCode=2,url=http://www.arte.tv/hbbtvv2/index.html?lang=fr_FR&page=PLUS7) Metadata[2]: + presentationTimeUs = 384000 entry[0] = Ait(controlCode=1,url=http://static-cdn.arte.tv/redbutton/index_fr.html) entry[1] = Ait(controlCode=2,url=http://www.arte.tv/hbbtvv2/index.html?lang=fr_FR&page=PLUS7) diff --git a/libraries/test_data/src/test/assets/playbackdumps/ts/sample_scte35.ts.dump b/libraries/test_data/src/test/assets/playbackdumps/ts/sample_scte35.ts.dump index d57c3ed9df..03a95ad04a 100644 --- a/libraries/test_data/src/test/assets/playbackdumps/ts/sample_scte35.ts.dump +++ b/libraries/test_data/src/test/assets/playbackdumps/ts/sample_scte35.ts.dump @@ -12,8 +12,11 @@ MediaCodecAdapter (exotest.video.mpeg2): buffers[2] = length 0, hash 1 MetadataOutput: Metadata[0]: + presentationTimeUs = 33366 entry[0] = SCTE-35 splice command: type=SpliceInsertCommand Metadata[1]: + presentationTimeUs = 33366 entry[0] = SCTE-35 splice command: type=SpliceInsertCommand Metadata[2]: + presentationTimeUs = 33366 entry[0] = SCTE-35 splice command: type=SpliceInsertCommand diff --git a/libraries/test_data/src/test/assets/playbackdumps/ts/sample_with_id3.adts.dump b/libraries/test_data/src/test/assets/playbackdumps/ts/sample_with_id3.adts.dump index 0434d23c38..f272f7f348 100644 --- a/libraries/test_data/src/test/assets/playbackdumps/ts/sample_with_id3.adts.dump +++ b/libraries/test_data/src/test/assets/playbackdumps/ts/sample_with_id3.adts.dump @@ -147,7 +147,9 @@ MediaCodecAdapter (exotest.audio.aac): buffers[144] = length 0, hash 1 MetadataOutput: Metadata[0]: + presentationTimeUs = 0 entry[0] = APIC: mimeType=image/jpeg, description=Hello World Metadata[1]: + presentationTimeUs = 23219 entry[0] = COMM: language=eng, description=description entry[1] = APIC: mimeType=image/jpeg, description=Hello World diff --git a/libraries/test_utils_robolectric/src/main/java/androidx/media3/test/utils/robolectric/PlaybackOutput.java b/libraries/test_utils_robolectric/src/main/java/androidx/media3/test/utils/robolectric/PlaybackOutput.java index 8fc015fc9c..f5f1bfb8ec 100644 --- a/libraries/test_utils_robolectric/src/main/java/androidx/media3/test/utils/robolectric/PlaybackOutput.java +++ b/libraries/test_utils_robolectric/src/main/java/androidx/media3/test/utils/robolectric/PlaybackOutput.java @@ -122,6 +122,7 @@ public final class PlaybackOutput implements Dumper.Dumpable { for (int i = 0; i < metadatas.size(); i++) { dumper.startBlock("Metadata[" + i + "]"); Metadata metadata = metadatas.get(i); + dumper.add("presentationTimeUs", metadata.presentationTimeUs); for (int j = 0; j < metadata.length(); j++) { dumper.add("entry[" + j + "]", getEntryAsString(metadata.get(j))); }