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