MediaItem.java revision f8b04868e6fa1f7ca9c1fe3f39ae1f46a530b6df
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 (effect.getMediaItem() != this) { 209 throw new IllegalArgumentException("Media item mismatch"); 210 } 211 212 if (mEffects.contains(effect)) { 213 throw new IllegalArgumentException("Effect already exists: " + effect.getId()); 214 } 215 216 if (effect.getStartTime() + effect.getDuration() > getTimelineDuration()) { 217 throw new IllegalArgumentException( 218 "Effect start time + effect duration > media clip duration"); 219 } 220 221 mEffects.add(effect); 222 invalidateTransitions(effect); 223 } 224 225 /** 226 * Remove the effect with the specified id. 227 * 228 * This method invalidates a transition video clip if the effect overlaps 229 * with a transition. 230 * 231 * @param effectId The id of the effect to be removed 232 * 233 * @return The effect that was removed 234 * @throws IllegalStateException if a preview or an export is in progress 235 */ 236 public Effect removeEffect(String effectId) { 237 for (Effect effect : mEffects) { 238 if (effect.getId().equals(effectId)) { 239 mEffects.remove(effect); 240 invalidateTransitions(effect); 241 return effect; 242 } 243 } 244 245 return null; 246 } 247 248 /** 249 * Find the effect with the specified id 250 * 251 * @param effectId The effect id 252 * 253 * @return The effect with the specified id (null if it does not exist) 254 */ 255 public Effect getEffect(String effectId) { 256 for (Effect effect : mEffects) { 257 if (effect.getId().equals(effectId)) { 258 return effect; 259 } 260 } 261 262 return null; 263 } 264 265 /** 266 * Get the list of effects. 267 * 268 * @return the effects list. If no effects exist an empty list will be returned. 269 */ 270 public List<Effect> getAllEffects() { 271 return mEffects; 272 } 273 274 /** 275 * Add an overlay to the storyboard. This method invalidates a transition 276 * video clip if the overlay overlaps with a transition. 277 * 278 * @param overlay The overlay to add 279 * @throws IllegalStateException if a preview or an export is in progress or 280 * if the overlay id is not unique across all the overlays 281 * added or if the bitmap is not specified or if the dimensions of 282 * the bitmap do not match the dimensions of the media item 283 */ 284 public void addOverlay(Overlay overlay) { 285 if (overlay.getMediaItem() != this) { 286 throw new IllegalArgumentException("Media item mismatch"); 287 } 288 289 if (mOverlays.contains(overlay)) { 290 throw new IllegalArgumentException("Overlay already exists: " + overlay.getId()); 291 } 292 293 if (overlay.getStartTime() + overlay.getDuration() > getTimelineDuration()) { 294 throw new IllegalArgumentException( 295 "Overlay start time + overlay duration > media clip duration"); 296 } 297 298 if (overlay instanceof OverlayFrame) { 299 final OverlayFrame frame = (OverlayFrame)overlay; 300 final Bitmap bitmap = frame.getBitmap(); 301 if (bitmap == null) { 302 throw new IllegalArgumentException("Overlay bitmap not specified"); 303 } 304 305 final int scaledWidth, scaledHeight; 306 if (this instanceof MediaVideoItem) { 307 scaledWidth = getWidth(); 308 scaledHeight = getHeight(); 309 } else { 310 scaledWidth = ((MediaImageItem)this).getScaledWidth(); 311 scaledHeight = ((MediaImageItem)this).getScaledHeight(); 312 } 313 314 // The dimensions of the overlay bitmap must be the same as the 315 // media item dimensions 316 if (bitmap.getWidth() != scaledWidth || bitmap.getHeight() != scaledHeight) { 317 throw new IllegalArgumentException( 318 "Bitmap dimensions must match media item dimensions"); 319 } 320 } else { 321 throw new IllegalArgumentException("Overlay not supported"); 322 } 323 324 mOverlays.add(overlay); 325 invalidateTransitions(overlay); 326 } 327 328 /** 329 * Remove the overlay with the specified id. 330 * 331 * This method invalidates a transition video clip if the overlay overlaps 332 * with a transition. 333 * 334 * @param overlayId The id of the overlay to be removed 335 * 336 * @return The overlay that was removed 337 * @throws IllegalStateException if a preview or an export is in progress 338 */ 339 public Overlay removeOverlay(String overlayId) { 340 for (Overlay overlay : mOverlays) { 341 if (overlay.getId().equals(overlayId)) { 342 mOverlays.remove(overlay); 343 if (overlay instanceof OverlayFrame) { 344 ((OverlayFrame)overlay).invalidate(); 345 } 346 invalidateTransitions(overlay); 347 return overlay; 348 } 349 } 350 351 return null; 352 } 353 354 /** 355 * Find the overlay with the specified id 356 * 357 * @param overlayId The overlay id 358 * 359 * @return The overlay with the specified id (null if it does not exist) 360 */ 361 public Overlay getOverlay(String overlayId) { 362 for (Overlay overlay : mOverlays) { 363 if (overlay.getId().equals(overlayId)) { 364 return overlay; 365 } 366 } 367 368 return null; 369 } 370 371 /** 372 * Get the list of overlays associated with this media item 373 * 374 * Note that if any overlay source files are not accessible anymore, 375 * this method will still provide the full list of overlays. 376 * 377 * @return The list of overlays. If no overlays exist an empty list will 378 * be returned. 379 */ 380 public List<Overlay> getAllOverlays() { 381 return mOverlays; 382 } 383 384 /** 385 * Create a thumbnail at specified time in a video stream in Bitmap format 386 * 387 * @param width width of the thumbnail in pixels 388 * @param height height of the thumbnail in pixels 389 * @param timeMs The time in the source video file at which the thumbnail is 390 * requested (even if trimmed). 391 * 392 * @return The thumbnail as a Bitmap. 393 * 394 * @throws IOException if a file error occurs 395 * @throws IllegalArgumentException if time is out of video duration 396 */ 397 public abstract Bitmap getThumbnail(int width, int height, long timeMs) throws IOException; 398 399 /** 400 * Get the array of Bitmap thumbnails between start and end. 401 * 402 * @param width width of the thumbnail in pixels 403 * @param height height of the thumbnail in pixels 404 * @param startMs The start of time range in milliseconds 405 * @param endMs The end of the time range in milliseconds 406 * @param thumbnailCount The thumbnail count 407 * 408 * @return The array of Bitmaps 409 * 410 * @throws IOException if a file error occurs 411 */ 412 public abstract Bitmap[] getThumbnailList(int width, int height, long startMs, long endMs, 413 int thumbnailCount) throws IOException; 414 415 /* 416 * {@inheritDoc} 417 */ 418 @Override 419 public boolean equals(Object object) { 420 if (!(object instanceof MediaItem)) { 421 return false; 422 } 423 return mUniqueId.equals(((MediaItem)object).mUniqueId); 424 } 425 426 /* 427 * {@inheritDoc} 428 */ 429 @Override 430 public int hashCode() { 431 return mUniqueId.hashCode(); 432 } 433 434 /** 435 * Invalidate the start and end transitions if necessary 436 * 437 * @param effect The effect that was added or removed 438 */ 439 void invalidateTransitions(Effect effect) { 440 // Check if the effect overlaps with the beginning and end transitions 441 if (mBeginTransition != null) { 442 if (effect.getStartTime() < mBeginTransition.getDuration()) { 443 mBeginTransition.invalidate(); 444 } 445 } 446 447 if (mEndTransition != null) { 448 if (effect.getStartTime() + effect.getDuration() > getTimelineDuration() 449 - mEndTransition.getDuration()) { 450 mEndTransition.invalidate(); 451 } 452 } 453 } 454 455 /** 456 * Invalidate the start and end transitions if necessary 457 * 458 * @param overlay The effect that was added or removed 459 */ 460 void invalidateTransitions(Overlay overlay) { 461 // Check if the overlay overlaps with the beginning and end transitions 462 if (mBeginTransition != null) { 463 if (overlay.getStartTime() < mBeginTransition.getDuration()) { 464 mBeginTransition.invalidate(); 465 } 466 } 467 468 if (mEndTransition != null) { 469 if (overlay.getStartTime() + overlay.getDuration() > getTimelineDuration() 470 - mEndTransition.getDuration()) { 471 mEndTransition.invalidate(); 472 } 473 } 474 } 475} 476