VideoEditorImpl.java revision eea200a96a561d8eb9e26831aa6aaf7ddf6ad5a5
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
17
18package android.media.videoeditor;
19
20import java.io.File;
21import java.io.FileInputStream;
22import java.io.FileNotFoundException;
23import java.io.FileOutputStream;
24import java.io.IOException;
25import java.io.StringWriter;
26import java.util.ArrayList;
27import java.util.Iterator;
28import java.util.List;
29import java.util.Map;
30import java.util.concurrent.Semaphore;
31
32import org.xmlpull.v1.XmlPullParser;
33import org.xmlpull.v1.XmlPullParserException;
34import org.xmlpull.v1.XmlSerializer;
35
36import android.graphics.Rect;
37import android.util.Log;
38import android.util.Xml;
39import android.view.Surface;
40import android.view.SurfaceHolder;
41
42import android.graphics.Bitmap;
43
44/**
45 * The VideoEditor implementation {@hide}
46 */
47public class VideoEditorImpl implements VideoEditor {
48    /*
49     *  Logging
50     */
51    private static final String TAG = "VideoEditorImpl";
52
53    /*
54     *  The project filename
55     */
56    private static final String PROJECT_FILENAME = "videoeditor.xml";
57    /*
58     *  Semaphore to control preview calls
59     */
60    final Semaphore mPreviewSemaphore = new Semaphore(1, true);
61
62    /*
63     *  Semaphore to control export calls
64     */
65    final Semaphore mExportSemaphore = new Semaphore(1, true);
66
67    /*
68     *  XML tags
69     */
70    private static final String TAG_PROJECT = "project";
71    private static final String TAG_MEDIA_ITEMS = "media_items";
72    private static final String TAG_MEDIA_ITEM = "media_item";
73    private static final String TAG_TRANSITIONS = "transitions";
74    private static final String TAG_TRANSITION = "transition";
75    private static final String TAG_OVERLAYS = "overlays";
76    private static final String TAG_OVERLAY = "overlay";
77    private static final String TAG_OVERLAY_USER_ATTRIBUTES =
78                                                      "overlay_user_attributes";
79    private static final String TAG_EFFECTS = "effects";
80    private static final String TAG_EFFECT = "effect";
81    private static final String TAG_AUDIO_TRACKS = "audio_tracks";
82    private static final String TAG_AUDIO_TRACK = "audio_track";
83
84    private static final String ATTR_ID = "id";
85    private static final String ATTR_FILENAME = "filename";
86    private static final String ATTR_AUDIO_WAVEFORM_FILENAME = "waveform";
87    private static final String ATTR_RENDERING_MODE = "rendering_mode";
88    private static final String ATTR_ASPECT_RATIO = "aspect_ratio";
89    private static final String ATTR_PREVIEW_PREPARE = "preview_prepare_invalid";
90    private static final String ATTR_REGENERATE_PCM = "regeneratePCMFlag";
91    private static final String ATTR_TYPE = "type";
92    private static final String ATTR_DURATION = "duration";
93    private static final String ATTR_START_TIME = "start_time";
94    private static final String ATTR_BEGIN_TIME = "begin_time";
95    private static final String ATTR_END_TIME = "end_time";
96    private static final String ATTR_VOLUME = "volume";
97    private static final String ATTR_BEHAVIOR = "behavior";
98    private static final String ATTR_DIRECTION = "direction";
99    private static final String ATTR_BLENDING = "blending";
100    private static final String ATTR_INVERT = "invert";
101    private static final String ATTR_MASK = "mask";
102    private static final String ATTR_BEFORE_MEDIA_ITEM_ID = "before_media_item";
103    private static final String ATTR_AFTER_MEDIA_ITEM_ID = "after_media_item";
104    private static final String ATTR_COLOR_EFFECT_TYPE = "color_type";
105    private static final String ATTR_COLOR_EFFECT_VALUE = "color_value";
106    private static final String ATTR_START_RECT_LEFT = "start_l";
107    private static final String ATTR_START_RECT_TOP = "start_t";
108    private static final String ATTR_START_RECT_RIGHT = "start_r";
109    private static final String ATTR_START_RECT_BOTTOM = "start_b";
110    private static final String ATTR_END_RECT_LEFT = "end_l";
111    private static final String ATTR_END_RECT_TOP = "end_t";
112    private static final String ATTR_END_RECT_RIGHT = "end_r";
113    private static final String ATTR_END_RECT_BOTTOM = "end_b";
114    private static final String ATTR_LOOP = "loop";
115    private static final String ATTR_MUTED = "muted";
116    private static final String ATTR_DUCK_ENABLED = "ducking_enabled";
117    private static final String ATTR_DUCK_THRESHOLD = "ducking_threshold";
118    private static final String ATTR_DUCKED_TRACK_VOLUME = "ducking_volume";
119    private static final String ATTR_GENERATED_IMAGE_CLIP =
120                                                         "generated_image_clip";
121    private static final String ATTR_GENERATED_TRANSITION_CLIP =
122                                                    "generated_transition_clip";
123    private static final String ATTR_IS_TRANSITION_GENERATED =
124                                                      "is_transition_generated";
125    private static final String ATTR_OVERLAY_RGB_FILENAME =
126                                                         "overlay_rgb_filename";
127    private static final String ATTR_OVERLAY_FRAME_WIDTH =
128                                                          "overlay_frame_width";
129    private static final String ATTR_OVERLAY_FRAME_HEIGHT =
130                                                         "overlay_frame_height";
131
132    /*
133     *  Instance variables
134     */
135    private long mDurationMs;
136    private final String mProjectPath;
137    private final List<MediaItem> mMediaItems = new ArrayList<MediaItem>();
138    private final List<AudioTrack> mAudioTracks = new ArrayList<AudioTrack>();
139    private final List<Transition> mTransitions = new ArrayList<Transition>();
140    private int mAspectRatio;
141
142    /*
143     * Private Object for calling native Methods via MediaArtistNativeHelper
144     */
145    private MediaArtistNativeHelper mMANativeHelper;
146    private VideoEditor veObject = null;
147    private boolean mPreviewInProgress = false;
148
149    /**
150     * Constructor
151     *
152     * @param projectPath - The path where the VideoEditor stores all files
153     *        related to the project
154     */
155    public VideoEditorImpl(String projectPath) throws IOException {
156
157        mMANativeHelper = new MediaArtistNativeHelper(projectPath, this);
158        mProjectPath = projectPath;
159        final File projectXml = new File(projectPath, PROJECT_FILENAME);
160        if (projectXml.exists()) {
161            try {
162                load();
163            } catch (Exception ex) {
164                ex.printStackTrace();
165                throw new IOException(ex.toString());
166            }
167        } else {
168            mAspectRatio = MediaProperties.ASPECT_RATIO_16_9;
169            mDurationMs = 0;
170        }
171    }
172
173    /*
174     * @return The MediaArtistNativeHelper object
175     */
176    MediaArtistNativeHelper getNativeContext() {
177        return mMANativeHelper;
178    }
179
180    /*
181     * {@inheritDoc}
182     */
183    public synchronized void addAudioTrack(AudioTrack audioTrack) {
184        if (audioTrack == null) {
185            throw new IllegalArgumentException("Audio Track is null");
186        }
187        if (mAudioTracks.size() == 1) {
188            throw new IllegalArgumentException("No more tracks can be added");
189        }
190
191        /*
192         * Add the audio track to AudioTrack list
193         */
194        mAudioTracks.add(audioTrack);
195
196        /*
197         * Form the audio PCM file path
198         */
199        String audioTrackPCMFilePath = String.format(mProjectPath + "/"
200                    + "AudioPcm" + audioTrack.getId() + ".pcm");
201
202        /*
203         * Create PCM only if not generated in previous session
204         */
205        if (new File(audioTrackPCMFilePath).exists())
206        {
207            mMANativeHelper.setAudioflag(false);
208        }
209        mMANativeHelper.setGeneratePreview(true);
210    }
211
212    /*
213     * {@inheritDoc}
214     */
215    public synchronized void addMediaItem(MediaItem mediaItem) {
216        /*
217         * Validate Media Item
218         */
219        if (mediaItem == null) {
220            throw new IllegalArgumentException("Media item is null");
221        }
222        /*
223         * Add the Media item to MediaItem list
224         */
225        if (mMediaItems.contains(mediaItem)) {
226            throw new IllegalArgumentException("Media item already exists: " +
227            mediaItem.getId());
228        }
229
230        /*
231         *  Invalidate the end transition if necessary
232         */
233        final int mediaItemsCount = mMediaItems.size();
234        if ( mediaItemsCount > 0) {
235            removeTransitionAfter(mediaItemsCount - 1);
236        }
237
238        /*
239         *  Add the new media item
240         */
241        mMediaItems.add(mediaItem);
242
243        computeTimelineDuration();
244        mMANativeHelper.setGeneratePreview(true);
245        /*
246         *  Generate project thumbnail only from first media Item on storyboard
247         */
248        if (mMediaItems.size() == 1) {
249            generateProjectThumbnail();
250        }
251    }
252
253
254    /*
255     * {@inheritDoc}
256     */
257    public synchronized void addTransition(Transition transition) {
258        if (transition == null) {
259            throw new IllegalArgumentException("Null Transition");
260        }
261        final MediaItem beforeMediaItem = transition.getBeforeMediaItem();
262        final MediaItem afterMediaItem = transition.getAfterMediaItem();
263        /*
264         * Check if the MediaItems are in sequence
265         */
266        if (mMediaItems == null) {
267            throw new IllegalArgumentException("No media items are added");
268        }
269        if ((afterMediaItem != null) &&  (beforeMediaItem != null)) {
270            int afterMediaItemIndex = mMediaItems.indexOf(afterMediaItem);
271            int beforeMediaItemIndex = mMediaItems.indexOf(beforeMediaItem);
272
273
274            if ((afterMediaItemIndex == -1) || (beforeMediaItemIndex == -1)) {
275                throw new IllegalArgumentException
276                    ("Either of the mediaItem is not found in the list");
277            }
278            if (afterMediaItemIndex != (beforeMediaItemIndex - 1) ) {
279                throw new IllegalArgumentException("MediaItems are not in sequence");
280            }
281         }
282
283        mTransitions.add(transition);
284        /*
285         *  Cross reference the transitions
286         */
287        if (afterMediaItem != null) {
288            /*
289             *  If a transition already exists at the specified position then
290             *  invalidate it.
291             */
292            if (afterMediaItem.getEndTransition() != null) {
293                afterMediaItem.getEndTransition().invalidate();
294                mTransitions.remove(afterMediaItem.getEndTransition());
295            }
296            afterMediaItem.setEndTransition(transition);
297        }
298
299        if (beforeMediaItem != null) {
300            /*
301             *  If a transition already exists at the specified position then
302             *  invalidate it.
303             */
304            if (beforeMediaItem.getBeginTransition() != null) {
305                beforeMediaItem.getBeginTransition().invalidate();
306                mTransitions.remove(beforeMediaItem.getBeginTransition());
307            }
308            beforeMediaItem.setBeginTransition(transition);
309        }
310
311        computeTimelineDuration();
312        mMANativeHelper.setGeneratePreview(true);
313    }
314
315    /*
316     * {@inheritDoc}
317     */
318    public void cancelExport(String filename) {
319        if (mMANativeHelper != null && filename != null) {
320            mMANativeHelper.stop(filename);
321        }
322    }
323
324    /*
325     * {@inheritDoc}
326     */
327    public void export(String filename, int height, int bitrate,
328                       int audioCodec, int videoCodec,
329                       ExportProgressListener listener) throws IOException {
330
331        switch (audioCodec) {
332            case MediaProperties.ACODEC_AAC_LC:
333                break;
334            case MediaProperties.ACODEC_AMRNB:
335                break;
336
337            default :
338                String message = "Unsupported audio codec type " + audioCodec;
339                throw new IllegalArgumentException(message);
340        }
341
342        switch (videoCodec) {
343            case MediaProperties.VCODEC_H263:
344                break;
345            case MediaProperties.VCODEC_H264BP:
346                break;
347            case MediaProperties.VCODEC_MPEG4:
348                break;
349
350            default :
351                String message = "Unsupported video codec type " + videoCodec;
352                throw new IllegalArgumentException(message);
353        }
354
355        export(filename, height, bitrate, listener);
356    }
357
358    /*
359     * {@inheritDoc}
360     */
361    public void export(String filename, int height, int bitrate,
362                       ExportProgressListener listener) throws IOException {
363        if ( filename == null) {
364            throw new IllegalArgumentException("export: filename is null");
365        }
366        File tempPathFile = new File(filename);
367        if (tempPathFile == null) {
368            throw new IOException(filename + "can not be created");
369        }
370        if (mMediaItems.size() == 0) {
371            throw new IllegalStateException("No MediaItems added");
372        }
373
374        switch (height) {
375            case MediaProperties.HEIGHT_144:
376                break;
377            case MediaProperties.HEIGHT_360:
378                break;
379            case MediaProperties.HEIGHT_480:
380                break;
381            case MediaProperties.HEIGHT_720:
382                break;
383
384            default:
385                String message = "Unsupported height value " + height;
386                throw new IllegalArgumentException(message);
387        }
388        switch (bitrate) {
389            case MediaProperties.BITRATE_28K:
390                break;
391            case MediaProperties.BITRATE_40K:
392                break;
393            case MediaProperties.BITRATE_64K:
394                break;
395            case MediaProperties.BITRATE_96K:
396                break;
397            case MediaProperties.BITRATE_128K:
398                break;
399            case MediaProperties.BITRATE_192K:
400                break;
401            case MediaProperties.BITRATE_256K:
402                break;
403            case MediaProperties.BITRATE_384K:
404                break;
405            case MediaProperties.BITRATE_512K:
406                break;
407            case MediaProperties.BITRATE_800K:
408                break;
409            case MediaProperties.BITRATE_2M:
410                break;
411            case MediaProperties.BITRATE_5M:
412                break;
413            case MediaProperties.BITRATE_8M:
414                break;
415
416            default:
417                final String message = "Unsupported bitrate value " + bitrate;
418                throw new IllegalArgumentException(message);
419        }
420
421        try {
422            mExportSemaphore.acquire();
423            mMANativeHelper.export(filename, mProjectPath, height,bitrate,
424                               mMediaItems, mTransitions, mAudioTracks,
425                               listener);
426        } catch (InterruptedException  ex) {
427            Log.e(TAG, "Sem acquire NOT successful in export");
428        } finally {
429            mExportSemaphore.release();
430        }
431    }
432
433    /*
434     * {@inheritDoc}
435     */
436    public void generatePreview(MediaProcessingProgressListener listener) {
437        boolean semAcquireDone = false;
438        try {
439            mPreviewSemaphore.acquire();
440            semAcquireDone = true;
441            mMANativeHelper.setGeneratePreview(true);
442            if ((mMediaItems.size() > 0) || (mAudioTracks.size() > 0)) {
443                mMANativeHelper.previewStoryBoard(mMediaItems, mTransitions,
444                        mAudioTracks, listener);
445            }
446        } catch (InterruptedException  ex) {
447            Log.e(TAG, "Sem acquire NOT successful in previewStoryBoard");
448        } finally {
449            if (semAcquireDone) {
450                mPreviewSemaphore.release();
451            }
452        }
453    }
454
455    /*
456     * {@inheritDoc}
457     */
458    public List<AudioTrack> getAllAudioTracks() {
459        return mAudioTracks;
460    }
461
462    /*
463     * {@inheritDoc}
464     */
465    public List<MediaItem> getAllMediaItems() {
466        return mMediaItems;
467    }
468
469    /*
470     * {@inheritDoc}
471     */
472    public List<Transition> getAllTransitions() {
473        return mTransitions;
474    }
475
476    /*
477     * {@inheritDoc}
478     */
479    public int getAspectRatio() {
480        return mAspectRatio;
481    }
482
483    /*
484     * {@inheritDoc}
485     */
486    public AudioTrack getAudioTrack(String audioTrackId) {
487        for (AudioTrack at : mAudioTracks) {
488            if (at.getId().equals(audioTrackId)) {
489                return at;
490            }
491        }
492        return null;
493    }
494
495    /*
496     * {@inheritDoc}
497     */
498    public long getDuration() {
499        /**
500         *  Since MediaImageItem can change duration we need to compute the
501         *  duration here
502         */
503        computeTimelineDuration();
504        return mDurationMs;
505    }
506
507    /*
508     * Force updates the timeline duration
509     */
510    void updateTimelineDuration() {
511        computeTimelineDuration();
512    }
513
514    /*
515     * {@inheritDoc}
516     */
517    public synchronized MediaItem getMediaItem(String mediaItemId) {
518        for (MediaItem mediaItem : mMediaItems) {
519            if (mediaItem.getId().equals(mediaItemId)) {
520                return mediaItem;
521            }
522        }
523        return null;
524    }
525
526    /*
527     * {@inheritDoc}
528     */
529    public String getPath() {
530        return mProjectPath;
531    }
532
533    /*
534     * {@inheritDoc}
535     */
536    public Transition getTransition(String transitionId) {
537        for (Transition transition : mTransitions) {
538            if (transition.getId().equals(transitionId)) {
539                return transition;
540            }
541        }
542        return null;
543    }
544
545    /*
546     * {@inheritDoc}
547     */
548    public synchronized void insertAudioTrack(AudioTrack audioTrack,
549                                              String afterAudioTrackId) {
550        if (mAudioTracks.size() == 1) {
551            throw new IllegalArgumentException("No more tracks can be added");
552        }
553
554        if (afterAudioTrackId == null) {
555            mAudioTracks.add(0, audioTrack);
556        } else {
557            final int audioTrackCount = mAudioTracks.size();
558            for (int i = 0; i < audioTrackCount; i++) {
559                AudioTrack at = mAudioTracks.get(i);
560                if (at.getId().equals(afterAudioTrackId)) {
561                    mAudioTracks.add(i + 1, audioTrack);
562                    return;
563                }
564            }
565            throw new IllegalArgumentException("AudioTrack not found: "
566                                                           + afterAudioTrackId);
567        }
568        mMANativeHelper.setGeneratePreview(true);
569    }
570
571    /*
572     * {@inheritDoc}
573     */
574    public synchronized void insertMediaItem(MediaItem mediaItem,
575                                             String afterMediaItemId) {
576        if (mMediaItems.contains(mediaItem)) {
577            throw new IllegalArgumentException("Media item already exists: "
578                                                           + mediaItem.getId());
579        }
580
581        if (afterMediaItemId == null) {
582            if (mMediaItems.size() > 0) {
583                /**
584                 *  Invalidate the transition at the beginning of the timeline
585                 */
586                removeTransitionBefore(0);
587            }
588            mMediaItems.add(0, mediaItem);
589            computeTimelineDuration();
590            generateProjectThumbnail();
591        } else {
592            final int mediaItemCount = mMediaItems.size();
593            for (int i = 0; i < mediaItemCount; i++) {
594                final MediaItem mi = mMediaItems.get(i);
595                if (mi.getId().equals(afterMediaItemId)) {
596                    /**
597                     *  Invalidate the transition at this position
598                     */
599                    removeTransitionAfter(i);
600                    /**
601                     *  Insert the new media item
602                     */
603                    mMediaItems.add(i + 1, mediaItem);
604                    computeTimelineDuration();
605                    mMANativeHelper.setGeneratePreview(true);
606                    return;
607                }
608            }
609            throw new IllegalArgumentException("MediaItem not found: "
610                                                            + afterMediaItemId);
611        }
612        mMANativeHelper.setGeneratePreview(true);
613    }
614
615    /*
616     * {@inheritDoc}
617     */
618    public synchronized void moveAudioTrack(String audioTrackId,
619                                            String afterAudioTrackId) {
620        throw new IllegalStateException("Not supported");
621    }
622
623    /*
624     * {@inheritDoc}
625     */
626    public synchronized void moveMediaItem(String mediaItemId,
627                                           String afterMediaItemId) {
628        final MediaItem moveMediaItem = removeMediaItem(mediaItemId,true);
629        if (moveMediaItem == null) {
630            throw new IllegalArgumentException("Target MediaItem not found: "
631                                                                 + mediaItemId);
632        }
633
634        if (afterMediaItemId == null) {
635            if (mMediaItems.size() > 0) {
636                /**
637                 *  Invalidate adjacent transitions at the insertion point
638                 */
639                removeTransitionBefore(0);
640
641                /**
642                 *  Insert the media item at the new position
643                 */
644                mMediaItems.add(0, moveMediaItem);
645                computeTimelineDuration();
646                generateProjectThumbnail();
647            } else {
648                throw new IllegalStateException("Cannot move media item (it is the only item)");
649            }
650        } else {
651            final int mediaItemCount = mMediaItems.size();
652            for (int i = 0; i < mediaItemCount; i++) {
653                final MediaItem mi = mMediaItems.get(i);
654                if (mi.getId().equals(afterMediaItemId)) {
655                    /**
656                     *  Invalidate adjacent transitions at the insertion point
657                     */
658                    removeTransitionAfter(i);
659                    /**
660                     *  Insert the media item at the new position
661                     */
662                    mMediaItems.add(i + 1, moveMediaItem);
663                    computeTimelineDuration();
664                    mMANativeHelper.setGeneratePreview(true);
665                    return;
666                }
667            }
668
669            throw new IllegalArgumentException("MediaItem not found: "
670                                                            + afterMediaItemId);
671        }
672        mMANativeHelper.setGeneratePreview(true);
673    }
674
675    /*
676     * {@inheritDoc}
677     */
678    public void release() {
679        stopPreview();
680        mMediaItems.clear();
681        mAudioTracks.clear();
682        mTransitions.clear();
683        mMANativeHelper.releaseNativeHelper();
684        if (mMANativeHelper!= null)
685            mMANativeHelper = null;
686        if (veObject != null)
687            veObject= null;
688    }
689
690    /*
691     * {@inheritDoc}
692     */
693    public synchronized void removeAllMediaItems() {
694        mMediaItems.clear();
695
696        /**
697         *  Invalidate all transitions
698         */
699        for (Transition transition : mTransitions) {
700            transition.invalidate();
701        }
702        mTransitions.clear();
703
704        mDurationMs = 0;
705        mMANativeHelper.setGeneratePreview(true);
706        /**
707         * If a thumbnail already exists, then delete it
708         */
709        if ((new File(mProjectPath + "/" + THUMBNAIL_FILENAME)).exists()) {
710            (new File(mProjectPath + "/" + THUMBNAIL_FILENAME)).delete();
711        }
712
713    }
714
715    /*
716     * {@inheritDoc}
717     */
718    public synchronized AudioTrack removeAudioTrack(String audioTrackId) {
719        final AudioTrack audioTrack = getAudioTrack(audioTrackId);
720        if (audioTrack != null) {
721            mAudioTracks.remove(audioTrack);
722            audioTrack.invalidate();
723            mMANativeHelper.invalidatePcmFile();
724            mMANativeHelper.setAudioflag(true);
725        }
726        else {
727            throw new IllegalArgumentException(" No more audio tracks");
728        }
729        mMANativeHelper.setGeneratePreview(true);
730        return audioTrack;
731    }
732
733    /*
734     * {@inheritDoc}
735     */
736    public synchronized MediaItem removeMediaItem(String mediaItemId) {
737        final String firstItemString = mMediaItems.get(0).getId();
738        final MediaItem mediaItem = getMediaItem(mediaItemId);
739        if (mediaItem != null) {
740            /**
741             *  Remove the media item
742             */
743            mMediaItems.remove(mediaItem);
744            if (mediaItem instanceof MediaImageItem) {
745                ((MediaImageItem)mediaItem).invalidate();
746            }
747            final List<Overlay> overlays = mediaItem.getAllOverlays();
748            if (overlays.size() > 0) {
749                for (Overlay overlay : overlays) {
750                    if (overlay instanceof OverlayFrame) {
751                        final OverlayFrame overlayFrame = (OverlayFrame)overlay;
752                        overlayFrame.invalidate();
753                    }
754                }
755            }
756
757            /**
758             *  Remove the adjacent transitions
759             */
760            removeAdjacentTransitions(mediaItem);
761            computeTimelineDuration();
762        }
763        mMANativeHelper.setGeneratePreview(true);
764        /**
765         * If string equals first mediaItem, then
766         * generate Project thumbail
767         */
768        if (firstItemString.equals(mediaItemId)) {
769            generateProjectThumbnail();
770        }
771
772        if (mediaItem instanceof MediaVideoItem) {
773            /**
774             * Delete the graph file
775             */
776            ((MediaVideoItem)mediaItem).invalidate();
777        }
778        return mediaItem;
779    }
780
781    private synchronized MediaItem removeMediaItem(String mediaItemId,
782                                                   boolean flag) {
783        final String firstItemString = mMediaItems.get(0).getId();
784
785        final MediaItem mediaItem = getMediaItem(mediaItemId);
786        if (mediaItem != null) {
787            /**
788             *  Remove the media item
789             */
790            mMediaItems.remove(mediaItem);
791            /**
792             *  Remove the adjacent transitions
793             */
794            removeAdjacentTransitions(mediaItem);
795            computeTimelineDuration();
796        }
797        mMANativeHelper.setGeneratePreview(true);
798
799        /**
800         * If string equals first mediaItem, then
801         * generate Project thumbail
802         */
803        if (firstItemString.equals(mediaItemId)) {
804            generateProjectThumbnail();
805        }
806        return mediaItem;
807    }
808
809    /*
810     * {@inheritDoc}
811     */
812    public synchronized Transition removeTransition(String transitionId) {
813        final Transition transition = getTransition(transitionId);
814        if (transition == null) {
815            throw new IllegalStateException("Transition not found: "
816                                                                + transitionId);
817        }
818
819        /**
820         *  Remove the transition references
821         */
822        final MediaItem afterMediaItem = transition.getAfterMediaItem();
823        if (afterMediaItem != null) {
824            afterMediaItem.setEndTransition(null);
825        }
826
827        final MediaItem beforeMediaItem = transition.getBeforeMediaItem();
828        if (beforeMediaItem != null) {
829            beforeMediaItem.setBeginTransition(null);
830        }
831
832        mTransitions.remove(transition);
833        transition.invalidate();
834        computeTimelineDuration();
835        mMANativeHelper.setGeneratePreview(true);
836        return transition;
837    }
838
839    /*
840     * {@inheritDoc}
841     */
842    public long renderPreviewFrame(SurfaceHolder surfaceHolder, long timeMs,
843                                    OverlayData overlayData) {
844        long result = 0;
845        int surfaceWidth = 0;
846        int surfaceHeight = 0;
847        Rect frame;
848
849        if (surfaceHolder == null) {
850            throw new IllegalArgumentException("Surface Holder is null");
851        }
852
853        if (timeMs < 0) {
854            throw new IllegalArgumentException("requested time not correct");
855        } else if (timeMs > mDurationMs) {
856            throw new IllegalArgumentException("requested time more than duration");
857        }
858        if (mMANativeHelper != null) {
859            if (mMANativeHelper.mInvalidatePreviewArray) {
860                return -1;
861            }
862        }
863        else {
864            return -1;
865        }
866        boolean semAcquireDone = false;
867
868        try{
869            mPreviewSemaphore.acquire();
870            semAcquireDone = true;
871            Surface surface = surfaceHolder.getSurface();
872            frame = surfaceHolder.getSurfaceFrame();
873            surfaceWidth = frame.width();
874            surfaceHeight = frame.height();
875
876            if (surface == null) {
877                throw new RuntimeException("Surface could not be retrieved from Surface holder");
878            }
879
880            if (!mMANativeHelper.mInvalidatePreviewArray) {
881                if (mMediaItems.size() > 0) {
882                    result = mMANativeHelper.renderPreviewFrame(surface,
883                                             timeMs,surfaceWidth,surfaceHeight, overlayData);
884                }
885                else {
886                    result = 0;
887                }
888            }
889            else {
890                result = -1;
891            }
892
893        } catch (InterruptedException  ex) {
894            Log.e(TAG, "Sem acquire NOT successful in renderPreviewFrame");
895        }
896        finally {
897            if (semAcquireDone) {
898                mPreviewSemaphore.release();
899            }
900        }
901        return result;
902    }
903
904    /**
905     *  the project form XML
906     */
907    private void load() throws FileNotFoundException, XmlPullParserException,
908                               IOException {
909        final File file = new File(mProjectPath, PROJECT_FILENAME);
910        /**
911         *  Load the metadata
912         */
913        final FileInputStream fis = new FileInputStream(file);
914        try {
915            final XmlPullParser parser = Xml.newPullParser();
916            parser.setInput(fis, "UTF-8");
917            int eventType = parser.getEventType();
918            String name;
919            MediaItem currentMediaItem = null;
920            Overlay currentOverlay = null;
921            while (eventType != XmlPullParser.END_DOCUMENT) {
922                switch (eventType) {
923                    case XmlPullParser.START_TAG: {
924                        name = parser.getName();
925                        if (TAG_PROJECT.equals(name)) {
926                            mAspectRatio =
927                                   Integer.parseInt(parser.getAttributeValue("",
928                                   ATTR_ASPECT_RATIO));
929                            final boolean mInvalidatePreviewArray =
930                               Boolean.parseBoolean(parser.getAttributeValue("",
931                                    ATTR_PREVIEW_PREPARE));
932                            mMANativeHelper.setGeneratePreview(mInvalidatePreviewArray);
933
934                            final boolean mRegenPCM =
935                               Boolean.parseBoolean(parser.getAttributeValue("",
936                                    ATTR_REGENERATE_PCM));
937                            mMANativeHelper.setAudioflag(mRegenPCM);
938
939                        } else if (TAG_MEDIA_ITEM.equals(name)) {
940                            final String mediaItemId =
941                                          parser.getAttributeValue("", ATTR_ID);
942                            final String type =
943                                        parser.getAttributeValue("", ATTR_TYPE);
944                            final String filename =
945                                    parser.getAttributeValue("", ATTR_FILENAME);
946                            final int renderingMode =
947                                   Integer.parseInt(parser.getAttributeValue("",
948                                   ATTR_RENDERING_MODE));
949
950                            if (MediaImageItem.class.getSimpleName().equals(type)) {
951                                final long durationMs = Long.parseLong(parser.getAttributeValue("",
952                                        ATTR_DURATION));
953                                currentMediaItem = new MediaImageItem(this, mediaItemId, filename,
954                                        durationMs, renderingMode);
955                            } else if (MediaVideoItem.class.getSimpleName().equals(type)) {
956                                final long beginMs = Long.parseLong(parser.getAttributeValue("",
957                                        ATTR_BEGIN_TIME));
958                                final long endMs = Long.parseLong(parser.getAttributeValue("",
959                                        ATTR_END_TIME));
960                                final int volume = Integer.parseInt(parser.getAttributeValue("",
961                                        ATTR_VOLUME));
962                                final boolean muted = Boolean.parseBoolean(parser.getAttributeValue("",
963                                        ATTR_MUTED));
964                                final String audioWaveformFilename = parser.getAttributeValue("",
965                                        ATTR_AUDIO_WAVEFORM_FILENAME);
966                                currentMediaItem = new MediaVideoItem(this, mediaItemId, filename,
967                                        renderingMode, beginMs, endMs, volume, muted,
968                                        audioWaveformFilename);
969
970                                final long beginTimeMs = Long.parseLong(parser.getAttributeValue("",
971                                        ATTR_BEGIN_TIME));
972                                final long endTimeMs = Long.parseLong(parser.getAttributeValue("",
973                                        ATTR_END_TIME));
974                                ((MediaVideoItem)currentMediaItem).setExtractBoundaries(beginTimeMs,
975                                        endTimeMs);
976
977                                final int volumePercent = Integer.parseInt(parser.getAttributeValue("",
978                                        ATTR_VOLUME));
979                                ((MediaVideoItem)currentMediaItem).setVolume(volumePercent);
980                            } else {
981                                Log.e(TAG, "Unknown media item type: " + type);
982                                currentMediaItem = null;
983                            }
984
985                            if (currentMediaItem != null) {
986                                mMediaItems.add(currentMediaItem);
987                            }
988                        } else if (TAG_TRANSITION.equals(name)) {
989                            final Transition transition = parseTransition(parser);
990                            if (transition != null) {
991                                mTransitions.add(transition);
992                            }
993                        } else if (TAG_OVERLAY.equals(name)) {
994                            if (currentMediaItem != null) {
995                                currentOverlay = parseOverlay(parser, currentMediaItem);
996                                if (currentOverlay != null) {
997                                    currentMediaItem.addOverlay(currentOverlay);
998                                }
999                            }
1000                        } else if (TAG_OVERLAY_USER_ATTRIBUTES.equals(name)) {
1001                            if (currentOverlay != null) {
1002                                final int attributesCount = parser.getAttributeCount();
1003                                for (int i = 0; i < attributesCount; i++) {
1004                                    currentOverlay.setUserAttribute(parser.getAttributeName(i),
1005                                            parser.getAttributeValue(i));
1006                                }
1007                            }
1008                        } else if (TAG_EFFECT.equals(name)) {
1009                            if (currentMediaItem != null) {
1010                                final Effect effect = parseEffect(parser, currentMediaItem);
1011                                if (effect != null) {
1012                                    currentMediaItem.addEffect(effect);
1013                                }
1014                                if (effect instanceof EffectKenBurns) {
1015                                    String filename = parser.getAttributeValue("", ATTR_GENERATED_IMAGE_CLIP);
1016
1017                                    if (new File(filename).exists() == true) {
1018                                        ((MediaImageItem)currentMediaItem).setGeneratedImageClip(filename);
1019                                        ((MediaImageItem)currentMediaItem).setRegenerateClip(false);
1020                                    }
1021                                    else {
1022                                        ((MediaImageItem)currentMediaItem).setGeneratedImageClip(null);
1023                                        ((MediaImageItem)currentMediaItem).setRegenerateClip(true);
1024                                    }
1025                                }
1026                            }
1027                        } else if (TAG_AUDIO_TRACK.equals(name)) {
1028                            final AudioTrack audioTrack = parseAudioTrack(parser);
1029                            if (audioTrack != null) {
1030                                addAudioTrack(audioTrack);
1031                            }
1032                        }
1033                        break;
1034                    }
1035
1036                    case XmlPullParser.END_TAG: {
1037                        name = parser.getName();
1038                        if (TAG_MEDIA_ITEM.equals(name)) {
1039                            currentMediaItem = null;
1040                        } else if (TAG_OVERLAY.equals(name)) {
1041                            currentOverlay = null;
1042                        }
1043                        break;
1044                    }
1045
1046                    default: {
1047                        break;
1048                    }
1049                }
1050                eventType = parser.next();
1051            }
1052            computeTimelineDuration();
1053        } finally {
1054            if (fis != null) {
1055                fis.close();
1056            }
1057        }
1058    }
1059
1060    /**
1061     * Parse the transition
1062     *
1063     * @param parser The parser
1064     * @return The transition
1065     */
1066    private Transition parseTransition(XmlPullParser parser) {
1067        final String transitionId = parser.getAttributeValue("", ATTR_ID);
1068        final String type = parser.getAttributeValue("", ATTR_TYPE);
1069        final long durationMs = Long.parseLong(parser.getAttributeValue("",
1070                                               ATTR_DURATION));
1071        final int behavior = Integer.parseInt(parser.getAttributeValue("",
1072                                              ATTR_BEHAVIOR));
1073        final boolean isTransitionGenerated;
1074
1075
1076        final String beforeMediaItemId = parser.getAttributeValue("",
1077                                                     ATTR_BEFORE_MEDIA_ITEM_ID);
1078        final MediaItem beforeMediaItem;
1079        if (beforeMediaItemId != null) {
1080            beforeMediaItem = getMediaItem(beforeMediaItemId);
1081        } else {
1082            beforeMediaItem = null;
1083        }
1084
1085        final String afterMediaItemId = parser.getAttributeValue("",
1086                                                      ATTR_AFTER_MEDIA_ITEM_ID);
1087        final MediaItem afterMediaItem;
1088        if (afterMediaItemId != null) {
1089            afterMediaItem = getMediaItem(afterMediaItemId);
1090        } else {
1091            afterMediaItem = null;
1092        }
1093
1094        final Transition transition;
1095        if (TransitionAlpha.class.getSimpleName().equals(type)) {
1096            final int blending = Integer.parseInt(parser.getAttributeValue("", ATTR_BLENDING));
1097            final String maskFilename = parser.getAttributeValue("", ATTR_MASK);
1098            final boolean invert = Boolean.getBoolean(parser.getAttributeValue("", ATTR_INVERT));
1099            transition = new TransitionAlpha(transitionId, afterMediaItem, beforeMediaItem,
1100                    durationMs, behavior, maskFilename, blending, invert);
1101        } else if (TransitionCrossfade.class.getSimpleName().equals(type)) {
1102            transition = new TransitionCrossfade(transitionId, afterMediaItem, beforeMediaItem,
1103                    durationMs, behavior);
1104        } else if (TransitionSliding.class.getSimpleName().equals(type)) {
1105            final int direction = Integer.parseInt(parser.getAttributeValue("", ATTR_DIRECTION));
1106            transition = new TransitionSliding(transitionId, afterMediaItem, beforeMediaItem,
1107                    durationMs, behavior, direction);
1108        } else if (TransitionFadeBlack.class.getSimpleName().equals(type)) {
1109            transition = new TransitionFadeBlack(transitionId, afterMediaItem, beforeMediaItem,
1110                    durationMs, behavior);
1111        } else {
1112            transition = null;
1113        }
1114
1115        if (beforeMediaItem != null) {
1116            beforeMediaItem.setBeginTransition(transition);
1117        }
1118
1119        if (afterMediaItem != null) {
1120            afterMediaItem.setEndTransition(transition);
1121        }
1122
1123        isTransitionGenerated = Boolean.parseBoolean(parser.getAttributeValue("",
1124                                                 ATTR_IS_TRANSITION_GENERATED));
1125        if (isTransitionGenerated == true) {
1126            final String transitionFile = parser.getAttributeValue("",
1127                                                ATTR_GENERATED_TRANSITION_CLIP);
1128
1129            if (new File(transitionFile).exists() == true) {
1130                transition.setFilename(transitionFile);
1131            } else {
1132                transition.setFilename(null);
1133            }
1134        }
1135        return transition;
1136    }
1137
1138
1139    /**
1140     * Parse the overlay
1141     *
1142     * @param parser The parser
1143     * @param mediaItem The media item owner
1144     *
1145     * @return The overlay
1146     */
1147    private Overlay parseOverlay(XmlPullParser parser, MediaItem mediaItem) {
1148        final String overlayId = parser.getAttributeValue("", ATTR_ID);
1149        final String type = parser.getAttributeValue("", ATTR_TYPE);
1150        final long durationMs = Long.parseLong(parser.getAttributeValue("",
1151                                                                ATTR_DURATION));
1152        final long startTimeMs = Long.parseLong(parser.getAttributeValue("",
1153                                                              ATTR_BEGIN_TIME));
1154
1155        final Overlay overlay;
1156        if (OverlayFrame.class.getSimpleName().equals(type)) {
1157            final String filename = parser.getAttributeValue("", ATTR_FILENAME);
1158            overlay = new OverlayFrame(mediaItem, overlayId, filename,
1159                                       startTimeMs, durationMs);
1160        } else {
1161            overlay = null;
1162        }
1163
1164        final String overlayRgbFileName = parser.getAttributeValue("",
1165                                                     ATTR_OVERLAY_RGB_FILENAME);
1166        if (overlayRgbFileName != null) {
1167            ((OverlayFrame)overlay).setFilename(overlayRgbFileName);
1168
1169            final int overlayFrameWidth =
1170                                   Integer.parseInt(parser.getAttributeValue("",
1171                                   ATTR_OVERLAY_FRAME_WIDTH));
1172            final int overlayFrameHeight =
1173                                   Integer.parseInt(parser.getAttributeValue("",
1174                                   ATTR_OVERLAY_FRAME_HEIGHT));
1175
1176            ((OverlayFrame)overlay).setOverlayFrameWidth(overlayFrameWidth);
1177            ((OverlayFrame)overlay).setOverlayFrameHeight(overlayFrameHeight);
1178        }
1179
1180        return overlay;
1181    }
1182
1183    /**
1184     * Parse the effect
1185     *
1186     * @param parser The parser
1187     * @param mediaItem The media item owner
1188     *
1189     * @return The effect
1190     */
1191    private Effect parseEffect(XmlPullParser parser, MediaItem mediaItem) {
1192        final String effectId = parser.getAttributeValue("", ATTR_ID);
1193        final String type = parser.getAttributeValue("", ATTR_TYPE);
1194        final long durationMs = Long.parseLong(parser.getAttributeValue("",
1195                                                                ATTR_DURATION));
1196        final long startTimeMs = Long.parseLong(parser.getAttributeValue("",
1197                                                              ATTR_BEGIN_TIME));
1198
1199        final Effect effect;
1200        if (EffectColor.class.getSimpleName().equals(type)) {
1201            final int colorEffectType =
1202                Integer.parseInt(parser.getAttributeValue("",
1203                                                       ATTR_COLOR_EFFECT_TYPE));
1204            final int color;
1205            if (colorEffectType == EffectColor.TYPE_COLOR
1206                    || colorEffectType == EffectColor.TYPE_GRADIENT) {
1207                color = Integer.parseInt(parser.getAttributeValue("",
1208                                                      ATTR_COLOR_EFFECT_VALUE));
1209            } else {
1210                color = 0;
1211            }
1212            effect = new EffectColor(mediaItem, effectId, startTimeMs,
1213                    durationMs, colorEffectType, color);
1214        } else if (EffectKenBurns.class.getSimpleName().equals(type)) {
1215            final Rect startRect = new Rect(
1216                    Integer.parseInt(parser.getAttributeValue("",
1217                                                         ATTR_START_RECT_LEFT)),
1218                    Integer.parseInt(parser.getAttributeValue("",
1219                                                          ATTR_START_RECT_TOP)),
1220                    Integer.parseInt(parser.getAttributeValue("",
1221                                                        ATTR_START_RECT_RIGHT)),
1222                    Integer.parseInt(parser.getAttributeValue("",
1223                                                      ATTR_START_RECT_BOTTOM)));
1224            final Rect endRect = new Rect(
1225                    Integer.parseInt(parser.getAttributeValue("",
1226                                                           ATTR_END_RECT_LEFT)),
1227                    Integer.parseInt(parser.getAttributeValue("",
1228                                                            ATTR_END_RECT_TOP)),
1229                    Integer.parseInt(parser.getAttributeValue("",
1230                                                          ATTR_END_RECT_RIGHT)),
1231                    Integer.parseInt(parser.getAttributeValue("",
1232                                                        ATTR_END_RECT_BOTTOM)));
1233            effect = new EffectKenBurns(mediaItem, effectId, startRect, endRect,
1234                                        startTimeMs, durationMs);
1235        } else {
1236            effect = null;
1237        }
1238
1239        return effect;
1240    }
1241
1242    /**
1243     * Parse the audio track
1244     *
1245     * @param parser The parser
1246     *
1247     * @return The audio track
1248     */
1249    private AudioTrack parseAudioTrack(XmlPullParser parser) {
1250        final String audioTrackId = parser.getAttributeValue("", ATTR_ID);
1251        final String filename = parser.getAttributeValue("", ATTR_FILENAME);
1252        final long startTimeMs = Long.parseLong(parser.getAttributeValue("",
1253                                                              ATTR_START_TIME));
1254        final long beginMs = Long.parseLong(parser.getAttributeValue("",
1255                                                              ATTR_BEGIN_TIME));
1256        final long endMs = Long.parseLong(parser.getAttributeValue("",
1257                                                                ATTR_END_TIME));
1258        final int volume = Integer.parseInt(parser.getAttributeValue("",
1259                                                                  ATTR_VOLUME));
1260        final boolean muted = Boolean.parseBoolean(parser.getAttributeValue("",
1261                                                                   ATTR_MUTED));
1262        final boolean loop = Boolean.parseBoolean(parser.getAttributeValue("",
1263                                                                    ATTR_LOOP));
1264        final boolean duckingEnabled =
1265                               Boolean.parseBoolean(parser.getAttributeValue("",
1266                                                    ATTR_DUCK_ENABLED));
1267        final int duckThreshold = Integer.parseInt(parser.getAttributeValue("",
1268                                                          ATTR_DUCK_THRESHOLD));
1269        final int duckedTrackVolume =
1270                                   Integer.parseInt(parser.getAttributeValue("",
1271                                                     ATTR_DUCKED_TRACK_VOLUME));
1272
1273        final String waveformFilename = parser.getAttributeValue("",
1274                                                  ATTR_AUDIO_WAVEFORM_FILENAME);
1275        try {
1276            final AudioTrack audioTrack = new AudioTrack(this, audioTrackId,
1277                                                         filename, startTimeMs,
1278                                                         beginMs, endMs, loop,
1279                                                         volume, muted,
1280                                                         duckingEnabled,
1281                                                         duckThreshold,
1282                                                         duckedTrackVolume,
1283                                                         waveformFilename);
1284
1285            return audioTrack;
1286        } catch (IOException ex) {
1287            return null;
1288        }
1289    }
1290
1291
1292    /*
1293     * {@inheritDoc}
1294     */
1295    public void save() throws IOException {
1296        final XmlSerializer serializer = Xml.newSerializer();
1297        final StringWriter writer = new StringWriter();
1298        serializer.setOutput(writer);
1299        serializer.startDocument("UTF-8", true);
1300        serializer.startTag("", TAG_PROJECT);
1301        serializer.attribute("",
1302                             ATTR_ASPECT_RATIO, Integer.toString(mAspectRatio));
1303        serializer.attribute("", ATTR_PREVIEW_PREPARE,
1304                        Boolean.toString(mMANativeHelper.getGeneratePreview()));
1305
1306        serializer.attribute("", ATTR_REGENERATE_PCM,
1307                        Boolean.toString(mMANativeHelper.getAudioflag()));
1308
1309        serializer.startTag("", TAG_MEDIA_ITEMS);
1310        for (MediaItem mediaItem : mMediaItems) {
1311            serializer.startTag("", TAG_MEDIA_ITEM);
1312            serializer.attribute("", ATTR_ID, mediaItem.getId());
1313            serializer.attribute("", ATTR_TYPE,
1314                                          mediaItem.getClass().getSimpleName());
1315            serializer.attribute("", ATTR_FILENAME, mediaItem.getFilename());
1316            serializer.attribute("", ATTR_RENDERING_MODE, Integer.toString(
1317                    mediaItem.getRenderingMode()));
1318            if (mediaItem instanceof MediaVideoItem) {
1319                final MediaVideoItem mvi = (MediaVideoItem)mediaItem;
1320                serializer
1321                .attribute("", ATTR_BEGIN_TIME,
1322                                     Long.toString(mvi.getBoundaryBeginTime()));
1323                serializer.attribute("", ATTR_END_TIME,
1324                                       Long.toString(mvi.getBoundaryEndTime()));
1325                serializer.attribute("", ATTR_VOLUME,
1326                                             Integer.toString(mvi.getVolume()));
1327                serializer.attribute("", ATTR_MUTED,
1328                                               Boolean.toString(mvi.isMuted()));
1329                if (mvi.getAudioWaveformFilename() != null) {
1330                    serializer.attribute("", ATTR_AUDIO_WAVEFORM_FILENAME,
1331                            mvi.getAudioWaveformFilename());
1332                }
1333            } else if (mediaItem instanceof MediaImageItem) {
1334                serializer.attribute("", ATTR_DURATION,
1335                        Long.toString(mediaItem.getTimelineDuration()));
1336            }
1337
1338            final List<Overlay> overlays = mediaItem.getAllOverlays();
1339            if (overlays.size() > 0) {
1340                serializer.startTag("", TAG_OVERLAYS);
1341                for (Overlay overlay : overlays) {
1342                    serializer.startTag("", TAG_OVERLAY);
1343                    serializer.attribute("", ATTR_ID, overlay.getId());
1344                    serializer.attribute("",
1345                                 ATTR_TYPE, overlay.getClass().getSimpleName());
1346                    serializer.attribute("", ATTR_BEGIN_TIME,
1347                                         Long.toString(overlay.getStartTime()));
1348                    serializer.attribute("", ATTR_DURATION,
1349                                          Long.toString(overlay.getDuration()));
1350                    if (overlay instanceof OverlayFrame) {
1351                        final OverlayFrame overlayFrame = (OverlayFrame)overlay;
1352                        overlayFrame.save(getPath());
1353                        if (overlayFrame.getBitmapImageFileName() != null) {
1354                            serializer.attribute("", ATTR_FILENAME,
1355                                         overlayFrame.getBitmapImageFileName());
1356                        }
1357
1358                        if (overlayFrame.getFilename() != null) {
1359                            serializer.attribute("",
1360                                                 ATTR_OVERLAY_RGB_FILENAME,
1361                                                 overlayFrame.getFilename());
1362                            serializer.attribute("", ATTR_OVERLAY_FRAME_WIDTH,
1363                                                 Integer.toString(overlayFrame.getOverlayFrameWidth()));
1364                            serializer.attribute("", ATTR_OVERLAY_FRAME_HEIGHT,
1365                                                 Integer.toString(overlayFrame.getOverlayFrameHeight()));
1366                        }
1367
1368                    }
1369
1370                    /**
1371                     *  Save the user attributes
1372                     */
1373                    serializer.startTag("", TAG_OVERLAY_USER_ATTRIBUTES);
1374                    final Map<String, String> userAttributes = overlay.getUserAttributes();
1375                    for (String name : userAttributes.keySet()) {
1376                        final String value = userAttributes.get(name);
1377                        if (value != null) {
1378                            serializer.attribute("", name, value);
1379                        }
1380                    }
1381                    serializer.endTag("", TAG_OVERLAY_USER_ATTRIBUTES);
1382
1383                    serializer.endTag("", TAG_OVERLAY);
1384                }
1385                serializer.endTag("", TAG_OVERLAYS);
1386            }
1387
1388            final List<Effect> effects = mediaItem.getAllEffects();
1389            if (effects.size() > 0) {
1390                serializer.startTag("", TAG_EFFECTS);
1391                for (Effect effect : effects) {
1392                    serializer.startTag("", TAG_EFFECT);
1393                    serializer.attribute("", ATTR_ID, effect.getId());
1394                    serializer.attribute("",
1395                                  ATTR_TYPE, effect.getClass().getSimpleName());
1396                    serializer.attribute("", ATTR_BEGIN_TIME,
1397                            Long.toString(effect.getStartTime()));
1398                    serializer.attribute("", ATTR_DURATION,
1399                                           Long.toString(effect.getDuration()));
1400                    if (effect instanceof EffectColor) {
1401                        final EffectColor colorEffect = (EffectColor)effect;
1402                        serializer.attribute("", ATTR_COLOR_EFFECT_TYPE,
1403                                Integer.toString(colorEffect.getType()));
1404                        if (colorEffect.getType() == EffectColor.TYPE_COLOR ||
1405                                colorEffect.getType() == EffectColor.TYPE_GRADIENT) {
1406                            serializer.attribute("", ATTR_COLOR_EFFECT_VALUE,
1407                                    Integer.toString(colorEffect.getColor()));
1408                        }
1409                    } else if (effect instanceof EffectKenBurns) {
1410                        final Rect startRect = ((EffectKenBurns)effect).getStartRect();
1411                        serializer.attribute("", ATTR_START_RECT_LEFT,
1412                                Integer.toString(startRect.left));
1413                        serializer.attribute("", ATTR_START_RECT_TOP,
1414                                Integer.toString(startRect.top));
1415                        serializer.attribute("", ATTR_START_RECT_RIGHT,
1416                                Integer.toString(startRect.right));
1417                        serializer.attribute("", ATTR_START_RECT_BOTTOM,
1418                                Integer.toString(startRect.bottom));
1419
1420                        final Rect endRect = ((EffectKenBurns)effect).getEndRect();
1421                        serializer.attribute("", ATTR_END_RECT_LEFT,
1422                                                Integer.toString(endRect.left));
1423                        serializer.attribute("", ATTR_END_RECT_TOP,
1424                                                 Integer.toString(endRect.top));
1425                        serializer.attribute("", ATTR_END_RECT_RIGHT,
1426                                               Integer.toString(endRect.right));
1427                        serializer.attribute("", ATTR_END_RECT_BOTTOM,
1428                                Integer.toString(endRect.bottom));
1429                        final MediaItem mItem = effect.getMediaItem();
1430                        serializer.attribute("", ATTR_GENERATED_IMAGE_CLIP,
1431                               ((MediaImageItem)mItem).getGeneratedImageClip());
1432                    }
1433
1434                    serializer.endTag("", TAG_EFFECT);
1435                }
1436                serializer.endTag("", TAG_EFFECTS);
1437            }
1438
1439            serializer.endTag("", TAG_MEDIA_ITEM);
1440        }
1441        serializer.endTag("", TAG_MEDIA_ITEMS);
1442
1443        serializer.startTag("", TAG_TRANSITIONS);
1444
1445        for (Transition transition : mTransitions) {
1446            serializer.startTag("", TAG_TRANSITION);
1447            serializer.attribute("", ATTR_ID, transition.getId());
1448            serializer.attribute("", ATTR_TYPE,
1449                                         transition.getClass().getSimpleName());
1450            serializer.attribute("", ATTR_DURATION,
1451                                       Long.toString(transition.getDuration()));
1452            serializer.attribute("", ATTR_BEHAVIOR,
1453                                    Integer.toString(transition.getBehavior()));
1454            serializer.attribute("", ATTR_IS_TRANSITION_GENERATED,
1455                                    Boolean.toString(transition.isGenerated()));
1456            if (transition.isGenerated() == true) {
1457                serializer.attribute("", ATTR_GENERATED_TRANSITION_CLIP,
1458                                                          transition.mFilename);
1459            }
1460            final MediaItem afterMediaItem = transition.getAfterMediaItem();
1461            if (afterMediaItem != null) {
1462                serializer.attribute("", ATTR_AFTER_MEDIA_ITEM_ID,
1463                                                        afterMediaItem.getId());
1464            }
1465
1466            final MediaItem beforeMediaItem = transition.getBeforeMediaItem();
1467            if (beforeMediaItem != null) {
1468                serializer.attribute("", ATTR_BEFORE_MEDIA_ITEM_ID,
1469                                                       beforeMediaItem.getId());
1470            }
1471
1472            if (transition instanceof TransitionSliding) {
1473                serializer.attribute("", ATTR_DIRECTION,
1474                        Integer.toString(((TransitionSliding)transition).getDirection()));
1475            } else if (transition instanceof TransitionAlpha) {
1476                TransitionAlpha ta = (TransitionAlpha)transition;
1477                serializer.attribute("", ATTR_BLENDING,
1478                                     Integer.toString(ta.getBlendingPercent()));
1479                serializer.attribute("", ATTR_INVERT,
1480                                               Boolean.toString(ta.isInvert()));
1481                if (ta.getMaskFilename() != null) {
1482                    serializer.attribute("", ATTR_MASK, ta.getMaskFilename());
1483                }
1484            }
1485            serializer.endTag("", TAG_TRANSITION);
1486        }
1487        serializer.endTag("", TAG_TRANSITIONS);
1488        serializer.startTag("", TAG_AUDIO_TRACKS);
1489        for (AudioTrack at : mAudioTracks) {
1490            serializer.startTag("", TAG_AUDIO_TRACK);
1491            serializer.attribute("", ATTR_ID, at.getId());
1492            serializer.attribute("", ATTR_FILENAME, at.getFilename());
1493            serializer.attribute("", ATTR_START_TIME,
1494                                              Long.toString(at.getStartTime()));
1495            serializer.attribute("", ATTR_BEGIN_TIME,
1496                                      Long.toString(at.getBoundaryBeginTime()));
1497            serializer.attribute("", ATTR_END_TIME,
1498                                        Long.toString(at.getBoundaryEndTime()));
1499            serializer.attribute("", ATTR_VOLUME,
1500                                              Integer.toString(at.getVolume()));
1501            serializer.attribute("", ATTR_DUCK_ENABLED,
1502                                       Boolean.toString(at.isDuckingEnabled()));
1503            serializer.attribute("", ATTR_DUCKED_TRACK_VOLUME,
1504                                   Integer.toString(at.getDuckedTrackVolume()));
1505            serializer.attribute("", ATTR_DUCK_THRESHOLD,
1506                                   Integer.toString(at.getDuckingThreshhold()));
1507            serializer.attribute("", ATTR_MUTED, Boolean.toString(at.isMuted()));
1508            serializer.attribute("", ATTR_LOOP, Boolean.toString(at.isLooping()));
1509            if (at.getAudioWaveformFilename() != null) {
1510                serializer.attribute("", ATTR_AUDIO_WAVEFORM_FILENAME,
1511                        at.getAudioWaveformFilename());
1512            }
1513
1514            serializer.endTag("", TAG_AUDIO_TRACK);
1515        }
1516        serializer.endTag("", TAG_AUDIO_TRACKS);
1517
1518        serializer.endTag("", TAG_PROJECT);
1519        serializer.endDocument();
1520
1521        /**
1522         *  Save the metadata XML file
1523         */
1524        final FileOutputStream out = new FileOutputStream(new File(getPath(),
1525                                                          PROJECT_FILENAME));
1526        out.write(writer.toString().getBytes());
1527        out.flush();
1528        out.close();
1529    }
1530
1531    /*
1532     * {@inheritDoc}
1533     */
1534    public void setAspectRatio(int aspectRatio) {
1535        mAspectRatio = aspectRatio;
1536        /**
1537         *  Invalidate all transitions
1538         */
1539        for (Transition transition : mTransitions) {
1540            transition.invalidate();
1541        }
1542    }
1543
1544    /*
1545     * {@inheritDoc}
1546     */
1547    public void startPreview(SurfaceHolder surfaceHolder, long fromMs, long toMs,
1548                             boolean loop, int callbackAfterFrameCount,
1549                             PreviewProgressListener listener) {
1550
1551        if (surfaceHolder == null) {
1552            throw new IllegalArgumentException();
1553        }
1554        if (listener == null) {
1555            throw new IllegalArgumentException();
1556        }
1557        if (fromMs >= mDurationMs) {
1558            throw new IllegalArgumentException("requested time not correct");
1559        }
1560
1561        if (fromMs < 0) {
1562            throw new IllegalArgumentException("requested time not correct");
1563        }
1564
1565        boolean semAcquireDone = false;
1566        try{
1567            mPreviewSemaphore.acquire();
1568            semAcquireDone = true;
1569        } catch (InterruptedException  ex) {
1570            Log.e(TAG, "Sem acquire NOT successful in startPreview");
1571        }
1572
1573        if (semAcquireDone) {
1574            Surface mSurface = surfaceHolder.getSurface();
1575
1576            if (mSurface == null) {
1577                throw new RuntimeException("Surface could not be retrieved from surface holder");
1578            }
1579
1580            if (mMediaItems.size() > 0) {
1581                try {
1582                    mMANativeHelper.previewStoryBoard(mMediaItems, mTransitions,
1583                                                      mAudioTracks, null);
1584                    mMANativeHelper.doPreview(mSurface, fromMs, toMs, loop,
1585                                     callbackAfterFrameCount, listener);
1586                    mPreviewInProgress = true;
1587                } catch (IllegalArgumentException ex) {
1588                    mPreviewSemaphore.release();
1589                    Log.e(TAG, "Illegal Argument exception in do preview");
1590                    throw ex;
1591                } catch (IllegalStateException ex) {
1592                    mPreviewSemaphore.release();
1593                    Log.e(TAG, "Illegal State exception in do preview");
1594                    throw ex;
1595                } catch (RuntimeException ex) {
1596                    mPreviewSemaphore.release();
1597                    Log.e(TAG, "Runtime exception in do preview");
1598                    throw ex;
1599                }
1600            }
1601            /**
1602             *  release on complete by calling stopPreview
1603             */
1604        }
1605    }
1606
1607    /*
1608     * {@inheritDoc}
1609     */
1610    public long stopPreview() {
1611        if (mPreviewInProgress) {
1612            long result = mMANativeHelper.stopPreview();
1613            mPreviewInProgress = false;
1614            /**
1615             *  release the sem acquired in startPreview
1616             */
1617            mPreviewSemaphore.release();
1618            return result;
1619        }
1620        else {
1621            return 0;
1622        }
1623    }
1624
1625    /*
1626     * Remove transitions associated with the specified media item
1627     *
1628     * @param mediaItem The media item
1629     */
1630    private void removeAdjacentTransitions(MediaItem mediaItem) {
1631        final Transition beginTransition = mediaItem.getBeginTransition();
1632        if (beginTransition != null) {
1633            if (beginTransition.getAfterMediaItem() != null) {
1634                beginTransition.getAfterMediaItem().setEndTransition(null);
1635            }
1636            beginTransition.invalidate();
1637            mTransitions.remove(beginTransition);
1638        }
1639
1640        final Transition endTransition = mediaItem.getEndTransition();
1641        if (endTransition != null) {
1642            if (endTransition.getBeforeMediaItem() != null) {
1643                endTransition.getBeforeMediaItem().setBeginTransition(null);
1644            }
1645            endTransition.invalidate();
1646            mTransitions.remove(endTransition);
1647        }
1648
1649        mediaItem.setBeginTransition(null);
1650        mediaItem.setEndTransition(null);
1651    }
1652
1653    /**
1654     * Remove the transition before this media item
1655     *
1656     * @param index The media item index
1657     */
1658    private void removeTransitionBefore(int index) {
1659        final MediaItem mediaItem = mMediaItems.get(index);
1660        final Iterator<Transition> it = mTransitions.iterator();
1661        while (it.hasNext()) {
1662            Transition t = it.next();
1663            if (t.getBeforeMediaItem() == mediaItem) {
1664                it.remove();
1665                t.invalidate();
1666                mediaItem.setBeginTransition(null);
1667                if (index > 0) {
1668                    mMediaItems.get(index - 1).setEndTransition(null);
1669                }
1670                break;
1671            }
1672        }
1673    }
1674
1675    /**
1676     * Remove the transition after this media item
1677     *
1678     * @param mediaItem The media item
1679     */
1680    private void removeTransitionAfter(int index) {
1681        final MediaItem mediaItem = mMediaItems.get(index);
1682        final Iterator<Transition> it = mTransitions.iterator();
1683        while (it.hasNext()) {
1684            Transition t = it.next();
1685            if (t.getAfterMediaItem() == mediaItem) {
1686                it.remove();
1687                t.invalidate();
1688                mediaItem.setEndTransition(null);
1689                /**
1690                 *  Invalidate the reference in the next media item
1691                 */
1692                if (index < mMediaItems.size() - 1) {
1693                    mMediaItems.get(index + 1).setBeginTransition(null);
1694                }
1695                break;
1696            }
1697        }
1698    }
1699
1700    /**
1701     * Compute the duration
1702     */
1703    private void computeTimelineDuration() {
1704        mDurationMs = 0;
1705        final int mediaItemsCount = mMediaItems.size();
1706        for (int i = 0; i < mediaItemsCount; i++) {
1707            final MediaItem mediaItem = mMediaItems.get(i);
1708            mDurationMs += mediaItem.getTimelineDuration();
1709            if (mediaItem.getEndTransition() != null) {
1710                if (i < mediaItemsCount - 1) {
1711                    mDurationMs -= mediaItem.getEndTransition().getDuration();
1712                }
1713            }
1714        }
1715    }
1716
1717    /*
1718     * Generate the project thumbnail
1719     */
1720    private void generateProjectThumbnail() {
1721        /*
1722         * If a thumbnail already exists, then delete it first
1723         */
1724        if ((new File(mProjectPath + "/" + THUMBNAIL_FILENAME)).exists()) {
1725            (new File(mProjectPath + "/" + THUMBNAIL_FILENAME)).delete();
1726        }
1727        /*
1728         * Generate a new thumbnail for the project from first media Item
1729         */
1730        if (mMediaItems.size() > 0) {
1731            MediaItem mI = mMediaItems.get(0);
1732            /*
1733             * Lets initialiZe the width for default aspect ratio i.e 16:9
1734             */
1735            int height = 480;
1736            int width = 854;
1737            switch (mI.getAspectRatio()) {
1738                case MediaProperties.ASPECT_RATIO_3_2:
1739                    width =  720;
1740                    break;
1741                case MediaProperties.ASPECT_RATIO_4_3:
1742                    width =  640;
1743                    break;
1744                case MediaProperties.ASPECT_RATIO_5_3:
1745                    width =  800;
1746                    break;
1747                case MediaProperties.ASPECT_RATIO_11_9:
1748                    width =  586;
1749                    break;
1750                case MediaProperties.ASPECT_RATIO_16_9:
1751                case MediaProperties.ASPECT_RATIO_UNDEFINED:
1752                    break;
1753            }
1754
1755            Bitmap projectBitmap = null;
1756            try {
1757                projectBitmap = mI.getThumbnail(width, height, 500);
1758            } catch (IllegalArgumentException e) {
1759                throw new IllegalArgumentException ("Illegal Argument Error creating project thumbnail");
1760            } catch (IOException e) {
1761                throw new IllegalArgumentException ("IO Error creating project thumbnail");
1762            }
1763            try {
1764                FileOutputStream stream = new FileOutputStream(mProjectPath + "/"
1765                                                          + THUMBNAIL_FILENAME);
1766                projectBitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream);
1767                stream.flush();
1768                stream.close();
1769            } catch (IOException e) {
1770
1771                throw new IllegalArgumentException ("Error creating project thumbnail");
1772            } finally {
1773                projectBitmap.recycle();
1774            }
1775        }
1776    }
1777
1778    /**
1779     * Clears the preview surface
1780     *
1781     * @param surfaceHolder SurfaceHolder where the preview is rendered
1782     * and needs to be cleared.
1783     */
1784    public void clearSurface(SurfaceHolder surfaceHolder) {
1785        if (surfaceHolder == null) {
1786            throw new IllegalArgumentException("Invalid surface holder");
1787        }
1788
1789        final Surface surface = surfaceHolder.getSurface();
1790        if (surface == null) {
1791            throw new RuntimeException("Surface could not be retrieved from surface holder");
1792        }
1793
1794        if (mMANativeHelper != null) {
1795            mMANativeHelper.clearPreviewSurface(surface);
1796        } else {
1797            Log.w(TAG, "Native helper was not ready!");
1798        }
1799    }
1800}
1801