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