Commit 3171c86b by hoangtc Committed by Oliver Woodman

Supports Out-of-band, in MPD EventStream.

MPD file may include multiple EventStreams in its Periods, which contains Events
that the application may need to handle/respond to.
This change adds support for parsing the EventStream/Event nodes from MPD
file, and exposing these EventStreams as a metadata sample stream that application
can respond in a similar way to other metadata events.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=175017697
parent ed2e4dd9
......@@ -19,6 +19,7 @@ import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.MetadataDecoder;
import com.google.android.exoplayer2.metadata.MetadataInputBuffer;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util;
import java.nio.ByteBuffer;
import java.util.Arrays;
......@@ -40,7 +41,7 @@ public final class EventMessageDecoder implements MetadataDecoder {
String value = emsgData.readNullTerminatedString();
long timescale = emsgData.readUnsignedInt();
emsgData.skipBytes(4); // presentation_time_delta
long durationMs = (emsgData.readUnsignedInt() * 1000) / 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));
......
/*
* Copyright (C) 2017 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.metadata.emsg;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
/**
* Encodes data that can be decoded by {@link EventMessageDecoder}. This class isn't thread safe.
*/
public final class EventMessageEncoder {
private final ByteArrayOutputStream byteArrayOutputStream;
private final DataOutputStream dataOutputStream;
public EventMessageEncoder() {
byteArrayOutputStream = new ByteArrayOutputStream(512);
dataOutputStream = new DataOutputStream(byteArrayOutputStream);
}
/**
* Encodes an {@link EventMessage} to a byte array that can be decoded by
* {@link EventMessageDecoder}.
*
* @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) {
Assertions.checkArgument(timescale >= 0);
byteArrayOutputStream.reset();
try {
writeNullTerminatedString(dataOutputStream, eventMessage.schemeIdUri);
String nonNullValue = eventMessage.value != null ? eventMessage.value : "";
writeNullTerminatedString(dataOutputStream, nonNullValue);
writeUnsignedInt(dataOutputStream, timescale);
long presentationTime = Util.scaleLargeTimestamp(presentationTimeUs, timescale,
C.MICROS_PER_SECOND);
writeUnsignedInt(dataOutputStream, presentationTime);
long duration = Util.scaleLargeTimestamp(eventMessage.durationMs, timescale, 1000);
writeUnsignedInt(dataOutputStream, duration);
writeUnsignedInt(dataOutputStream, eventMessage.id);
dataOutputStream.write(eventMessage.messageData);
return byteArrayOutputStream.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
dataOutputStream.close();
} catch (IOException ignored) {
// ignored
}
}
}
private static void writeNullTerminatedString(DataOutputStream dataOutputStream, String value)
throws IOException {
dataOutputStream.writeBytes(value);
dataOutputStream.writeByte(0);
}
private static void writeUnsignedInt(DataOutputStream outputStream, long value)
throws IOException {
outputStream.writeByte((int) (value >>> 24) & 0xFF);
outputStream.writeByte((int) (value >>> 16) & 0xFF);
outputStream.writeByte((int) (value >>> 8) & 0xFF);
outputStream.writeByte((int) value & 0xFF);
}
}
......@@ -19,6 +19,7 @@ import static com.google.common.truth.Truth.assertThat;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.MetadataInputBuffer;
import java.io.IOException;
import java.nio.ByteBuffer;
import org.junit.Test;
import org.junit.runner.RunWith;
......@@ -55,4 +56,63 @@ public final class EventMessageDecoderTest {
assertThat(eventMessage.messageData).isEqualTo(new byte[]{0, 1, 2, 3, 4});
}
@Test
public void testEncodeEventStream() throws IOException {
EventMessage eventMessage = new EventMessage("urn:test", "123", 3000, 1000403,
new byte[] {0, 1, 2, 3, 4});
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, 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);
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);
MetadataInputBuffer buffer = new MetadataInputBuffer();
buffer.data = ByteBuffer.allocate(encodedByteArray.length).put(encodedByteArray);
EventMessageDecoder decoder = new EventMessageDecoder();
Metadata metadata = decoder.decode(buffer);
assertThat(metadata.length()).isEqualTo(1);
assertThat(metadata.get(0)).isEqualTo(expectedEmsg);
}
@Test
public void testEncodeEventStreamMultipleTimesWorkingCorrectly() throws IOException {
EventMessage eventMessage = new EventMessage("urn:test", "123", 3000, 1000403,
new byte[] {0, 1, 2, 3, 4});
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, 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});
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, 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);
assertThat(encodedByteArray).isEqualTo(expectedEmsgBody);
byte[] encodedByteArray1 = eventMessageEncoder.encode(eventMessage1, 48000, 1000000);
assertThat(encodedByteArray1).isEqualTo(expectedEmsgBody1);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<MPD xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:mpeg:DASH:schema:MPD:2011" xmlns:yt="http://youtube.com/yt/2012/10/10" xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd" minBufferTime="PT1.500S" profiles="urn:mpeg:dash:profile:isoff-main:2011" type="dynamic" availabilityStartTime="2016-10-14T17:00:17" timeShiftBufferDepth="PT7200.000S" minimumUpdatePeriod="PT2.000S" yt:earliestMediaSequence="0" yt:mpdRequestTime="2016-10-14T18:29:17.082" yt:mpdResponseTime="2016-10-14T18:29:17.194">
<Period start="PT0.000S" yt:segmentIngestTime="2016-10-14T17:00:14.257">
<EventStream schemeIdUri="urn:uuid:XYZY" timescale="1000" value="call">
<Event presentationTime="0" duration="10000" id="0">+ 1 800 10101010</Event>
</EventStream>
<EventStream schemeIdUri="urn:dvb:iptv:cpm:2014">
<Event presentationTime="300" duration="1500" id="1"><![CDATA[<BroadcastEvent>
<Program crid="crid://broadcaster.example.com/ABCDEF"/>
<InstanceDescription>
<Title xml:lang="en">The title</Title>
<Synopsis xml:lang="en" length="medium">The description</Synopsis>
<ParentalGuidance>
<mpeg7:ParentalRating href="urn:dvb:iptv:rating:2014:15"/>
<mpeg7:Region>GB</mpeg7:Region>
</ParentalGuidance>
</InstanceDescription>
</BroadcastEvent>]]></Event>
</EventStream>
<EventStream schemeIdUri="urn:scte:scte35:2014:xml+bin">
<Event timescale="90000" presentationTime="1000" duration="1000" id="2"><scte35:Signal>
<scte35:Binary>
/DAIAAAAAAAAAAAQAAZ/I0VniQAQAgBDVUVJQAAAAH+cAAAAAA==
</scte35:Binary>
</scte35:Signal></Event>
</EventStream>
<SegmentTemplate startNumber="0" timescale="1000" media="sq/$Number$">
<SegmentTimeline>
<S d="2002" t="6009" r="2"/>
<S d="1985"/>
<S d="2000"/>
</SegmentTimeline>
</SegmentTemplate>
<AdaptationSet id="0" mimeType="audio/mp4" subsegmentAlignment="true">
<Role schemeIdUri="urn:mpeg:DASH:role:2011" value="main"/>
<Representation id="140" codecs="mp4a.40.2" audioSamplingRate="48000" startWithSAP="1" bandwidth="144000">
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
<BaseURL>http://redirector.googlevideo.com/videoplayback/id/BktsoMO3OMs.0/itag/140/source/yt_live_broadcast/ratebypass/yes/cmbypass/yes/mime/audio%2Fmp4/live/1/gir/yes/noclen/1/signature/B5137EA0CC278C07DD056D204E863CC81EDEB39E.1AD5D242EBC94922EDA7165353A89A5E08A4103A/key/dg_test0/mpd_version/5/ip/0.0.0.0/ipbits/0/expire/1476490914/sparams/ip,ipbits,expire,id,itag,source,ratebypass,cmbypass,mime,live,gir,noclen/</BaseURL>
</Representation>
</AdaptationSet>
<AdaptationSet id="1" mimeType="video/mp4" subsegmentAlignment="true">
<Role schemeIdUri="urn:mpeg:DASH:role:2011" value="main"/>
<Representation id="133" codecs="avc1.4d4015" width="426" height="240" startWithSAP="1" maxPlayoutRate="1" bandwidth="258000" frameRate="30">
<BaseURL>http://redirector.googlevideo.com/videoplayback/id/BktsoMO3OMs.0/itag/133/source/yt_live_broadcast/ratebypass/yes/cmbypass/yes/mime/video%2Fmp4/live/1/gir/yes/noclen/1/signature/90154AE9C5C9D9D519CBF2E43AB0A1778375992D.40E2E855ADFB38FA7E95E168FEEEA6796B080BD7/key/dg_test0/mpd_version/5/ip/0.0.0.0/ipbits/0/expire/1476490914/sparams/ip,ipbits,expire,id,itag,source,ratebypass,cmbypass,mime,live,gir,noclen/</BaseURL>
</Representation>
<Representation id="134" codecs="avc1.4d401e" width="640" height="360" startWithSAP="1" maxPlayoutRate="1" bandwidth="646000" frameRate="30">
<BaseURL>http://redirector.googlevideo.com/videoplayback/id/BktsoMO3OMs.0/itag/134/source/yt_live_broadcast/ratebypass/yes/cmbypass/yes/mime/video%2Fmp4/live/1/gir/yes/noclen/1/signature/5C094AEFDCEB1A4D2F3C05F8BD095C336EF0E1C3.7AE6B9951B0237AAE6F031927AACAC4974BAFFAA/key/dg_test0/mpd_version/5/ip/0.0.0.0/ipbits/0/expire/1476490914/sparams/ip,ipbits,expire,id,itag,source,ratebypass,cmbypass,mime,live,gir,noclen/</BaseURL>
</Representation>
<Representation id="135" codecs="avc1.4d401f" width="854" height="480" startWithSAP="1" maxPlayoutRate="1" bandwidth="1171000" frameRate="30">
<BaseURL>http://redirector.googlevideo.com/videoplayback/id/BktsoMO3OMs.0/itag/135/source/yt_live_broadcast/ratebypass/yes/cmbypass/yes/mime/video%2Fmp4/live/1/gir/yes/noclen/1/signature/1F7660CA4E5B4AE4D60E18795680E34CDD2EF3C9.800B0A1D5F490DE142CCF4C88C64FD21D42129/key/dg_test0/mpd_version/5/ip/0.0.0.0/ipbits/0/expire/1476490914/sparams/ip,ipbits,expire,id,itag,source,ratebypass,cmbypass,mime,live,gir,noclen/</BaseURL>
</Representation>
<Representation id="160" codecs="avc1.42c00b" width="256" height="144" startWithSAP="1" maxPlayoutRate="1" bandwidth="124000" frameRate="30">
<BaseURL>http://redirector.googlevideo.com/videoplayback/id/BktsoMO3OMs.0/itag/160/source/yt_live_broadcast/ratebypass/yes/cmbypass/yes/mime/video%2Fmp4/live/1/gir/yes/noclen/1/signature/94EB61673784DF0C4237A1A866F2E171C8A64ADB.AEC00AA06C2278FEA8702FB62693B70D8977F46C/key/dg_test0/mpd_version/5/ip/0.0.0.0/ipbits/0/expire/1476490914/sparams/ip,ipbits,expire,id,itag,source,ratebypass,cmbypass,mime,live,gir,noclen/</BaseURL>
</Representation>
<Representation id="136" codecs="avc1.4d401f" width="1280" height="720" startWithSAP="1" maxPlayoutRate="1" bandwidth="2326000" frameRate="30">
<BaseURL>http://redirector.googlevideo.com/videoplayback/id/BktsoMO3OMs.0/itag/136/source/yt_live_broadcast/ratebypass/yes/cmbypass/yes/mime/video%2Fmp4/live/1/gir/yes/noclen/1/signature/6D8C34FC30A1F1A4F700B61180D1C4CCF6274844.29EBCB4A837DE626C52C66CF650519E61C2FF0BF/key/dg_test0/mpd_version/5/ip/0.0.0.0/ipbits/0/expire/1476490914/sparams/ip,ipbits,expire,id,itag,source,ratebypass,cmbypass,mime,live,gir,noclen/</BaseURL>
</Representation>
</AdaptationSet>
</Period>
</MPD>
......@@ -18,6 +18,7 @@ package com.google.android.exoplayer2.source.dash.manifest;
import android.net.Uri;
import android.test.InstrumentationTestCase;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.metadata.emsg.EventMessage;
import com.google.android.exoplayer2.testutil.TestUtil;
import java.io.IOException;
import java.util.Collections;
......@@ -31,6 +32,7 @@ public class DashManifestParserTest extends InstrumentationTestCase {
private static final String SAMPLE_MPD_1 = "sample_mpd_1";
private static final String SAMPLE_MPD_2_UNKNOWN_MIME_TYPE = "sample_mpd_2_unknown_mime_type";
private static final String SAMPLE_MPD_3_SEGMENT_TEMPLATE = "sample_mpd_3_segment_template";
private static final String SAMPLE_MPD_4_EVENT_STREAM = "sample_mpd_4_event_stream";
/**
* Simple test to ensure the sample manifests parse without any exceptions being thrown.
......@@ -69,6 +71,52 @@ public class DashManifestParserTest extends InstrumentationTestCase {
}
}
public void testParseMediaPresentationDescriptionCanParseEventStream()
throws IOException {
DashManifestParser parser = new DashManifestParser();
DashManifest mpd = parser.parse(Uri.parse("https://example.com/test.mpd"),
TestUtil.getInputStream(getInstrumentation(), SAMPLE_MPD_4_EVENT_STREAM));
Period period = mpd.getPeriod(0);
assertEquals(3, period.eventStreams.size());
// assert text-only event stream
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());
assertEquals(expectedEvent1, eventStream1.events[0]);
// assert CData-structured event stream
EventStream eventStream2 = period.eventStreams.get(1);
assertEquals(1, eventStream2.events.length);
assertEquals(
new EventMessage("urn:dvb:iptv:cpm:2014", "", 1500000, 1,
("<![CDATA[<BroadcastEvent>\n"
+ " <Program crid=\"crid://broadcaster.example.com/ABCDEF\"/>\n"
+ " <InstanceDescription>\n"
+ " <Title xml:lang=\"en\">The title</Title>\n"
+ " <Synopsis xml:lang=\"en\" length=\"medium\">The description</Synopsis>\n"
+ " <ParentalGuidance>\n"
+ " <mpeg7:ParentalRating href=\"urn:dvb:iptv:rating:2014:15\"/>\n"
+ " <mpeg7:Region>GB</mpeg7:Region>\n"
+ " </ParentalGuidance>\n"
+ " </InstanceDescription>\n"
+ " </BroadcastEvent>]]>").getBytes()),
eventStream2.events[0]);
// assert xml-structured event stream
EventStream eventStream3 = period.eventStreams.get(2);
assertEquals(1, eventStream3.events.length);
assertEquals(
new EventMessage("urn:scte:scte35:2014:xml+bin", "", 1000000, 2,
("<scte35:Signal>\n"
+ " <scte35:Binary>\n"
+ " /DAIAAAAAAAAAAAQAAZ/I0VniQAQAgBDVUVJQAAAAH+cAAAAAA==\n"
+ " </scte35:Binary>\n"
+ " </scte35:Signal>").getBytes()),
eventStream3.events[0]);
}
public void testParseCea608AccessibilityChannel() {
assertEquals(1, DashManifestParser.parseCea608AccessibilityChannel(
buildCea608AccessibilityDescriptors("CC1=eng")));
......
/*
* Copyright (C) 2017 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.source.dash;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.FormatHolder;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.android.exoplayer2.metadata.emsg.EventMessage;
import com.google.android.exoplayer2.metadata.emsg.EventMessageEncoder;
import com.google.android.exoplayer2.source.SampleStream;
import com.google.android.exoplayer2.source.dash.manifest.EventStream;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
/**
* A {@link SampleStream} consisting of serialized {@link EventMessage}s read from an
* {@link EventStream}.
*/
/* package */ final class EventSampleStream implements SampleStream {
private final Format upstreamFormat;
private final EventMessageEncoder eventMessageEncoder;
private long[] eventTimesUs;
private boolean eventStreamUpdatable;
private EventStream eventStream;
private boolean isFormatSentDownstream;
private int currentIndex;
private long pendingSeekPositionUs;
EventSampleStream(EventStream eventStream, Format upstreamFormat, boolean eventStreamUpdatable) {
this.upstreamFormat = upstreamFormat;
eventMessageEncoder = new EventMessageEncoder();
pendingSeekPositionUs = C.TIME_UNSET;
updateEventStream(eventStream, eventStreamUpdatable);
}
void updateEventStream(EventStream eventStream, boolean eventStreamUpdatable) {
long lastReadPositionUs = currentIndex == 0 ? C.TIME_UNSET : eventTimesUs[currentIndex - 1];
this.eventStreamUpdatable = eventStreamUpdatable;
this.eventStream = eventStream;
this.eventTimesUs = eventStream.presentationTimesUs;
if (pendingSeekPositionUs != C.TIME_UNSET) {
seekToUs(pendingSeekPositionUs);
} else if (lastReadPositionUs != C.TIME_UNSET) {
currentIndex = Util.binarySearchCeil(eventTimesUs, lastReadPositionUs, false, false);
}
}
String eventStreamId() {
return eventStream.id();
}
@Override
public boolean isReady() {
return true;
}
@Override
public void maybeThrowError() throws IOException {
// Do nothing.
}
@Override
public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer,
boolean formatRequired) {
if (formatRequired || !isFormatSentDownstream) {
formatHolder.format = upstreamFormat;
isFormatSentDownstream = true;
return C.RESULT_FORMAT_READ;
}
if (currentIndex == eventTimesUs.length) {
if (!eventStreamUpdatable) {
buffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM);
return C.RESULT_BUFFER_READ;
} else {
return C.RESULT_NOTHING_READ;
}
}
int sampleIndex = currentIndex++;
byte[] serializedEvent = eventMessageEncoder.encode(eventStream.events[sampleIndex],
eventStream.timescale, eventTimesUs[sampleIndex]);
if (serializedEvent != null) {
buffer.ensureSpaceForWrite(serializedEvent.length);
buffer.setFlags(C.BUFFER_FLAG_KEY_FRAME);
buffer.data.put(serializedEvent);
buffer.timeUs = eventTimesUs[sampleIndex];
return C.RESULT_BUFFER_READ;
} else {
return C.RESULT_NOTHING_READ;
}
}
@Override
public int skipData(long positionUs) {
int newIndex =
Math.max(currentIndex, Util.binarySearchCeil(eventTimesUs, positionUs, true, false));
int skipped = newIndex - currentIndex;
currentIndex = newIndex;
return skipped;
}
/**
* Seeks to the specified position in microseconds.
*
* @param positionUs The seek position in microseconds.
*/
public void seekToUs(long positionUs) {
currentIndex = Util.binarySearchCeil(eventTimesUs, positionUs, true, false);
boolean isPendingSeek = eventStreamUpdatable && currentIndex == eventTimesUs.length;
pendingSeekPositionUs = isPendingSeek ? positionUs : C.TIME_UNSET;
}
}
......@@ -107,7 +107,9 @@ public class DashManifest {
Period period = getPeriod(periodIndex);
ArrayList<AdaptationSet> copyAdaptationSets =
copyAdaptationSets(period.adaptationSets, keys);
copyPeriods.add(new Period(period.id, period.startMs - shiftMs, copyAdaptationSets));
Period copiedPeriod = new Period(period.id, period.startMs - shiftMs, copyAdaptationSets,
period.eventStreams);
copyPeriods.add(copiedPeriod);
}
}
long newDuration = duration != C.TIME_UNSET ? duration - shiftMs : C.TIME_UNSET;
......
/*
* Copyright (C) 2017 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.source.dash.manifest;
import com.google.android.exoplayer2.metadata.emsg.EventMessage;
/**
* A DASH in-MPD EventStream element, as defined by ISO/IEC 23009-1, 2nd edition, section 5.10.
*/
public final class EventStream {
/**
* {@link EventMessage}s in the event stream.
*/
public final EventMessage[] events;
/**
* Presentation time of the events in microsecond, sorted in ascending order.
*/
public final long[] presentationTimesUs;
/**
* The scheme URI.
*/
public final String schemeIdUri;
/**
* The value of the event stream. Use empty string if not defined in manifest.
*/
public final String value;
/**
* The timescale in units per seconds, as defined in the manifest.
*/
public final long timescale;
public EventStream(String schemeIdUri, String value, long timescale, long[] presentationTimesUs,
EventMessage[] events) {
this.schemeIdUri = schemeIdUri;
this.value = value;
this.timescale = timescale;
this.presentationTimesUs = presentationTimesUs;
this.events = events;
}
/**
* A constructed id of this {@link EventStream}. Equal to {@code schemeIdUri + "/" + value}.
*/
public String id() {
return schemeIdUri + "/" + value;
}
}
......@@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.source.dash.manifest;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C;
import java.util.Collections;
import java.util.List;
......@@ -27,7 +28,7 @@ public class Period {
/**
* The period identifier, if one exists.
*/
public final String id;
@Nullable public final String id;
/**
* The start time of the period in milliseconds.
......@@ -40,14 +41,31 @@ public class Period {
public final List<AdaptationSet> adaptationSets;
/**
* The event stream belonging to the period.
*/
public final List<EventStream> eventStreams;
/**
* @param id The period identifier. May be null.
* @param startMs The start time of the period in milliseconds.
* @param adaptationSets The adaptation sets belonging to the period.
*/
public Period(@Nullable String id, long startMs, List<AdaptationSet> adaptationSets) {
this(id, startMs, adaptationSets, Collections.<EventStream>emptyList());
}
/**
* @param id The period identifier. May be null.
* @param startMs The start time of the period in milliseconds.
* @param adaptationSets The adaptation sets belonging to the period.
* @param eventStreams The {@link EventStream}s belonging to the period.
*/
public Period(String id, long startMs, List<AdaptationSet> adaptationSets) {
public Period(@Nullable String id, long startMs, List<AdaptationSet> adaptationSets,
List<EventStream> eventStreams) {
this.id = id;
this.startMs = startMs;
this.adaptationSets = Collections.unmodifiableList(adaptationSets);
this.eventStreams = Collections.unmodifiableList(eventStreams);
}
/**
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment