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