content: /*
   * 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 {
  
      /**
       * 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<ComponentSplice> 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<ComponentSplice> 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<ComponentSplice> 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<ComponentSplice> 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<Event> events;
  
    private SpliceScheduleCommand(List<Event> events) {
      this.events = Collections.unmodifiableList(events);
    }
  
    private SpliceScheduleCommand(Parcel in) {
      int eventsSize = in.readInt();
      ArrayList<Event> 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<Event> 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<SpliceScheduleCommand> CREATOR =
        new Parcelable.Creator<SpliceScheduleCommand>() {
  
      @Override
      public SpliceScheduleCommand createFromParcel(Parcel in) {
        return new SpliceScheduleCommand(in);
      }
  
      @Override
      public SpliceScheduleCommand[] newArray(int size) {
        return new SpliceScheduleCommand[size];
      }
  
    };
  
  }
  