Add timestamp to Metadata
`MetadataRenderer` is updated to output `Metadata` with its presentation time, in microseconds. PiperOrigin-RevId: 457444718
This commit is contained in:
parent
5c2752b4a9
commit
b2831d8559
@ -20,6 +20,7 @@ import android.os.Parcelable;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
|
import com.google.common.primitives.Longs;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -61,11 +62,28 @@ public final class Metadata implements Parcelable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private final Entry[] entries;
|
private final Entry[] entries;
|
||||||
|
/**
|
||||||
|
* The presentation time of the metadata, in microseconds.
|
||||||
|
*
|
||||||
|
* <p>This time is an offset from the start of the current {@link Timeline.Period}.
|
||||||
|
*
|
||||||
|
* <p>This time is {@link C#TIME_UNSET} when not known or undefined.
|
||||||
|
*/
|
||||||
|
public final long presentationTimeUs;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param entries The metadata entries.
|
* @param entries The metadata entries.
|
||||||
*/
|
*/
|
||||||
public Metadata(Entry... 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;
|
this.entries = entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,7 +91,15 @@ public final class Metadata implements Parcelable {
|
|||||||
* @param entries The metadata entries.
|
* @param entries The metadata entries.
|
||||||
*/
|
*/
|
||||||
public Metadata(List<? extends Entry> entries) {
|
public Metadata(List<? extends Entry> 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<? extends Entry> entries) {
|
||||||
|
this(presentationTimeUs, entries.toArray(new Entry[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* package */ Metadata(Parcel in) {
|
/* package */ Metadata(Parcel in) {
|
||||||
@ -81,6 +107,7 @@ public final class Metadata implements Parcelable {
|
|||||||
for (int i = 0; i < entries.length; i++) {
|
for (int i = 0; i < entries.length; i++) {
|
||||||
entries[i] = in.readParcelable(Entry.class.getClassLoader());
|
entries[i] = in.readParcelable(Entry.class.getClassLoader());
|
||||||
}
|
}
|
||||||
|
presentationTimeUs = in.readLong();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the number of metadata entries. */
|
/** Returns the number of metadata entries. */
|
||||||
@ -123,7 +150,8 @@ public final class Metadata implements Parcelable {
|
|||||||
if (entriesToAppend.length == 0) {
|
if (entriesToAppend.length == 0) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
return new Metadata(Util.nullSafeArrayConcatenation(entries, entriesToAppend));
|
return new Metadata(
|
||||||
|
presentationTimeUs, Util.nullSafeArrayConcatenation(entries, entriesToAppend));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -135,17 +163,21 @@ public final class Metadata implements Parcelable {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Metadata other = (Metadata) obj;
|
Metadata other = (Metadata) obj;
|
||||||
return Arrays.equals(entries, other.entries);
|
return Arrays.equals(entries, other.entries) && presentationTimeUs == other.presentationTimeUs;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Arrays.hashCode(entries);
|
int result = Arrays.hashCode(entries);
|
||||||
|
result = 31 * result + Longs.hashCode(presentationTimeUs);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "entries=" + Arrays.toString(entries);
|
return "entries="
|
||||||
|
+ Arrays.toString(entries)
|
||||||
|
+ (presentationTimeUs == C.TIME_UNSET ? "" : ", presentationTimeUs=" + presentationTimeUs);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parcelable implementation.
|
// Parcelable implementation.
|
||||||
@ -161,6 +193,7 @@ public final class Metadata implements Parcelable {
|
|||||||
for (Entry entry : entries) {
|
for (Entry entry : entries) {
|
||||||
dest.writeParcelable(entry, 0);
|
dest.writeParcelable(entry, 0);
|
||||||
}
|
}
|
||||||
|
dest.writeLong(presentationTimeUs);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final Parcelable.Creator<Metadata> CREATOR =
|
public static final Parcelable.Creator<Metadata> CREATOR =
|
||||||
|
@ -47,7 +47,7 @@ public final class CueGroup implements Bundleable {
|
|||||||
/**
|
/**
|
||||||
* The presentation time of the {@link #cues}, in microseconds.
|
* The presentation time of the {@link #cues}, in microseconds.
|
||||||
*
|
*
|
||||||
* <p>This time is an offset from the start of the current {@link Timeline.Period}
|
* <p>This time is an offset from the start of the current {@link Timeline.Period}.
|
||||||
*/
|
*/
|
||||||
@UnstableApi public final long presentationTimeUs;
|
@UnstableApi public final long presentationTimeUs;
|
||||||
|
|
||||||
|
@ -30,7 +30,10 @@ public class MetadataTest {
|
|||||||
@Test
|
@Test
|
||||||
public void parcelable() {
|
public void parcelable() {
|
||||||
Metadata metadataToParcel =
|
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();
|
Parcel parcel = Parcel.obtain();
|
||||||
metadataToParcel.writeToParcel(parcel, 0);
|
metadataToParcel.writeToParcel(parcel, 0);
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.exoplayer.metadata;
|
package androidx.media3.exoplayer.metadata;
|
||||||
|
|
||||||
|
import static androidx.media3.common.util.Assertions.checkState;
|
||||||
import static androidx.media3.common.util.Util.castNonNull;
|
import static androidx.media3.common.util.Util.castNonNull;
|
||||||
|
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
@ -36,6 +37,7 @@ import androidx.media3.extractor.metadata.MetadataDecoder;
|
|||||||
import androidx.media3.extractor.metadata.MetadataInputBuffer;
|
import androidx.media3.extractor.metadata.MetadataInputBuffer;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import org.checkerframework.dataflow.qual.SideEffectFree;
|
||||||
|
|
||||||
/** A renderer for metadata. */
|
/** A renderer for metadata. */
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@ -53,8 +55,8 @@ public final class MetadataRenderer extends BaseRenderer implements Callback {
|
|||||||
private boolean inputStreamEnded;
|
private boolean inputStreamEnded;
|
||||||
private boolean outputStreamEnded;
|
private boolean outputStreamEnded;
|
||||||
private long subsampleOffsetUs;
|
private long subsampleOffsetUs;
|
||||||
private long pendingMetadataTimestampUs;
|
|
||||||
@Nullable private Metadata pendingMetadata;
|
@Nullable private Metadata pendingMetadata;
|
||||||
|
private long outputStreamOffsetUs;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param output The output.
|
* @param output The output.
|
||||||
@ -85,7 +87,7 @@ public final class MetadataRenderer extends BaseRenderer implements Callback {
|
|||||||
outputLooper == null ? null : Util.createHandler(outputLooper, /* callback= */ this);
|
outputLooper == null ? null : Util.createHandler(outputLooper, /* callback= */ this);
|
||||||
this.decoderFactory = Assertions.checkNotNull(decoderFactory);
|
this.decoderFactory = Assertions.checkNotNull(decoderFactory);
|
||||||
buffer = new MetadataInputBuffer();
|
buffer = new MetadataInputBuffer();
|
||||||
pendingMetadataTimestampUs = C.TIME_UNSET;
|
outputStreamOffsetUs = C.TIME_UNSET;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -106,12 +108,12 @@ public final class MetadataRenderer extends BaseRenderer implements Callback {
|
|||||||
@Override
|
@Override
|
||||||
protected void onStreamChanged(Format[] formats, long startPositionUs, long offsetUs) {
|
protected void onStreamChanged(Format[] formats, long startPositionUs, long offsetUs) {
|
||||||
decoder = decoderFactory.createDecoder(formats[0]);
|
decoder = decoderFactory.createDecoder(formats[0]);
|
||||||
|
outputStreamOffsetUs = offsetUs;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPositionReset(long positionUs, boolean joining) {
|
protected void onPositionReset(long positionUs, boolean joining) {
|
||||||
pendingMetadata = null;
|
pendingMetadata = null;
|
||||||
pendingMetadataTimestampUs = C.TIME_UNSET;
|
|
||||||
inputStreamEnded = false;
|
inputStreamEnded = false;
|
||||||
outputStreamEnded = false;
|
outputStreamEnded = false;
|
||||||
}
|
}
|
||||||
@ -158,8 +160,8 @@ public final class MetadataRenderer extends BaseRenderer implements Callback {
|
|||||||
@Override
|
@Override
|
||||||
protected void onDisabled() {
|
protected void onDisabled() {
|
||||||
pendingMetadata = null;
|
pendingMetadata = null;
|
||||||
pendingMetadataTimestampUs = C.TIME_UNSET;
|
|
||||||
decoder = null;
|
decoder = null;
|
||||||
|
outputStreamOffsetUs = C.TIME_UNSET;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -200,9 +202,9 @@ public final class MetadataRenderer extends BaseRenderer implements Callback {
|
|||||||
List<Metadata.Entry> entries = new ArrayList<>(metadata.length());
|
List<Metadata.Entry> entries = new ArrayList<>(metadata.length());
|
||||||
decodeWrappedMetadata(metadata, entries);
|
decodeWrappedMetadata(metadata, entries);
|
||||||
if (!entries.isEmpty()) {
|
if (!entries.isEmpty()) {
|
||||||
Metadata expandedMetadata = new Metadata(entries);
|
Metadata expandedMetadata =
|
||||||
|
new Metadata(getPresentationTimeUs(buffer.timeUs), entries);
|
||||||
pendingMetadata = expandedMetadata;
|
pendingMetadata = expandedMetadata;
|
||||||
pendingMetadataTimestampUs = buffer.timeUs;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -214,10 +216,10 @@ public final class MetadataRenderer extends BaseRenderer implements Callback {
|
|||||||
|
|
||||||
private boolean outputMetadata(long positionUs) {
|
private boolean outputMetadata(long positionUs) {
|
||||||
boolean didOutput = false;
|
boolean didOutput = false;
|
||||||
if (pendingMetadata != null && pendingMetadataTimestampUs <= positionUs) {
|
if (pendingMetadata != null
|
||||||
|
&& pendingMetadata.presentationTimeUs <= getPresentationTimeUs(positionUs)) {
|
||||||
invokeRenderer(pendingMetadata);
|
invokeRenderer(pendingMetadata);
|
||||||
pendingMetadata = null;
|
pendingMetadata = null;
|
||||||
pendingMetadataTimestampUs = C.TIME_UNSET;
|
|
||||||
didOutput = true;
|
didOutput = true;
|
||||||
}
|
}
|
||||||
if (inputStreamEnded && pendingMetadata == null) {
|
if (inputStreamEnded && pendingMetadata == null) {
|
||||||
@ -237,4 +239,12 @@ public final class MetadataRenderer extends BaseRenderer implements Callback {
|
|||||||
private void invokeRendererInternal(Metadata metadata) {
|
private void invokeRendererInternal(Metadata metadata) {
|
||||||
output.onMetadata(metadata);
|
output.onMetadata(metadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SideEffectFree
|
||||||
|
private long getPresentationTimeUs(long positionUs) {
|
||||||
|
checkState(positionUs != C.TIME_UNSET);
|
||||||
|
checkState(outputStreamOffsetUs != C.TIME_UNSET);
|
||||||
|
|
||||||
|
return positionUs - outputStreamOffsetUs;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -94,8 +94,11 @@ MediaCodecAdapter (exotest.audio.aac):
|
|||||||
buffers[91] = length 0, hash 1
|
buffers[91] = length 0, hash 1
|
||||||
MetadataOutput:
|
MetadataOutput:
|
||||||
Metadata[0]:
|
Metadata[0]:
|
||||||
|
presentationTimeUs = 100000
|
||||||
entry[0] = EMSG: scheme=urn:mpeg:dash:event:callback:2015, id=0, durationMs=1000, value=1
|
entry[0] = EMSG: scheme=urn:mpeg:dash:event:callback:2015, id=0, durationMs=1000, value=1
|
||||||
Metadata[1]:
|
Metadata[1]:
|
||||||
|
presentationTimeUs = 100000
|
||||||
entry[0] = EMSG: scheme=urn:mpeg:dash:event:callback:2015, id=1, durationMs=1000, value=1
|
entry[0] = EMSG: scheme=urn:mpeg:dash:event:callback:2015, id=1, durationMs=1000, value=1
|
||||||
Metadata[2]:
|
Metadata[2]:
|
||||||
|
presentationTimeUs = 1000000
|
||||||
entry[0] = EMSG: scheme=urn:mpeg:dash:event:callback:2015, id=2, durationMs=1000, value=1
|
entry[0] = EMSG: scheme=urn:mpeg:dash:event:callback:2015, id=2, durationMs=1000, value=1
|
||||||
|
@ -22,11 +22,14 @@ MediaCodecAdapter (exotest.audio.eac3):
|
|||||||
buffers[19] = length 0, hash 1
|
buffers[19] = length 0, hash 1
|
||||||
MetadataOutput:
|
MetadataOutput:
|
||||||
Metadata[0]:
|
Metadata[0]:
|
||||||
|
presentationTimeUs = 0
|
||||||
entry[0] = Ait(controlCode=1,url=http://static-cdn.arte.tv/redbutton/index_fr.html)
|
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)
|
entry[1] = Ait(controlCode=2,url=http://www.arte.tv/hbbtvv2/index.html?lang=fr_FR&page=PLUS7)
|
||||||
Metadata[1]:
|
Metadata[1]:
|
||||||
|
presentationTimeUs = 192000
|
||||||
entry[0] = Ait(controlCode=1,url=http://static-cdn.arte.tv/redbutton/index_fr.html)
|
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)
|
entry[1] = Ait(controlCode=2,url=http://www.arte.tv/hbbtvv2/index.html?lang=fr_FR&page=PLUS7)
|
||||||
Metadata[2]:
|
Metadata[2]:
|
||||||
|
presentationTimeUs = 384000
|
||||||
entry[0] = Ait(controlCode=1,url=http://static-cdn.arte.tv/redbutton/index_fr.html)
|
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)
|
entry[1] = Ait(controlCode=2,url=http://www.arte.tv/hbbtvv2/index.html?lang=fr_FR&page=PLUS7)
|
||||||
|
@ -12,8 +12,11 @@ MediaCodecAdapter (exotest.video.mpeg2):
|
|||||||
buffers[2] = length 0, hash 1
|
buffers[2] = length 0, hash 1
|
||||||
MetadataOutput:
|
MetadataOutput:
|
||||||
Metadata[0]:
|
Metadata[0]:
|
||||||
|
presentationTimeUs = 33366
|
||||||
entry[0] = SCTE-35 splice command: type=SpliceInsertCommand
|
entry[0] = SCTE-35 splice command: type=SpliceInsertCommand
|
||||||
Metadata[1]:
|
Metadata[1]:
|
||||||
|
presentationTimeUs = 33366
|
||||||
entry[0] = SCTE-35 splice command: type=SpliceInsertCommand
|
entry[0] = SCTE-35 splice command: type=SpliceInsertCommand
|
||||||
Metadata[2]:
|
Metadata[2]:
|
||||||
|
presentationTimeUs = 33366
|
||||||
entry[0] = SCTE-35 splice command: type=SpliceInsertCommand
|
entry[0] = SCTE-35 splice command: type=SpliceInsertCommand
|
||||||
|
@ -147,7 +147,9 @@ MediaCodecAdapter (exotest.audio.aac):
|
|||||||
buffers[144] = length 0, hash 1
|
buffers[144] = length 0, hash 1
|
||||||
MetadataOutput:
|
MetadataOutput:
|
||||||
Metadata[0]:
|
Metadata[0]:
|
||||||
|
presentationTimeUs = 0
|
||||||
entry[0] = APIC: mimeType=image/jpeg, description=Hello World
|
entry[0] = APIC: mimeType=image/jpeg, description=Hello World
|
||||||
Metadata[1]:
|
Metadata[1]:
|
||||||
|
presentationTimeUs = 23219
|
||||||
entry[0] = COMM: language=eng, description=description
|
entry[0] = COMM: language=eng, description=description
|
||||||
entry[1] = APIC: mimeType=image/jpeg, description=Hello World
|
entry[1] = APIC: mimeType=image/jpeg, description=Hello World
|
||||||
|
@ -122,6 +122,7 @@ public final class PlaybackOutput implements Dumper.Dumpable {
|
|||||||
for (int i = 0; i < metadatas.size(); i++) {
|
for (int i = 0; i < metadatas.size(); i++) {
|
||||||
dumper.startBlock("Metadata[" + i + "]");
|
dumper.startBlock("Metadata[" + i + "]");
|
||||||
Metadata metadata = metadatas.get(i);
|
Metadata metadata = metadatas.get(i);
|
||||||
|
dumper.add("presentationTimeUs", metadata.presentationTimeUs);
|
||||||
for (int j = 0; j < metadata.length(); j++) {
|
for (int j = 0; j < metadata.length(); j++) {
|
||||||
dumper.add("entry[" + j + "]", getEntryAsString(metadata.get(j)));
|
dumper.add("entry[" + j + "]", getEntryAsString(metadata.get(j)));
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user