1 /* 2 * Copyright 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.camera.video; 18 19 import android.location.Location; 20 21 import androidx.annotation.IntRange; 22 import androidx.core.util.Preconditions; 23 24 import org.jspecify.annotations.NonNull; 25 import org.jspecify.annotations.Nullable; 26 27 /** 28 * Options for configuring output destination for generating a recording. 29 * 30 * <p>A {@link PendingRecording} can be generated with {@link Recorder#prepareRecording} for 31 * different types of output destination, such as {@link FileOutputOptions}, 32 * {@link FileDescriptorOutputOptions} and {@link MediaStoreOutputOptions}. 33 * 34 * @see FileOutputOptions 35 * @see FileDescriptorOutputOptions 36 * @see MediaStoreOutputOptions 37 */ 38 public abstract class OutputOptions { 39 40 /** Represents an unbound file size. */ 41 public static final int FILE_SIZE_UNLIMITED = 0; 42 43 /** Represents an unlimited duration. */ 44 public static final int DURATION_UNLIMITED = 0; 45 46 private final OutputOptionsInternal mOutputOptionsInternal; 47 OutputOptions(@onNull OutputOptionsInternal outputOptionsInternal)48 OutputOptions(@NonNull OutputOptionsInternal outputOptionsInternal) { 49 mOutputOptionsInternal = outputOptionsInternal; 50 } 51 52 /** 53 * Gets the limit for the file size in bytes. 54 * 55 * @return the file size limit in bytes or {@link #FILE_SIZE_UNLIMITED} if it's unlimited. 56 */ 57 @IntRange(from = 0) getFileSizeLimit()58 public long getFileSizeLimit() { 59 return mOutputOptionsInternal.getFileSizeLimit(); 60 } 61 62 /** 63 * Returns a {@link Location} object representing the geographic location where the video was 64 * recorded. 65 * 66 * @return the location object or {@code null} if no location was set. 67 */ getLocation()68 public @Nullable Location getLocation() { 69 return mOutputOptionsInternal.getLocation(); 70 } 71 72 /** 73 * Gets the limit for the video duration in milliseconds. 74 * 75 * @return the video duration limit in milliseconds or {@link #DURATION_UNLIMITED} if it's 76 * unlimited. 77 */ 78 @IntRange(from = 0) getDurationLimitMillis()79 public long getDurationLimitMillis() { 80 return mOutputOptionsInternal.getDurationLimitMillis(); 81 } 82 83 /** 84 * The builder of the {@link OutputOptions}. 85 */ 86 @SuppressWarnings("unchecked") // Cast to type B 87 abstract static class Builder<T extends OutputOptions, B> { 88 89 final OutputOptionsInternal.Builder<?> mRootInternalBuilder; 90 Builder(OutputOptionsInternal.@onNull Builder<?> builder)91 Builder(OutputOptionsInternal.@NonNull Builder<?> builder) { 92 mRootInternalBuilder = builder; 93 // Apply default value 94 mRootInternalBuilder.setFileSizeLimit(FILE_SIZE_UNLIMITED); 95 mRootInternalBuilder.setDurationLimitMillis(DURATION_UNLIMITED); 96 } 97 98 /** 99 * Sets the limit for the file length in bytes. 100 * 101 * <p>When used with {@link Recorder} to generate recording, if the specified file size 102 * limit is reached while the recording is being recorded, the recording will be 103 * finalized with {@link VideoRecordEvent.Finalize#ERROR_FILE_SIZE_LIMIT_REACHED}. 104 * 105 * <p>If not set or set with zero, the file size will be {@linkplain #FILE_SIZE_UNLIMITED 106 * unlimited}. If set with a negative value, an {@link IllegalArgumentException} will be 107 * thrown. 108 * 109 * @param fileSizeLimitBytes the file size limit in bytes. 110 * @return this Builder. 111 * @throws IllegalArgumentException if the specified file size limit is negative. 112 */ setFileSizeLimit(@ntRangefrom = 0) long fileSizeLimitBytes)113 public @NonNull B setFileSizeLimit(@IntRange(from = 0) long fileSizeLimitBytes) { 114 Preconditions.checkArgument(fileSizeLimitBytes >= 0, "The specified file size limit " 115 + "can't be negative."); 116 mRootInternalBuilder.setFileSizeLimit(fileSizeLimitBytes); 117 return (B) this; 118 } 119 120 /** 121 * Sets the limit for the video duration in milliseconds. 122 * 123 * <p>When used to generate recording with {@link Recorder}, if the specified duration 124 * limit is reached while the recording is being recorded, the recording will be 125 * finalized with {@link VideoRecordEvent.Finalize#ERROR_DURATION_LIMIT_REACHED}. 126 * 127 * <p>If not set or set with zero, the duration will be {@linkplain #DURATION_UNLIMITED 128 * unlimited}. If set with a negative value, an {@link IllegalArgumentException} will be 129 * thrown. 130 * 131 * @param durationLimitMillis the video duration limit in milliseconds. 132 * @return this Builder. 133 * @throws IllegalArgumentException if the specified duration limit is negative. 134 */ setDurationLimitMillis(@ntRangefrom = 0) long durationLimitMillis)135 public @NonNull B setDurationLimitMillis(@IntRange(from = 0) long durationLimitMillis) { 136 Preconditions.checkArgument(durationLimitMillis >= 0, "The specified duration limit " 137 + "can't be negative."); 138 mRootInternalBuilder.setDurationLimitMillis(durationLimitMillis); 139 return (B) this; 140 } 141 142 /** 143 * Sets a {@link Location} object representing a geographic location where the video was 144 * recorded. 145 * 146 * <p>When use with {@link Recorder}, the geographic location is stored in udta box if the 147 * output format is MP4, and is ignored for other formats. The geographic location is 148 * stored according to ISO-6709 standard. 149 * 150 * <p>If {@code null}, no location information will be saved with the video. Default 151 * value is {@code null}. 152 * 153 * @throws IllegalArgumentException if the latitude of the location is not in the range 154 * {@code [-90, 90]} or the longitude of the location is not in the range {@code [-180, 155 * 180]}. 156 */ setLocation(@ullable Location location)157 public @NonNull B setLocation(@Nullable Location location) { 158 if (location != null) { 159 Preconditions.checkArgument( 160 location.getLatitude() >= -90 && location.getLatitude() <= 90, 161 "Latitude must be in the range [-90, 90]"); 162 Preconditions.checkArgument( 163 location.getLongitude() >= -180 && location.getLongitude() <= 180, 164 "Longitude must be in the range [-180, 180]"); 165 } 166 mRootInternalBuilder.setLocation(location); 167 return (B) this; 168 } 169 170 /** 171 * Builds the {@link OutputOptions} instance. 172 */ build()173 abstract @NonNull T build(); 174 } 175 176 // A base class of a @AutoValue class 177 abstract static class OutputOptionsInternal { 178 179 @IntRange(from = 0) getFileSizeLimit()180 abstract long getFileSizeLimit(); 181 182 @IntRange(from = 0) getDurationLimitMillis()183 abstract long getDurationLimitMillis(); 184 getLocation()185 abstract @Nullable Location getLocation(); 186 187 // A base class of a @AutoValue.Builder class 188 @SuppressWarnings("NullableProblems") // Nullable problem in AutoValue generated class 189 abstract static class Builder<B> { 190 setFileSizeLimit(@ntRangefrom = 0) long fileSizeLimitBytes)191 abstract @NonNull B setFileSizeLimit(@IntRange(from = 0) long fileSizeLimitBytes); 192 setDurationLimitMillis( @ntRangefrom = 0) long durationLimitMillis)193 abstract @NonNull B setDurationLimitMillis( 194 @IntRange(from = 0) long durationLimitMillis); 195 setLocation(@ullable Location location)196 abstract @NonNull B setLocation(@Nullable Location location); 197 build()198 abstract @NonNull OutputOptionsInternal build(); 199 } 200 } 201 } 202