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