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