MediaItem.java revision 4b66f7a53f1b5a77c3ca1c12f256cdef078c1799
1/* 2 * Copyright (C) 2010 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 17package android.media.videoeditor; 18 19import java.io.IOException; 20import java.util.ArrayList; 21import java.util.List; 22 23import android.graphics.Bitmap; 24 25/** 26 * This abstract class describes the base class for any MediaItem. Objects are 27 * defined with a file path as a source data. 28 * {@hide} 29s */ 30public abstract class MediaItem { 31 // A constant which can be used to specify the end of the file (instead of 32 // providing the actual duration of the media item). 33 public final static int END_OF_FILE = -1; 34 35 // Rendering modes 36 /** 37 * When using the RENDERING_MODE_BLACK_BORDER rendering mode video frames 38 * are resized by preserving the aspect ratio until the movie matches one of 39 * the dimensions of the output movie. The areas outside the resized video 40 * clip are rendered black. 41 */ 42 public static final int RENDERING_MODE_BLACK_BORDER = 0; 43 /** 44 * When using the RENDERING_MODE_STRETCH rendering mode video frames are 45 * stretched horizontally or vertically to match the current aspect ratio of 46 * the movie. 47 */ 48 public static final int RENDERING_MODE_STRETCH = 1; 49 50 51 // The unique id of the MediaItem 52 private final String mUniqueId; 53 54 // The name of the file associated with the MediaItem 55 protected final String mFilename; 56 57 // List of effects 58 private final List<Effect> mEffects; 59 60 // List of overlays 61 private final List<Overlay> mOverlays; 62 63 // The rendering mode 64 private int mRenderingMode; 65 66 // Beginning and end transitions 67 protected Transition mBeginTransition; 68 protected Transition mEndTransition; 69 70 /** 71 * Constructor 72 * 73 * @param mediaItemId The MediaItem id 74 * @param filename name of the media file. 75 * @param renderingMode The rendering mode 76 * 77 * @throws IOException if file is not found 78 * @throws IllegalArgumentException if a capability such as file format is not 79 * supported the exception object contains the unsupported 80 * capability 81 */ 82 protected MediaItem(String mediaItemId, String filename, int renderingMode) throws IOException { 83 mUniqueId = mediaItemId; 84 mFilename = filename; 85 mRenderingMode = renderingMode; 86 mEffects = new ArrayList<Effect>(); 87 mOverlays = new ArrayList<Overlay>(); 88 mBeginTransition = null; 89 mEndTransition = null; 90 } 91 92 /** 93 * @return The id of the media item 94 */ 95 public String getId() { 96 return mUniqueId; 97 } 98 99 /** 100 * @return The media source file name 101 */ 102 public String getFilename() { 103 return mFilename; 104 } 105 106 /** 107 * If aspect ratio of the MediaItem is different from the aspect ratio of 108 * the editor then this API controls the rendering mode. 109 * 110 * @param renderingMode rendering mode. It is one of: 111 * {@link #RENDERING_MODE_BLACK_BORDER}, 112 * {@link #RENDERING_MODE_STRETCH} 113 */ 114 public void setRenderingMode(int renderingMode) { 115 mRenderingMode = renderingMode; 116 if (mBeginTransition != null) { 117 mBeginTransition.invalidate(); 118 } 119 120 if (mEndTransition != null) { 121 mEndTransition.invalidate(); 122 } 123 } 124 125 /** 126 * @return The rendering mode 127 */ 128 public int getRenderingMode() { 129 return mRenderingMode; 130 } 131 132 /** 133 * @param transition The beginning transition 134 */ 135 void setBeginTransition(Transition transition) { 136 mBeginTransition = transition; 137 } 138 139 /** 140 * @return The begin transition 141 */ 142 public Transition getBeginTransition() { 143 return mBeginTransition; 144 } 145 146 /** 147 * @param transition The end transition 148 */ 149 void setEndTransition(Transition transition) { 150 mEndTransition = transition; 151 } 152 153 /** 154 * @return The end transition 155 */ 156 public Transition getEndTransition() { 157 return mEndTransition; 158 } 159 160 /** 161 * @return The timeline duration. This is the actual duration in the 162 * timeline (trimmed duration) 163 */ 164 public abstract long getTimelineDuration(); 165 166 /** 167 * @return The source file type 168 */ 169 public abstract int getFileType(); 170 171 /** 172 * @return Get the native width of the media item 173 */ 174 public abstract int getWidth(); 175 176 /** 177 * @return Get the native height of the media item 178 */ 179 public abstract int getHeight(); 180 181 /** 182 * Get aspect ratio of the source media item. 183 * 184 * @return the aspect ratio as described in MediaProperties. 185 * MediaProperties.ASPECT_RATIO_UNDEFINED if aspect ratio is not 186 * supported as in MediaProperties 187 */ 188 public abstract int getAspectRatio(); 189 190 /** 191 * Add the specified effect to this media item. 192 * 193 * Note that certain types of effects cannot be applied to video and to 194 * image media items. For example in certain implementation a Ken Burns 195 * implementation cannot be applied to video media item. 196 * 197 * This method invalidates transition video clips if the 198 * effect overlaps with the beginning and/or the end transition. 199 * 200 * @param effect The effect to apply 201 * @throws IllegalStateException if a preview or an export is in progress 202 * @throws IllegalArgumentException if the effect start and/or duration are 203 * invalid or if the effect cannot be applied to this type of media 204 * item or if the effect id is not unique across all the Effects 205 * added. 206 */ 207 public void addEffect(Effect effect) { 208 if (mEffects.contains(effect)) { 209 throw new IllegalArgumentException("Effect already exists: " + effect.getId()); 210 } 211 212 if (effect.getStartTime() + effect.getDuration() > getTimelineDuration()) { 213 throw new IllegalArgumentException( 214 "Effect start time + effect duration > media clip duration"); 215 } 216 217 mEffects.add(effect); 218 invalidateTransitions(effect); 219 } 220 221 /** 222 * Remove the effect with the specified id. 223 * 224 * This method invalidates a transition video clip if the effect overlaps 225 * with a transition. 226 * 227 * @param effectId The id of the effect to be removed 228 * 229 * @return The effect that was removed 230 * @throws IllegalStateException if a preview or an export is in progress 231 */ 232 public Effect removeEffect(String effectId) { 233 for (Effect effect : mEffects) { 234 if (effect.getId().equals(effectId)) { 235 mEffects.remove(effect); 236 invalidateTransitions(effect); 237 return effect; 238 } 239 } 240 241 return null; 242 } 243 244 /** 245 * Find the effect with the specified id 246 * 247 * @param effectId The effect id 248 * 249 * @return The effect with the specified id (null if it does not exist) 250 */ 251 public Effect getEffect(String effectId) { 252 for (Effect effect : mEffects) { 253 if (effect.getId().equals(effectId)) { 254 return effect; 255 } 256 } 257 258 return null; 259 } 260 261 /** 262 * Get the list of effects. 263 * 264 * @return the effects list. If no effects exist an empty list will be returned. 265 */ 266 public List<Effect> getAllEffects() { 267 return mEffects; 268 } 269 270 /** 271 * Add an overlay to the storyboard. This method invalidates a transition 272 * video clip if the overlay overlaps with a transition. 273 * 274 * @param overlay The overlay to add 275 * @throws IllegalStateException if a preview or an export is in progress or 276 * if the overlay id is not unique across all the overlays 277 * added or if the bitmap is not specified or if the dimensions of 278 * the bitmap do not match the dimensions of the media item 279 */ 280 public void addOverlay(Overlay overlay) { 281 if (mOverlays.contains(overlay)) { 282 throw new IllegalArgumentException("Overlay already exists: " + overlay.getId()); 283 } 284 285 if (overlay.getStartTime() + overlay.getDuration() > getTimelineDuration()) { 286 throw new IllegalArgumentException( 287 "Overlay start time + overlay duration > media clip duration"); 288 } 289 290 if (overlay instanceof OverlayFrame) { 291 final OverlayFrame frame = (OverlayFrame)overlay; 292 final Bitmap bitmap = frame.getBitmap(); 293 if (bitmap == null) { 294 throw new IllegalArgumentException("Overlay bitmap not specified"); 295 } 296 297 final int scaledWidth, scaledHeight; 298 if (this instanceof MediaVideoItem) { 299 scaledWidth = getWidth(); 300 scaledHeight = getHeight(); 301 } else { 302 scaledWidth = ((MediaImageItem)this).getScaledWidth(); 303 scaledHeight = ((MediaImageItem)this).getScaledHeight(); 304 } 305 306 // The dimensions of the overlay bitmap must be the same as the 307 // media item dimensions 308 if (bitmap.getWidth() != scaledWidth || bitmap.getHeight() != scaledHeight) { 309 throw new IllegalArgumentException( 310 "Bitmap dimensions must match media item dimensions"); 311 } 312 } else { 313 throw new IllegalArgumentException("Overlay not supported"); 314 } 315 316 mOverlays.add(overlay); 317 invalidateTransitions(overlay); 318 } 319 320 /** 321 * Remove the overlay with the specified id. 322 * 323 * This method invalidates a transition video clip if the overlay overlaps 324 * with a transition. 325 * 326 * @param overlayId The id of the overlay to be removed 327 * 328 * @return The overlay that was removed 329 * @throws IllegalStateException if a preview or an export is in progress 330 */ 331 public Overlay removeOverlay(String overlayId) { 332 for (Overlay overlay : mOverlays) { 333 if (overlay.getId().equals(overlayId)) { 334 mOverlays.remove(overlay); 335 if (overlay instanceof OverlayFrame) { 336 ((OverlayFrame)overlay).invalidate(); 337 } 338 invalidateTransitions(overlay); 339 return overlay; 340 } 341 } 342 343 return null; 344 } 345 346 /** 347 * Find the overlay with the specified id 348 * 349 * @param overlayId The overlay id 350 * 351 * @return The overlay with the specified id (null if it does not exist) 352 */ 353 public Overlay getOverlay(String overlayId) { 354 for (Overlay overlay : mOverlays) { 355 if (overlay.getId().equals(overlayId)) { 356 return overlay; 357 } 358 } 359 360 return null; 361 } 362 363 /** 364 * Get the list of overlays associated with this media item 365 * 366 * Note that if any overlay source files are not accessible anymore, 367 * this method will still provide the full list of overlays. 368 * 369 * @return The list of overlays. If no overlays exist an empty list will 370 * be returned. 371 */ 372 public List<Overlay> getAllOverlays() { 373 return mOverlays; 374 } 375 376 /** 377 * Create a thumbnail at specified time in a video stream in Bitmap format 378 * 379 * @param width width of the thumbnail in pixels 380 * @param height height of the thumbnail in pixels 381 * @param timeMs The time in the source video file at which the thumbnail is 382 * requested (even if trimmed). 383 * 384 * @return The thumbnail as a Bitmap. 385 * 386 * @throws IOException if a file error occurs 387 * @throws IllegalArgumentException if time is out of video duration 388 */ 389 public abstract Bitmap getThumbnail(int width, int height, long timeMs) throws IOException; 390 391 /** 392 * Get the array of Bitmap thumbnails between start and end. 393 * 394 * @param width width of the thumbnail in pixels 395 * @param height height of the thumbnail in pixels 396 * @param startMs The start of time range in milliseconds 397 * @param endMs The end of the time range in milliseconds 398 * @param thumbnailCount The thumbnail count 399 * 400 * @return The array of Bitmaps 401 * 402 * @throws IOException if a file error occurs 403 */ 404 public abstract Bitmap[] getThumbnailList(int width, int height, long startMs, long endMs, 405 int thumbnailCount) throws IOException; 406 407 /* 408 * {@inheritDoc} 409 */ 410 @Override 411 public boolean equals(Object object) { 412 if (!(object instanceof MediaItem)) { 413 return false; 414 } 415 return mUniqueId.equals(((MediaItem)object).mUniqueId); 416 } 417 418 /* 419 * {@inheritDoc} 420 */ 421 @Override 422 public int hashCode() { 423 return mUniqueId.hashCode(); 424 } 425 426 /** 427 * Invalidate the start and end transitions if necessary 428 * 429 * @param effect The effect that was added or removed 430 */ 431 private void invalidateTransitions(Effect effect) { 432 // Check if the effect overlaps with the beginning and end transitions 433 if (mBeginTransition != null) { 434 if (effect.getStartTime() < mBeginTransition.getDuration()) { 435 mBeginTransition.invalidate(); 436 } 437 } 438 439 if (mEndTransition != null) { 440 if (effect.getStartTime() + effect.getDuration() > getTimelineDuration() 441 - mEndTransition.getDuration()) { 442 mEndTransition.invalidate(); 443 } 444 } 445 } 446 447 /** 448 * Invalidate the start and end transitions if necessary 449 * 450 * @param overlay The effect that was added or removed 451 */ 452 private void invalidateTransitions(Overlay overlay) { 453 // Check if the overlay overlaps with the beginning and end transitions 454 if (mBeginTransition != null) { 455 if (overlay.getStartTime() < mBeginTransition.getDuration()) { 456 mBeginTransition.invalidate(); 457 } 458 } 459 460 if (mEndTransition != null) { 461 if (overlay.getStartTime() + overlay.getDuration() > getTimelineDuration() 462 - mEndTransition.getDuration()) { 463 mEndTransition.invalidate(); 464 } 465 } 466 } 467} 468