content: /*
   * Copyright 2020 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.jpeg;
  
  import androidx.annotation.Nullable;
  import com.google.android.exoplayer2.C;
  import com.google.android.exoplayer2.metadata.mp4.MotionPhotoMetadata;
  import com.google.android.exoplayer2.util.MimeTypes;
  import java.util.List;
  
  /** Describes the layout and metadata of a motion photo file. */
  /* package */ final class MotionPhotoDescription {
  
    /** Describes a media item in the motion photo. */
    public static final class ContainerItem {
      /** The MIME type of the media item. */
      public final String mime;
      /** The application-specific meaning of the media item. */
      public final String semantic;
      /**
       * The positive integer length in bytes of the media item, or 0 for primary media items and
       * secondary media items that share their resource with the preceding media item.
       */
      public final long length;
      /**
       * The number of bytes of additional padding between the end of the primary media item and the
       * start of the next media item. 0 for secondary media items.
       */
      public final long padding;
  
      public ContainerItem(String mime, String semantic, long length, long padding) {
        this.mime = mime;
        this.semantic = semantic;
        this.length = length;
        this.padding = padding;
      }
    }
  
    /**
     * The presentation timestamp of the primary media item, in microseconds, or {@link C#TIME_UNSET}
     * if unknown.
     */
    public final long photoPresentationTimestampUs;
    /**
     * The media items represented by the motion photo file, in order. The primary media item is
     * listed first, followed by any secondary media items.
     */
    public final List<ContainerItem> items;
  
    public MotionPhotoDescription(long photoPresentationTimestampUs, List<ContainerItem> items) {
      this.photoPresentationTimestampUs = photoPresentationTimestampUs;
      this.items = items;
    }
  
    /**
     * Returns the {@link MotionPhotoMetadata} for the motion photo represented by this instance, or
     * {@code null} if there wasn't enough information to derive the metadata.
     *
     * @param motionPhotoLength The length of the motion photo file, in bytes.
     * @return The motion photo metadata, or {@code null}.
     */
    @Nullable
    public MotionPhotoMetadata getMotionPhotoMetadata(long motionPhotoLength) {
      if (items.size() < 2) {
        // We need a primary item (photo) and at least one secondary item (video).
        return null;
      }
      // Iterate backwards through the items to find the earlier video in the list. If we find a video
      // item with length zero, we need to keep scanning backwards to find the preceding item with
      // non-zero length, which is the item that contains the video data.
      long photoStartPosition = C.POSITION_UNSET;
      long photoLength = C.LENGTH_UNSET;
      long mp4StartPosition = C.POSITION_UNSET;
      long mp4Length = C.LENGTH_UNSET;
      boolean itemContainsMp4 = false;
      long itemStartPosition = motionPhotoLength;
      long itemEndPosition = motionPhotoLength;
      for (int i = items.size() - 1; i >= 0; i--) {
        MotionPhotoDescription.ContainerItem item = items.get(i);
        itemContainsMp4 |= MimeTypes.VIDEO_MP4.equals(item.mime);
        itemEndPosition = itemStartPosition;
        if (i == 0) {
          // Padding is only applied for the primary item.
          itemStartPosition = 0;
          itemEndPosition -= item.padding;
        } else {
          itemStartPosition -= item.length;
        }
        if (itemContainsMp4 && itemStartPosition != itemEndPosition) {
          mp4StartPosition = itemStartPosition;
          mp4Length = itemEndPosition - itemStartPosition;
          // Reset in case there's another video earlier in the list.
          itemContainsMp4 = false;
        }
        if (i == 0) {
          photoStartPosition = itemStartPosition;
          photoLength = itemEndPosition;
        }
      }
      if (mp4StartPosition == C.POSITION_UNSET
          || mp4Length == C.LENGTH_UNSET
          || photoStartPosition == C.POSITION_UNSET
          || photoLength == C.LENGTH_UNSET) {
        return null;
      }
      return new MotionPhotoMetadata(
          photoStartPosition, photoLength, photoPresentationTimestampUs, mp4StartPosition, mp4Length);
    }
  }
  