mirror of
https://github.com/samsonjs/media.git
synced 2026-03-30 10:15:48 +00:00
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
This commit is contained in:
parent
ed2e4dd91e
commit
3171c86bdb
11 changed files with 871 additions and 74 deletions
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
<?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")));
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
*/
|
||||
package com.google.android.exoplayer2.source.dash;
|
||||
|
||||
import android.support.annotation.IntDef;
|
||||
import android.util.Pair;
|
||||
import android.util.SparseIntArray;
|
||||
import com.google.android.exoplayer2.C;
|
||||
|
|
@ -32,16 +33,21 @@ import com.google.android.exoplayer2.source.chunk.ChunkSampleStream.EmbeddedSamp
|
|||
import com.google.android.exoplayer2.source.dash.manifest.AdaptationSet;
|
||||
import com.google.android.exoplayer2.source.dash.manifest.DashManifest;
|
||||
import com.google.android.exoplayer2.source.dash.manifest.Descriptor;
|
||||
import com.google.android.exoplayer2.source.dash.manifest.EventStream;
|
||||
import com.google.android.exoplayer2.source.dash.manifest.Period;
|
||||
import com.google.android.exoplayer2.source.dash.manifest.Representation;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||
import com.google.android.exoplayer2.upstream.Allocator;
|
||||
import com.google.android.exoplayer2.upstream.LoaderErrorThrower;
|
||||
import com.google.android.exoplayer2.util.MimeTypes;
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A DASH {@link MediaPeriod}.
|
||||
|
|
@ -61,9 +67,11 @@ import java.util.List;
|
|||
|
||||
private Callback callback;
|
||||
private ChunkSampleStream<DashChunkSource>[] sampleStreams;
|
||||
private EventSampleStream[] eventSampleStreams;
|
||||
private CompositeSequenceableLoader sequenceableLoader;
|
||||
private DashManifest manifest;
|
||||
private int periodIndex;
|
||||
private List<EventStream> eventStreams;
|
||||
|
||||
public DashMediaPeriod(int id, DashManifest manifest, int periodIndex,
|
||||
DashChunkSource.Factory chunkSourceFactory, int minLoadableRetryCount,
|
||||
|
|
@ -79,22 +87,41 @@ import java.util.List;
|
|||
this.manifestLoaderErrorThrower = manifestLoaderErrorThrower;
|
||||
this.allocator = allocator;
|
||||
sampleStreams = newSampleStreamArray(0);
|
||||
eventSampleStreams = new EventSampleStream[0];
|
||||
sequenceableLoader = new CompositeSequenceableLoader(sampleStreams);
|
||||
Pair<TrackGroupArray, TrackGroupInfo[]> result =
|
||||
buildTrackGroups(manifest.getPeriod(periodIndex).adaptationSets);
|
||||
Period period = manifest.getPeriod(periodIndex);
|
||||
eventStreams = period.eventStreams;
|
||||
Pair<TrackGroupArray, TrackGroupInfo[]> result = buildTrackGroups(period.adaptationSets,
|
||||
eventStreams);
|
||||
trackGroups = result.first;
|
||||
trackGroupInfos = result.second;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the {@link DashManifest} and the index of this period in the manifest.
|
||||
* <p>
|
||||
* @param manifest The updated manifest.
|
||||
* @param periodIndex the new index of this period in the updated manifest.
|
||||
*/
|
||||
public void updateManifest(DashManifest manifest, int periodIndex) {
|
||||
this.manifest = manifest;
|
||||
this.periodIndex = periodIndex;
|
||||
Period period = manifest.getPeriod(periodIndex);
|
||||
if (sampleStreams != null) {
|
||||
for (ChunkSampleStream<DashChunkSource> sampleStream : sampleStreams) {
|
||||
sampleStream.getChunkSource().updateManifest(manifest, periodIndex);
|
||||
}
|
||||
callback.onContinueLoadingRequested(this);
|
||||
}
|
||||
eventStreams = period.eventStreams;
|
||||
for (EventSampleStream eventSampleStream : eventSampleStreams) {
|
||||
for (EventStream eventStream : eventStreams) {
|
||||
if (eventStream.id().equals(eventSampleStream.eventStreamId())) {
|
||||
eventSampleStream.updateEventStream(eventStream, manifest.dynamic);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void release() {
|
||||
|
|
@ -122,8 +149,27 @@ import java.util.List;
|
|||
@Override
|
||||
public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamFlags,
|
||||
SampleStream[] streams, boolean[] streamResetFlags, long positionUs) {
|
||||
HashMap<Integer, ChunkSampleStream<DashChunkSource>> primarySampleStreams = new HashMap<>();
|
||||
// First pass for primary tracks.
|
||||
Map<Integer, ChunkSampleStream<DashChunkSource>> primarySampleStreams = new HashMap<>();
|
||||
List<EventSampleStream> eventSampleStreamList = new ArrayList<>();
|
||||
|
||||
selectPrimarySampleStreams(selections, mayRetainStreamFlags, streams, streamResetFlags,
|
||||
positionUs, primarySampleStreams);
|
||||
selectEventSampleStreams(selections, mayRetainStreamFlags, streams,
|
||||
streamResetFlags, eventSampleStreamList);
|
||||
selectEmbeddedSampleStreams(selections, mayRetainStreamFlags, streams, streamResetFlags,
|
||||
positionUs, primarySampleStreams);
|
||||
|
||||
sampleStreams = newSampleStreamArray(primarySampleStreams.size());
|
||||
primarySampleStreams.values().toArray(sampleStreams);
|
||||
eventSampleStreams = new EventSampleStream[eventSampleStreamList.size()];
|
||||
eventSampleStreamList.toArray(eventSampleStreams);
|
||||
sequenceableLoader = new CompositeSequenceableLoader(sampleStreams);
|
||||
return positionUs;
|
||||
}
|
||||
|
||||
private void selectPrimarySampleStreams(TrackSelection[] selections,
|
||||
boolean[] mayRetainStreamFlags, SampleStream[] streams, boolean[] streamResetFlags,
|
||||
long positionUs, Map<Integer, ChunkSampleStream<DashChunkSource>> primarySampleStreams) {
|
||||
for (int i = 0; i < selections.length; i++) {
|
||||
if (streams[i] instanceof ChunkSampleStream) {
|
||||
@SuppressWarnings("unchecked")
|
||||
|
|
@ -136,10 +182,11 @@ import java.util.List;
|
|||
primarySampleStreams.put(trackGroupIndex, stream);
|
||||
}
|
||||
}
|
||||
|
||||
if (streams[i] == null && selections[i] != null) {
|
||||
int trackGroupIndex = trackGroups.indexOf(selections[i].getTrackGroup());
|
||||
TrackGroupInfo trackGroupInfo = trackGroupInfos[trackGroupIndex];
|
||||
if (trackGroupInfo.isPrimary) {
|
||||
if (trackGroupInfo.trackGroupCategory == TrackGroupInfo.CATEGORY_PRIMARY) {
|
||||
ChunkSampleStream<DashChunkSource> stream = buildSampleStream(trackGroupInfo,
|
||||
selections[i], positionUs);
|
||||
primarySampleStreams.put(trackGroupIndex, stream);
|
||||
|
|
@ -148,7 +195,39 @@ import java.util.List;
|
|||
}
|
||||
}
|
||||
}
|
||||
// Second pass for embedded tracks.
|
||||
}
|
||||
|
||||
private void selectEventSampleStreams(TrackSelection[] selections, boolean[] mayRetainStreamFlags,
|
||||
SampleStream[] streams, boolean[] streamResetFlags,
|
||||
List<EventSampleStream> eventSampleStreamsList) {
|
||||
for (int i = 0; i < selections.length; i++) {
|
||||
if (streams[i] instanceof EventSampleStream) {
|
||||
EventSampleStream stream = (EventSampleStream) streams[i];
|
||||
if (selections[i] == null || !mayRetainStreamFlags[i]) {
|
||||
streams[i] = null;
|
||||
} else {
|
||||
eventSampleStreamsList.add(stream);
|
||||
}
|
||||
}
|
||||
|
||||
if (streams[i] == null && selections[i] != null) {
|
||||
int trackGroupIndex = trackGroups.indexOf(selections[i].getTrackGroup());
|
||||
TrackGroupInfo trackGroupInfo = trackGroupInfos[trackGroupIndex];
|
||||
if (trackGroupInfo.trackGroupCategory == TrackGroupInfo.CATEGORY_MANIFEST_EVENTS) {
|
||||
EventStream eventStream = eventStreams.get(trackGroupInfo.eventStreamGroupIndex);
|
||||
Format format = selections[i].getTrackGroup().getFormat(0);
|
||||
EventSampleStream stream = new EventSampleStream(eventStream, format, manifest.dynamic);
|
||||
streams[i] = stream;
|
||||
streamResetFlags[i] = true;
|
||||
eventSampleStreamsList.add(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void selectEmbeddedSampleStreams(TrackSelection[] selections,
|
||||
boolean[] mayRetainStreamFlags, SampleStream[] streams, boolean[] streamResetFlags,
|
||||
long positionUs, Map<Integer, ChunkSampleStream<DashChunkSource>> primarySampleStreams) {
|
||||
for (int i = 0; i < selections.length; i++) {
|
||||
if ((streams[i] instanceof EmbeddedSampleStream || streams[i] instanceof EmptySampleStream)
|
||||
&& (selections[i] == null || !mayRetainStreamFlags[i])) {
|
||||
|
|
@ -161,7 +240,7 @@ import java.util.List;
|
|||
if (selections[i] != null) {
|
||||
int trackGroupIndex = trackGroups.indexOf(selections[i].getTrackGroup());
|
||||
TrackGroupInfo trackGroupInfo = trackGroupInfos[trackGroupIndex];
|
||||
if (!trackGroupInfo.isPrimary) {
|
||||
if (trackGroupInfo.trackGroupCategory == TrackGroupInfo.CATEGORY_EMBEDDED) {
|
||||
ChunkSampleStream<?> primaryStream = primarySampleStreams.get(
|
||||
trackGroupInfo.primaryTrackGroupIndex);
|
||||
SampleStream stream = streams[i];
|
||||
|
|
@ -177,10 +256,6 @@ import java.util.List;
|
|||
}
|
||||
}
|
||||
}
|
||||
sampleStreams = newSampleStreamArray(primarySampleStreams.size());
|
||||
primarySampleStreams.values().toArray(sampleStreams);
|
||||
sequenceableLoader = new CompositeSequenceableLoader(sampleStreams);
|
||||
return positionUs;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -215,6 +290,9 @@ import java.util.List;
|
|||
for (ChunkSampleStream<DashChunkSource> sampleStream : sampleStreams) {
|
||||
sampleStream.seekToUs(positionUs);
|
||||
}
|
||||
for (EventSampleStream sampleStream : eventSampleStreams) {
|
||||
sampleStream.seekToUs(positionUs);
|
||||
}
|
||||
return positionUs;
|
||||
}
|
||||
|
||||
|
|
@ -228,62 +306,25 @@ import java.util.List;
|
|||
// Internal methods.
|
||||
|
||||
private static Pair<TrackGroupArray, TrackGroupInfo[]> buildTrackGroups(
|
||||
List<AdaptationSet> adaptationSets) {
|
||||
List<AdaptationSet> adaptationSets, List<EventStream> eventStreams) {
|
||||
int[][] groupedAdaptationSetIndices = getGroupedAdaptationSetIndices(adaptationSets);
|
||||
|
||||
int primaryGroupCount = groupedAdaptationSetIndices.length;
|
||||
boolean[] primaryGroupHasEventMessageTrackFlags = new boolean[primaryGroupCount];
|
||||
boolean[] primaryGroupHasCea608TrackFlags = new boolean[primaryGroupCount];
|
||||
int totalGroupCount = primaryGroupCount;
|
||||
for (int i = 0; i < primaryGroupCount; i++) {
|
||||
if (hasEventMessageTrack(adaptationSets, groupedAdaptationSetIndices[i])) {
|
||||
primaryGroupHasEventMessageTrackFlags[i] = true;
|
||||
totalGroupCount++;
|
||||
}
|
||||
if (hasCea608Track(adaptationSets, groupedAdaptationSetIndices[i])) {
|
||||
primaryGroupHasCea608TrackFlags[i] = true;
|
||||
totalGroupCount++;
|
||||
}
|
||||
}
|
||||
int totalEmbeddedTrackGroupCount = identifyEmbeddedTracks(primaryGroupCount, adaptationSets,
|
||||
groupedAdaptationSetIndices, primaryGroupHasEventMessageTrackFlags,
|
||||
primaryGroupHasCea608TrackFlags);
|
||||
|
||||
int totalGroupCount = primaryGroupCount + totalEmbeddedTrackGroupCount + eventStreams.size();
|
||||
TrackGroup[] trackGroups = new TrackGroup[totalGroupCount];
|
||||
TrackGroupInfo[] trackGroupInfos = new TrackGroupInfo[totalGroupCount];
|
||||
|
||||
int trackGroupCount = 0;
|
||||
for (int i = 0; i < primaryGroupCount; i++) {
|
||||
int[] adaptationSetIndices = groupedAdaptationSetIndices[i];
|
||||
List<Representation> representations = new ArrayList<>();
|
||||
for (int adaptationSetIndex : adaptationSetIndices) {
|
||||
representations.addAll(adaptationSets.get(adaptationSetIndex).representations);
|
||||
}
|
||||
Format[] formats = new Format[representations.size()];
|
||||
for (int j = 0; j < formats.length; j++) {
|
||||
formats[j] = representations.get(j).format;
|
||||
}
|
||||
int trackGroupCount = buildPrimaryAndEmbeddedTrackGroupInfos(adaptationSets,
|
||||
groupedAdaptationSetIndices, primaryGroupCount, primaryGroupHasEventMessageTrackFlags,
|
||||
primaryGroupHasCea608TrackFlags, trackGroups, trackGroupInfos);
|
||||
|
||||
AdaptationSet firstAdaptationSet = adaptationSets.get(adaptationSetIndices[0]);
|
||||
int primaryTrackGroupIndex = trackGroupCount;
|
||||
boolean hasEventMessageTrack = primaryGroupHasEventMessageTrackFlags[i];
|
||||
boolean hasCea608Track = primaryGroupHasCea608TrackFlags[i];
|
||||
|
||||
trackGroups[trackGroupCount] = new TrackGroup(formats);
|
||||
trackGroupInfos[trackGroupCount++] = new TrackGroupInfo(firstAdaptationSet.type,
|
||||
adaptationSetIndices, primaryTrackGroupIndex, true, hasEventMessageTrack, hasCea608Track);
|
||||
if (hasEventMessageTrack) {
|
||||
Format format = Format.createSampleFormat(firstAdaptationSet.id + ":emsg",
|
||||
MimeTypes.APPLICATION_EMSG, null, Format.NO_VALUE, null);
|
||||
trackGroups[trackGroupCount] = new TrackGroup(format);
|
||||
trackGroupInfos[trackGroupCount++] = new TrackGroupInfo(C.TRACK_TYPE_METADATA,
|
||||
adaptationSetIndices, primaryTrackGroupIndex, false, false, false);
|
||||
}
|
||||
if (hasCea608Track) {
|
||||
Format format = Format.createTextSampleFormat(firstAdaptationSet.id + ":cea608",
|
||||
MimeTypes.APPLICATION_CEA608, 0, null);
|
||||
trackGroups[trackGroupCount] = new TrackGroup(format);
|
||||
trackGroupInfos[trackGroupCount++] = new TrackGroupInfo(C.TRACK_TYPE_TEXT,
|
||||
adaptationSetIndices, primaryTrackGroupIndex, false, false, false);
|
||||
}
|
||||
}
|
||||
buildManifestEventTrackGroupInfos(eventStreams, trackGroups, trackGroupInfos, trackGroupCount);
|
||||
|
||||
return Pair.create(new TrackGroupArray(trackGroups), trackGroupInfos);
|
||||
}
|
||||
|
|
@ -326,6 +367,90 @@ import java.util.List;
|
|||
? Arrays.copyOf(groupedAdaptationSetIndices, groupCount) : groupedAdaptationSetIndices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates through list of primary track groups and identifies embedded tracks.
|
||||
* <p>
|
||||
* @param primaryGroupCount The number of primary track groups.
|
||||
* @param adaptationSets The list of {@link AdaptationSet} of the current DASH period.
|
||||
* @param groupedAdaptationSetIndices The indices of {@link AdaptationSet} that belongs to
|
||||
* the same primary group, grouped in primary track groups order.
|
||||
* @param primaryGroupHasEventMessageTrackFlags An output array containing boolean flag, each
|
||||
* indicates whether the corresponding primary track group contains an embedded event message
|
||||
* track.
|
||||
* @param primaryGroupHasCea608TrackFlags An output array containing boolean flag, each
|
||||
* indicates whether the corresponding primary track group contains an embedded Cea608 track.
|
||||
* @return Total number of embedded tracks.
|
||||
*/
|
||||
private static int identifyEmbeddedTracks(int primaryGroupCount,
|
||||
List<AdaptationSet> adaptationSets, int[][] groupedAdaptationSetIndices,
|
||||
boolean[] primaryGroupHasEventMessageTrackFlags, boolean[] primaryGroupHasCea608TrackFlags) {
|
||||
int numEmbeddedTrack = 0;
|
||||
for (int i = 0; i < primaryGroupCount; i++) {
|
||||
if (hasEventMessageTrack(adaptationSets, groupedAdaptationSetIndices[i])) {
|
||||
primaryGroupHasEventMessageTrackFlags[i] = true;
|
||||
numEmbeddedTrack++;
|
||||
}
|
||||
if (hasCea608Track(adaptationSets, groupedAdaptationSetIndices[i])) {
|
||||
primaryGroupHasCea608TrackFlags[i] = true;
|
||||
numEmbeddedTrack++;
|
||||
}
|
||||
}
|
||||
return numEmbeddedTrack;
|
||||
}
|
||||
|
||||
private static int buildPrimaryAndEmbeddedTrackGroupInfos(List<AdaptationSet> adaptationSets,
|
||||
int[][] groupedAdaptationSetIndices, int primaryGroupCount,
|
||||
boolean[] primaryGroupHasEventMessageTrackFlags, boolean[] primaryGroupHasCea608TrackFlags,
|
||||
TrackGroup[] trackGroups, TrackGroupInfo[] trackGroupInfos) {
|
||||
int trackGroupCount = 0;
|
||||
for (int i = 0; i < primaryGroupCount; i++) {
|
||||
int[] adaptationSetIndices = groupedAdaptationSetIndices[i];
|
||||
List<Representation> representations = new ArrayList<>();
|
||||
for (int adaptationSetIndex : adaptationSetIndices) {
|
||||
representations.addAll(adaptationSets.get(adaptationSetIndex).representations);
|
||||
}
|
||||
Format[] formats = new Format[representations.size()];
|
||||
for (int j = 0; j < formats.length; j++) {
|
||||
formats[j] = representations.get(j).format;
|
||||
}
|
||||
|
||||
AdaptationSet firstAdaptationSet = adaptationSets.get(adaptationSetIndices[0]);
|
||||
int primaryTrackGroupIndex = trackGroupCount;
|
||||
boolean hasEventMessageTrack = primaryGroupHasEventMessageTrackFlags[i];
|
||||
boolean hasCea608Track = primaryGroupHasCea608TrackFlags[i];
|
||||
|
||||
trackGroups[trackGroupCount] = new TrackGroup(formats);
|
||||
trackGroupInfos[trackGroupCount++] = TrackGroupInfo.primaryTrack(firstAdaptationSet.type,
|
||||
adaptationSetIndices, primaryTrackGroupIndex, hasEventMessageTrack, hasCea608Track);
|
||||
if (hasEventMessageTrack) {
|
||||
Format format = Format.createSampleFormat(firstAdaptationSet.id + ":emsg",
|
||||
MimeTypes.APPLICATION_EMSG, null, Format.NO_VALUE, null);
|
||||
trackGroups[trackGroupCount] = new TrackGroup(format);
|
||||
trackGroupInfos[trackGroupCount++] = TrackGroupInfo.embeddedEmsgTrack(adaptationSetIndices,
|
||||
primaryTrackGroupIndex);
|
||||
}
|
||||
if (hasCea608Track) {
|
||||
Format format = Format.createTextSampleFormat(firstAdaptationSet.id + ":cea608",
|
||||
MimeTypes.APPLICATION_CEA608, 0, null);
|
||||
trackGroups[trackGroupCount] = new TrackGroup(format);
|
||||
trackGroupInfos[trackGroupCount++] = TrackGroupInfo.embeddedCea608Track(
|
||||
adaptationSetIndices, primaryTrackGroupIndex);
|
||||
}
|
||||
}
|
||||
return trackGroupCount;
|
||||
}
|
||||
|
||||
private static void buildManifestEventTrackGroupInfos(List<EventStream> eventStreams,
|
||||
TrackGroup[] trackGroups, TrackGroupInfo[] trackGroupInfos, int existingTrackGroupCount) {
|
||||
for (int i = 0; i < eventStreams.size(); i++) {
|
||||
EventStream eventStream = eventStreams.get(i);
|
||||
Format format = Format.createSampleFormat(eventStream.id(), MimeTypes.APPLICATION_EMSG, null,
|
||||
Format.NO_VALUE, null);
|
||||
trackGroups[existingTrackGroupCount] = new TrackGroup(format);
|
||||
trackGroupInfos[existingTrackGroupCount++] = TrackGroupInfo.mpdEventTrack(i);
|
||||
}
|
||||
}
|
||||
|
||||
private ChunkSampleStream<DashChunkSource> buildSampleStream(TrackGroupInfo trackGroupInfo,
|
||||
TrackSelection selection, long positionUs) {
|
||||
int embeddedTrackCount = 0;
|
||||
|
|
@ -402,24 +527,75 @@ import java.util.List;
|
|||
|
||||
private static final class TrackGroupInfo {
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({CATEGORY_PRIMARY, CATEGORY_EMBEDDED, CATEGORY_MANIFEST_EVENTS})
|
||||
public @interface TrackGroupCategory {}
|
||||
|
||||
/**
|
||||
* A normal track group that has its samples drawn from the stream.
|
||||
* For example: a video Track Group or an audio Track Group.
|
||||
*/
|
||||
private static final int CATEGORY_PRIMARY = 0;
|
||||
|
||||
/**
|
||||
* A track group whose samples are embedded within one of the primary streams.
|
||||
* For example: an EMSG track has its sample embedded in `emsg' atoms in one of the primary
|
||||
* streams.
|
||||
*/
|
||||
private static final int CATEGORY_EMBEDDED = 1;
|
||||
|
||||
/**
|
||||
* A track group that has its samples listed explicitly in the DASH manifest file.
|
||||
* For example: an EventStream track has its sample (Events) included directly in the DASH
|
||||
* manifest file.
|
||||
*/
|
||||
private static final int CATEGORY_MANIFEST_EVENTS = 2;
|
||||
|
||||
public final int[] adaptationSetIndices;
|
||||
public final int trackType;
|
||||
public final boolean isPrimary;
|
||||
public @TrackGroupCategory final int trackGroupCategory;
|
||||
|
||||
public final int eventStreamGroupIndex;
|
||||
public final int primaryTrackGroupIndex;
|
||||
public final boolean hasEmbeddedEventMessageTrack;
|
||||
public final boolean hasEmbeddedCea608Track;
|
||||
|
||||
public TrackGroupInfo(int trackType, int[] adaptationSetIndices, int primaryTrackGroupIndex,
|
||||
boolean isPrimary, boolean hasEmbeddedEventMessageTrack, boolean hasEmbeddedCea608Track) {
|
||||
this.trackType = trackType;
|
||||
this.adaptationSetIndices = adaptationSetIndices;
|
||||
this.primaryTrackGroupIndex = primaryTrackGroupIndex;
|
||||
this.isPrimary = isPrimary;
|
||||
this.hasEmbeddedEventMessageTrack = hasEmbeddedEventMessageTrack;
|
||||
this.hasEmbeddedCea608Track = hasEmbeddedCea608Track;
|
||||
public static TrackGroupInfo primaryTrack(int trackType, int[] adaptationSetIndices,
|
||||
int primaryTrackGroupIndex, boolean hasEmbeddedEventMessageTrack,
|
||||
boolean hasEmbeddedCea608Track) {
|
||||
return new TrackGroupInfo(trackType, CATEGORY_PRIMARY, adaptationSetIndices,
|
||||
primaryTrackGroupIndex, hasEmbeddedEventMessageTrack, hasEmbeddedCea608Track, -1);
|
||||
}
|
||||
|
||||
public static TrackGroupInfo embeddedEmsgTrack(int[] adaptationSetIndices,
|
||||
int primaryTrackGroupIndex) {
|
||||
return new TrackGroupInfo(C.TRACK_TYPE_METADATA, CATEGORY_EMBEDDED,
|
||||
adaptationSetIndices, primaryTrackGroupIndex, false, false, -1);
|
||||
}
|
||||
|
||||
public static TrackGroupInfo embeddedCea608Track(int[] adaptationSetIndices,
|
||||
int primaryTrackGroupIndex) {
|
||||
return new TrackGroupInfo(C.TRACK_TYPE_TEXT, CATEGORY_EMBEDDED,
|
||||
adaptationSetIndices, primaryTrackGroupIndex, false, false, -1);
|
||||
}
|
||||
|
||||
public static TrackGroupInfo mpdEventTrack(int eventStreamIndex) {
|
||||
return new TrackGroupInfo(C.TRACK_TYPE_METADATA, CATEGORY_MANIFEST_EVENTS,
|
||||
null, -1, false, false, eventStreamIndex);
|
||||
}
|
||||
|
||||
private TrackGroupInfo(int trackType, @TrackGroupCategory int trackGroupCategory,
|
||||
int[] adaptationSetIndices, int primaryTrackGroupIndex,
|
||||
boolean hasEmbeddedEventMessageTrack, boolean hasEmbeddedCea608Track,
|
||||
int eventStreamGroupIndex) {
|
||||
this.trackType = trackType;
|
||||
this.adaptationSetIndices = adaptationSetIndices;
|
||||
this.trackGroupCategory = trackGroupCategory;
|
||||
this.primaryTrackGroupIndex = primaryTrackGroupIndex;
|
||||
this.hasEmbeddedEventMessageTrack = hasEmbeddedEventMessageTrack;
|
||||
this.hasEmbeddedCea608Track = hasEmbeddedCea608Track;
|
||||
this.eventStreamGroupIndex = eventStreamGroupIndex;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
* 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;
|
||||
|
|
|
|||
|
|
@ -20,12 +20,14 @@ import android.text.TextUtils;
|
|||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.util.Xml;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
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.metadata.emsg.EventMessage;
|
||||
import com.google.android.exoplayer2.source.dash.manifest.SegmentBase.SegmentList;
|
||||
import com.google.android.exoplayer2.source.dash.manifest.SegmentBase.SegmentTemplate;
|
||||
import com.google.android.exoplayer2.source.dash.manifest.SegmentBase.SegmentTimelineElement;
|
||||
|
|
@ -36,6 +38,7 @@ import com.google.android.exoplayer2.util.MimeTypes;
|
|||
import com.google.android.exoplayer2.util.UriUtil;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import com.google.android.exoplayer2.util.XmlPullParserUtil;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
|
|
@ -47,6 +50,7 @@ import org.xml.sax.helpers.DefaultHandler;
|
|||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
import org.xmlpull.v1.XmlPullParserFactory;
|
||||
import org.xmlpull.v1.XmlSerializer;
|
||||
|
||||
/**
|
||||
* A parser of media presentation description files.
|
||||
|
|
@ -197,6 +201,7 @@ public class DashManifestParser extends DefaultHandler
|
|||
long durationMs = parseDuration(xpp, "duration", C.TIME_UNSET);
|
||||
SegmentBase segmentBase = null;
|
||||
List<AdaptationSet> adaptationSets = new ArrayList<>();
|
||||
List<EventStream> eventStreams = new ArrayList<>();
|
||||
boolean seenFirstBaseUrl = false;
|
||||
do {
|
||||
xpp.next();
|
||||
|
|
@ -207,6 +212,8 @@ public class DashManifestParser extends DefaultHandler
|
|||
}
|
||||
} else if (XmlPullParserUtil.isStartTag(xpp, "AdaptationSet")) {
|
||||
adaptationSets.add(parseAdaptationSet(xpp, baseUrl, segmentBase));
|
||||
} else if (XmlPullParserUtil.isStartTag(xpp, "EventStream")) {
|
||||
eventStreams.add(parseEventStream(xpp));
|
||||
} else if (XmlPullParserUtil.isStartTag(xpp, "SegmentBase")) {
|
||||
segmentBase = parseSegmentBase(xpp, null);
|
||||
} else if (XmlPullParserUtil.isStartTag(xpp, "SegmentList")) {
|
||||
|
|
@ -216,11 +223,12 @@ public class DashManifestParser extends DefaultHandler
|
|||
}
|
||||
} while (!XmlPullParserUtil.isEndTag(xpp, "Period"));
|
||||
|
||||
return Pair.create(buildPeriod(id, startMs, adaptationSets), durationMs);
|
||||
return Pair.create(buildPeriod(id, startMs, adaptationSets, eventStreams), durationMs);
|
||||
}
|
||||
|
||||
protected Period buildPeriod(String id, long startMs, List<AdaptationSet> adaptationSets) {
|
||||
return new Period(id, startMs, adaptationSets);
|
||||
protected Period buildPeriod(String id, long startMs, List<AdaptationSet> adaptationSets,
|
||||
List<EventStream> eventStreams) {
|
||||
return new Period(id, startMs, adaptationSets, eventStreams);
|
||||
}
|
||||
|
||||
// AdaptationSet parsing.
|
||||
|
|
@ -387,7 +395,7 @@ public class DashManifestParser extends DefaultHandler
|
|||
Log.w(TAG, "Skipping malformed cenc:pssh data");
|
||||
data = null;
|
||||
}
|
||||
} else if (uuid == C.PLAYREADY_UUID && XmlPullParserUtil.isStartTag(xpp, "mspr:pro")
|
||||
} else if (C.PLAYREADY_UUID.equals(uuid) && XmlPullParserUtil.isStartTag(xpp, "mspr:pro")
|
||||
&& xpp.next() == XmlPullParser.TEXT) {
|
||||
// The mspr:pro element is defined in DASH Content Protection using Microsoft PlayReady.
|
||||
data = PsshAtomUtil.buildPsshAtom(C.PLAYREADY_UUID,
|
||||
|
|
@ -660,6 +668,143 @@ public class DashManifestParser extends DefaultHandler
|
|||
startNumber, duration, timeline, initializationTemplate, mediaTemplate);
|
||||
}
|
||||
|
||||
/**
|
||||
* /**
|
||||
* Parses a single EventStream node in the manifest.
|
||||
* <p>
|
||||
* @param xpp The current xml parser.
|
||||
* @return The {@link EventStream} 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 EventStream parseEventStream(XmlPullParser xpp)
|
||||
throws XmlPullParserException, IOException {
|
||||
String schemeIdUri = parseString(xpp, "schemeIdUri", "");
|
||||
String value = parseString(xpp, "value", "");
|
||||
long timescale = parseLong(xpp, "timescale", 1);
|
||||
List<Pair<Long, EventMessage>> timedEvents = new ArrayList<>();
|
||||
ByteArrayOutputStream scratchOutputStream = new ByteArrayOutputStream(512);
|
||||
do {
|
||||
xpp.next();
|
||||
if (XmlPullParserUtil.isStartTag(xpp, "Event")) {
|
||||
Pair<Long, EventMessage> timedEvent = parseEvent(xpp, schemeIdUri, value, timescale,
|
||||
scratchOutputStream);
|
||||
timedEvents.add(timedEvent);
|
||||
}
|
||||
} 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<Long, EventMessage> timedEvent = timedEvents.get(i);
|
||||
presentationTimesUs[i] = timedEvent.first;
|
||||
events[i] = timedEvent.second;
|
||||
}
|
||||
return buildEventStream(schemeIdUri, value, timescale, presentationTimesUs, events);
|
||||
}
|
||||
|
||||
protected EventStream buildEventStream(String schemeIdUri, String value, long timescale,
|
||||
long[] presentationTimesUs, EventMessage[] events) {
|
||||
return new EventStream(schemeIdUri, value, timescale, presentationTimesUs, events);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a single Event node in the manifest.
|
||||
* <p>
|
||||
* @param xpp The current xml parser.
|
||||
* @param schemeIdUri The schemeIdUri of the parent EventStream.
|
||||
* @param value The schemeIdUri of the parent EventStream.
|
||||
* @param timescale The timescale of the parent EventStream.
|
||||
* @param scratchOutputStream A {@link ByteArrayOutputStream} that is used to write serialize data
|
||||
* in between <Event> and </Event> tags into.
|
||||
* @return The {@link EventStream} 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<Long, EventMessage> parseEvent(XmlPullParser xpp, String schemeIdUri, String value,
|
||||
long timescale, ByteArrayOutputStream scratchOutputStream)
|
||||
throws IOException, XmlPullParserException {
|
||||
long id = parseLong(xpp, "id", 0);
|
||||
long duration = parseLong(xpp, "duration", C.TIME_UNSET);
|
||||
long presentationTime = parseLong(xpp, "presentationTime", 0);
|
||||
long durationMs = Util.scaleLargeTimestamp(duration, 1000, timescale);
|
||||
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));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses everything between <Event></Event> as a byte array string.
|
||||
*
|
||||
* @param xpp The current xml parser.
|
||||
* @param scratchOutputStream A {@link ByteArrayOutputStream} that is used to write serialize byte
|
||||
* array data into.
|
||||
* @return The serialized byte array.
|
||||
* @throws XmlPullParserException If there is any error parsing this node.
|
||||
* @throws IOException If there is any error reading from the underlying input stream.
|
||||
*/
|
||||
protected byte[] parseEventObject(XmlPullParser xpp, ByteArrayOutputStream scratchOutputStream)
|
||||
throws XmlPullParserException, IOException {
|
||||
scratchOutputStream.reset();
|
||||
XmlSerializer xmlSerializer = Xml.newSerializer();
|
||||
xmlSerializer.setOutput(scratchOutputStream, null);
|
||||
// Start reading everything between <Event> and </Event>, and serialize them into an Xml
|
||||
// byte array.
|
||||
xpp.nextToken();
|
||||
while (!XmlPullParserUtil.isEndTag(xpp, "Event")) {
|
||||
switch (xpp.getEventType()) {
|
||||
case (XmlPullParser.START_DOCUMENT):
|
||||
xmlSerializer.startDocument(null, false);
|
||||
break;
|
||||
case (XmlPullParser.END_DOCUMENT):
|
||||
xmlSerializer.endDocument();
|
||||
break;
|
||||
case (XmlPullParser.START_TAG):
|
||||
xmlSerializer.startTag(xpp.getNamespace(), xpp.getName());
|
||||
for (int i = 0; i < xpp.getAttributeCount(); i++) {
|
||||
xmlSerializer.attribute(xpp.getAttributeNamespace(i), xpp.getAttributeName(i),
|
||||
xpp.getAttributeValue(i));
|
||||
}
|
||||
break;
|
||||
case (XmlPullParser.END_TAG):
|
||||
xmlSerializer.endTag(xpp.getNamespace(), xpp.getName());
|
||||
break;
|
||||
case (XmlPullParser.TEXT):
|
||||
xmlSerializer.text(xpp.getText());
|
||||
break;
|
||||
case (XmlPullParser.CDSECT):
|
||||
xmlSerializer.cdsect(xpp.getText());
|
||||
break;
|
||||
case (XmlPullParser.ENTITY_REF):
|
||||
xmlSerializer.entityRef(xpp.getText());
|
||||
break;
|
||||
case (XmlPullParser.IGNORABLE_WHITESPACE):
|
||||
xmlSerializer.ignorableWhitespace(xpp.getText());
|
||||
break;
|
||||
case (XmlPullParser.PROCESSING_INSTRUCTION):
|
||||
xmlSerializer.processingInstruction(xpp.getText());
|
||||
break;
|
||||
case (XmlPullParser.COMMENT):
|
||||
xmlSerializer.comment(xpp.getText());
|
||||
break;
|
||||
case (XmlPullParser.DOCDECL):
|
||||
xmlSerializer.docdecl(xpp.getText());
|
||||
break;
|
||||
default: // fall out
|
||||
}
|
||||
xpp.nextToken();
|
||||
}
|
||||
xmlSerializer.flush();
|
||||
return scratchOutputStream.toByteArray();
|
||||
}
|
||||
|
||||
protected EventMessage buildEvent(String schemeIdUri, String value, long id,
|
||||
long durationMs, byte[] messageData) {
|
||||
return new EventMessage(schemeIdUri, value, durationMs, id, messageData);
|
||||
}
|
||||
|
||||
protected List<SegmentTimelineElement> parseSegmentTimeline(XmlPullParser xpp)
|
||||
throws XmlPullParserException, IOException {
|
||||
List<SegmentTimelineElement> segmentTimeline = new ArrayList<>();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* 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.
|
||||
|
|
@ -39,15 +40,32 @@ 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(String id, long startMs, List<AdaptationSet> adaptationSets) {
|
||||
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(@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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in a new issue