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