VideoSize.java

/*
 * Copyright 2021 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 androidx.media3.common;

import static java.lang.annotation.ElementType.TYPE_USE;

import android.os.Bundle;
import androidx.annotation.FloatRange;
import androidx.annotation.IntDef;
import androidx.annotation.IntRange;
import androidx.annotation.Nullable;
import androidx.media3.common.util.UnstableApi;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/** Represents the video size. */
public final class VideoSize implements Bundleable {

  private static final int DEFAULT_WIDTH = 0;
  private static final int DEFAULT_HEIGHT = 0;
  private static final int DEFAULT_UNAPPLIED_ROTATION_DEGREES = 0;
  private static final float DEFAULT_PIXEL_WIDTH_HEIGHT_RATIO = 1F;

  public static final VideoSize UNKNOWN = new VideoSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);

  /** The video width in pixels, 0 when unknown. */
  @IntRange(from = 0)
  public final int width;

  /** The video height in pixels, 0 when unknown. */
  @IntRange(from = 0)
  public final int height;

  /**
   * Clockwise rotation in degrees that the application should apply for the video for it to be
   * rendered in the correct orientation.
   *
   * <p>Is 0 if unknown or if no rotation is needed.
   *
   * <p>Player should apply video rotation internally, in which case unappliedRotationDegrees is 0.
   * But when a player can't apply the rotation, for example before API level 21, the unapplied
   * rotation is reported by this field for application to handle.
   *
   * <p>Applications that use {@link android.view.TextureView} can apply the rotation by calling
   * {@link android.view.TextureView#setTransform}.
   */
  @IntRange(from = 0, to = 359)
  public final int unappliedRotationDegrees;

  /**
   * The width to height ratio of each pixel, 1 if unknown.
   *
   * <p>For the normal case of square pixels this will be equal to 1.0. Different values are
   * indicative of anamorphic content.
   */
  @FloatRange(from = 0, fromInclusive = false)
  public final float pixelWidthHeightRatio;

  /**
   * Creates a VideoSize without unapplied rotation or anamorphic content.
   *
   * @param width The video width in pixels.
   * @param height The video height in pixels.
   */
  @UnstableApi
  public VideoSize(@IntRange(from = 0) int width, @IntRange(from = 0) int height) {
    this(width, height, DEFAULT_UNAPPLIED_ROTATION_DEGREES, DEFAULT_PIXEL_WIDTH_HEIGHT_RATIO);
  }

  /**
   * Creates a VideoSize.
   *
   * @param width The video width in pixels.
   * @param height The video height in pixels.
   * @param unappliedRotationDegrees Clockwise rotation in degrees that the application should apply
   *     for the video for it to be rendered in the correct orientation. See {@link
   *     #unappliedRotationDegrees}.
   * @param pixelWidthHeightRatio The width to height ratio of each pixel. For the normal case of
   *     square pixels this will be equal to 1.0. Different values are indicative of anamorphic
   *     content.
   */
  @UnstableApi
  public VideoSize(
      @IntRange(from = 0) int width,
      @IntRange(from = 0) int height,
      @IntRange(from = 0, to = 359) int unappliedRotationDegrees,
      @FloatRange(from = 0, fromInclusive = false) float pixelWidthHeightRatio) {
    this.width = width;
    this.height = height;
    this.unappliedRotationDegrees = unappliedRotationDegrees;
    this.pixelWidthHeightRatio = pixelWidthHeightRatio;
  }

  @Override
  public boolean equals(@Nullable Object obj) {
    if (this == obj) {
      return true;
    }
    if (obj instanceof VideoSize) {
      VideoSize other = (VideoSize) obj;
      return width == other.width
          && height == other.height
          && unappliedRotationDegrees == other.unappliedRotationDegrees
          && pixelWidthHeightRatio == other.pixelWidthHeightRatio;
    }
    return false;
  }

  @Override
  public int hashCode() {
    int result = 7;
    result = 31 * result + width;
    result = 31 * result + height;
    result = 31 * result + unappliedRotationDegrees;
    result = 31 * result + Float.floatToRawIntBits(pixelWidthHeightRatio);
    return result;
  }

  // Bundleable implementation.
  @Documented
  @Retention(RetentionPolicy.SOURCE)
  @Target(TYPE_USE)
  @IntDef({
    FIELD_WIDTH,
    FIELD_HEIGHT,
    FIELD_UNAPPLIED_ROTATION_DEGREES,
    FIELD_PIXEL_WIDTH_HEIGHT_RATIO,
  })
  private @interface FieldNumber {}

  private static final int FIELD_WIDTH = 0;
  private static final int FIELD_HEIGHT = 1;
  private static final int FIELD_UNAPPLIED_ROTATION_DEGREES = 2;
  private static final int FIELD_PIXEL_WIDTH_HEIGHT_RATIO = 3;

  @UnstableApi
  @Override
  public Bundle toBundle() {
    Bundle bundle = new Bundle();
    bundle.putInt(keyForField(FIELD_WIDTH), width);
    bundle.putInt(keyForField(FIELD_HEIGHT), height);
    bundle.putInt(keyForField(FIELD_UNAPPLIED_ROTATION_DEGREES), unappliedRotationDegrees);
    bundle.putFloat(keyForField(FIELD_PIXEL_WIDTH_HEIGHT_RATIO), pixelWidthHeightRatio);
    return bundle;
  }

  @UnstableApi
  public static final Creator<VideoSize> CREATOR =
      bundle -> {
        int width = bundle.getInt(keyForField(FIELD_WIDTH), DEFAULT_WIDTH);
        int height = bundle.getInt(keyForField(FIELD_HEIGHT), DEFAULT_HEIGHT);
        int unappliedRotationDegrees =
            bundle.getInt(
                keyForField(FIELD_UNAPPLIED_ROTATION_DEGREES), DEFAULT_UNAPPLIED_ROTATION_DEGREES);
        float pixelWidthHeightRatio =
            bundle.getFloat(
                keyForField(FIELD_PIXEL_WIDTH_HEIGHT_RATIO), DEFAULT_PIXEL_WIDTH_HEIGHT_RATIO);
        return new VideoSize(width, height, unappliedRotationDegrees, pixelWidthHeightRatio);
      };

  private static String keyForField(@FieldNumber int field) {
    return Integer.toString(field, Character.MAX_RADIX);
  }
}