MediaItem.java revision ce03445c320042ca144bd4efd8c0356bb7775f9d
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 source file type 178 */ 179 public abstract int getFileType(); 180 181 /** 182 * @return Get the native width of the media item 183 */ 184 public abstract int getWidth(); 185 186 /** 187 * @return Get the native height of the media item 188 */ 189 public abstract int getHeight(); 190 191 /** 192 * Get aspect ratio of the source media item. 193 * 194 * @return the aspect ratio as described in MediaProperties. 195 * MediaProperties.ASPECT_RATIO_UNDEFINED if aspect ratio is not 196 * supported as in MediaProperties 197 */ 198 public abstract int getAspectRatio(); 199 200 /** 201 * Add the specified effect to this media item. 202 * 203 * Note that certain types of effects cannot be applied to video and to 204 * image media items. For example in certain implementation a Ken Burns 205 * implementation cannot be applied to video media item. 206 * 207 * This method invalidates transition video clips if the 208 * effect overlaps with the beginning and/or the end transition. 209 * 210 * @param effect The effect to apply 211 * @throws IllegalStateException if a preview or an export is in progress 212 * @throws IllegalArgumentException if the effect start and/or duration are 213 * invalid or if the effect cannot be applied to this type of media 214 * item or if the effect id is not unique across all the Effects 215 * added. 216 */ 217 public void addEffect(Effect effect) { 218 if (effect.getMediaItem() != this) { 219 throw new IllegalArgumentException("Media item mismatch"); 220 } 221 222 if (mEffects.contains(effect)) { 223 throw new IllegalArgumentException("Effect already exists: " + effect.getId()); 224 } 225 226 if (effect.getStartTime() + effect.getDuration() > getTimelineDuration()) { 227 throw new IllegalArgumentException( 228 "Effect start time + effect duration > media clip duration"); 229 } 230 231 mEffects.add(effect); 232 invalidateTransitions(effect); 233 } 234 235 /** 236 * Remove the effect with the specified id. 237 * 238 * This method invalidates a transition video clip if the effect overlaps 239 * with a transition. 240 * 241 * @param effectId The id of the effect to be removed 242 * 243 * @return The effect that was removed 244 * @throws IllegalStateException if a preview or an export is in progress 245 */ 246 public Effect removeEffect(String effectId) { 247 for (Effect effect : mEffects) { 248 if (effect.getId().equals(effectId)) { 249 mEffects.remove(effect); 250 invalidateTransitions(effect); 251 return effect; 252 } 253 } 254 255 return null; 256 } 257 258 /** 259 * Find the effect with the specified id 260 * 261 * @param effectId The effect id 262 * 263 * @return The effect with the specified id (null if it does not exist) 264 */ 265 public Effect getEffect(String effectId) { 266 for (Effect effect : mEffects) { 267 if (effect.getId().equals(effectId)) { 268 return effect; 269 } 270 } 271 272 return null; 273 } 274 275 /** 276 * Get the list of effects. 277 * 278 * @return the effects list. If no effects exist an empty list will be returned. 279 */ 280 public List<Effect> getAllEffects() { 281 return mEffects; 282 } 283 284 /** 285 * Add an overlay to the storyboard. This method invalidates a transition 286 * video clip if the overlay overlaps with a transition. 287 * 288 * @param overlay The overlay to add 289 * @throws IllegalStateException if a preview or an export is in progress or 290 * if the overlay id is not unique across all the overlays 291 * added or if the bitmap is not specified or if the dimensions of 292 * the bitmap do not match the dimensions of the media item 293 */ 294 public void addOverlay(Overlay overlay) { 295 if (overlay.getMediaItem() != this) { 296 throw new IllegalArgumentException("Media item mismatch"); 297 } 298 299 if (mOverlays.contains(overlay)) { 300 throw new IllegalArgumentException("Overlay already exists: " + overlay.getId()); 301 } 302 303 if (overlay.getStartTime() + overlay.getDuration() > getTimelineDuration()) { 304 throw new IllegalArgumentException( 305 "Overlay start time + overlay duration > media clip duration"); 306 } 307 308 if (overlay instanceof OverlayFrame) { 309 final OverlayFrame frame = (OverlayFrame)overlay; 310 final Bitmap bitmap = frame.getBitmap(); 311 if (bitmap == null) { 312 throw new IllegalArgumentException("Overlay bitmap not specified"); 313 } 314 315 final int scaledWidth, scaledHeight; 316 if (this instanceof MediaVideoItem) { 317 scaledWidth = getWidth(); 318 scaledHeight = getHeight(); 319 } else { 320 scaledWidth = ((MediaImageItem)this).getScaledWidth(); 321 scaledHeight = ((MediaImageItem)this).getScaledHeight(); 322 } 323 324 // The dimensions of the overlay bitmap must be the same as the 325 // media item dimensions 326 if (bitmap.getWidth() != scaledWidth || bitmap.getHeight() != scaledHeight) { 327 throw new IllegalArgumentException( 328 "Bitmap dimensions must match media item dimensions"); 329 } 330 } else { 331 throw new IllegalArgumentException("Overlay not supported"); 332 } 333 334 mOverlays.add(overlay); 335 invalidateTransitions(overlay); 336 } 337 338 /** 339 * Remove the overlay with the specified id. 340 * 341 * This method invalidates a transition video clip if the overlay overlaps 342 * with a transition. 343 * 344 * @param overlayId The id of the overlay to be removed 345 * 346 * @return The overlay that was removed 347 * @throws IllegalStateException if a preview or an export is in progress 348 */ 349 public Overlay removeOverlay(String overlayId) { 350 for (Overlay overlay : mOverlays) { 351 if (overlay.getId().equals(overlayId)) { 352 mOverlays.remove(overlay); 353 if (overlay instanceof OverlayFrame) { 354 ((OverlayFrame)overlay).invalidate(); 355 } 356 invalidateTransitions(overlay); 357 return overlay; 358 } 359 } 360 361 return null; 362 } 363 364 /** 365 * Find the overlay with the specified id 366 * 367 * @param overlayId The overlay id 368 * 369 * @return The overlay with the specified id (null if it does not exist) 370 */ 371 public Overlay getOverlay(String overlayId) { 372 for (Overlay overlay : mOverlays) { 373 if (overlay.getId().equals(overlayId)) { 374 return overlay; 375 } 376 } 377 378 return null; 379 } 380 381 /** 382 * Get the list of overlays associated with this media item 383 * 384 * Note that if any overlay source files are not accessible anymore, 385 * this method will still provide the full list of overlays. 386 * 387 * @return The list of overlays. If no overlays exist an empty list will 388 * be returned. 389 */ 390 public List<Overlay> getAllOverlays() { 391 return mOverlays; 392 } 393 394 /** 395 * Create a thumbnail at specified time in a video stream in Bitmap format 396 * 397 * @param width width of the thumbnail in pixels 398 * @param height height of the thumbnail in pixels 399 * @param timeMs The time in the source video file at which the thumbnail is 400 * requested (even if trimmed). 401 * 402 * @return The thumbnail as a Bitmap. 403 * 404 * @throws IOException if a file error occurs 405 * @throws IllegalArgumentException if time is out of video duration 406 */ 407 public abstract Bitmap getThumbnail(int width, int height, long timeMs) throws IOException; 408 409 /** 410 * Get the array of Bitmap thumbnails between start and end. 411 * 412 * @param width width of the thumbnail in pixels 413 * @param height height of the thumbnail in pixels 414 * @param startMs The start of time range in milliseconds 415 * @param endMs The end of the time range in milliseconds 416 * @param thumbnailCount The thumbnail count 417 * 418 * @return The array of Bitmaps 419 * 420 * @throws IOException if a file error occurs 421 */ 422 public abstract Bitmap[] getThumbnailList(int width, int height, long startMs, long endMs, 423 int thumbnailCount) throws IOException; 424 425 /* 426 * {@inheritDoc} 427 */ 428 @Override 429 public boolean equals(Object object) { 430 if (!(object instanceof MediaItem)) { 431 return false; 432 } 433 return mUniqueId.equals(((MediaItem)object).mUniqueId); 434 } 435 436 /* 437 * {@inheritDoc} 438 */ 439 @Override 440 public int hashCode() { 441 return mUniqueId.hashCode(); 442 } 443 444 /** 445 * Invalidate the start and end transitions if necessary 446 * 447 * @param effect The effect that was added or removed 448 */ 449 void invalidateTransitions(Effect effect) { 450 // Check if the effect overlaps with the beginning and end transitions 451 if (mBeginTransition != null) { 452 if (effect.getStartTime() < mBeginTransition.getDuration()) { 453 mBeginTransition.invalidate(); 454 } 455 } 456 457 if (mEndTransition != null) { 458 if (effect.getStartTime() + effect.getDuration() > getTimelineDuration() 459 - mEndTransition.getDuration()) { 460 mEndTransition.invalidate(); 461 } 462 } 463 } 464 465 /** 466 * Invalidate the start and end transitions if necessary 467 * 468 * @param overlay The effect that was added or removed 469 */ 470 void invalidateTransitions(Overlay overlay) { 471 // Check if the overlay overlaps with the beginning and end transitions 472 if (mBeginTransition != null) { 473 if (overlay.getStartTime() < mBeginTransition.getDuration()) { 474 mBeginTransition.invalidate(); 475 } 476 } 477 478 if (mEndTransition != null) { 479 if (overlay.getStartTime() + overlay.getDuration() > getTimelineDuration() 480 - mEndTransition.getDuration()) { 481 mEndTransition.invalidate(); 482 } 483 } 484 } 485 486 /** 487 * Adjust the duration transitions. 488 */ 489 protected void adjustTransitions() { 490 // Check if the duration of transitions need to be adjusted 491 if (mBeginTransition != null) { 492 final long maxDurationMs = mBeginTransition.getMaximumDuration(); 493 if (mBeginTransition.getDuration() > maxDurationMs) { 494 mBeginTransition.setDuration(maxDurationMs); 495 } 496 } 497 498 if (mEndTransition != null) { 499 final long maxDurationMs = mEndTransition.getMaximumDuration(); 500 if (mEndTransition.getDuration() > maxDurationMs) { 501 mEndTransition.setDuration(maxDurationMs); 502 } 503 } 504 } 505 506 /** 507 * Adjust the start time and/or duration of effects. 508 */ 509 protected void adjustEffects() { 510 final List<Effect> effects = getAllEffects(); 511 for (Effect effect : effects) { 512 // Adjust the start time if necessary 513 final long effectStartTimeMs; 514 if (effect.getStartTime() > getTimelineDuration()) { 515 effectStartTimeMs = 0; 516 } else { 517 effectStartTimeMs = effect.getStartTime(); 518 } 519 520 // Adjust the duration if necessary 521 final long effectDurationMs; 522 if (effectStartTimeMs + effect.getDuration() > getTimelineDuration()) { 523 effectDurationMs = getTimelineDuration() - effectStartTimeMs; 524 } else { 525 effectDurationMs = effect.getDuration(); 526 } 527 528 if (effectStartTimeMs != effect.getStartTime() || 529 effectDurationMs != effect.getDuration()) { 530 effect.setStartTimeAndDuration(effectStartTimeMs, effectDurationMs); 531 } 532 } 533 } 534 535 /** 536 * Adjust the start time and/or duration of overlays. 537 */ 538 protected void adjustOverlays() { 539 final List<Overlay> overlays = getAllOverlays(); 540 for (Overlay overlay : overlays) { 541 // Adjust the start time if necessary 542 final long overlayStartTimeMs; 543 if (overlay.getStartTime() > getTimelineDuration()) { 544 overlayStartTimeMs = 0; 545 } else { 546 overlayStartTimeMs = overlay.getStartTime(); 547 } 548 549 // Adjust the duration if necessary 550 final long overlayDurationMs; 551 if (overlayStartTimeMs + overlay.getDuration() > getTimelineDuration()) { 552 overlayDurationMs = getTimelineDuration() - overlayStartTimeMs; 553 } else { 554 overlayDurationMs = overlay.getDuration(); 555 } 556 557 if (overlayStartTimeMs != overlay.getStartTime() || 558 overlayDurationMs != overlay.getDuration()) { 559 overlay.setStartTimeAndDuration(overlayStartTimeMs, overlayDurationMs); 560 } 561 } 562 } 563} 564