diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java index 96f7ff423f..438d426c93 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java @@ -78,6 +78,8 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact (flags & FLAG_DETECT_ACCESS_UNITS) != 0)); case TsExtractor.TS_STREAM_TYPE_H265: return new PesReader(new H265Reader()); + case TsExtractor.TS_STREAM_TYPE_SPLICE_INFO: + return new SectionReader(new SpliceInfoSectionReader()); case TsExtractor.TS_STREAM_TYPE_ID3: return new PesReader(new Id3Reader()); default: diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/SpliceInfoSectionReader.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/SpliceInfoSectionReader.java new file mode 100644 index 0000000000..b1e71d6651 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/SpliceInfoSectionReader.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2016 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.extractor.ts; + +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.extractor.ExtractorOutput; +import com.google.android.exoplayer2.extractor.TimestampAdjuster; +import com.google.android.exoplayer2.extractor.TrackOutput; +import com.google.android.exoplayer2.util.MimeTypes; +import com.google.android.exoplayer2.util.ParsableByteArray; + +/** + * Parses splice info sections as defined by SCTE35. + */ +public final class SpliceInfoSectionReader implements SectionPayloadReader { + + private TrackOutput output; + + @Override + public void init(TimestampAdjuster timestampAdjuster, ExtractorOutput extractorOutput, + TsPayloadReader.TrackIdGenerator idGenerator) { + output = extractorOutput.track(idGenerator.getNextId()); + output.format(Format.createSampleFormat(null, MimeTypes.APPLICATION_SCTE35, null, + Format.NO_VALUE, null)); + } + + @Override + public void consume(ParsableByteArray sectionData) { + int sampleSize = sectionData.bytesLeft(); + output.sampleData(sectionData, sampleSize); + output.sampleMetadata(0, C.BUFFER_FLAG_KEY_FRAME, sampleSize, 0, null); + } + +} diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java index 219101b8d3..6808c14cf9 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java @@ -64,6 +64,7 @@ public final class TsExtractor implements Extractor { public static final int TS_STREAM_TYPE_H264 = 0x1B; public static final int TS_STREAM_TYPE_H265 = 0x24; public static final int TS_STREAM_TYPE_ID3 = 0x15; + public static final int TS_STREAM_TYPE_SPLICE_INFO = 0x86; private static final int TS_PACKET_SIZE = 188; private static final int TS_SYNC_BYTE = 0x47; // First byte of each TS packet. diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/scte35/PrivateCommand.java b/library/src/main/java/com/google/android/exoplayer2/metadata/scte35/PrivateCommand.java new file mode 100644 index 0000000000..f75a1b46a4 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer2/metadata/scte35/PrivateCommand.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2016 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.scte35; + +import android.os.Parcel; +import android.os.Parcelable; +import com.google.android.exoplayer2.util.ParsableByteArray; + +/** + * Represents a private command as defined in SCTE35, Section 9.3.6. + */ +public final class PrivateCommand extends SpliceCommand { + + public final long ptsAdjustment; + public final long identifier; + + public final byte[] commandBytes; + + private PrivateCommand(long identifier, byte[] commandBytes, long ptsAdjustment) { + this.ptsAdjustment = ptsAdjustment; + this.identifier = identifier; + this.commandBytes = commandBytes; + } + + private PrivateCommand(Parcel in) { + ptsAdjustment = in.readLong(); + identifier = in.readLong(); + commandBytes = new byte[in.readInt()]; + in.readByteArray(commandBytes); + } + + /* package */ static PrivateCommand parseFromSection(ParsableByteArray sectionData, + int commandLength, long ptsAdjustment) { + long identifier = sectionData.readUnsignedInt(); + byte[] privateBytes = new byte[commandLength - 4 /* identifier size */]; + sectionData.readBytes(privateBytes, 0, privateBytes.length); + return new PrivateCommand(identifier, privateBytes, ptsAdjustment); + } + + // Parcelable implementation. + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(ptsAdjustment); + dest.writeLong(identifier); + dest.writeInt(commandBytes.length); + dest.writeByteArray(commandBytes); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + + @Override + public PrivateCommand createFromParcel(Parcel in) { + return new PrivateCommand(in); + } + + @Override + public PrivateCommand[] newArray(int size) { + return new PrivateCommand[size]; + } + + }; + +} diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceCommand.java b/library/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceCommand.java new file mode 100644 index 0000000000..8dfa3b8942 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceCommand.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2016 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.scte35; + +import com.google.android.exoplayer2.metadata.Metadata; + +/** + * Superclass for SCTE35 splice commands. + */ +public abstract class SpliceCommand implements Metadata.Entry { + + @Override + public int describeContents() { + return 0; + } + +} diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceInfoDecoder.java b/library/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceInfoDecoder.java new file mode 100644 index 0000000000..5af0f25481 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceInfoDecoder.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2016 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.scte35; + +import android.text.TextUtils; +import com.google.android.exoplayer2.metadata.Metadata; +import com.google.android.exoplayer2.metadata.MetadataDecoder; +import com.google.android.exoplayer2.metadata.MetadataDecoderException; +import com.google.android.exoplayer2.util.MimeTypes; +import com.google.android.exoplayer2.util.ParsableBitArray; +import com.google.android.exoplayer2.util.ParsableByteArray; + +/** + * Decodes splice info sections and produces splice commands. + */ +public final class SpliceInfoDecoder implements MetadataDecoder { + + private static final int TYPE_SPLICE_NULL = 0x00; + private static final int TYPE_SPLICE_SCHEDULE = 0x04; + private static final int TYPE_SPLICE_INSERT = 0x05; + private static final int TYPE_TIME_SIGNAL = 0x06; + private static final int TYPE_PRIVATE_COMMAND = 0xFF; + + private final ParsableByteArray sectionData; + private final ParsableBitArray sectionHeader; + + public SpliceInfoDecoder() { + sectionData = new ParsableByteArray(); + sectionHeader = new ParsableBitArray(); + } + + @Override + public boolean canDecode(String mimeType) { + return TextUtils.equals(mimeType, MimeTypes.APPLICATION_SCTE35); + } + + @Override + public Metadata decode(byte[] data, int size) throws MetadataDecoderException { + sectionData.reset(data, size); + sectionHeader.reset(data, size); + // table_id(8), section_syntax_indicator(1), private_indicator(1), reserved(2), + // section_length(12), protocol_version(8), encrypted_packet(1), encryption_algorithm(6). + sectionHeader.skipBits(39); + long ptsAdjustment = sectionHeader.readBits(1); + ptsAdjustment = (ptsAdjustment << 32) | sectionHeader.readBits(32); + // cw_index(8), tier(12). + sectionHeader.skipBits(20); + int spliceCommandLength = sectionHeader.readBits(12); + int spliceCommandType = sectionHeader.readBits(8); + SpliceCommand command = null; + // Go to the start of the command by skipping all fields up to command_type. + sectionData.skipBytes(14); + switch (spliceCommandType) { + case TYPE_SPLICE_NULL: + command = new SpliceNullCommand(); + break; + case TYPE_SPLICE_SCHEDULE: + command = SpliceScheduleCommand.parseFromSection(sectionData); + break; + case TYPE_SPLICE_INSERT: + command = SpliceInsertCommand.parseFromSection(sectionData, ptsAdjustment); + break; + case TYPE_TIME_SIGNAL: + command = TimeSignalCommand.parseFromSection(sectionData, ptsAdjustment); + break; + case TYPE_PRIVATE_COMMAND: + command = PrivateCommand.parseFromSection(sectionData, spliceCommandLength, ptsAdjustment); + break; + } + return command == null ? new Metadata() : new Metadata(command); + } + +} diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceInsertCommand.java b/library/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceInsertCommand.java new file mode 100644 index 0000000000..1e025aeb35 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceInsertCommand.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2016 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.scte35; + +import android.os.Parcel; +import android.os.Parcelable; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.util.ParsableByteArray; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Represents a splice insert command defined in SCTE35, Section 9.3.3. + */ +public final class SpliceInsertCommand extends SpliceCommand { + + public final long spliceEventId; + public final boolean spliceEventCancelIndicator; + public final boolean outOfNetworkIndicator; + public final boolean programSpliceFlag; + public final boolean spliceImmediateFlag; + public final long programSplicePts; + public final List componentSpliceList; + public final boolean autoReturn; + public final long breakDuration; + public final int uniqueProgramId; + public final int availNum; + public final int availsExpected; + + private SpliceInsertCommand(long spliceEventId, boolean spliceEventCancelIndicator, + boolean outOfNetworkIndicator, boolean programSpliceFlag, boolean spliceImmediateFlag, + long programSplicePts, List componentSpliceList, boolean autoReturn, + long breakDuration, int uniqueProgramId, int availNum, int availsExpected) { + this.spliceEventId = spliceEventId; + this.spliceEventCancelIndicator = spliceEventCancelIndicator; + this.outOfNetworkIndicator = outOfNetworkIndicator; + this.programSpliceFlag = programSpliceFlag; + this.spliceImmediateFlag = spliceImmediateFlag; + this.programSplicePts = programSplicePts; + this.componentSpliceList = Collections.unmodifiableList(componentSpliceList); + this.autoReturn = autoReturn; + this.breakDuration = breakDuration; + this.uniqueProgramId = uniqueProgramId; + this.availNum = availNum; + this.availsExpected = availsExpected; + } + + private SpliceInsertCommand(Parcel in) { + spliceEventId = in.readLong(); + spliceEventCancelIndicator = in.readByte() == 1; + outOfNetworkIndicator = in.readByte() == 1; + programSpliceFlag = in.readByte() == 1; + spliceImmediateFlag = in.readByte() == 1; + programSplicePts = in.readLong(); + int componentSpliceListSize = in.readInt(); + List componentSpliceList = new ArrayList<>(componentSpliceListSize); + for (int i = 0; i < componentSpliceListSize; i++) { + componentSpliceList.add(ComponentSplice.createFromParcel(in)); + } + this.componentSpliceList = Collections.unmodifiableList(componentSpliceList); + autoReturn = in.readByte() == 1; + breakDuration = in.readLong(); + uniqueProgramId = in.readInt(); + availNum = in.readInt(); + availsExpected = in.readInt(); + } + + /* package */ static SpliceInsertCommand parseFromSection(ParsableByteArray sectionData, + long ptsAdjustment) { + long spliceEventId = sectionData.readUnsignedInt(); + // splice_event_cancel_indicator(1), reserved(7). + boolean spliceEventCancelIndicator = (sectionData.readUnsignedByte() & 0x80) != 0; + boolean outOfNetworkIndicator = false; + boolean programSpliceFlag = false; + boolean spliceImmediateFlag = false; + long programSplicePts = C.TIME_UNSET; + ArrayList componentSplices = new ArrayList<>(); + int uniqueProgramId = 0; + int availNum = 0; + int availsExpected = 0; + boolean autoReturn = false; + long duration = C.TIME_UNSET; + if (!spliceEventCancelIndicator) { + int headerByte = sectionData.readUnsignedByte(); + outOfNetworkIndicator = (headerByte & 0x80) != 0; + programSpliceFlag = (headerByte & 0x40) != 0; + boolean durationFlag = (headerByte & 0x20) != 0; + spliceImmediateFlag = (headerByte & 0x10) != 0; + if (programSpliceFlag && !spliceImmediateFlag) { + programSplicePts = TimeSignalCommand.parseSpliceTime(sectionData, ptsAdjustment); + } + if (!programSpliceFlag) { + int componentCount = sectionData.readUnsignedByte(); + componentSplices = new ArrayList<>(componentCount); + for (int i = 0; i < componentCount; i++) { + int componentTag = sectionData.readUnsignedByte(); + long componentSplicePts = C.TIME_UNSET; + if (!spliceImmediateFlag) { + componentSplicePts = TimeSignalCommand.parseSpliceTime(sectionData, ptsAdjustment); + } + componentSplices.add(new ComponentSplice(componentTag, componentSplicePts)); + } + } + if (durationFlag) { + long firstByte = sectionData.readUnsignedByte(); + autoReturn = (firstByte & 0x80) != 0; + duration = ((firstByte & 0x01) << 32) | sectionData.readUnsignedInt(); + } + uniqueProgramId = sectionData.readUnsignedShort(); + availNum = sectionData.readUnsignedByte(); + availsExpected = sectionData.readUnsignedByte(); + } + return new SpliceInsertCommand(spliceEventId, spliceEventCancelIndicator, outOfNetworkIndicator, + programSpliceFlag, spliceImmediateFlag, programSplicePts, componentSplices, autoReturn, + duration, uniqueProgramId, availNum, availsExpected); + } + + /** + * Holds splicing information for specific splice insert command components. + */ + public static final class ComponentSplice { + + public final int componentTag; + public final long componentSplicePts; + + private ComponentSplice(int componentTag, long componentSplicePts) { + this.componentTag = componentTag; + this.componentSplicePts = componentSplicePts; + } + + public void writeToParcel(Parcel dest) { + dest.writeInt(componentTag); + dest.writeLong(componentSplicePts); + } + + public static ComponentSplice createFromParcel(Parcel in) { + return new ComponentSplice(in.readInt(), in.readLong()); + } + + } + + // Parcelable implementation. + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(spliceEventId); + dest.writeByte((byte) (spliceEventCancelIndicator ? 1 : 0)); + dest.writeByte((byte) (outOfNetworkIndicator ? 1 : 0)); + dest.writeByte((byte) (programSpliceFlag ? 1 : 0)); + dest.writeByte((byte) (spliceImmediateFlag ? 1 : 0)); + dest.writeLong(programSplicePts); + int componentSpliceListSize = componentSpliceList.size(); + dest.writeInt(componentSpliceListSize); + for (int i = 0; i < componentSpliceListSize; i++) { + componentSpliceList.get(i).writeToParcel(dest); + } + dest.writeByte((byte) (autoReturn ? 1 : 0)); + dest.writeLong(breakDuration); + dest.writeInt(uniqueProgramId); + dest.writeInt(availNum); + dest.writeInt(availsExpected); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + + @Override + public SpliceInsertCommand createFromParcel(Parcel in) { + return new SpliceInsertCommand(in); + } + + @Override + public SpliceInsertCommand[] newArray(int size) { + return new SpliceInsertCommand[size]; + } + + }; + +} diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceNullCommand.java b/library/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceNullCommand.java new file mode 100644 index 0000000000..461d49ebb4 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceNullCommand.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2016 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.scte35; + +import android.os.Parcel; + +/** + * Represents a splice null command as defined in SCTE35, Section 9.3.1. + */ +public final class SpliceNullCommand extends SpliceCommand { + + // Parcelable implementation. + + @Override + public void writeToParcel(Parcel dest, int flags) { + // Do nothing. + } + + public static final Creator CREATOR = + new Creator() { + + @Override + public SpliceNullCommand createFromParcel(Parcel in) { + return new SpliceNullCommand(); + } + + @Override + public SpliceNullCommand[] newArray(int size) { + return new SpliceNullCommand[size]; + } + + }; + +} diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceScheduleCommand.java b/library/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceScheduleCommand.java new file mode 100644 index 0000000000..9b391cea6c --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceScheduleCommand.java @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2016 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.scte35; + +import android.os.Parcel; +import android.os.Parcelable; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.util.ParsableByteArray; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Represents a splice schedule command as defined in SCTE35, Section 9.3.2. + */ +public final class SpliceScheduleCommand extends SpliceCommand { + + /** + * Represents a splice event as contained in a {@link SpliceScheduleCommand}. + */ + public static final class Event { + + public final long spliceEventId; + public final boolean spliceEventCancelIndicator; + public final boolean outOfNetworkIndicator; + public final boolean programSpliceFlag; + public final long utcSpliceTime; + public final List componentSpliceList; + public final boolean autoReturn; + public final long breakDuration; + public final int uniqueProgramId; + public final int availNum; + public final int availsExpected; + + private Event(long spliceEventId, boolean spliceEventCancelIndicator, + boolean outOfNetworkIndicator, boolean programSpliceFlag, + List componentSpliceList, long utcSpliceTime, boolean autoReturn, + long breakDuration, int uniqueProgramId, int availNum, int availsExpected) { + this.spliceEventId = spliceEventId; + this.spliceEventCancelIndicator = spliceEventCancelIndicator; + this.outOfNetworkIndicator = outOfNetworkIndicator; + this.programSpliceFlag = programSpliceFlag; + this.componentSpliceList = Collections.unmodifiableList(componentSpliceList); + this.utcSpliceTime = utcSpliceTime; + this.autoReturn = autoReturn; + this.breakDuration = breakDuration; + this.uniqueProgramId = uniqueProgramId; + this.availNum = availNum; + this.availsExpected = availsExpected; + } + + private Event(Parcel in) { + this.spliceEventId = in.readLong(); + this.spliceEventCancelIndicator = in.readByte() == 1; + this.outOfNetworkIndicator = in.readByte() == 1; + this.programSpliceFlag = in.readByte() == 1; + int componentSpliceListLength = in.readInt(); + ArrayList componentSpliceList = new ArrayList<>(componentSpliceListLength); + for (int i = 0; i < componentSpliceListLength; i++) { + componentSpliceList.add(ComponentSplice.createFromParcel(in)); + } + this.componentSpliceList = Collections.unmodifiableList(componentSpliceList); + this.utcSpliceTime = in.readLong(); + this.autoReturn = in.readByte() == 1; + this.breakDuration = in.readLong(); + this.uniqueProgramId = in.readInt(); + this.availNum = in.readInt(); + this.availsExpected = in.readInt(); + } + + private static Event parseFromSection(ParsableByteArray sectionData) { + long spliceEventId = sectionData.readUnsignedInt(); + // splice_event_cancel_indicator(1), reserved(7). + boolean spliceEventCancelIndicator = (sectionData.readUnsignedByte() & 0x80) != 0; + boolean outOfNetworkIndicator = false; + boolean programSpliceFlag = false; + long utcSpliceTime = C.TIME_UNSET; + ArrayList componentSplices = new ArrayList<>(); + int uniqueProgramId = 0; + int availNum = 0; + int availsExpected = 0; + boolean autoReturn = false; + long duration = C.TIME_UNSET; + if (!spliceEventCancelIndicator) { + int headerByte = sectionData.readUnsignedByte(); + outOfNetworkIndicator = (headerByte & 0x80) != 0; + programSpliceFlag = (headerByte & 0x40) != 0; + boolean durationFlag = (headerByte & 0x20) != 0; + if (programSpliceFlag) { + utcSpliceTime = sectionData.readUnsignedInt(); + } + if (!programSpliceFlag) { + int componentCount = sectionData.readUnsignedByte(); + componentSplices = new ArrayList<>(componentCount); + for (int i = 0; i < componentCount; i++) { + int componentTag = sectionData.readUnsignedByte(); + long componentUtcSpliceTime = sectionData.readUnsignedInt(); + componentSplices.add(new ComponentSplice(componentTag, componentUtcSpliceTime)); + } + } + if (durationFlag) { + long firstByte = sectionData.readUnsignedByte(); + autoReturn = (firstByte & 0x80) != 0; + duration = ((firstByte & 0x01) << 32) | sectionData.readUnsignedInt(); + } + uniqueProgramId = sectionData.readUnsignedShort(); + availNum = sectionData.readUnsignedByte(); + availsExpected = sectionData.readUnsignedByte(); + } + return new Event(spliceEventId, spliceEventCancelIndicator, outOfNetworkIndicator, + programSpliceFlag, componentSplices, utcSpliceTime, autoReturn, duration, uniqueProgramId, + availNum, availsExpected); + } + + private void writeToParcel(Parcel dest) { + dest.writeLong(spliceEventId); + dest.writeByte((byte) (spliceEventCancelIndicator ? 1 : 0)); + dest.writeByte((byte) (outOfNetworkIndicator ? 1 : 0)); + dest.writeByte((byte) (programSpliceFlag ? 1 : 0)); + int componentSpliceListSize = componentSpliceList.size(); + dest.writeInt(componentSpliceListSize); + for (int i = 0; i < componentSpliceListSize; i++) { + componentSpliceList.get(i).writeToParcel(dest); + } + dest.writeLong(utcSpliceTime); + dest.writeByte((byte) (autoReturn ? 1 : 0)); + dest.writeLong(breakDuration); + dest.writeInt(uniqueProgramId); + dest.writeInt(availNum); + dest.writeInt(availsExpected); + } + + private static Event createFromParcel(Parcel in) { + return new Event(in); + } + + } + + /** + * Holds splicing information for specific splice schedule command components. + */ + public static final class ComponentSplice { + + public final int componentTag; + public final long utcSpliceTime; + + private ComponentSplice(int componentTag, long utcSpliceTime) { + this.componentTag = componentTag; + this.utcSpliceTime = utcSpliceTime; + } + + private static ComponentSplice createFromParcel(Parcel in) { + return new ComponentSplice(in.readInt(), in.readLong()); + } + + private void writeToParcel(Parcel dest) { + dest.writeInt(componentTag); + dest.writeLong(utcSpliceTime); + } + + } + + public final List events; + + private SpliceScheduleCommand(List events) { + this.events = Collections.unmodifiableList(events); + } + + private SpliceScheduleCommand(Parcel in) { + int eventsSize = in.readInt(); + ArrayList events = new ArrayList<>(eventsSize); + for (int i = 0; i < eventsSize; i++) { + events.add(Event.createFromParcel(in)); + } + this.events = Collections.unmodifiableList(events); + } + + /* package */ static SpliceScheduleCommand parseFromSection(ParsableByteArray sectionData) { + int spliceCount = sectionData.readUnsignedByte(); + ArrayList events = new ArrayList<>(spliceCount); + for (int i = 0; i < spliceCount; i++) { + events.add(Event.parseFromSection(sectionData)); + } + return new SpliceScheduleCommand(events); + } + + // Parcelable implementation. + + @Override + public void writeToParcel(Parcel dest, int flags) { + int eventsSize = events.size(); + dest.writeInt(eventsSize); + for (int i = 0; i < eventsSize; i++) { + events.get(i).writeToParcel(dest); + } + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + + @Override + public SpliceScheduleCommand createFromParcel(Parcel in) { + return new SpliceScheduleCommand(in); + } + + @Override + public SpliceScheduleCommand[] newArray(int size) { + return new SpliceScheduleCommand[size]; + } + + }; + +} diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/scte35/TimeSignalCommand.java b/library/src/main/java/com/google/android/exoplayer2/metadata/scte35/TimeSignalCommand.java new file mode 100644 index 0000000000..c31f4dedc8 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer2/metadata/scte35/TimeSignalCommand.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2016 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.scte35; + +import android.os.Parcel; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.util.ParsableByteArray; + +/** + * Represents a time signal command as defined in SCTE35, Section 9.3.4. + */ +public final class TimeSignalCommand extends SpliceCommand { + + public final long ptsTime; + + private TimeSignalCommand(long ptsTime) { + this.ptsTime = ptsTime; + } + + /* package */ static TimeSignalCommand parseFromSection(ParsableByteArray sectionData, + long ptsAdjustment) { + return new TimeSignalCommand(parseSpliceTime(sectionData, ptsAdjustment)); + } + + /** + * Parses pts_time from splice_time(), defined in Section 9.4.1. Returns {@link C#TIME_UNSET}, if + * time_specified_flag is false. + * + * @param sectionData The section data from which the pts_time is parsed. + * @param ptsAdjustment The pts adjustment provided by the splice info section header. + * @return The pts_time defined by splice_time(), or {@link C#TIME_UNSET}, if time_specified_flag + * is false. + */ + /* package */ static long parseSpliceTime(ParsableByteArray sectionData, long ptsAdjustment) { + long firstByte = sectionData.readUnsignedByte(); + long ptsTime = C.TIME_UNSET; + if ((firstByte & 0x80) != 0 /* time_specified_flag */) { + // See SCTE35 9.2.1 for more information about pts adjustment. + ptsTime = (firstByte & 0x01) << 32 | sectionData.readUnsignedInt(); + ptsTime += ptsAdjustment; + ptsTime &= 0x1FFFFFFFFL; + } + return ptsTime; + } + + // Parcelable implementation. + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(ptsTime); + } + + public static final Creator CREATOR = + new Creator() { + + @Override + public TimeSignalCommand createFromParcel(Parcel in) { + return new TimeSignalCommand(in.readLong()); + } + + @Override + public TimeSignalCommand[] newArray(int size) { + return new TimeSignalCommand[size]; + } + + }; + +} diff --git a/library/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java b/library/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java index fb91b17cc0..641dd670af 100644 --- a/library/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java +++ b/library/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java @@ -74,6 +74,7 @@ public final class MimeTypes { public static final String APPLICATION_RAWCC = BASE_TYPE_APPLICATION + "/x-rawcc"; public static final String APPLICATION_VOBSUB = BASE_TYPE_APPLICATION + "/vobsub"; public static final String APPLICATION_PGS = BASE_TYPE_APPLICATION + "/pgs"; + public static final String APPLICATION_SCTE35 = BASE_TYPE_APPLICATION + "/x-scte35"; private MimeTypes() {}