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