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