diff --git a/library/core/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessage.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessage.java index fbe3184c0d..57e7f0bfd6 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessage.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessage.java @@ -41,6 +41,13 @@ public final class EventMessage implements Metadata.Entry { */ public final long durationMs; + /** + * The presentation time value of this event message in microseconds. + *

+ * Except in special cases, application code should not use this field. + */ + public final long presentationTimeUs; + /** * The instance identifier. */ @@ -55,25 +62,27 @@ public final class EventMessage implements Metadata.Entry { private int hashCode; /** - * * @param schemeIdUri The message scheme. * @param value The value for the event. * @param durationMs The duration of the event in milliseconds. * @param id The instance identifier. * @param messageData The body of the message. + * @param presentationTimeUs The presentation time value of this event message in microseconds. */ public EventMessage(String schemeIdUri, String value, long durationMs, long id, - byte[] messageData) { + byte[] messageData, long presentationTimeUs) { this.schemeIdUri = schemeIdUri; this.value = value; this.durationMs = durationMs; this.id = id; this.messageData = messageData; + this.presentationTimeUs = presentationTimeUs; } /* package */ EventMessage(Parcel in) { schemeIdUri = in.readString(); value = in.readString(); + presentationTimeUs = in.readLong(); durationMs = in.readLong(); id = in.readLong(); messageData = in.createByteArray(); @@ -85,6 +94,7 @@ public final class EventMessage implements Metadata.Entry { int result = 17; result = 31 * result + (schemeIdUri != null ? schemeIdUri.hashCode() : 0); result = 31 * result + (value != null ? value.hashCode() : 0); + result = 31 * result + (int) (presentationTimeUs ^ (presentationTimeUs >>> 32)); result = 31 * result + (int) (durationMs ^ (durationMs >>> 32)); result = 31 * result + (int) (id ^ (id >>> 32)); result = 31 * result + Arrays.hashCode(messageData); @@ -102,9 +112,9 @@ public final class EventMessage implements Metadata.Entry { return false; } EventMessage other = (EventMessage) obj; - return durationMs == other.durationMs && id == other.id - && Util.areEqual(schemeIdUri, other.schemeIdUri) && Util.areEqual(value, other.value) - && Arrays.equals(messageData, other.messageData); + return presentationTimeUs == other.presentationTimeUs && durationMs == other.durationMs + && id == other.id && Util.areEqual(schemeIdUri, other.schemeIdUri) + && Util.areEqual(value, other.value) && Arrays.equals(messageData, other.messageData); } // Parcelable implementation. @@ -118,6 +128,7 @@ public final class EventMessage implements Metadata.Entry { public void writeToParcel(Parcel dest, int flags) { dest.writeString(schemeIdUri); dest.writeString(value); + dest.writeLong(presentationTimeUs); dest.writeLong(durationMs); dest.writeLong(id); dest.writeByteArray(messageData); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoder.java index 266988246d..7e5125e71c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoder.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.metadata.emsg; +import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.MetadataDecoder; import com.google.android.exoplayer2.metadata.MetadataInputBuffer; @@ -24,7 +25,7 @@ import java.nio.ByteBuffer; import java.util.Arrays; /** - * Decodes Event Message (emsg) atoms, as defined in ISO 23009-1. + * Decodes Event Message (emsg) atoms, as defined in ISO/IEC 23009-1:2014, Section 5.10.3.3. *

* Atom data should be provided to the decoder without the full atom header (i.e. starting from the * first byte of the scheme_id_uri field). @@ -40,11 +41,13 @@ public final class EventMessageDecoder implements MetadataDecoder { String schemeIdUri = emsgData.readNullTerminatedString(); String value = emsgData.readNullTerminatedString(); long timescale = emsgData.readUnsignedInt(); - emsgData.skipBytes(4); // presentation_time_delta + long presentationTimeUs = Util.scaleLargeTimestamp(emsgData.readUnsignedInt(), + C.MICROS_PER_SECOND, timescale); long durationMs = Util.scaleLargeTimestamp(emsgData.readUnsignedInt(), 1000, timescale); long id = emsgData.readUnsignedInt(); byte[] messageData = Arrays.copyOfRange(data, emsgData.getPosition(), size); - return new Metadata(new EventMessage(schemeIdUri, value, durationMs, id, messageData)); + return new Metadata(new EventMessage(schemeIdUri, value, durationMs, id, messageData, + presentationTimeUs)); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessageEncoder.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessageEncoder.java index 2bd54367e1..eca498a6df 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessageEncoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessageEncoder.java @@ -42,11 +42,10 @@ public final class EventMessageEncoder { * * @param eventMessage The event message to be encoded. * @param timescale Timescale of the event message, in units per second. - * @param presentationTimeUs The presentation time of the event message in microseconds. * @return The serialized byte array. */ @Nullable - public byte[] encode(EventMessage eventMessage, long timescale, long presentationTimeUs) { + public byte[] encode(EventMessage eventMessage, long timescale) { Assertions.checkArgument(timescale >= 0); byteArrayOutputStream.reset(); try { @@ -54,8 +53,8 @@ public final class EventMessageEncoder { String nonNullValue = eventMessage.value != null ? eventMessage.value : ""; writeNullTerminatedString(dataOutputStream, nonNullValue); writeUnsignedInt(dataOutputStream, timescale); - long presentationTime = Util.scaleLargeTimestamp(presentationTimeUs, timescale, - C.MICROS_PER_SECOND); + long presentationTime = Util.scaleLargeTimestamp(eventMessage.presentationTimeUs, + timescale, C.MICROS_PER_SECOND); writeUnsignedInt(dataOutputStream, presentationTime); long duration = Util.scaleLargeTimestamp(eventMessage.durationMs, timescale, 1000); writeUnsignedInt(dataOutputStream, duration); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoderTest.java b/library/core/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoderTest.java index 1ce0ccb93d..3a6e96b3e8 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoderTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoderTest.java @@ -38,7 +38,7 @@ public final class EventMessageDecoderTest { 117, 114, 110, 58, 116, 101, 115, 116, 0, // scheme_id_uri = "urn:test" 49, 50, 51, 0, // value = "123" 0, 0, -69, -128, // timescale = 48000 - 0, 0, 0, 0, // presentation_time_delta (ignored) = 0 + 0, 0, -69, -128, // presentation_time_delta = 48000 0, 2, 50, -128, // event_duration = 144000 0, 15, 67, -45, // id = 1000403 0, 1, 2, 3, 4}; // message_data = {0, 1, 2, 3, 4} @@ -53,6 +53,7 @@ public final class EventMessageDecoderTest { assertThat(eventMessage.durationMs).isEqualTo(3000); assertThat(eventMessage.id).isEqualTo(1000403); assertThat(eventMessage.messageData).isEqualTo(new byte[]{0, 1, 2, 3, 4}); + assertThat(eventMessage.presentationTimeUs).isEqualTo(1000000); } } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageEncoderTest.java b/library/core/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageEncoderTest.java index f526fc3451..f0a6d3e19b 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageEncoderTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageEncoderTest.java @@ -36,24 +36,24 @@ public final class EventMessageEncoderTest { @Test public void testEncodeEventStream() throws IOException { EventMessage eventMessage = new EventMessage("urn:test", "123", 3000, 1000403, - new byte[] {0, 1, 2, 3, 4}); + new byte[] {0, 1, 2, 3, 4}, 1000000); byte[] expectedEmsgBody = new byte[] { 117, 114, 110, 58, 116, 101, 115, 116, 0, // scheme_id_uri = "urn:test" 49, 50, 51, 0, // value = "123" 0, 0, -69, -128, // timescale = 48000 - 0, 0, -69, -128, // presentation_time_delta = 48 + 0, 0, -69, -128, // presentation_time_delta = 48000 0, 2, 50, -128, // event_duration = 144000 0, 15, 67, -45, // id = 1000403 0, 1, 2, 3, 4}; // message_data = {0, 1, 2, 3, 4} - byte[] encodedByteArray = new EventMessageEncoder().encode(eventMessage, 48000, 1000000); + byte[] encodedByteArray = new EventMessageEncoder().encode(eventMessage, 48000); assertThat(encodedByteArray).isEqualTo(expectedEmsgBody); } @Test public void testEncodeDecodeEventStream() throws IOException { EventMessage expectedEmsg = new EventMessage("urn:test", "123", 3000, 1000403, - new byte[] {0, 1, 2, 3, 4}); - byte[] encodedByteArray = new EventMessageEncoder().encode(expectedEmsg, 48000, 1); + new byte[] {0, 1, 2, 3, 4}, 1000000); + byte[] encodedByteArray = new EventMessageEncoder().encode(expectedEmsg, 48000); MetadataInputBuffer buffer = new MetadataInputBuffer(); buffer.data = ByteBuffer.allocate(encodedByteArray.length).put(encodedByteArray); @@ -66,29 +66,29 @@ public final class EventMessageEncoderTest { @Test public void testEncodeEventStreamMultipleTimesWorkingCorrectly() throws IOException { EventMessage eventMessage = new EventMessage("urn:test", "123", 3000, 1000403, - new byte[] {0, 1, 2, 3, 4}); + new byte[] {0, 1, 2, 3, 4}, 1000000); byte[] expectedEmsgBody = new byte[] { 117, 114, 110, 58, 116, 101, 115, 116, 0, // scheme_id_uri = "urn:test" 49, 50, 51, 0, // value = "123" 0, 0, -69, -128, // timescale = 48000 - 0, 0, -69, -128, // presentation_time_delta = 48 + 0, 0, -69, -128, // presentation_time_delta = 48000 0, 2, 50, -128, // event_duration = 144000 0, 15, 67, -45, // id = 1000403 0, 1, 2, 3, 4}; // message_data = {0, 1, 2, 3, 4} EventMessage eventMessage1 = new EventMessage("urn:test", "123", 3000, 1000402, - new byte[] {4, 3, 2, 1, 0}); + new byte[] {4, 3, 2, 1, 0}, 1000000); byte[] expectedEmsgBody1 = new byte[] { 117, 114, 110, 58, 116, 101, 115, 116, 0, // scheme_id_uri = "urn:test" 49, 50, 51, 0, // value = "123" 0, 0, -69, -128, // timescale = 48000 - 0, 0, -69, -128, // presentation_time_delta = 48 + 0, 0, -69, -128, // presentation_time_delta = 48000 0, 2, 50, -128, // event_duration = 144000 0, 15, 67, -46, // id = 1000402 4, 3, 2, 1, 0}; // message_data = {4, 3, 2, 1, 0} EventMessageEncoder eventMessageEncoder = new EventMessageEncoder(); - byte[] encodedByteArray = eventMessageEncoder.encode(eventMessage, 48000, 1000000); + byte[] encodedByteArray = eventMessageEncoder.encode(eventMessage, 48000); assertThat(encodedByteArray).isEqualTo(expectedEmsgBody); - byte[] encodedByteArray1 = eventMessageEncoder.encode(eventMessage1, 48000, 1000000); + byte[] encodedByteArray1 = eventMessageEncoder.encode(eventMessage1, 48000); assertThat(encodedByteArray1).isEqualTo(expectedEmsgBody1); } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageTest.java b/library/core/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageTest.java index b48a071d0d..58f2b9f55d 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageTest.java @@ -33,7 +33,7 @@ public final class EventMessageTest { @Test public void testEventMessageParcelable() { EventMessage eventMessage = new EventMessage("urn:test", "123", 3000, 1000403, - new byte[] {0, 1, 2, 3, 4}); + new byte[] {0, 1, 2, 3, 4}, 1000); // Write to parcel. Parcel parcel = Parcel.obtain(); eventMessage.writeToParcel(parcel, 0); diff --git a/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java b/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java index 37dc6a748e..5c54a7884b 100644 --- a/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java +++ b/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java @@ -84,7 +84,7 @@ public class DashManifestParserTest extends InstrumentationTestCase { EventStream eventStream1 = period.eventStreams.get(0); assertEquals(1, eventStream1.events.length); EventMessage expectedEvent1 = new EventMessage("urn:uuid:XYZY", "call", 10000, 0, - "+ 1 800 10101010".getBytes()); + "+ 1 800 10101010".getBytes(), 0); assertEquals(expectedEvent1, eventStream1.events[0]); // assert CData-structured event stream @@ -102,7 +102,7 @@ public class DashManifestParserTest extends InstrumentationTestCase { + " GB\n" + " \n" + " \n" - + " ]]>").getBytes()), + + " ]]>").getBytes(), 300000000), eventStream2.events[0]); // assert xml-structured event stream @@ -114,7 +114,7 @@ public class DashManifestParserTest extends InstrumentationTestCase { + " \n" + " /DAIAAAAAAAAAAAQAAZ/I0VniQAQAgBDVUVJQAAAAH+cAAAAAA==\n" + " \n" - + " ").getBytes()), + + " ").getBytes(), 1000000000), eventStream3.events[0]); } diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/EventSampleStream.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/EventSampleStream.java index 549bfdef7b..694f9f843e 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/EventSampleStream.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/EventSampleStream.java @@ -95,7 +95,7 @@ import java.io.IOException; } int sampleIndex = currentIndex++; byte[] serializedEvent = eventMessageEncoder.encode(eventStream.events[sampleIndex], - eventStream.timescale, eventTimesUs[sampleIndex]); + eventStream.timescale); if (serializedEvent != null) { buffer.ensureSpaceForWrite(serializedEvent.length); buffer.setFlags(C.BUFFER_FLAG_KEY_FRAME); diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java index d3906acdf6..07f9660755 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java @@ -688,23 +688,23 @@ public class DashManifestParser extends DefaultHandler String schemeIdUri = parseString(xpp, "schemeIdUri", ""); String value = parseString(xpp, "value", ""); long timescale = parseLong(xpp, "timescale", 1); - List> timedEvents = new ArrayList<>(); + List eventMessages = new ArrayList<>(); ByteArrayOutputStream scratchOutputStream = new ByteArrayOutputStream(512); do { xpp.next(); if (XmlPullParserUtil.isStartTag(xpp, "Event")) { - Pair timedEvent = parseEvent(xpp, schemeIdUri, value, timescale, + EventMessage event = parseEvent(xpp, schemeIdUri, value, timescale, scratchOutputStream); - timedEvents.add(timedEvent); + eventMessages.add(event); } } while (!XmlPullParserUtil.isEndTag(xpp, "EventStream")); - long[] presentationTimesUs = new long[timedEvents.size()]; - EventMessage[] events = new EventMessage[timedEvents.size()]; - for (int i = 0; i < timedEvents.size(); i++) { - Pair timedEvent = timedEvents.get(i); - presentationTimesUs[i] = timedEvent.first; - events[i] = timedEvent.second; + long[] presentationTimesUs = new long[eventMessages.size()]; + EventMessage[] events = new EventMessage[eventMessages.size()]; + for (int i = 0; i < eventMessages.size(); i++) { + EventMessage event = eventMessages.get(i); + presentationTimesUs[i] = event.presentationTimeUs; + events[i] = event; } return buildEventStream(schemeIdUri, value, timescale, presentationTimesUs, events); } @@ -723,11 +723,11 @@ public class DashManifestParser extends DefaultHandler * @param timescale The timescale of the parent EventStream. * @param scratchOutputStream A {@link ByteArrayOutputStream} that is used to write serialize data * in between and tags into. - * @return The {@link EventStream} parsed from this EventStream node. + * @return The {@link EventMessage} parsed from this EventStream node. * @throws XmlPullParserException If there is any error parsing this node. * @throws IOException If there is any error reading from the underlying input stream. */ - protected Pair parseEvent(XmlPullParser xpp, String schemeIdUri, String value, + protected EventMessage parseEvent(XmlPullParser xpp, String schemeIdUri, String value, long timescale, ByteArrayOutputStream scratchOutputStream) throws IOException, XmlPullParserException { long id = parseLong(xpp, "id", 0); @@ -737,8 +737,7 @@ public class DashManifestParser extends DefaultHandler long presentationTimesUs = Util.scaleLargeTimestamp(presentationTime, C.MICROS_PER_SECOND, timescale); byte[] eventObject = parseEventObject(xpp, scratchOutputStream); - return new Pair<>(presentationTimesUs, buildEvent(schemeIdUri, value, id, durationMs, - eventObject)); + return buildEvent(schemeIdUri, value, id, durationMs, eventObject, presentationTimesUs); } /** @@ -807,8 +806,8 @@ public class DashManifestParser extends DefaultHandler } protected EventMessage buildEvent(String schemeIdUri, String value, long id, - long durationMs, byte[] messageData) { - return new EventMessage(schemeIdUri, value, durationMs, id, messageData); + long durationMs, byte[] messageData, long presentationTimeUs) { + return new EventMessage(schemeIdUri, value, durationMs, id, messageData, presentationTimeUs); } protected List parseSegmentTimeline(XmlPullParser xpp)