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