/* * 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 org.mozilla.thirdparty.com.google.android.exoplayer2.metadata.scte35; import android.os.Parcel; import android.os.Parcelable; import org.mozilla.thirdparty.com.google.android.exoplayer2.C; import org.mozilla.thirdparty.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 { /** * The splice event id. */ public final long spliceEventId; /** * True if the event with id {@link #spliceEventId} has been canceled. */ public final boolean spliceEventCancelIndicator; /** * If true, the splice event is an opportunity to exit from the network feed. If false, * indicates an opportunity to return to the network feed. */ public final boolean outOfNetworkIndicator; /** * Whether the splice mode is program splice mode, whereby all PIDs/components are to be * spliced. If false, splicing is done per PID/component. */ public final boolean programSpliceFlag; /** * Represents the time of the signaled splice event as the number of seconds since 00 hours UTC, * January 6th, 1980, with the count of intervening leap seconds included. */ public final long utcSpliceTime; /** * If {@link #programSpliceFlag} is false, a non-empty list containing the * {@link ComponentSplice}s. Otherwise, an empty list. */ public final List componentSpliceList; /** * If {@link #breakDurationUs} is not {@link C#TIME_UNSET}, defines whether * {@link #breakDurationUs} should be used to know when to return to the network feed. If * {@link #breakDurationUs} is {@link C#TIME_UNSET}, the value is undefined. */ public final boolean autoReturn; /** * The duration of the splice in microseconds, or {@link C#TIME_UNSET} if no duration is * present. */ public final long breakDurationUs; /** * The unique program id as defined in SCTE35, Section 9.3.2. */ public final int uniqueProgramId; /** * Holds the value of {@code avail_num} as defined in SCTE35, Section 9.3.2. */ public final int availNum; /** * Holds the value of {@code avails_expected} as defined in SCTE35, Section 9.3.2. */ public final int availsExpected; private Event(long spliceEventId, boolean spliceEventCancelIndicator, boolean outOfNetworkIndicator, boolean programSpliceFlag, List componentSpliceList, long utcSpliceTime, boolean autoReturn, long breakDurationUs, 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.breakDurationUs = breakDurationUs; 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.breakDurationUs = 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 breakDurationUs = 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; long breakDuration90khz = ((firstByte & 0x01) << 32) | sectionData.readUnsignedInt(); breakDurationUs = breakDuration90khz * 1000 / 90; } uniqueProgramId = sectionData.readUnsignedShort(); availNum = sectionData.readUnsignedByte(); availsExpected = sectionData.readUnsignedByte(); } return new Event(spliceEventId, spliceEventCancelIndicator, outOfNetworkIndicator, programSpliceFlag, componentSplices, utcSpliceTime, autoReturn, breakDurationUs, 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(breakDurationUs); 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); } } /** * The list of scheduled events. */ 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]; } }; }