/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.example.android.videoeditor.service; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import android.app.Service; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.Rect; import roughcut.media.videoeditor.AudioTrack; import roughcut.media.videoeditor.Effect; import roughcut.media.videoeditor.EffectColor; import roughcut.media.videoeditor.EffectKenBurns; import roughcut.media.videoeditor.ExtractAudioWaveformProgressListener; import roughcut.media.videoeditor.MediaImageItem; import roughcut.media.videoeditor.MediaItem; import roughcut.media.videoeditor.MediaProperties; import roughcut.media.videoeditor.MediaVideoItem; import roughcut.media.videoeditor.Overlay; import roughcut.media.videoeditor.OverlayFrame; import roughcut.media.videoeditor.Transition; import roughcut.media.videoeditor.TransitionAlpha; import roughcut.media.videoeditor.TransitionCrossfade; import roughcut.media.videoeditor.TransitionFadeBlack; import roughcut.media.videoeditor.TransitionSliding; import roughcut.media.videoeditor.VideoEditor; import roughcut.media.videoeditor.VideoEditorFactory; import roughcut.media.videoeditor.WaveformData; import roughcut.media.videoeditor.MediaItem.GetThumbnailListCallback; import roughcut.media.videoeditor.VideoEditor.ExportProgressListener; import roughcut.media.videoeditor.VideoEditor.MediaProcessingProgressListener; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.provider.MediaStore.Audio; import android.provider.MediaStore.Images; import android.provider.MediaStore.Video; import android.util.Log; import com.example.android.videoeditor.R; import com.example.android.videoeditor.util.FileUtils; import com.example.android.videoeditor.util.ImageUtils; import com.example.android.videoeditor.util.MediaItemUtils; import com.example.android.videoeditor.util.StringUtils; /** * VideoEditor service API */ public class ApiService extends Service { // Logging private static final String TAG = "VEApiService"; // Additional updates public static final int ACTION_UPDATE_FRAME = MediaProcessingProgressListener.ACTION_DECODE + 100; public static final int ACTION_NO_FRAME_UPDATE = MediaProcessingProgressListener.ACTION_DECODE + 101; // Parameters private static final String PARAM_OP = "op"; private static final String PARAM_REQUEST_ID = "rid"; private static final String PARAM_PROJECT_PATH = "project"; private static final String PARAM_FILENAME = "filename"; private static final String PARAM_STORYBOARD_ITEM_ID = "item_id"; private static final String PARAM_RELATIVE_STORYBOARD_ITEM_ID = "r_item_id"; private static final String PARAM_PROGRESS_VALUE = "prog_value"; private static final String PARAM_EXCEPTION = "ex"; private static final String PARAM_START_TIME = "s_time"; private static final String PARAM_END_TIME = "e_time"; private static final String PARAM_DURATION = "duration"; private static final String PARAM_WIDTH = "width"; private static final String PARAM_HEIGHT = "height"; private static final String PARAM_BITRATE = "bitrate"; private static final String PARAM_MEDIA_ITEM_RENDERING_MODE = "rm"; private static final String PARAM_MEDIA_ITEM_START_RECT = "start_rect"; private static final String PARAM_MEDIA_ITEM_END_RECT = "end_rect"; private static final String PARAM_EFFECT_TYPE = "e_type"; private static final String PARAM_EFFECT_PARAM = "e_param"; private static final String PARAM_TRANSITION_BEHAVIOR = "behavior"; private static final String PARAM_TRANSITION_MASK = "t_mask"; private static final String PARAM_TRANSITION_BLENDING = "t_blending"; private static final String PARAM_TRANSITION_INVERT = "t_invert"; private static final String PARAM_TRANSITION_DIRECTION = "t_dir"; private static final String PARAM_INTENT = "req_intent"; private static final String PARAM_PROJECT_NAME = "name"; private static final String PARAM_MOVIES_FILENAMES = "movies"; private static final String PARAM_PHOTOS_FILENAMES = "images"; private static final String PARAM_ASPECT_RATIO = "aspect_ratio"; private static final String PARAM_BEGIN_BOUNDARY = "b_boundary"; private static final String PARAM_END_BOUNDARY = "e_boundary"; private static final String PARAM_ATTRIBUTES = "attributes"; private static final String PARAM_VOLUME = "volume"; private static final String PARAM_LOOP = "loop"; private static final String PARAM_MUTE = "mute"; private static final String PARAM_DUCK = "duck"; private static final String PARAM_MOVIE_URI = "uri"; private static final String PARAM_THEME = "theme"; private static final String PARAM_ACTION = "action"; private static final String PARAM_COUNT = "count"; private static final String PARAM_TOKEN = "token"; private static final String PARAM_INDICES = "indices"; private static final String PARAM_CANCELLED = "cancelled"; // Operations private static final int OP_VIDEO_EDITOR_CREATE = 1; private static final int OP_VIDEO_EDITOR_LOAD = 2; private static final int OP_VIDEO_EDITOR_SAVE = 3; private static final int OP_VIDEO_EDITOR_EXPORT = 4; private static final int OP_VIDEO_EDITOR_CANCEL_EXPORT = 5; private static final int OP_VIDEO_EDITOR_EXPORT_STATUS = 6; private static final int OP_VIDEO_EDITOR_RELEASE = 8; private static final int OP_VIDEO_EDITOR_DELETE = 9; private static final int OP_VIDEO_EDITOR_SET_ASPECT_RATIO = 10; private static final int OP_VIDEO_EDITOR_APPLY_THEME = 11; private static final int OP_VIDEO_EDITOR_GENERATE_PREVIEW_PROGRESS = 12; private static final int OP_VIDEO_EDITOR_LOAD_PROJECTS = 13; private static final int OP_MEDIA_ITEM_ADD_VIDEO_URI = 100; private static final int OP_MEDIA_ITEM_ADD_IMAGE_URI = 101; private static final int OP_MEDIA_ITEM_MOVE = 102; private static final int OP_MEDIA_ITEM_REMOVE = 103; private static final int OP_MEDIA_ITEM_SET_RENDERING_MODE = 104; private static final int OP_MEDIA_ITEM_SET_DURATION = 105; private static final int OP_MEDIA_ITEM_SET_BOUNDARIES = 106; private static final int OP_MEDIA_ITEM_SET_VOLUME = 107; private static final int OP_MEDIA_ITEM_SET_MUTE = 108; private static final int OP_MEDIA_ITEM_EXTRACT_AUDIO_WAVEFORM = 109; private static final int OP_MEDIA_ITEM_EXTRACT_AUDIO_WAVEFORM_STATUS = 110; private static final int OP_MEDIA_ITEM_GET_THUMBNAILS = 112; private static final int OP_MEDIA_ITEM_LOAD = 113; private static final int OP_MEDIA_ITEM_LOAD_STATUS = 114; private static final int OP_EFFECT_ADD_COLOR = 200; private static final int OP_EFFECT_ADD_IMAGE_KEN_BURNS = 201; private static final int OP_EFFECT_REMOVE = 202; private static final int OP_TRANSITION_INSERT_ALPHA = 300; private static final int OP_TRANSITION_INSERT_CROSSFADE = 301; private static final int OP_TRANSITION_INSERT_FADE_BLACK = 302; private static final int OP_TRANSITION_INSERT_SLIDING = 303; private static final int OP_TRANSITION_REMOVE = 304; private static final int OP_TRANSITION_SET_DURATION = 305; private static final int OP_TRANSITION_GET_THUMBNAIL = 306; private static final int OP_OVERLAY_ADD = 400; private static final int OP_OVERLAY_REMOVE = 401; private static final int OP_OVERLAY_SET_START_TIME = 402; private static final int OP_OVERLAY_SET_DURATION = 403; private static final int OP_OVERLAY_SET_ATTRIBUTES = 404; private static final int OP_AUDIO_TRACK_ADD = 500; private static final int OP_AUDIO_TRACK_REMOVE = 501; private static final int OP_AUDIO_TRACK_SET_VOLUME = 502; private static final int OP_AUDIO_TRACK_SET_MUTE = 503; private static final int OP_AUDIO_TRACK_SET_BOUNDARIES = 505; private static final int OP_AUDIO_TRACK_SET_LOOP = 506; private static final int OP_AUDIO_TRACK_SET_DUCK = 507; private static final int OP_AUDIO_TRACK_EXTRACT_AUDIO_WAVEFORM = 508; private static final int OP_AUDIO_TRACK_EXTRACT_AUDIO_WAVEFORM_STATUS = 509; private static final int DUCK_THRESHOLD = 20; private static final int DUCK_TRACK_VOLUME = 65; // The default audio track volume private static final int DEFAULT_AUDIO_TRACK_VOLUME = 50; // Static member variables private static final Map<String, Intent> mPendingIntents = new HashMap<String, Intent>(); private static final List<ApiServiceListener> mListeners = new ArrayList<ApiServiceListener>(); private static final IntentPool mIntentPool = new IntentPool(8); private static VideoEditorProject mVideoProject; private static VideoEditor mVideoEditor; private static ServiceMediaProcessingProgressListener mGeneratePreviewListener; private static volatile boolean mExportCancelled; private IntentProcessor mVideoThread; private IntentProcessor mAudioThread; private IntentProcessor mThumbnailThread; private Handler mHandler; private final Runnable mStopRunnable = new Runnable() { @Override public void run() { if (mPendingIntents.size() == 0) { logd("Stop runnable: Stopping service"); stopSelf(); } } }; /** * Generate preview listener */ private final class ServiceMediaProcessingProgressListener implements MediaProcessingProgressListener { // Instance variables private final String mProjectPath; /** * Constructor * * @param projectPath The project path */ public ServiceMediaProcessingProgressListener(String projectPath) { mProjectPath = projectPath; } @Override public void onProgress(Object item, int action, int progress) { final Intent intent = mIntentPool.get(); intent.putExtra(PARAM_OP, OP_VIDEO_EDITOR_GENERATE_PREVIEW_PROGRESS); intent.putExtra(PARAM_PROJECT_PATH, mProjectPath); intent.putExtra(PARAM_ACTION, action); intent.putExtra(PARAM_PROGRESS_VALUE, progress); if (item == null) { // Last callback uses null } else if (item instanceof MediaItem) { intent.putExtra(PARAM_STORYBOARD_ITEM_ID, ((MediaItem)item).getId()); intent.putExtra(PARAM_ATTRIBUTES, MediaItem.class.getCanonicalName()); } else if (item instanceof Transition) { intent.putExtra(PARAM_STORYBOARD_ITEM_ID, ((Transition)item).getId()); intent.putExtra(PARAM_ATTRIBUTES, Transition.class.getCanonicalName()); } else if (item instanceof AudioTrack) { intent.putExtra(PARAM_STORYBOARD_ITEM_ID, ((AudioTrack)item).getId()); intent.putExtra(PARAM_ATTRIBUTES, AudioTrack.class.getCanonicalName()); } else { Log.w(TAG, "Unsupported storyboard item type: " + item.getClass()); return; } completeRequest(intent, null, null, null, null, true); } } /** * @return A unique id */ public static String generateId() { return StringUtils.randomString(6); } /** * Register a listener * * @param listener The listener */ public static void registerListener(ApiServiceListener listener) { mListeners.add(listener); } /** * Unregister a listener * * @param listener The listener */ public static void unregisterListener(ApiServiceListener listener) { mListeners.remove(listener); } /** * Load the projects * * @param context The context */ public static void loadProjects(Context context) { final Intent intent = mIntentPool.get(context, ApiService.class); intent.putExtra(PARAM_OP, OP_VIDEO_EDITOR_LOAD_PROJECTS); startCommand(context, intent); } /** * Create a new VideoEditor project * * @param context The context * @param projectPath The project path * @param projectName The project name * @param movies The array of movie file names to add to the newly * created project * @param photos The array of photo file names to add to the newly * created project * @param themeType The theme type */ public static void createVideoEditor(Context context, String projectPath, String projectName, String[] movies, String[] photos, String themeType) { final Intent intent = mIntentPool.get(context, ApiService.class); intent.putExtra(PARAM_OP, OP_VIDEO_EDITOR_CREATE); intent.putExtra(PARAM_PROJECT_PATH, projectPath); intent.putExtra(PARAM_PROJECT_NAME, projectName); intent.putExtra(PARAM_MOVIES_FILENAMES, movies); intent.putExtra(PARAM_PHOTOS_FILENAMES, photos); intent.putExtra(PARAM_THEME, themeType); startCommand(context, intent); } /** * Create a new VideoEditor project * * @param context The context * @param projectPath The project path */ public static void loadVideoEditor(Context context, String projectPath) { final Intent intent = mIntentPool.get(context, ApiService.class); intent.putExtra(PARAM_OP, OP_VIDEO_EDITOR_LOAD); intent.putExtra(PARAM_PROJECT_PATH, projectPath); startCommand(context, intent); } /** * Export the VideoEditor movie * * @param context The context * @param projectPath The project path * @param filename The export filename * @param height The output movie height * @param bitrate The output movie bitrate */ public static void exportVideoEditor(Context context, String projectPath, String filename, int height, int bitrate) { final Intent intent = mIntentPool.get(context, ApiService.class); intent.putExtra(PARAM_OP, OP_VIDEO_EDITOR_EXPORT); intent.putExtra(PARAM_PROJECT_PATH, projectPath); intent.putExtra(PARAM_FILENAME, filename); intent.putExtra(PARAM_HEIGHT, height); intent.putExtra(PARAM_BITRATE, bitrate); startCommand(context, intent); } /** * Check if export is pending * * @param projectPath The project path * @param filename The export filename * * @return true if the export is pending */ public static boolean isVideoEditorExportPending(String projectPath, String filename) { for (Intent intent : mPendingIntents.values()) { final int op = intent.getIntExtra(PARAM_OP, -1); if (op == OP_VIDEO_EDITOR_EXPORT) { String pp = intent.getStringExtra(PARAM_PROJECT_PATH); if (pp.equals(projectPath)) { String fn = intent.getStringExtra(PARAM_FILENAME); if (fn.equals(filename)) { return true; } } } } return false; } /** * Cancel the export of the specified VideoEditor movie * * @param context The context * @param projectPath The project path * @param filename The export filename */ public static void cancelExportVideoEditor(Context context, String projectPath, String filename) { mExportCancelled = true; final Intent intent = mIntentPool.get(context, ApiService.class); intent.putExtra(PARAM_OP, OP_VIDEO_EDITOR_CANCEL_EXPORT); intent.putExtra(PARAM_PROJECT_PATH, projectPath); intent.putExtra(PARAM_FILENAME, filename); startCommand(context, intent); } /** * Change the aspect ratio * * @param context The context * @param projectPath The project path * @param aspectRatio The aspect ratio */ public static void setAspectRatio(Context context, String projectPath, int aspectRatio) { final Intent intent = mIntentPool.get(context, ApiService.class); intent.putExtra(PARAM_OP, OP_VIDEO_EDITOR_SET_ASPECT_RATIO); intent.putExtra(PARAM_PROJECT_PATH, projectPath); intent.putExtra(PARAM_ASPECT_RATIO, aspectRatio); startCommand(context, intent); } /** * Apply a theme * * @param context The context * @param projectPath The project path * @param theme The theme */ public static void applyTheme(Context context, String projectPath, String theme) { final Intent intent = mIntentPool.get(context, ApiService.class); intent.putExtra(PARAM_OP, OP_VIDEO_EDITOR_APPLY_THEME); intent.putExtra(PARAM_PROJECT_PATH, projectPath); intent.putExtra(PARAM_THEME, theme); startCommand(context, intent); } /** * Checks if the service is busy modifying the timeline. While * the video editor is busy the application should not attempt * to preview the movie. * * @param projectPath The project path * * @return {@code true} if the video editor is modifying the timeline */ public static boolean isProjectBeingEdited(String projectPath) { for (Intent intent : mPendingIntents.values()) { final int op = intent.getIntExtra(PARAM_OP, -1); switch (op) { // When these operations are pending the video editor is not busy. case OP_VIDEO_EDITOR_LOAD_PROJECTS: case OP_VIDEO_EDITOR_SAVE: case OP_MEDIA_ITEM_SET_VOLUME: case OP_MEDIA_ITEM_SET_MUTE: case OP_MEDIA_ITEM_GET_THUMBNAILS: case OP_MEDIA_ITEM_LOAD: case OP_TRANSITION_GET_THUMBNAIL: case OP_AUDIO_TRACK_SET_VOLUME: case OP_AUDIO_TRACK_SET_MUTE: { break; } default: { final String pp = intent.getStringExtra(PARAM_PROJECT_PATH); if (pp != null && pp.equals(projectPath)) { return true; } break; } } } return false; } /** * Save the VideoEditor project * * @param context The context * @param projectPath The project path */ public static void saveVideoEditor(Context context, String projectPath) { final Intent intent = mIntentPool.get(context, ApiService.class); intent.putExtra(PARAM_OP, OP_VIDEO_EDITOR_SAVE); intent.putExtra(PARAM_PROJECT_PATH, projectPath); startCommand(context, intent); } /** * Release the VideoEditor project * * @param context The context * @param projectPath The project path */ public static void releaseVideoEditor(Context context, String projectPath) { final Intent intent = mIntentPool.get(context, ApiService.class); intent.putExtra(PARAM_OP, OP_VIDEO_EDITOR_RELEASE); intent.putExtra(PARAM_PROJECT_PATH, projectPath); startCommand(context, intent); } /** * Delete the project specified by the project path * * @param context The context * @param projectPath The project path */ public static void deleteProject(Context context, String projectPath) { final Intent intent = mIntentPool.get(context, ApiService.class); intent.putExtra(PARAM_OP, OP_VIDEO_EDITOR_DELETE); intent.putExtra(PARAM_PROJECT_PATH, projectPath); startCommand(context, intent); } /** * Add a new video media item after the specified media item id * * @param context The context * @param projectPath The project path * @param mediaItemId The mediaItem id * @param afterMediaItemId The id of the media item preceding the media item * @param uri The media item URI * @param renderingMode The rendering mode * @param themeId The theme id */ public static void addMediaItemVideoUri(Context context, String projectPath, String mediaItemId, String afterMediaItemId, Uri uri, int renderingMode, String themeId) { final Intent intent = mIntentPool.get(context, ApiService.class); intent.putExtra(PARAM_OP, OP_MEDIA_ITEM_ADD_VIDEO_URI); intent.putExtra(PARAM_PROJECT_PATH, projectPath); intent.putExtra(PARAM_STORYBOARD_ITEM_ID, mediaItemId); intent.putExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID, afterMediaItemId); intent.putExtra(PARAM_FILENAME, uri); intent.putExtra(PARAM_MEDIA_ITEM_RENDERING_MODE, renderingMode); intent.putExtra(PARAM_THEME, themeId); startCommand(context, intent); } /** * Add a new image media item after the specified media item id * * @param context The context * @param projectPath The project path * @param mediaItemId The mediaItem id * @param afterMediaItemId The id of the media item preceding the media item * @param uri The media item URI * @param renderingMode The rendering mode * @param durationMs The duration of the item (for images only, ignored for videos) * @param themeId The theme id */ public static void addMediaItemImageUri(Context context, String projectPath, String mediaItemId, String afterMediaItemId, Uri uri, int renderingMode, long durationMs, String themeId) { final Intent intent = mIntentPool.get(context, ApiService.class); intent.putExtra(PARAM_OP, OP_MEDIA_ITEM_ADD_IMAGE_URI); intent.putExtra(PARAM_PROJECT_PATH, projectPath); intent.putExtra(PARAM_STORYBOARD_ITEM_ID, mediaItemId); intent.putExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID, afterMediaItemId); intent.putExtra(PARAM_FILENAME, uri); intent.putExtra(PARAM_MEDIA_ITEM_RENDERING_MODE, renderingMode); intent.putExtra(PARAM_DURATION, durationMs); intent.putExtra(PARAM_THEME, themeId); startCommand(context, intent); } /** * Download or make a copy of an image from the specified URI * * @param context The context * @param projectPath The project path * @param uri The media item URI * @param mimeType The MIME type */ public static void loadMediaItem(Context context, String projectPath, Uri uri, String mimeType) { final Intent intent = mIntentPool.get(context, ApiService.class); intent.putExtra(PARAM_OP, OP_MEDIA_ITEM_LOAD); intent.putExtra(PARAM_PROJECT_PATH, projectPath); intent.putExtra(PARAM_FILENAME, uri); intent.putExtra(PARAM_ATTRIBUTES, mimeType); startCommand(context, intent); } /** * Move a media item after the specified media id * * @param context The context * @param projectPath The project path * @param mediaItemId The id of the media item to move * @param afterMediaItemId The id of the relative media item * @param themeId The theme id */ public static void moveMediaItem(Context context, String projectPath, String mediaItemId, String afterMediaItemId, String themeId) { final Intent intent = mIntentPool.get(context, ApiService.class); intent.putExtra(PARAM_OP, OP_MEDIA_ITEM_MOVE); intent.putExtra(PARAM_PROJECT_PATH, projectPath); intent.putExtra(PARAM_STORYBOARD_ITEM_ID, mediaItemId); intent.putExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID, afterMediaItemId); intent.putExtra(PARAM_THEME, themeId); startCommand(context, intent); } /** * Remove a media item * * @param context The context * @param projectPath The project path * @param mediaItemId The id of the media item to remove * @param themeId The theme id */ public static void removeMediaItem(Context context, String projectPath, String mediaItemId, String themeId) { final Intent intent = mIntentPool.get(context, ApiService.class); intent.putExtra(PARAM_OP, OP_MEDIA_ITEM_REMOVE); intent.putExtra(PARAM_PROJECT_PATH, projectPath); intent.putExtra(PARAM_STORYBOARD_ITEM_ID, mediaItemId); intent.putExtra(PARAM_THEME, themeId); startCommand(context, intent); } /** * Set the rendering mode for a media item * * @param context The context * @param projectPath The project path * @param mediaItemId The id of the media item */ public static void setMediaItemRenderingMode(Context context, String projectPath, String mediaItemId, int renderingMode) { final Intent intent = mIntentPool.get(context, ApiService.class); intent.putExtra(PARAM_OP, OP_MEDIA_ITEM_SET_RENDERING_MODE); intent.putExtra(PARAM_PROJECT_PATH, projectPath); intent.putExtra(PARAM_STORYBOARD_ITEM_ID, mediaItemId); intent.putExtra(PARAM_MEDIA_ITEM_RENDERING_MODE, renderingMode); startCommand(context, intent); } /** * Get the thumbnails of the specified size * * @param context The context * @param projectPath The project path * @param mediaItemId The id of the media item * @param width The width * @param height The height * @param startMs The start time in milliseconds * @param endMs The end time in milliseconds * @param count The number of thumbnails */ public static void getMediaItemThumbnails(Context context, String projectPath, String mediaItemId, int width, int height, long startMs, long endMs, int count, int token, int[] indices) { final Intent intent = mIntentPool.get(context, ApiService.class); intent.putExtra(PARAM_OP, OP_MEDIA_ITEM_GET_THUMBNAILS); intent.putExtra(PARAM_PROJECT_PATH, projectPath); intent.putExtra(PARAM_STORYBOARD_ITEM_ID, mediaItemId); intent.putExtra(PARAM_WIDTH, width); intent.putExtra(PARAM_HEIGHT, height); intent.putExtra(PARAM_START_TIME, startMs); intent.putExtra(PARAM_END_TIME, endMs); intent.putExtra(PARAM_COUNT, count); intent.putExtra(PARAM_TOKEN, token); intent.putExtra(PARAM_INDICES, indices); startCommand(context, intent); } /** * Set the media item duration * * @param context The context * @param projectPath The project path * @param mediaItemId The id of the media item * @param durationMs The media item duration */ public static void setMediaItemDuration(Context context, String projectPath, String mediaItemId, long durationMs) { final Intent intent = mIntentPool.get(context, ApiService.class); intent.putExtra(PARAM_OP, OP_MEDIA_ITEM_SET_DURATION); intent.putExtra(PARAM_PROJECT_PATH, projectPath); intent.putExtra(PARAM_STORYBOARD_ITEM_ID, mediaItemId); intent.putExtra(PARAM_DURATION, durationMs); startCommand(context, intent); } /** * Set the media item boundaries * * @param context The context * @param projectPath The project path * @param mediaItemId The id of the media item * @param beginBoundaryMs The media item begin boundary * @param endBoundaryMs The media item end boundary */ public static void setMediaItemBoundaries(Context context, String projectPath, String mediaItemId, long beginBoundaryMs, long endBoundaryMs) { final Intent intent = mIntentPool.get(context, ApiService.class); intent.putExtra(PARAM_OP, OP_MEDIA_ITEM_SET_BOUNDARIES); intent.putExtra(PARAM_PROJECT_PATH, projectPath); intent.putExtra(PARAM_STORYBOARD_ITEM_ID, mediaItemId); intent.putExtra(PARAM_BEGIN_BOUNDARY, beginBoundaryMs); intent.putExtra(PARAM_END_BOUNDARY, endBoundaryMs); startCommand(context, intent); } /** * Set the media item volume (MediaVideoItem only) * * @param context The context * @param projectPath The project path * @param mediaItemId The id of the media item * @param volumePercentage The volume */ public static void setMediaItemVolume(Context context, String projectPath, String mediaItemId, int volumePercentage) { final Intent intent = mIntentPool.get(context, ApiService.class); intent.putExtra(PARAM_OP, OP_MEDIA_ITEM_SET_VOLUME); intent.putExtra(PARAM_PROJECT_PATH, projectPath); intent.putExtra(PARAM_STORYBOARD_ITEM_ID, mediaItemId); intent.putExtra(PARAM_VOLUME, volumePercentage); startCommand(context, intent); } /** * Mute/unmute the media item (MediaVideoItem only) * * @param context The context * @param projectPath The project path * @param mediaItemId The id of the media item * @param muted true to mute the media item */ public static void setMediaItemMute(Context context, String projectPath, String mediaItemId, boolean muted) { final Intent intent = mIntentPool.get(context, ApiService.class); intent.putExtra(PARAM_OP, OP_MEDIA_ITEM_SET_MUTE); intent.putExtra(PARAM_PROJECT_PATH, projectPath); intent.putExtra(PARAM_STORYBOARD_ITEM_ID, mediaItemId); intent.putExtra(PARAM_MUTE, muted); startCommand(context, intent); } /** * Extract the media item audio waveform * * @param context The context * @param projectPath The project path * @param mediaItemId The id of the media item */ public static void extractMediaItemAudioWaveform(Context context, String projectPath, String mediaItemId) { if (isMediaItemAudioWaveformPending(projectPath, mediaItemId)) { return; } final Intent intent = mIntentPool.get(context, ApiService.class); intent.putExtra(PARAM_OP, OP_MEDIA_ITEM_EXTRACT_AUDIO_WAVEFORM); intent.putExtra(PARAM_PROJECT_PATH, projectPath); intent.putExtra(PARAM_STORYBOARD_ITEM_ID, mediaItemId); startCommand(context, intent); } /** * Check if extract audio waveform is pending for the specified MediaItem * * @param projectPath The project path * @param mediaItemId The MediaItem id * * @return true if the extract audio waveform is pending */ public static boolean isMediaItemAudioWaveformPending(String projectPath, String mediaItemId) { for (Intent intent : mPendingIntents.values()) { int op = intent.getIntExtra(PARAM_OP, -1); if (op == OP_MEDIA_ITEM_EXTRACT_AUDIO_WAVEFORM) { String pp = intent.getStringExtra(PARAM_PROJECT_PATH); if (pp.equals(projectPath)) { String mid = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); if (mid.equals(mediaItemId)) { return true; } } } } return false; } /** * Insert an alpha transition after the specified media item * * @param context The context * @param projectPath The project path * @param afterMediaItemId Insert the transition after the media item with this id * @param transitionId The transition id * @param durationMs The duration in milliseconds * @param behavior The transition behavior * @param maskRawResourceId The mask raw resource id * @param blending The transition blending * @param invert The transition invert */ public static void insertAlphaTransition(Context context, String projectPath, String afterMediaItemId, String transitionId, long durationMs, int behavior, int maskRawResourceId, int blending, boolean invert) { final Intent intent = mIntentPool.get(context, ApiService.class); intent.putExtra(PARAM_OP, OP_TRANSITION_INSERT_ALPHA); intent.putExtra(PARAM_PROJECT_PATH, projectPath); intent.putExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID, afterMediaItemId); intent.putExtra(PARAM_STORYBOARD_ITEM_ID, transitionId); intent.putExtra(PARAM_DURATION, durationMs); intent.putExtra(PARAM_TRANSITION_BEHAVIOR, behavior); intent.putExtra(PARAM_TRANSITION_MASK, maskRawResourceId); intent.putExtra(PARAM_TRANSITION_BLENDING, blending); intent.putExtra(PARAM_TRANSITION_INVERT, invert); startCommand(context, intent); } /** * Insert an crossfade transition after the specified media item * * @param context The context * @param projectPath The project path * @param afterMediaItemId Insert the transition after the media item with this id * @param transitionId The transition id * @param durationMs The duration in milliseconds * @param behavior The transition behavior */ public static void insertCrossfadeTransition(Context context, String projectPath, String afterMediaItemId, String transitionId, long durationMs, int behavior) { final Intent intent = mIntentPool.get(context, ApiService.class); intent.putExtra(PARAM_OP, OP_TRANSITION_INSERT_CROSSFADE); intent.putExtra(PARAM_PROJECT_PATH, projectPath); intent.putExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID, afterMediaItemId); intent.putExtra(PARAM_STORYBOARD_ITEM_ID, transitionId); intent.putExtra(PARAM_DURATION, durationMs); intent.putExtra(PARAM_TRANSITION_BEHAVIOR, behavior); startCommand(context, intent); } /** * Insert a fade-to-black transition after the specified media item * * @param context The context * @param projectPath The project path * @param afterMediaItemId Insert the transition after the media item with this id * @param transitionId The transition id * @param durationMs The duration in milliseconds * @param behavior The transition behavior */ public static void insertFadeBlackTransition(Context context, String projectPath, String afterMediaItemId, String transitionId, long durationMs, int behavior) { final Intent intent = mIntentPool.get(context, ApiService.class); intent.putExtra(PARAM_OP, OP_TRANSITION_INSERT_FADE_BLACK); intent.putExtra(PARAM_PROJECT_PATH, projectPath); intent.putExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID, afterMediaItemId); intent.putExtra(PARAM_STORYBOARD_ITEM_ID, transitionId); intent.putExtra(PARAM_DURATION, durationMs); intent.putExtra(PARAM_TRANSITION_BEHAVIOR, behavior); startCommand(context, intent); } /** * Insert a sliding transition after the specified media item * * @param context The context * @param projectPath The project path * @param afterMediaItemId Insert the transition after the media item with this id * @param transitionId The transition id * @param durationMs The duration in milliseconds * @param behavior The transition behavior * @param direction The slide direction */ public static void insertSlidingTransition(Context context, String projectPath, String afterMediaItemId, String transitionId, long durationMs, int behavior, int direction) { final Intent intent = mIntentPool.get(context, ApiService.class); intent.putExtra(PARAM_OP, OP_TRANSITION_INSERT_SLIDING); intent.putExtra(PARAM_PROJECT_PATH, projectPath); intent.putExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID, afterMediaItemId); intent.putExtra(PARAM_DURATION, durationMs); intent.putExtra(PARAM_STORYBOARD_ITEM_ID, transitionId); intent.putExtra(PARAM_TRANSITION_BEHAVIOR, behavior); intent.putExtra(PARAM_TRANSITION_DIRECTION, direction); startCommand(context, intent); } /** * Remove a transition * * @param context The context * @param projectPath The project path * @param transitionId The id of the transition to remove */ public static void removeTransition(Context context, String projectPath, String transitionId) { final Intent intent = mIntentPool.get(context, ApiService.class); intent.putExtra(PARAM_OP, OP_TRANSITION_REMOVE); intent.putExtra(PARAM_PROJECT_PATH, projectPath); intent.putExtra(PARAM_STORYBOARD_ITEM_ID, transitionId); startCommand(context, intent); } /** * Set a transition duration * * @param context The context * @param projectPath The project path * @param transitionId The id of the transition * @param durationMs The transition duration */ public static void setTransitionDuration(Context context, String projectPath, String transitionId, long durationMs) { final Intent intent = mIntentPool.get(context, ApiService.class); intent.putExtra(PARAM_OP, OP_TRANSITION_SET_DURATION); intent.putExtra(PARAM_PROJECT_PATH, projectPath); intent.putExtra(PARAM_STORYBOARD_ITEM_ID, transitionId); intent.putExtra(PARAM_DURATION, durationMs); startCommand(context, intent); } /** * Get the thumbnail of the specified height * * @param context The context * @param projectPath The project path * @param transitionId The id of the transition * @param height The height */ public static void getTransitionThumbnails(Context context, String projectPath, String transitionId, int height) { final Intent intent = mIntentPool.get(context, ApiService.class); intent.putExtra(PARAM_OP, OP_TRANSITION_GET_THUMBNAIL); intent.putExtra(PARAM_PROJECT_PATH, projectPath); intent.putExtra(PARAM_STORYBOARD_ITEM_ID, transitionId); intent.putExtra(PARAM_HEIGHT, height); startCommand(context, intent); } /** * Check if the transition thumbnailing is in progress * * @param projectPath The project path * @param transitionId The id of the transition * * @return true if the transition thumbnailing is in progress */ public static boolean isTransitionThumbnailsPending(String projectPath, String transitionId) { for (Intent intent : mPendingIntents.values()) { int op = intent.getIntExtra(PARAM_OP, -1); if (op == OP_TRANSITION_GET_THUMBNAIL) { String pp = intent.getStringExtra(PARAM_PROJECT_PATH); if (pp.equals(projectPath)) { String mid = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); if (mid.equals(transitionId)) { return true; } } } } return false; } /** * Add a color effect * * @param context The context * @param projectPath The project path * @param mediaItemId The media item id * @param effectId The effect id * @param startTimeMs The start time in milliseconds * @param durationMs The duration in milliseconds * @param type The effect type * @param param The effect param (if any) */ public static void addEffectColor(Context context, String projectPath, String mediaItemId, String effectId, long startTimeMs, long durationMs, int type, int param) { final Intent intent = mIntentPool.get(context, ApiService.class); intent.putExtra(PARAM_OP, OP_EFFECT_ADD_COLOR); intent.putExtra(PARAM_PROJECT_PATH, projectPath); intent.putExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID, mediaItemId); intent.putExtra(PARAM_STORYBOARD_ITEM_ID, effectId); intent.putExtra(PARAM_START_TIME, startTimeMs); intent.putExtra(PARAM_DURATION, durationMs); intent.putExtra(PARAM_EFFECT_TYPE, type); intent.putExtra(PARAM_EFFECT_PARAM, param); startCommand(context, intent); } /** * Add a Ken Burns effect * * @param context The context * @param projectPath The project path * @param mediaItemId The media item id * @param effectId The effect id * @param startTimeMs The start time * @param durationMs The duration of the item * @param startRect The start rectangle for the Ken Burns effect * @param endRect The end rectangle for the Ken Burns effect */ public static void addEffectKenBurns(Context context, String projectPath, String mediaItemId, String effectId, long startTimeMs, long durationMs, Rect startRect, Rect endRect) { final Intent intent = mIntentPool.get(context, ApiService.class); intent.putExtra(PARAM_OP, OP_EFFECT_ADD_IMAGE_KEN_BURNS); intent.putExtra(PARAM_PROJECT_PATH, projectPath); intent.putExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID, mediaItemId); intent.putExtra(PARAM_STORYBOARD_ITEM_ID, effectId); intent.putExtra(PARAM_START_TIME, startTimeMs); intent.putExtra(PARAM_DURATION, durationMs); intent.putExtra(PARAM_MEDIA_ITEM_START_RECT, startRect); intent.putExtra(PARAM_MEDIA_ITEM_END_RECT, endRect); startCommand(context, intent); } /** * Remove an effect * * @param context The context * @param projectPath The project path * @param mediaItemId The media item id * @param effectId The id of the effect to remove */ public static void removeEffect(Context context, String projectPath, String mediaItemId, String effectId) { final Intent intent = mIntentPool.get(context, ApiService.class); intent.putExtra(PARAM_OP, OP_EFFECT_REMOVE); intent.putExtra(PARAM_PROJECT_PATH, projectPath); intent.putExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID, mediaItemId); intent.putExtra(PARAM_STORYBOARD_ITEM_ID, effectId); startCommand(context, intent); } /** * Add an overlay * * @param context The context * @param projectPath The project path * @param mediaItemId The media item id * @param userAttributes The overlay user attributes * @param startTimeMs The start time in milliseconds * @param durationMs The duration in milliseconds */ public static void addOverlay(Context context, String projectPath, String mediaItemId, String overlayId, Bundle userAttributes, long startTimeMs, long durationMs) { final Intent intent = mIntentPool.get(context, ApiService.class); intent.putExtra(PARAM_OP, OP_OVERLAY_ADD); intent.putExtra(PARAM_PROJECT_PATH, projectPath); intent.putExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID, mediaItemId); intent.putExtra(PARAM_STORYBOARD_ITEM_ID, overlayId); intent.putExtra(PARAM_START_TIME, startTimeMs); intent.putExtra(PARAM_DURATION, durationMs); intent.putExtra(PARAM_ATTRIBUTES, userAttributes); startCommand(context, intent); } /** * Remove an overlay * * @param context The context * @param projectPath The project path * @param mediaItemId The media item id * @param overlayId The id of the overlay to remove */ public static void removeOverlay(Context context, String projectPath, String mediaItemId, String overlayId) { final Intent intent = mIntentPool.get(context, ApiService.class); intent.putExtra(PARAM_OP, OP_OVERLAY_REMOVE); intent.putExtra(PARAM_PROJECT_PATH, projectPath); intent.putExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID, mediaItemId); intent.putExtra(PARAM_STORYBOARD_ITEM_ID, overlayId); startCommand(context, intent); } /** * Set the start time of an overlay * * @param context The context * @param projectPath The project path * @param mediaItemId The media item id * @param overlayId The id of the overlay * @param startTimeMs The start time in milliseconds */ public static void setOverlayStartTime(Context context, String projectPath, String mediaItemId, String overlayId, long startTimeMs) { final Intent intent = mIntentPool.get(context, ApiService.class); intent.putExtra(PARAM_OP, OP_OVERLAY_SET_START_TIME); intent.putExtra(PARAM_PROJECT_PATH, projectPath); intent.putExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID, mediaItemId); intent.putExtra(PARAM_STORYBOARD_ITEM_ID, overlayId); intent.putExtra(PARAM_START_TIME, startTimeMs); startCommand(context, intent); } /** * Set the duration of an overlay * * @param context The context * @param projectPath The project path * @param mediaItemId The media item id * @param overlayId The id of the overlay * @param durationMs The duration in milliseconds */ public static void setOverlayDuration(Context context, String projectPath, String mediaItemId, String overlayId, long durationMs) { final Intent intent = mIntentPool.get(context, ApiService.class); intent.putExtra(PARAM_OP, OP_OVERLAY_SET_DURATION); intent.putExtra(PARAM_PROJECT_PATH, projectPath); intent.putExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID, mediaItemId); intent.putExtra(PARAM_STORYBOARD_ITEM_ID, overlayId); intent.putExtra(PARAM_DURATION, durationMs); startCommand(context, intent); } /** * Set the user attributes of an overlay * * @param context The context * @param projectPath The project path * @param mediaItemId The media item id * @param overlayId The id of the overlay * @param userAttributes The user attributes */ public static void setOverlayUserAttributes(Context context, String projectPath, String mediaItemId, String overlayId, Bundle userAttributes) { final Intent intent = mIntentPool.get(context, ApiService.class); intent.putExtra(PARAM_OP, OP_OVERLAY_SET_ATTRIBUTES); intent.putExtra(PARAM_PROJECT_PATH, projectPath); intent.putExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID, mediaItemId); intent.putExtra(PARAM_STORYBOARD_ITEM_ID, overlayId); intent.putExtra(PARAM_ATTRIBUTES, userAttributes); startCommand(context, intent); } /** * Add an audio track * * @param context The context * @param projectPath The project path * @param id The audio track id * @param uri The audio track URI * @param loop true to loop the audio track */ public static void addAudioTrack(Context context, String projectPath, String id, Uri uri, boolean loop) { final Intent intent = mIntentPool.get(context, ApiService.class); intent.putExtra(PARAM_OP, OP_AUDIO_TRACK_ADD); intent.putExtra(PARAM_PROJECT_PATH, projectPath); intent.putExtra(PARAM_STORYBOARD_ITEM_ID, id); intent.putExtra(PARAM_FILENAME, uri); intent.putExtra(PARAM_LOOP, loop); startCommand(context, intent); } /** * Remove an audio track from the storyboard timeline * * @param context The context * @param projectPath The project path * @param audioTrackId The id of the audio track to remove */ public static void removeAudioTrack(Context context, String projectPath, String audioTrackId) { final Intent intent = mIntentPool.get(context, ApiService.class); intent.putExtra(PARAM_OP, OP_AUDIO_TRACK_REMOVE); intent.putExtra(PARAM_PROJECT_PATH, projectPath); intent.putExtra(PARAM_STORYBOARD_ITEM_ID, audioTrackId); startCommand(context, intent); } /** * Set the audio track boundaries * * @param context The context * @param projectPath The project path * @param audioTrackId The id of the audio track * @param beginBoundaryMs The audio track begin boundary * @param endBoundaryMs The audio track end boundary */ public static void setAudioTrackBoundaries(Context context, String projectPath, String audioTrackId, long beginBoundaryMs, long endBoundaryMs) { final Intent intent = mIntentPool.get(context, ApiService.class); intent.putExtra(PARAM_OP, OP_AUDIO_TRACK_SET_BOUNDARIES); intent.putExtra(PARAM_PROJECT_PATH, projectPath); intent.putExtra(PARAM_STORYBOARD_ITEM_ID, audioTrackId); intent.putExtra(PARAM_BEGIN_BOUNDARY, beginBoundaryMs); intent.putExtra(PARAM_END_BOUNDARY, endBoundaryMs); startCommand(context, intent); } /** * Set the loop flag for an audio track * * @param context The context * @param projectPath The project path * @param audioTrackId The id of the audio track * @param loop true to loop audio */ public static void setAudioTrackLoop(Context context, String projectPath, String audioTrackId, boolean loop) { final Intent intent = mIntentPool.get(context, ApiService.class); intent.putExtra(PARAM_OP, OP_AUDIO_TRACK_SET_LOOP); intent.putExtra(PARAM_PROJECT_PATH, projectPath); intent.putExtra(PARAM_STORYBOARD_ITEM_ID, audioTrackId); intent.putExtra(PARAM_LOOP, loop); startCommand(context, intent); } /** * Set the duck flag for an audio track * * @param context The context * @param projectPath The project path * @param audioTrackId The id of the audio track * @param duck true to enable ducking */ public static void setAudioTrackDuck(Context context, String projectPath, String audioTrackId, boolean duck) { final Intent intent = mIntentPool.get(context, ApiService.class); intent.putExtra(PARAM_OP, OP_AUDIO_TRACK_SET_DUCK); intent.putExtra(PARAM_PROJECT_PATH, projectPath); intent.putExtra(PARAM_STORYBOARD_ITEM_ID, audioTrackId); intent.putExtra(PARAM_DUCK, duck); startCommand(context, intent); } /** * Set the audio track volume * * @param context The context * @param projectPath The project path * @param audioTrackId The id of the audio track * @param volumePercentage The audio track volume (in percentage) */ public static void setAudioTrackVolume(Context context, String projectPath, String audioTrackId, int volumePercentage) { final Intent intent = mIntentPool.get(context, ApiService.class); intent.putExtra(PARAM_OP, OP_AUDIO_TRACK_SET_VOLUME); intent.putExtra(PARAM_PROJECT_PATH, projectPath); intent.putExtra(PARAM_STORYBOARD_ITEM_ID, audioTrackId); intent.putExtra(PARAM_VOLUME, volumePercentage); startCommand(context, intent); } /** * Mute/unmute the audio track * * @param context The context * @param projectPath The project path * @param audioTrackId The id of the audio track * @param muted true to mute the audio track */ public static void setAudioTrackMute(Context context, String projectPath, String audioTrackId, boolean muted) { final Intent intent = mIntentPool.get(context, ApiService.class); intent.putExtra(PARAM_OP, OP_AUDIO_TRACK_SET_MUTE); intent.putExtra(PARAM_PROJECT_PATH, projectPath); intent.putExtra(PARAM_STORYBOARD_ITEM_ID, audioTrackId); intent.putExtra(PARAM_MUTE, muted); startCommand(context, intent); } /** * Extract the audio track audio waveform * * @param context The context * @param projectPath The project path * @param audioTrackId The id of the audio track */ public static void extractAudioTrackAudioWaveform(Context context, String projectPath, String audioTrackId) { if (isAudioTrackAudioWaveformPending(projectPath, audioTrackId)) { return; } final Intent intent = mIntentPool.get(context, ApiService.class); intent.putExtra(PARAM_OP, OP_AUDIO_TRACK_EXTRACT_AUDIO_WAVEFORM); intent.putExtra(PARAM_PROJECT_PATH, projectPath); intent.putExtra(PARAM_STORYBOARD_ITEM_ID, audioTrackId); startCommand(context, intent); } /** * Check if extract audio waveform is pending for the specified audio track * * @param projectPath The project path * @param audioTrackId The audio track id * * @return true if the extract audio waveform is pending */ public static boolean isAudioTrackAudioWaveformPending(String projectPath, String audioTrackId) { for (Intent intent : mPendingIntents.values()) { int op = intent.getIntExtra(PARAM_OP, -1); if (op == OP_AUDIO_TRACK_EXTRACT_AUDIO_WAVEFORM) { String pp = intent.getStringExtra(PARAM_PROJECT_PATH); if (pp.equals(projectPath)) { String mid = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); if (mid.equals(audioTrackId)) { return true; } } } } return false; } /** * Start the service (if it is not running) with the specified Intent * * @param context The context * @param intent The intent * * @return The request id of the pending request */ private static String startCommand(Context context, Intent intent) { final String requestId = StringUtils.randomString(8); intent.putExtra(PARAM_REQUEST_ID, requestId); mPendingIntents.put(requestId, intent); context.startService(intent); final String projectPath = intent.getStringExtra(PARAM_PROJECT_PATH); if (projectPath != null) { final boolean projectEdited = isProjectBeingEdited(projectPath); if (projectEdited) { for (ApiServiceListener listener : mListeners) { listener.onProjectEditState(projectPath, projectEdited); } } } return requestId; } @Override public void onCreate() { super.onCreate(); mHandler = new Handler(Looper.getMainLooper()); mVideoThread = new IntentProcessor("VideoServiceThread"); mVideoThread.start(); mAudioThread = new IntentProcessor("AudioServiceThread"); mAudioThread.start(); mThumbnailThread = new IntentProcessor("ThumbnailServiceThread"); mThumbnailThread.start(); } @Override public int onStartCommand(final Intent intent, int flags, int startId) { final int op = intent.getIntExtra(PARAM_OP, -1); switch(op) { case OP_VIDEO_EDITOR_LOAD_PROJECTS: case OP_VIDEO_EDITOR_CREATE: case OP_VIDEO_EDITOR_LOAD: case OP_VIDEO_EDITOR_SAVE: case OP_VIDEO_EDITOR_RELEASE: case OP_VIDEO_EDITOR_DELETE: case OP_VIDEO_EDITOR_SET_ASPECT_RATIO: case OP_VIDEO_EDITOR_APPLY_THEME: case OP_VIDEO_EDITOR_EXPORT: case OP_VIDEO_EDITOR_CANCEL_EXPORT: case OP_VIDEO_EDITOR_EXPORT_STATUS: case OP_MEDIA_ITEM_ADD_VIDEO_URI: case OP_MEDIA_ITEM_ADD_IMAGE_URI: case OP_MEDIA_ITEM_MOVE: case OP_MEDIA_ITEM_REMOVE: case OP_MEDIA_ITEM_SET_RENDERING_MODE: case OP_MEDIA_ITEM_SET_DURATION: case OP_MEDIA_ITEM_SET_BOUNDARIES: case OP_MEDIA_ITEM_EXTRACT_AUDIO_WAVEFORM: case OP_MEDIA_ITEM_EXTRACT_AUDIO_WAVEFORM_STATUS: case OP_MEDIA_ITEM_LOAD: case OP_MEDIA_ITEM_LOAD_STATUS: case OP_EFFECT_ADD_COLOR: case OP_EFFECT_ADD_IMAGE_KEN_BURNS: case OP_EFFECT_REMOVE: case OP_TRANSITION_INSERT_ALPHA: case OP_TRANSITION_INSERT_CROSSFADE: case OP_TRANSITION_INSERT_FADE_BLACK: case OP_TRANSITION_INSERT_SLIDING: case OP_TRANSITION_REMOVE: case OP_TRANSITION_SET_DURATION: case OP_OVERLAY_ADD: case OP_OVERLAY_REMOVE: case OP_OVERLAY_SET_START_TIME: case OP_OVERLAY_SET_DURATION: case OP_OVERLAY_SET_ATTRIBUTES: case OP_AUDIO_TRACK_ADD: case OP_AUDIO_TRACK_REMOVE: case OP_AUDIO_TRACK_SET_BOUNDARIES: case OP_AUDIO_TRACK_EXTRACT_AUDIO_WAVEFORM: case OP_AUDIO_TRACK_EXTRACT_AUDIO_WAVEFORM_STATUS: { mVideoThread.submit(intent); break; } case OP_TRANSITION_GET_THUMBNAIL: { mThumbnailThread.submit(intent); break; } case OP_MEDIA_ITEM_GET_THUMBNAILS: { final String projectPath = intent.getStringExtra(PARAM_PROJECT_PATH); final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); final int token = intent.getIntExtra(PARAM_TOKEN, 0); // Cancel any pending thumbnail request for the same media item // but with a different token Iterator<Intent> intentQueueIterator = mThumbnailThread.getIntentQueueIterator(); while (intentQueueIterator.hasNext()) { Intent qIntent = intentQueueIterator.next(); int opi = qIntent.getIntExtra(PARAM_OP, -1); String pp = qIntent.getStringExtra(PARAM_PROJECT_PATH); String mid = qIntent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); int tk = qIntent.getIntExtra(PARAM_TOKEN, 0); if (opi == op && pp.equals(projectPath) && mid.equals(mediaItemId) && tk != token) { boolean canceled = mThumbnailThread.cancel(qIntent); if (canceled) { logd("Canceled operation: " + op + " for media item" + mediaItemId); mPendingIntents.remove(qIntent.getStringExtra(PARAM_REQUEST_ID)); mIntentPool.put(qIntent); } break; } } mThumbnailThread.submit(intent); break; } case OP_MEDIA_ITEM_SET_VOLUME: case OP_MEDIA_ITEM_SET_MUTE: case OP_AUDIO_TRACK_SET_VOLUME: case OP_AUDIO_TRACK_SET_MUTE: case OP_AUDIO_TRACK_SET_LOOP: case OP_AUDIO_TRACK_SET_DUCK: { mAudioThread.submit(intent); break; } default: { Log.e(TAG, "No thread assigned: " + op); break; } } return START_NOT_STICKY; } @Override public void onDestroy() { super.onDestroy(); if (mThumbnailThread != null) { mThumbnailThread.quit(); mThumbnailThread = null; } if (mAudioThread != null) { mAudioThread.quit(); mAudioThread = null; } if (mVideoThread != null) { mVideoThread.quit(); mVideoThread = null; } } @Override public IBinder onBind(Intent intent) { return null; } /** * Process the intent * * @param intent The intent */ public void processIntent(final Intent intent) { final int op = intent.getIntExtra(PARAM_OP, -1); VideoEditor videoEditor = null; try { final String projectPath = intent.getStringExtra(PARAM_PROJECT_PATH); // Check if the project path matches the current VideoEditor project switch (op) { case OP_VIDEO_EDITOR_LOAD_PROJECTS: case OP_VIDEO_EDITOR_CREATE: case OP_VIDEO_EDITOR_LOAD: case OP_VIDEO_EDITOR_DELETE: { break; } default: { videoEditor = getVideoEditor(projectPath); if (videoEditor == null) { throw new IllegalArgumentException("Invalid project path: " + projectPath + " for operation: " + op); } break; } } switch (op) { case OP_VIDEO_EDITOR_LOAD_PROJECTS: { logd("OP_LOAD_PROJECTS"); final List<VideoEditorProject> projects = new ArrayList<VideoEditorProject>(); final File dir = FileUtils.getProjectsRootDir(getApplicationContext()); if (dir != null) { // Collect valid projects (project with valid metadata). final File[] files = dir.listFiles(); if (files != null) { for (int i = 0; i < files.length; i++) { if (files[i].isDirectory()) { final String pp = files[i].getAbsolutePath(); try { projects.add(VideoEditorProject.fromXml(null, pp)); } catch (FileNotFoundException ex) { Log.w(TAG, "processIntent: Project file not found: " + pp); FileUtils.deleteDir(new File(pp)); } catch (Exception ex) { ex.printStackTrace(); } } } if (projects.size() > 0) { // Sort the projects in order of "last saved" Collections.sort(projects, new Comparator<VideoEditorProject>() { @Override public int compare(VideoEditorProject object1, VideoEditorProject object2) { if (object1.getLastSaved() > object2.getLastSaved()) { return -1; } else if (object1.getLastSaved() == object2.getLastSaved()) { return 0; } else { return 1; } } }); } } } completeRequest(intent, videoEditor, null, projects, null, true); break; } case OP_VIDEO_EDITOR_CREATE: { logd("OP_VIDEO_EDITOR_CREATE: " + projectPath); try { // Release the current video editor if any releaseEditor(); videoEditor = VideoEditorFactory.create(projectPath); // Add the movies to the timeline final String[] movies = intent.getStringArrayExtra(PARAM_MOVIES_FILENAMES); for (int i = 0; i < movies.length; i++) { final MediaItem mediaItem = new MediaVideoItem(videoEditor, generateId(), movies[i], MediaItem.RENDERING_MODE_BLACK_BORDER); videoEditor.addMediaItem(mediaItem); } // Add the photos to the timeline final String[] photos = intent.getStringArrayExtra(PARAM_PHOTOS_FILENAMES); for (int i = 0; i < photos.length; i++) { final MediaItem mediaItem = new MediaImageItem(videoEditor, generateId(), photos[i], MediaItemUtils.getDefaultImageDuration(), MediaItem.RENDERING_MODE_BLACK_BORDER); videoEditor.addMediaItem(mediaItem); } // Create the project final String projectName = intent.getStringExtra(PARAM_PROJECT_NAME); final String themeId = intent.getStringExtra(PARAM_THEME); if (themeId != null) { applyThemeToMovie(videoEditor, themeId); } // Set the aspect ratio to the aspect ratio of the first item final List<MediaItem> mediaItems = videoEditor.getAllMediaItems(); if (mediaItems.size() > 0) { videoEditor.setAspectRatio(mediaItems.get(0).getAspectRatio()); } // Create the video editor project final VideoEditorProject videoProject = new VideoEditorProject( videoEditor, projectPath, projectName, System.currentTimeMillis(), 0, 0, VideoEditorProject.DEFAULT_ZOOM_LEVEL, null, themeId, null); videoProject.setMediaItems(copyMediaItems( videoEditor.getAllMediaItems())); videoProject.setAudioTracks(copyAudioTracks( videoEditor.getAllAudioTracks())); // Make this project the current project mVideoEditor = videoEditor; mGeneratePreviewListener = new ServiceMediaProcessingProgressListener( projectPath); completeRequest(intent, videoEditor, null, videoProject, null, false); generatePreview(videoEditor, true); completeRequest(intent); } catch (Exception ex) { if (videoEditor != null) { videoEditor.release(); videoEditor = null; } throw ex; } break; } case OP_VIDEO_EDITOR_LOAD: { videoEditor = releaseEditorNot(projectPath); if (videoEditor == null) { // The old project was released. logd("OP_VIDEO_EDITOR_LOAD: Loading: " + projectPath); try { // Load the project videoEditor = VideoEditorFactory.load(projectPath, false); // Load the video editor project final VideoEditorProject videoProject = VideoEditorProject.fromXml( videoEditor, projectPath); videoProject.setMediaItems(copyMediaItems( videoEditor.getAllMediaItems())); videoProject.setAudioTracks(copyAudioTracks( videoEditor.getAllAudioTracks())); // Make this the current project mVideoEditor = videoEditor; mGeneratePreviewListener = new ServiceMediaProcessingProgressListener( projectPath); completeRequest(intent, videoEditor, null, videoProject, null, false); generatePreview(videoEditor, true); completeRequest(intent); } catch (Exception ex) { if (videoEditor != null) { videoEditor.release(); videoEditor = null; } throw ex; } } else { // The project is already loaded. logd("OP_VIDEO_EDITOR_LOAD: Was already loaded: " + projectPath); completeRequest(intent, videoEditor, null, null, null, true); } break; } case OP_VIDEO_EDITOR_SET_ASPECT_RATIO: { logd("OP_VIDEO_EDITOR_SET_ASPECT_RATIO"); videoEditor.setAspectRatio(intent.getIntExtra(PARAM_ASPECT_RATIO, MediaProperties.ASPECT_RATIO_UNDEFINED)); completeRequest(intent, videoEditor, null, null, null, false); generatePreview(videoEditor, true); completeRequest(intent); break; } case OP_VIDEO_EDITOR_APPLY_THEME: { logd("OP_VIDEO_EDITOR_APPLY_THEME"); // Apply the theme applyThemeToMovie(videoEditor, intent.getStringExtra(PARAM_THEME)); final List<MovieMediaItem> mediaItems = copyMediaItems(videoEditor.getAllMediaItems()); final List<MovieAudioTrack> audioTracks = copyAudioTracks(videoEditor.getAllAudioTracks()); completeRequest(intent, videoEditor, null, mediaItems, audioTracks, false); generatePreview(videoEditor, true); completeRequest(intent); break; } case OP_VIDEO_EDITOR_EXPORT: { logd("OP_VIDEO_EDITOR_EXPORT"); exportMovie(videoEditor, intent); break; } case OP_VIDEO_EDITOR_CANCEL_EXPORT: { logd("OP_VIDEO_EDITOR_CANCEL_EXPORT"); videoEditor.cancelExport(intent.getStringExtra(PARAM_FILENAME)); completeRequest(intent, videoEditor, null, null, null, true); break; } case OP_VIDEO_EDITOR_EXPORT_STATUS: { logd("OP_VIDEO_EDITOR_EXPORT_STATUS"); completeRequest(intent, videoEditor, null, null, null, true); break; } case OP_VIDEO_EDITOR_SAVE: { logd("OP_VIDEO_EDITOR_SAVE: " + projectPath); videoEditor.save(); final VideoEditorProject videoProject = getProject(projectPath); if (videoProject != null) { videoProject.saveToXml(); } completeRequest(intent, videoEditor, null, null, null, true); break; } case OP_VIDEO_EDITOR_RELEASE: { logd("OP_VIDEO_EDITOR_RELEASE: " + projectPath); releaseEditor(projectPath); completeRequest(intent, videoEditor, null, null, null, true); break; } case OP_VIDEO_EDITOR_DELETE: { logd("OP_VIDEO_EDITOR_DELETE: " + projectPath); releaseEditor(projectPath); // Delete all the files and the project folder. FileUtils.deleteDir(new File(projectPath)); completeRequest(intent, videoEditor, null, null, null, true); break; } case OP_MEDIA_ITEM_ADD_VIDEO_URI: { logd("OP_MEDIA_ITEM_ADD_VIDEO_URI: " + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); final Uri data = intent.getParcelableExtra(PARAM_FILENAME); String filename = null; // Get the filename Cursor cursor = null; try { cursor = getContentResolver().query(data, new String[] {Video.Media.DATA}, null, null, null); if (cursor.moveToFirst()) { filename = cursor.getString(0); } } finally { if (cursor != null) { cursor.close(); } } if (filename == null) { throw new IllegalArgumentException("Media file not found: " + data); } final MediaItem mediaItem = new MediaVideoItem(videoEditor, intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID), filename, intent.getIntExtra(PARAM_MEDIA_ITEM_RENDERING_MODE, 0)); videoEditor.insertMediaItem(mediaItem, intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID)); // If this is the first media item, change the aspect ratio final Integer aspectRatio; if (videoEditor.getAllMediaItems().size() == 1) { videoEditor.setAspectRatio(mediaItem.getAspectRatio()); aspectRatio = videoEditor.getAspectRatio(); } else { aspectRatio = null; } // Apply the theme if any final String themeId = intent.getStringExtra(PARAM_THEME); if (themeId != null) { applyThemeToMediaItem(videoEditor, themeId, mediaItem); } completeRequest(intent, videoEditor, null, new MovieMediaItem(mediaItem), aspectRatio, false); generatePreview(videoEditor, true); completeRequest(intent); break; } case OP_MEDIA_ITEM_ADD_IMAGE_URI: { logd("OP_MEDIA_ITEM_ADD_IMAGE_URI: " + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); final Uri data = intent.getParcelableExtra(PARAM_FILENAME); String filename = null; // Get the filename Cursor cursor = null; try { cursor = getContentResolver().query(data, new String[] {Images.Media.DATA, Images.Media.MIME_TYPE}, null, null, null); if (cursor.moveToFirst()) { filename = cursor.getString(0); final String mimeType = cursor.getString(1); if ("image/jpeg".equals(mimeType)) { try { final File outputFile = new File(projectPath, "gallery_image_" + generateId() + ".jpg"); if (ImageUtils.transformJpeg(filename, outputFile)) { filename = outputFile.getAbsolutePath(); } } catch (Exception ex) { // Ignore the exception and continue Log.w(TAG, "Could not transform JPEG: " + filename, ex); } } } } finally { if (cursor != null) { cursor.close(); } } if (filename == null) { throw new IllegalArgumentException("Media file not found: " + data); } final MediaItem mediaItem = new MediaImageItem(videoEditor, intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID), filename, intent.getLongExtra(PARAM_DURATION, 0), intent.getIntExtra(PARAM_MEDIA_ITEM_RENDERING_MODE, 0)); videoEditor.insertMediaItem(mediaItem, intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID)); // If this is the first media item, change the aspect ratio final Integer aspectRatio; if (videoEditor.getAllMediaItems().size() == 1) { videoEditor.setAspectRatio(mediaItem.getAspectRatio()); aspectRatio = videoEditor.getAspectRatio(); } else { aspectRatio = null; } // Apply the theme if any final String themeId = intent.getStringExtra(PARAM_THEME); if (themeId != null) { applyThemeToMediaItem(videoEditor, themeId, mediaItem); } completeRequest(intent, videoEditor, null, new MovieMediaItem(mediaItem), aspectRatio, false); generatePreview(videoEditor, true); completeRequest(intent); break; } case OP_MEDIA_ITEM_LOAD: { final Uri data = intent.getParcelableExtra(PARAM_FILENAME); logd("OP_MEDIA_ITEM_LOAD: " + data); final Intent requestIntent = intent; new Thread() { @Override public void run() { InputStream is = null; FileOutputStream fos = null; final File file = new File(projectPath, "download_" + generateId()); final Intent statusIntent = mIntentPool.get(); statusIntent.putExtra(PARAM_OP, OP_MEDIA_ITEM_LOAD_STATUS); statusIntent.putExtra(PARAM_PROJECT_PATH, requestIntent.getStringExtra(PARAM_PROJECT_PATH)); statusIntent.putExtra(PARAM_INTENT, requestIntent); try { is = getContentResolver().openInputStream(data); // Save the input stream to a file fos = new FileOutputStream(file); final byte[] readBuffer = new byte[2048]; int readBytes; while ((readBytes = is.read(readBuffer)) >= 0) { fos.write(readBuffer, 0, readBytes); } } catch (Exception ex) { Log.e(TAG, "Cannot open input stream for: " + data); statusIntent.putExtra(PARAM_EXCEPTION, ex); file.delete(); } finally { if (is != null) { try { is.close(); } catch (IOException ex) { Log.e(TAG, "Cannot close input stream for: " + data); } } if (fos != null) { try { fos.flush(); fos.close(); } catch (IOException ex) { Log.e(TAG, "Cannot close output stream for: " + data); } } } if (!statusIntent.hasExtra(PARAM_EXCEPTION)) { final String filename = file.getAbsolutePath(); try { final String mimeType = getContentResolver().getType(data); if ("image/jpeg".equals(mimeType)) { final File outputFile = new File(projectPath, "download_" + generateId() + ".jpg"); if (ImageUtils.transformJpeg(filename, outputFile)) { // Delete the downloaded file file.delete(); statusIntent.putExtra(PARAM_FILENAME, outputFile.getAbsolutePath()); } else { statusIntent.putExtra(PARAM_FILENAME, filename); } } else { statusIntent.putExtra(PARAM_FILENAME, filename); } } catch (Exception ex) { // Ignore the exception and continue Log.w(TAG, "Could not transform JPEG: " + filename, ex); statusIntent.putExtra(PARAM_FILENAME, filename); } } mVideoThread.submit(statusIntent); } }.start(); break; } case OP_MEDIA_ITEM_LOAD_STATUS: { final Intent originalIntent = (Intent)intent.getParcelableExtra(PARAM_INTENT); if (intent.hasExtra(PARAM_EXCEPTION)) { // final Exception exception = (Exception)intent.getSerializableExtra(PARAM_EXCEPTION); completeRequest(intent, videoEditor, exception, null, originalIntent, true); } else { completeRequest(intent, videoEditor, null, intent.getStringExtra(PARAM_FILENAME), originalIntent, true); } break; } case OP_MEDIA_ITEM_MOVE: { final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); logd("OP_MEDIA_ITEM_MOVE: " + mediaItemId); // Determine the position of the media item we are moving final List<MediaItem> mediaItems = videoEditor.getAllMediaItems(); final int mediaItemsCount = mediaItems.size(); int movedItemPosition = -1; MediaItem movedMediaItem = null; for (int i = 0; i < mediaItemsCount; i++) { final MediaItem mi = mediaItems.get(i); if (mi.getId().equals(mediaItemId)) { movedMediaItem = mi; movedItemPosition = i; break; } } if (movedItemPosition == -1) { throw new IllegalArgumentException("Moved MediaItem not found: " + mediaItemId); } final Transition beginTransition = movedMediaItem.getBeginTransition(); final Transition endTransition = movedMediaItem.getEndTransition(); final String afterMediaItemId = intent.getStringExtra( PARAM_RELATIVE_STORYBOARD_ITEM_ID); videoEditor.moveMediaItem(mediaItemId, afterMediaItemId); // Apply the theme if any final String themeId = intent.getStringExtra(PARAM_THEME); if (themeId != null) { // Apply the theme at the removed position applyThemeAfterMove(videoEditor, themeId, movedMediaItem, movedItemPosition, beginTransition, endTransition); } final List<MovieMediaItem> mediaItemsCopy = copyMediaItems(mediaItems); completeRequest(intent, videoEditor, null, mediaItemsCopy, null, false); generatePreview(videoEditor, true); completeRequest(intent); break; } case OP_MEDIA_ITEM_REMOVE: { final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); logd("OP_MEDIA_ITEM_REMOVE: " + mediaItemId); // Determine the position of the media item we are removing final List<MediaItem> mediaItems = videoEditor.getAllMediaItems(); final int mediaItemsCount = mediaItems.size(); int removedItemPosition = -1; MediaItem removedMediaItem = null; for (int i = 0; i < mediaItemsCount; i++) { if (mediaItems.get(i).getId().equals(mediaItemId)) { removedMediaItem = mediaItems.get(i); removedItemPosition = i; break; } } if (removedMediaItem == null) { throw new IllegalArgumentException("MediaItem not found: " + mediaItemId); } final Transition beginTransition = removedMediaItem.getBeginTransition(); final Transition endTransition = removedMediaItem.getEndTransition(); videoEditor.removeMediaItem(mediaItemId); // Apply the theme if any MovieTransition movieTransition = null; final String themeId = intent.getStringExtra(PARAM_THEME); if (themeId != null && mediaItems.size() > 0) { final Transition transition = applyThemeAfterRemove(videoEditor, themeId, removedItemPosition, beginTransition, endTransition); if (transition != null) { movieTransition = new MovieTransition(transition); } } completeRequest(intent, videoEditor, null, movieTransition, null, false); generatePreview(videoEditor, true); completeRequest(intent); break; } case OP_MEDIA_ITEM_SET_RENDERING_MODE: { final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); logd("OP_MEDIA_ITEM_SET_RENDERING_MODE: " + mediaItemId); final MediaItem mediaItem = videoEditor.getMediaItem(mediaItemId); if (mediaItem == null) { throw new IllegalArgumentException("MediaItem not found: " + mediaItemId); } mediaItem.setRenderingMode(intent.getIntExtra(PARAM_MEDIA_ITEM_RENDERING_MODE, MediaItem.RENDERING_MODE_BLACK_BORDER)); completeRequest(intent, videoEditor, null, null, null, false); generatePreview(videoEditor, true); completeRequest(intent); break; } case OP_MEDIA_ITEM_SET_DURATION: { final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); logd("OP_MEDIA_ITEM_SET_DURATION: " + mediaItemId); final MediaImageItem mediaItem = (MediaImageItem)videoEditor.getMediaItem(mediaItemId); if (mediaItem == null) { throw new IllegalArgumentException("MediaItem not found: " + mediaItemId); } final long durationMs = intent.getLongExtra(PARAM_DURATION, 0); mediaItem.setDuration(durationMs); // Adjust all effects to the new duration final List<Effect> effects = mediaItem.getAllEffects(); for (Effect effect : effects) { effect.setDuration(durationMs); } completeRequest(intent, videoEditor, null, new MovieMediaItem(mediaItem), null, false); generatePreview(videoEditor, true); completeRequest(intent); break; } case OP_MEDIA_ITEM_SET_BOUNDARIES: { final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); final MediaVideoItem mediaItem = (MediaVideoItem)videoEditor.getMediaItem(mediaItemId); if (mediaItem == null) { throw new IllegalArgumentException("MediaItem not found: " + mediaItemId); } mediaItem.setExtractBoundaries(intent.getLongExtra(PARAM_BEGIN_BOUNDARY, 0), intent.getLongExtra(PARAM_END_BOUNDARY, 0)); final List<Overlay> overlays = mediaItem.getAllOverlays(); if (overlays.size() > 0) { // Adjust the overlay final Overlay overlay = overlays.get(0); if (overlay.getStartTime() < mediaItem.getBoundaryBeginTime()) { overlay.setStartTime(mediaItem.getBoundaryBeginTime()); overlay.setDuration(Math.min(overlay.getDuration(), mediaItem.getTimelineDuration())); } else if (overlay.getStartTime() + overlay.getDuration() > mediaItem.getBoundaryEndTime()) { overlay.setStartTime(Math.max(mediaItem.getBoundaryBeginTime(), mediaItem.getBoundaryEndTime() - overlay.getDuration())); overlay.setDuration(mediaItem.getBoundaryEndTime() - overlay.getStartTime()); } } completeRequest(intent, videoEditor, null, new MovieMediaItem(mediaItem), null, false); generatePreview(videoEditor, true); completeRequest(intent); break; } case OP_MEDIA_ITEM_GET_THUMBNAILS: { // Note that this command is executed in the thumbnail thread final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); logd("OP_MEDIA_ITEM_GET_THUMBNAILS: " + mediaItemId); final MediaItem mediaItem = videoEditor.getMediaItem(mediaItemId); if (mediaItem == null) { throw new IllegalArgumentException("MediaItem not found: " + mediaItemId); } final VideoEditor ve = videoEditor; // Just to make it "final" mediaItem.getThumbnailList( intent.getIntExtra(PARAM_WIDTH, 0), intent.getIntExtra(PARAM_HEIGHT, 0), intent.getLongExtra(PARAM_START_TIME, 0), intent.getLongExtra(PARAM_END_TIME, 0), intent.getIntExtra(PARAM_COUNT, 0), intent.getIntArrayExtra(PARAM_INDICES), new GetThumbnailListCallback() { public void onThumbnail(Bitmap bitmap, int index) { completeRequest( intent, ve, null, bitmap, Integer.valueOf(index), false); } } ); completeRequest(intent, videoEditor, null, null, null, true); break; } case OP_MEDIA_ITEM_SET_VOLUME: { final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); logd("OP_MEDIA_ITEM_SET_VOLUME: " + mediaItemId); final MediaItem mediaItem = videoEditor.getMediaItem(mediaItemId); if (mediaItem != null && mediaItem instanceof MediaVideoItem) { ((MediaVideoItem)mediaItem).setVolume(intent.getIntExtra(PARAM_VOLUME, 0)); completeRequest(intent, videoEditor, null, null, null, false); generatePreview(videoEditor, false); completeRequest(intent); } else { throw new IllegalArgumentException("MediaItem not found: " + mediaItemId); } break; } case OP_MEDIA_ITEM_SET_MUTE: { final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); logd("OP_MEDIA_ITEM_SET_MUTE: " + mediaItemId); final MediaItem mediaItem = videoEditor.getMediaItem(mediaItemId); if (mediaItem != null && mediaItem instanceof MediaVideoItem) { ((MediaVideoItem)mediaItem).setMute(intent.getBooleanExtra(PARAM_MUTE, false)); completeRequest(intent, videoEditor, null, null, null, false); generatePreview(videoEditor, false); completeRequest(intent); } else { throw new IllegalArgumentException("MediaItem not found: " + mediaItemId); } break; } case OP_MEDIA_ITEM_EXTRACT_AUDIO_WAVEFORM: { final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); logd("OP_MEDIA_ITEM_EXTRACT_AUDIO_WAVEFORM: " + mediaItemId); final MediaItem mediaItem = videoEditor.getMediaItem(mediaItemId); if (mediaItem != null && mediaItem instanceof MediaVideoItem) { final MediaVideoItem movieMediaItem = ((MediaVideoItem)mediaItem); final WaveformData waveformData = movieMediaItem.getWaveformData(); if (waveformData == null) { extractMediaItemAudioWaveform(intent, videoEditor, movieMediaItem); completeRequest(intent, videoEditor, null, movieMediaItem.getWaveformData(), null, true); } else { completeRequest(intent, videoEditor, null, waveformData, null, true); } } else { throw new IllegalArgumentException("MediaItem not found: " + mediaItemId); } break; } case OP_TRANSITION_INSERT_ALPHA: { logd("OP_TRANSITION_INSERT_ALPHA: " + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); final String afterMediaItemId = intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID); final MediaItem afterMediaItem; if (afterMediaItemId != null) { afterMediaItem = videoEditor.getMediaItem(afterMediaItemId); } else { afterMediaItem = null; } final int maskRawResourceId = intent.getIntExtra(PARAM_TRANSITION_MASK, R.raw.mask_contour); final MediaItem beforeMediaItem = nextMediaItem(videoEditor, afterMediaItemId); final Transition transition = new TransitionAlpha( intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID), afterMediaItem, beforeMediaItem, intent.getLongExtra(PARAM_DURATION, 0), intent.getIntExtra(PARAM_TRANSITION_BEHAVIOR, Transition.BEHAVIOR_LINEAR), FileUtils.getMaskFilename(getApplicationContext(), maskRawResourceId), intent.getIntExtra(PARAM_TRANSITION_BLENDING, 100), intent.getBooleanExtra(PARAM_TRANSITION_INVERT, false)); videoEditor.addTransition(transition); completeRequest(intent, videoEditor, null, transition, null, false); generatePreview(videoEditor, true); completeRequest(intent); break; } case OP_TRANSITION_INSERT_CROSSFADE: { logd("OP_TRANSITION_INSERT_CROSSFADE: " + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); final String afterMediaItemId = intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID); final MediaItem afterMediaItem; if (afterMediaItemId != null) { afterMediaItem = videoEditor.getMediaItem(afterMediaItemId); } else { afterMediaItem = null; } final MediaItem beforeMediaItem = nextMediaItem(videoEditor, afterMediaItemId); final Transition transition = new TransitionCrossfade( intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID), afterMediaItem, beforeMediaItem, intent.getLongExtra(PARAM_DURATION, 0), intent.getIntExtra(PARAM_TRANSITION_BEHAVIOR, Transition.BEHAVIOR_LINEAR)); videoEditor.addTransition(transition); completeRequest(intent, videoEditor, null, transition, null, false); generatePreview(videoEditor, true); completeRequest(intent); break; } case OP_TRANSITION_INSERT_FADE_BLACK: { logd("OP_TRANSITION_INSERT_FADE_TO_BLACK: " + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); final String afterMediaItemId = intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID); final MediaItem afterMediaItem; if (afterMediaItemId != null) { afterMediaItem = videoEditor.getMediaItem(afterMediaItemId); } else { afterMediaItem = null; } final MediaItem beforeMediaItem = nextMediaItem(videoEditor, afterMediaItemId); final Transition transition = new TransitionFadeBlack( intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID), afterMediaItem, beforeMediaItem, intent.getLongExtra(PARAM_DURATION, 0), intent.getIntExtra(PARAM_TRANSITION_BEHAVIOR, Transition.BEHAVIOR_LINEAR)); videoEditor.addTransition(transition); completeRequest(intent, videoEditor, null, transition, null, false); generatePreview(videoEditor, true); completeRequest(intent); break; } case OP_TRANSITION_INSERT_SLIDING: { logd("OP_TRANSITION_INSERT_SLIDING: " + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); final String afterMediaItemId = intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID); final MediaItem afterMediaItem; if (afterMediaItemId != null) { afterMediaItem = videoEditor.getMediaItem(afterMediaItemId); } else { afterMediaItem = null; } final MediaItem beforeMediaItem = nextMediaItem(videoEditor, afterMediaItemId); final Transition transition = new TransitionSliding( intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID), afterMediaItem, beforeMediaItem, intent.getLongExtra(PARAM_DURATION, 0), intent.getIntExtra(PARAM_TRANSITION_BEHAVIOR, Transition.BEHAVIOR_LINEAR), intent.getIntExtra(PARAM_TRANSITION_DIRECTION, TransitionSliding.DIRECTION_RIGHT_OUT_LEFT_IN)); videoEditor.addTransition(transition); completeRequest(intent, videoEditor, null, transition, null, false); generatePreview(videoEditor, true); completeRequest(intent); break; } case OP_TRANSITION_REMOVE: { logd("OP_TRANSITION_REMOVE: " + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); videoEditor.removeTransition(intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); completeRequest(intent, videoEditor, null, null, null, false); generatePreview(videoEditor, true); completeRequest(intent); break; } case OP_TRANSITION_SET_DURATION: { final String transitionId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); logd("OP_TRANSITION_SET_DURATION: " + transitionId); final Transition transition = videoEditor.getTransition(transitionId); if (transition == null) { throw new IllegalArgumentException("Transition not found: " + transitionId); } transition.setDuration(intent.getLongExtra(PARAM_DURATION, 0)); completeRequest(intent, videoEditor, null, null, null, false); generatePreview(videoEditor, true); completeRequest(intent); break; } case OP_TRANSITION_GET_THUMBNAIL: { final String transitionId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); logd("OP_TRANSITION_GET_THUMBNAIL: " + transitionId); final Transition transition = videoEditor.getTransition(transitionId); if (transition == null) { throw new IllegalArgumentException("Transition not found: " + transitionId); } final int height = intent.getIntExtra(PARAM_HEIGHT, 0); final MediaItem afterMediaItem = transition.getAfterMediaItem(); final Bitmap[] thumbnails = new Bitmap[2]; if (afterMediaItem != null) { thumbnails[0] = afterMediaItem.getThumbnail( (afterMediaItem.getWidth() * height) / afterMediaItem.getHeight(), height, afterMediaItem.getTimelineDuration()); } else { thumbnails[0] = null; } final MediaItem beforeMediaItem = transition.getBeforeMediaItem(); if (beforeMediaItem != null) { thumbnails[1] = beforeMediaItem.getThumbnail( (beforeMediaItem.getWidth() * height) / beforeMediaItem.getHeight(), height, 0); } else { thumbnails[1] = null; } completeRequest(intent, videoEditor, null, thumbnails, null, true); break; } case OP_EFFECT_ADD_COLOR: { logd("OP_EFFECT_ADD_COLOR: " + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); final MediaItem mediaItem = videoEditor.getMediaItem( intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID)); if (mediaItem == null) { throw new IllegalArgumentException("MediaItem not found: " + intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID)); } // Remove any existing effect final List<Effect> effects = mediaItem.getAllEffects(); for (Effect effect : effects) { mediaItem.removeEffect(effect.getId()); } final Effect effect = new EffectColor(mediaItem, intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID), intent.getLongExtra(PARAM_START_TIME, -1), intent.getLongExtra(PARAM_DURATION, 0), intent.getIntExtra(PARAM_EFFECT_TYPE, -1), intent.getIntExtra(PARAM_EFFECT_PARAM, -1)); mediaItem.addEffect(effect); completeRequest(intent, videoEditor, null, new MovieEffect(effect), null, false); generatePreview(videoEditor, true); completeRequest(intent); break; } case OP_EFFECT_ADD_IMAGE_KEN_BURNS: { logd("OP_EFFECT_ADD_IMAGE_KEN_BURNS: " + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); final MediaItem mediaItem = videoEditor.getMediaItem( intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID)); if (mediaItem == null) { throw new IllegalArgumentException("MediaItem not found: " + intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID)); } // Remove any existing effect final List<Effect> effects = mediaItem.getAllEffects(); for (Effect effect : effects) { mediaItem.removeEffect(effect.getId()); } final Effect effect = new EffectKenBurns(mediaItem, intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID), (Rect)intent.getParcelableExtra(PARAM_MEDIA_ITEM_START_RECT), (Rect)intent.getParcelableExtra(PARAM_MEDIA_ITEM_END_RECT), intent.getLongExtra(PARAM_START_TIME, 0), intent.getLongExtra(PARAM_DURATION, 0)); mediaItem.addEffect(effect); completeRequest(intent, videoEditor, null, new MovieEffect(effect), null, false); generatePreview(videoEditor, true); completeRequest(intent); break; } case OP_EFFECT_REMOVE: { logd("OP_EFFECT_REMOVE: " + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); final MediaItem mediaItem = videoEditor.getMediaItem( intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID)); if (mediaItem == null) { throw new IllegalArgumentException("MediaItem not found: " + intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID)); } mediaItem.removeEffect(intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); completeRequest(intent, videoEditor, null, null, null, false); generatePreview(videoEditor, true); completeRequest(intent); break; } case OP_OVERLAY_ADD: { logd("OP_OVERLAY_ADD: " + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); final MediaItem mediaItem = videoEditor.getMediaItem( intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID)); if (mediaItem == null) { throw new IllegalArgumentException("MediaItem not found: " + intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID)); } // Remove any existing overlays final List<Overlay> overlays = mediaItem.getAllOverlays(); for (Overlay overlay : overlays) { mediaItem.removeOverlay(overlay.getId()); } final int scaledWidth, scaledHeight; if (mediaItem instanceof MediaVideoItem) { scaledWidth = ((MediaVideoItem)mediaItem).getWidth(); scaledHeight = ((MediaVideoItem)mediaItem).getHeight(); } else { scaledWidth = ((MediaImageItem)mediaItem).getScaledWidth(); scaledHeight = ((MediaImageItem)mediaItem).getScaledHeight(); } final Bundle userAttributes = intent.getBundleExtra(PARAM_ATTRIBUTES); final int overlayType = MovieOverlay.getType(userAttributes); final String title = MovieOverlay.getTitle(userAttributes); final String subTitle = MovieOverlay.getSubtitle(userAttributes); final OverlayFrame overlay = new OverlayFrame(mediaItem, intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID), ImageUtils.buildOverlayBitmap(getApplicationContext(), null, overlayType, title, subTitle, scaledWidth, scaledHeight), intent.getLongExtra(PARAM_START_TIME, -1), intent.getLongExtra(PARAM_DURATION, 0)); // Set the user attributes for (String name : userAttributes.keySet()) { if (MovieOverlay.getAttributeType(name).equals(Integer.class)) { overlay.setUserAttribute(name, Integer.toString(userAttributes.getInt(name))); } else { // Strings overlay.setUserAttribute(name, userAttributes.getString(name)); } } mediaItem.addOverlay(overlay); completeRequest(intent, videoEditor, null, new MovieOverlay(overlay), null, false); generatePreview(videoEditor, true); completeRequest(intent); break; } case OP_OVERLAY_REMOVE: { logd("OP_OVERLAY_REMOVE: " + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); final MediaItem mediaItem = videoEditor.getMediaItem( intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID)); if (mediaItem == null) { throw new IllegalArgumentException("MediaItem not found: " + intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID)); } mediaItem.removeOverlay(intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); completeRequest(intent, videoEditor, null, null, null, false); generatePreview(videoEditor, true); completeRequest(intent); break; } case OP_OVERLAY_SET_START_TIME: { logd("OP_OVERLAY_SET_START_TIME: " + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); final MediaItem mediaItem = videoEditor.getMediaItem( intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID)); if (mediaItem == null) { throw new IllegalArgumentException("MediaItem not found: " + intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID)); } final Overlay overlay = mediaItem.getOverlay( intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); if (overlay == null) { throw new IllegalArgumentException("Overlay not found: " + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); } overlay.setStartTime(intent.getLongExtra(PARAM_START_TIME, 0)); completeRequest(intent, videoEditor, null, null, null, false); generatePreview(videoEditor, true); completeRequest(intent); break; } case OP_OVERLAY_SET_DURATION: { logd("OP_OVERLAY_SET_DURATION: " + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); final MediaItem mediaItem = videoEditor.getMediaItem( intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID)); if (mediaItem == null) { throw new IllegalArgumentException("MediaItem not found: " + intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID)); } final Overlay overlay = mediaItem.getOverlay( intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); if (overlay == null) { throw new IllegalArgumentException("Overlay not found: " + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); } overlay.setDuration(intent.getLongExtra(PARAM_DURATION, 0)); completeRequest(intent, videoEditor, null, null, null, false); generatePreview(videoEditor, true); completeRequest(intent); break; } case OP_OVERLAY_SET_ATTRIBUTES: { logd("OP_OVERLAY_SET_ATTRIBUTES: " + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); final MediaItem mediaItem = videoEditor.getMediaItem( intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID)); if (mediaItem == null) { throw new IllegalArgumentException("MediaItem not found: " + intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID)); } final Overlay overlay = mediaItem.getOverlay( intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); if (overlay == null) { throw new IllegalArgumentException("Overlay not found: " + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); } final int scaledWidth, scaledHeight; if (mediaItem instanceof MediaVideoItem) { scaledWidth = ((MediaVideoItem)mediaItem).getWidth(); scaledHeight = ((MediaVideoItem)mediaItem).getHeight(); } else { scaledWidth = ((MediaImageItem)mediaItem).getScaledWidth(); scaledHeight = ((MediaImageItem)mediaItem).getScaledHeight(); } final Bundle userAttributes = intent.getBundleExtra(PARAM_ATTRIBUTES); final int overlayType = MovieOverlay.getType(userAttributes); final String title = MovieOverlay.getTitle(userAttributes); final String subTitle = MovieOverlay.getSubtitle(userAttributes); ((OverlayFrame)overlay).setBitmap( ImageUtils.buildOverlayBitmap(getApplicationContext(), null, overlayType, title, subTitle, scaledWidth, scaledHeight)); for (String name : userAttributes.keySet()) { if (MovieOverlay.getAttributeType(name).equals(Integer.class)) { overlay.setUserAttribute(name, Integer.toString(userAttributes.getInt(name))); } else { // Strings overlay.setUserAttribute(name, userAttributes.getString(name)); } } completeRequest(intent, videoEditor, null, null, null, false); generatePreview(videoEditor, true); completeRequest(intent); break; } case OP_AUDIO_TRACK_ADD: { logd("OP_AUDIO_TRACK_ADD: " + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); final Uri data = intent.getParcelableExtra(PARAM_FILENAME); String filename = null; // Get the filename Cursor cursor = null; try { cursor = getContentResolver().query(data, new String[] {Audio.Media.DATA}, null, null, null); if (cursor.moveToFirst()) { filename = cursor.getString(0); } } finally { if (cursor != null) { cursor.close(); } } if (filename == null) { throw new IllegalArgumentException("Media file not found: " + data); } final AudioTrack audioTrack = new AudioTrack(videoEditor, intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID), filename); audioTrack.enableDucking(DUCK_THRESHOLD, DUCK_TRACK_VOLUME); audioTrack.setVolume(DEFAULT_AUDIO_TRACK_VOLUME); if (intent.getBooleanExtra(PARAM_LOOP, false)) { audioTrack.enableLoop(); } else { audioTrack.disableLoop(); } videoEditor.addAudioTrack(audioTrack); completeRequest(intent, videoEditor, null, new MovieAudioTrack(audioTrack), null, false); // This is needed to decode the audio file into a PCM file generatePreview(videoEditor, false); completeRequest(intent); break; } case OP_AUDIO_TRACK_REMOVE: { logd("OP_AUDIO_TRACK_REMOVE: " + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); videoEditor.removeAudioTrack(intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); completeRequest(intent, videoEditor, null, null, null, false); generatePreview(videoEditor, false); completeRequest(intent); break; } case OP_AUDIO_TRACK_SET_BOUNDARIES: { final String audioTrackId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); logd("OP_AUDIO_TRACK_SET_BOUNDARIES: " + audioTrackId); final AudioTrack audioTrack = videoEditor.getAudioTrack(audioTrackId); if (audioTrack == null) { throw new IllegalArgumentException("AudioTrack not found: " + audioTrackId); } audioTrack.setExtractBoundaries(intent.getLongExtra(PARAM_BEGIN_BOUNDARY, 0), intent.getLongExtra(PARAM_END_BOUNDARY, 0)); completeRequest(intent, videoEditor, null, null, null, false); generatePreview(videoEditor, false); completeRequest(intent); break; } case OP_AUDIO_TRACK_SET_LOOP: { final String audioTrackId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); logd("OP_AUDIO_TRACK_SET_LOOP: " + audioTrackId); final AudioTrack audioTrack = videoEditor.getAudioTrack(audioTrackId); if (audioTrack == null) { throw new IllegalArgumentException("AudioTrack not found: " + audioTrackId); } if (intent.getBooleanExtra(PARAM_LOOP, false)) { audioTrack.enableLoop(); } else { audioTrack.disableLoop(); } completeRequest(intent, videoEditor, null, null, null, false); generatePreview(videoEditor, false); completeRequest(intent); break; } case OP_AUDIO_TRACK_SET_DUCK: { final String audioTrackId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); logd("OP_AUDIO_TRACK_SET_DUCK: " + audioTrackId); final AudioTrack audioTrack = videoEditor.getAudioTrack(audioTrackId); if (audioTrack == null) { throw new IllegalArgumentException("AudioTrack not found: " + audioTrackId); } if (intent.getBooleanExtra(PARAM_DUCK, false)) { audioTrack.enableDucking(DUCK_THRESHOLD, DUCK_TRACK_VOLUME); } else { audioTrack.disableDucking(); } completeRequest(intent, videoEditor, null, null, null, false); generatePreview(videoEditor, false); completeRequest(intent); break; } case OP_AUDIO_TRACK_SET_VOLUME: { final String audioTrackId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); logd("OP_AUDIO_TRACK_SET_VOLUME: " + audioTrackId); final AudioTrack audioTrack = videoEditor.getAudioTrack(audioTrackId); if (audioTrack == null) { throw new IllegalArgumentException("AudioTrack not found: " + audioTrackId); } audioTrack.setVolume(intent.getIntExtra(PARAM_VOLUME, 0)); completeRequest(intent, videoEditor, null, null, null, false); generatePreview(videoEditor, false); completeRequest(intent); break; } case OP_AUDIO_TRACK_SET_MUTE: { final String audioTrackId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); logd("OP_AUDIO_TRACK_SET_MUTE: " + audioTrackId); final AudioTrack audioTrack = videoEditor.getAudioTrack(audioTrackId); if (audioTrack == null) { throw new IllegalArgumentException("AudioTrack not found: " + audioTrackId); } audioTrack.setMute(intent.getBooleanExtra(PARAM_MUTE, false)); completeRequest(intent, videoEditor, null, null, null, false); generatePreview(videoEditor, false); completeRequest(intent); break; } case OP_AUDIO_TRACK_EXTRACT_AUDIO_WAVEFORM: { final String audioTrackId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); logd("OP_AUDIO_TRACK_EXTRACT_AUDIO_WAVEFORM: " + audioTrackId); final AudioTrack audioTrack = videoEditor.getAudioTrack(audioTrackId); if (audioTrack == null) { throw new IllegalArgumentException("AudioTrack not found: " + audioTrackId); } final WaveformData waveformData = audioTrack.getWaveformData(); if (waveformData == null) { extractAudioTrackAudioWaveform(intent, videoEditor, audioTrack); completeRequest(intent, videoEditor, null, audioTrack.getWaveformData(), null, true); } else { completeRequest(intent, videoEditor, null, waveformData, null, true); } break; } default: { throw new IllegalArgumentException("Unhandled operation: " + op); } } } catch (Exception ex) { ex.printStackTrace(); completeRequest(intent, videoEditor, ex, null, null, true); } } /** * Complete the request * * @param intent The intent * @param videoEditor The video editor * @param exception The exception * @param result The result object * @param extraResult The extra result object * @param finalize true if the request should be finalized */ private void completeRequest(final Intent intent, final VideoEditor videoEditor, final Exception exception, final Object result, final Object extraResult, final boolean finalize) { mHandler.post(new Runnable() { @Override public void run() { onIntentProcessed(intent, videoEditor, result, extraResult, exception, finalize); } }); } /** * Complete the request * * @param intent The intent */ private void completeRequest(final Intent intent) { mHandler.post (new Runnable() { @Override public void run() { finalizeRequest(intent); mIntentPool.put(intent); } }); } /** * Callback called after the specified intent is processed. * * @param intent The intent * @param videoEditor The VideoEditor on which the operation was performed * @param result The result object * @param extraResult The extra result object * @param ex The exception * @param finalize true if the request should be finalized */ @SuppressWarnings("unchecked") public void onIntentProcessed(final Intent intent, VideoEditor videoEditor, Object result, Object extraResult, Exception ex, boolean finalize) { final String projectPath = intent.getStringExtra(PARAM_PROJECT_PATH); final int op = intent.getIntExtra(PARAM_OP, -1); switch (op) { case OP_VIDEO_EDITOR_LOAD_PROJECTS: { if (finalize) { finalizeRequest(intent); } final List<VideoEditorProject> projects = (List<VideoEditorProject>)result; for (ApiServiceListener listener : mListeners) { listener.onProjectsLoaded(projects, ex); } break; } case OP_VIDEO_EDITOR_CREATE: { if (finalize) { finalizeRequest(intent); } // Release the old project if (mVideoProject != null) { mVideoProject.release(); mVideoProject = null; } if (ex != null) { FileUtils.deleteDir(new File(projectPath)); } else { mVideoProject = (VideoEditorProject)result; } for (ApiServiceListener listener : mListeners) { listener.onVideoEditorCreated(projectPath, mVideoProject, videoEditor != null ? videoEditor.getAllMediaItems() : null, videoEditor != null ? videoEditor.getAllAudioTracks() : null, ex); } break; } case OP_VIDEO_EDITOR_LOAD: { if (finalize) { finalizeRequest(intent); } if (result != null) { // A new project was created if (mVideoProject != null) { mVideoProject.release(); mVideoProject = null; } mVideoProject = (VideoEditorProject)result; } for (ApiServiceListener listener : mListeners) { listener.onVideoEditorLoaded(projectPath, mVideoProject, ex == null ? videoEditor.getAllMediaItems() : null, ex == null ? videoEditor.getAllAudioTracks() : null, ex); } break; } case OP_VIDEO_EDITOR_SET_ASPECT_RATIO: { if (finalize) { finalizeRequest(intent); } final int aspectRatio = intent.getIntExtra(PARAM_ASPECT_RATIO, MediaProperties.ASPECT_RATIO_UNDEFINED); if (ex == null) { final VideoEditorProject videoProject = getProject(projectPath); if (videoProject != null) { videoProject.setAspectRatio(aspectRatio); } } for (ApiServiceListener listener : mListeners) { listener.onVideoEditorAspectRatioSet(projectPath, aspectRatio, ex); } break; } case OP_VIDEO_EDITOR_APPLY_THEME: { if (finalize) { finalizeRequest(intent); } final String theme = intent.getStringExtra(PARAM_THEME); if (ex == null) { final VideoEditorProject videoProject = getProject(projectPath); if (videoProject != null) { videoProject.setTheme(theme); videoProject.setMediaItems((List<MovieMediaItem>)result); videoProject.setAudioTracks((List<MovieAudioTrack>)extraResult); } } for (ApiServiceListener listener : mListeners) { listener.onVideoEditorThemeApplied(projectPath, theme, ex); } break; } case OP_VIDEO_EDITOR_GENERATE_PREVIEW_PROGRESS: { final String className = intent.getStringExtra(PARAM_ATTRIBUTES); final String itemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); final int action = intent.getIntExtra(PARAM_ACTION, MediaProcessingProgressListener.ACTION_DECODE); final int progress = intent.getIntExtra(PARAM_PROGRESS_VALUE, 0); for (ApiServiceListener listener : mListeners) { listener.onVideoEditorGeneratePreviewProgress(projectPath, className, itemId, action, progress); } break; } case OP_VIDEO_EDITOR_EXPORT: { // The finalizeRequest() call and listener callbacks are done in // OP_VIDEO_EDITOR_EXPORT_STATUS intent handling (where we are // called originalIntent). break; } case OP_VIDEO_EDITOR_CANCEL_EXPORT: { if (finalize) { finalizeRequest(intent); } for (ApiServiceListener listener : mListeners) { listener.onVideoEditorExportCanceled(projectPath, intent.getStringExtra(PARAM_FILENAME)); } break; } case OP_VIDEO_EDITOR_EXPORT_STATUS: { // This operation is for the service internal use only if (finalize) { finalizeRequest(intent); } final String filename = intent.getStringExtra(PARAM_FILENAME); if (intent.hasExtra(PARAM_EXCEPTION)) { // Complete final Intent originalIntent = (Intent)intent.getParcelableExtra(PARAM_INTENT); finalizeRequest(originalIntent); mIntentPool.put(originalIntent); final Exception exception = (Exception)intent.getSerializableExtra(PARAM_EXCEPTION); final VideoEditorProject videoProject = getProject(projectPath); final boolean cancelled = intent.getBooleanExtra(PARAM_CANCELLED, false); if (!cancelled && videoProject != null && exception == null) { final Uri uri = (Uri)intent.getParcelableExtra(PARAM_MOVIE_URI); videoProject.addExportedMovieUri(uri); } for (ApiServiceListener listener : mListeners) { listener.onVideoEditorExportComplete( projectPath, filename, exception, cancelled); } } else { // Progress for (ApiServiceListener listener : mListeners) { listener.onVideoEditorExportProgress(projectPath, filename, intent.getIntExtra(PARAM_PROGRESS_VALUE, -1)); } // The original request is still pending } break; } case OP_VIDEO_EDITOR_SAVE: { if (finalize) { finalizeRequest(intent); } for (ApiServiceListener listener : mListeners) { listener.onVideoEditorSaved(projectPath, ex); } break; } case OP_VIDEO_EDITOR_RELEASE: { if (finalize) { finalizeRequest(intent); } final VideoEditorProject videoProject = getProject(projectPath); if (videoProject != null) { videoProject.release(); if (mVideoProject == videoProject) { mVideoProject = null; } } for (ApiServiceListener listener : mListeners) { listener.onVideoEditorReleased(projectPath, ex); } break; } case OP_VIDEO_EDITOR_DELETE: { if (finalize) { finalizeRequest(intent); } final VideoEditorProject videoProject = getProject(projectPath); if (videoProject != null) { videoProject.release(); if (mVideoProject == videoProject) { mVideoProject = null; } } for (ApiServiceListener listener : mListeners) { listener.onVideoEditorDeleted(projectPath, ex); } break; } case OP_MEDIA_ITEM_ADD_VIDEO_URI: { if (finalize) { finalizeRequest(intent); } final String afterMediaItemId = intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID); final MovieMediaItem movieMediaItem = (MovieMediaItem)result; final VideoEditorProject videoProject = getProject(projectPath); if (videoProject != null) { if (ex == null && extraResult != null) { // The aspect ratio has changed videoProject.setAspectRatio((Integer)extraResult); } if (ex == null) { videoProject.insertMediaItem(movieMediaItem, afterMediaItemId); } } for (ApiServiceListener listener : mListeners) { listener.onMediaItemAdded(projectPath, intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID), movieMediaItem, afterMediaItemId, MediaVideoItem.class, (Integer)extraResult, ex); } break; } case OP_MEDIA_ITEM_ADD_IMAGE_URI: { if (finalize) { finalizeRequest(intent); } final String afterMediaItemId = intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID); final MovieMediaItem movieMediaItem = (MovieMediaItem)result; final VideoEditorProject videoProject = getProject(projectPath); if (videoProject != null) { if (ex == null && extraResult != null) { // The aspect ratio has changed videoProject.setAspectRatio((Integer)extraResult); } if (ex == null) { videoProject.insertMediaItem(movieMediaItem, afterMediaItemId); } } for (ApiServiceListener listener : mListeners) { listener.onMediaItemAdded(projectPath, intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID), movieMediaItem, afterMediaItemId, MediaImageItem.class, (Integer)extraResult, ex); } break; } case OP_MEDIA_ITEM_LOAD: { // Note that this message is handled only if the download // cannot start. final Uri data = (Uri)intent.getParcelableExtra(PARAM_FILENAME); final String mimeType = intent.getStringExtra(PARAM_ATTRIBUTES); if (finalize) { finalizeRequest(intent); } for (ApiServiceListener listener : mListeners) { listener.onMediaLoaded(projectPath, data, mimeType, null, ex); } break; } case OP_MEDIA_ITEM_LOAD_STATUS: { if (finalize) { finalizeRequest(intent); } final Intent originalIntent = (Intent)intent.getParcelableExtra(PARAM_INTENT); final Uri data = (Uri)originalIntent.getParcelableExtra(PARAM_FILENAME); final String mimeType = originalIntent.getStringExtra(PARAM_ATTRIBUTES); finalizeRequest(originalIntent); mIntentPool.put(originalIntent); final String filename = intent.getStringExtra(PARAM_FILENAME); if (ex == null && filename != null) { final VideoEditorProject videoProject = getProject(projectPath); videoProject.addDownload(data.toString(), mimeType, filename); } for (ApiServiceListener listener : mListeners) { listener.onMediaLoaded(projectPath, data, mimeType, filename, ex); } break; } case OP_MEDIA_ITEM_MOVE: { if (finalize) { finalizeRequest(intent); } if (ex == null) { final VideoEditorProject videoProject = getProject(projectPath); if (videoProject != null) { videoProject.setMediaItems((List<MovieMediaItem>)result); } } for (ApiServiceListener listener : mListeners) { listener.onMediaItemMoved(projectPath, intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID), intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID), ex); } break; } case OP_MEDIA_ITEM_REMOVE: { if (finalize) { finalizeRequest(intent); } final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); final MovieTransition transition = (MovieTransition)result; if (ex == null) { final VideoEditorProject videoProject = getProject(projectPath); if (videoProject != null) { videoProject.removeMediaItem(mediaItemId, transition); } } for (ApiServiceListener listener : mListeners) { listener.onMediaItemRemoved(projectPath, mediaItemId, transition, ex); } break; } case OP_MEDIA_ITEM_SET_RENDERING_MODE: { if (finalize) { finalizeRequest(intent); } final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); final int renderingMode = intent.getIntExtra(PARAM_MEDIA_ITEM_RENDERING_MODE, MediaItem.RENDERING_MODE_BLACK_BORDER); final VideoEditorProject videoProject = getProject(projectPath); if (videoProject != null) { final MovieMediaItem mediaItem = videoProject.getMediaItem(mediaItemId); if (mediaItem != null) { videoProject.setClean(false); if (ex == null) { mediaItem.setRenderingMode(renderingMode); } else { mediaItem.setAppRenderingMode(mediaItem.getRenderingMode()); } } } for (ApiServiceListener listener : mListeners) { listener.onMediaItemRenderingModeSet(projectPath, mediaItemId, renderingMode, ex); } break; } case OP_MEDIA_ITEM_SET_DURATION: { if (finalize) { finalizeRequest(intent); } final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); final VideoEditorProject videoProject = getProject(projectPath); if (videoProject != null) { if (ex == null) { videoProject.updateMediaItem((MovieMediaItem)result); } else { final MovieMediaItem oldMediaItem = videoProject.getMediaItem(mediaItemId); if (oldMediaItem != null) { videoProject.setClean(false); oldMediaItem.setAppExtractBoundaries(0, oldMediaItem.getDuration()); } } } for (ApiServiceListener listener : mListeners) { listener.onMediaItemDurationSet(projectPath, mediaItemId, intent.getLongExtra(PARAM_DURATION, 0), ex); } break; } case OP_MEDIA_ITEM_SET_BOUNDARIES: { if (finalize) { finalizeRequest(intent); } final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); final VideoEditorProject videoProject = getProject(projectPath); if (videoProject != null) { if (ex == null) { final MovieMediaItem mediaItem = (MovieMediaItem)result; videoProject.updateMediaItem(mediaItem); } else { final MovieMediaItem oldMediaItem = videoProject.getMediaItem(mediaItemId); if (oldMediaItem != null) { videoProject.setClean(false); oldMediaItem.setAppExtractBoundaries( oldMediaItem.getBoundaryBeginTime(), oldMediaItem.getBoundaryEndTime()); } } } for (ApiServiceListener listener : mListeners) { listener.onMediaItemBoundariesSet(projectPath, mediaItemId, intent.getLongExtra(PARAM_BEGIN_BOUNDARY, 0), intent.getLongExtra(PARAM_END_BOUNDARY, 0), ex); } break; } case OP_MEDIA_ITEM_GET_THUMBNAILS: { if (finalize) { finalizeRequest(intent); break; } final Bitmap bitmap = (Bitmap)result; final int index = (Integer)extraResult; boolean used = false; for (ApiServiceListener listener : mListeners) { used |= listener.onMediaItemThumbnail(projectPath, intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID), bitmap, index, intent.getIntExtra(PARAM_TOKEN, 0), ex); } if (used == false) { if (bitmap != null) { bitmap.recycle(); } } break; } case OP_MEDIA_ITEM_SET_VOLUME: { if (finalize) { finalizeRequest(intent); } final VideoEditorProject videoProject = getProject(projectPath); if (videoProject != null) { final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); final MovieMediaItem mediaItem = videoProject.getMediaItem(mediaItemId); if (mediaItem != null) { videoProject.setClean(false); if (ex == null) { mediaItem.setVolume(intent.getIntExtra(PARAM_VOLUME, 0)); } else { mediaItem.setAppVolume(mediaItem.getVolume()); } } } break; } case OP_MEDIA_ITEM_SET_MUTE: { if (finalize) { finalizeRequest(intent); } final VideoEditorProject videoProject = getProject(projectPath); if (videoProject != null) { final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); final MovieMediaItem mediaItem = videoProject.getMediaItem(mediaItemId); if (mediaItem != null) { videoProject.setClean(false); if (ex == null) { mediaItem.setMute(intent.getBooleanExtra(PARAM_MUTE, false)); } else { mediaItem.setAppMute(mediaItem.isMuted()); } } } break; } case OP_MEDIA_ITEM_EXTRACT_AUDIO_WAVEFORM_STATUS: { if (finalize) { finalizeRequest(intent); } final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); final int progress = intent.getIntExtra(PARAM_PROGRESS_VALUE, 0); for (ApiServiceListener listener : mListeners) { listener.onMediaItemExtractAudioWaveformProgress(projectPath, mediaItemId, progress); } break; } case OP_MEDIA_ITEM_EXTRACT_AUDIO_WAVEFORM: { if (finalize) { finalizeRequest(intent); } final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); final VideoEditorProject videoProject = getProject(projectPath); if (ex == null && videoProject != null) { if (result != null) { final MovieMediaItem mediaItem = videoProject.getMediaItem(mediaItemId); if (mediaItem != null) { videoProject.setClean(false); mediaItem.setWaveformData((WaveformData)result); } } } for (ApiServiceListener listener : mListeners) { listener.onMediaItemExtractAudioWaveformComplete(projectPath, mediaItemId, ex); } break; } case OP_TRANSITION_INSERT_ALPHA: case OP_TRANSITION_INSERT_CROSSFADE: case OP_TRANSITION_INSERT_FADE_BLACK: case OP_TRANSITION_INSERT_SLIDING: { if (finalize) { finalizeRequest(intent); } final String afterMediaItemId = intent.getStringExtra( PARAM_RELATIVE_STORYBOARD_ITEM_ID); final MovieTransition movieTransition; final VideoEditorProject videoProject = getProject(projectPath); if (videoProject != null) { final Transition transition = (Transition)result; // This is null for start transitions if (ex == null) { movieTransition = new MovieTransition(transition); videoProject.addTransition(movieTransition, afterMediaItemId); } else { movieTransition = null; } } else { movieTransition = null; } for (ApiServiceListener listener : mListeners) { listener.onTransitionInserted(projectPath, movieTransition, afterMediaItemId, ex); } break; } case OP_TRANSITION_REMOVE: { if (finalize) { finalizeRequest(intent); } final String transitionId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); final VideoEditorProject videoProject = getProject(projectPath); if (videoProject != null) { if (ex == null) { videoProject.removeTransition(transitionId); } } for (ApiServiceListener listener : mListeners) { listener.onTransitionRemoved(projectPath, transitionId, ex); } break; } case OP_TRANSITION_SET_DURATION: { if (finalize) { finalizeRequest(intent); } final String transitionId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); final long durationMs = intent.getLongExtra(PARAM_DURATION, 0); final VideoEditorProject videoProject = getProject(projectPath); if (videoProject != null) { final MovieTransition transition = videoProject.getTransition(transitionId); if (transition != null) { videoProject.setClean(false); if (ex == null) { transition.setDuration(durationMs); } else { transition.setAppDuration(transition.getDuration()); } } } for (ApiServiceListener listener : mListeners) { listener.onTransitionDurationSet(projectPath, transitionId, durationMs, ex); } break; } case OP_TRANSITION_GET_THUMBNAIL: { if (finalize) { finalizeRequest(intent); } final Bitmap[] bitmaps = (Bitmap[])result; boolean used = false; for (ApiServiceListener listener : mListeners) { used |= listener.onTransitionThumbnails(projectPath, intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID), bitmaps, ex); } if (used == false) { if (bitmaps != null) { for (int i = 0; i < bitmaps.length; i++) { if (bitmaps[i] != null) { bitmaps[i].recycle(); } } } } break; } case OP_OVERLAY_ADD: { if (finalize) { finalizeRequest(intent); } final String mediaItemId = intent.getStringExtra( PARAM_RELATIVE_STORYBOARD_ITEM_ID); final MovieOverlay movieOverlay = (MovieOverlay)result; final VideoEditorProject videoProject = getProject(projectPath); if (videoProject != null) { if (ex == null) { videoProject.addOverlay(mediaItemId, movieOverlay); } } for (ApiServiceListener listener : mListeners) { listener.onOverlayAdded(projectPath, movieOverlay, mediaItemId, ex); } break; } case OP_OVERLAY_REMOVE: { if (finalize) { finalizeRequest(intent); } final String mediaItemId = intent.getStringExtra( PARAM_RELATIVE_STORYBOARD_ITEM_ID); final String overlayId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); final VideoEditorProject videoProject = getProject(projectPath); if (videoProject != null) { if (ex == null) { videoProject.removeOverlay(mediaItemId, overlayId); } } for (ApiServiceListener listener : mListeners) { listener.onOverlayRemoved(projectPath, overlayId, mediaItemId, ex); } break; } case OP_OVERLAY_SET_START_TIME: { if (finalize) { finalizeRequest(intent); } final String mediaItemId = intent.getStringExtra( PARAM_RELATIVE_STORYBOARD_ITEM_ID); final String overlayId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); final long startTimeMs = intent.getLongExtra(PARAM_START_TIME, 0); final VideoEditorProject videoProject = getProject(projectPath); if (videoProject != null) { final MovieOverlay overlay = videoProject.getOverlay(mediaItemId, overlayId); if (overlay != null) { videoProject.setClean(false); if (ex == null) { overlay.setStartTime(startTimeMs); } else { overlay.setAppStartTime(overlay.getStartTime()); } } } for (ApiServiceListener listener : mListeners) { listener.onOverlayStartTimeSet(projectPath, overlayId, mediaItemId, startTimeMs, ex); } break; } case OP_OVERLAY_SET_DURATION: { if (finalize) { finalizeRequest(intent); } final String mediaItemId = intent.getStringExtra( PARAM_RELATIVE_STORYBOARD_ITEM_ID); final String overlayId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); final long durationMs = intent.getLongExtra(PARAM_DURATION, 0); final VideoEditorProject videoProject = getProject(projectPath); if (videoProject != null) { final MovieOverlay overlay = videoProject.getOverlay(mediaItemId, overlayId); if (overlay != null) { videoProject.setClean(false); if (ex == null) { overlay.setDuration(durationMs); } else { overlay.setAppDuration(overlay.getDuration()); } } } for (ApiServiceListener listener : mListeners) { listener.onOverlayDurationSet(projectPath, overlayId, mediaItemId, durationMs, ex); } break; } case OP_OVERLAY_SET_ATTRIBUTES: { if (finalize) { finalizeRequest(intent); } final String mediaItemId = intent.getStringExtra( PARAM_RELATIVE_STORYBOARD_ITEM_ID); final String overlayId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); final Bundle userAttributes = intent.getBundleExtra(PARAM_ATTRIBUTES); final VideoEditorProject videoProject = getProject(projectPath); if (videoProject != null) { if (ex == null) { final MovieOverlay overlay = videoProject.getOverlay(mediaItemId, overlayId); if (overlay != null) { videoProject.setClean(false); overlay.updateUserAttributes(userAttributes); } } } for (ApiServiceListener listener : mListeners) { listener.onOverlayUserAttributesSet(projectPath, overlayId, mediaItemId, userAttributes, ex); } break; } case OP_EFFECT_ADD_COLOR: case OP_EFFECT_ADD_IMAGE_KEN_BURNS:{ if (finalize) { finalizeRequest(intent); } final String mediaItemId = intent.getStringExtra( PARAM_RELATIVE_STORYBOARD_ITEM_ID); final MovieEffect movieEffect = (MovieEffect)result; final VideoEditorProject videoProject = getProject(projectPath); if (videoProject != null) { if (ex == null) { videoProject.addEffect(mediaItemId, movieEffect); } } for (ApiServiceListener listener : mListeners) { listener.onEffectAdded(projectPath, movieEffect, mediaItemId, ex); } break; } case OP_EFFECT_REMOVE: { if (finalize) { finalizeRequest(intent); } final String mediaItemId = intent.getStringExtra( PARAM_RELATIVE_STORYBOARD_ITEM_ID); final String effectId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); final VideoEditorProject videoProject = getProject(projectPath); if (videoProject != null) { if (ex == null) { videoProject.removeEffect(mediaItemId, effectId); } } for (ApiServiceListener listener : mListeners) { listener.onEffectRemoved(projectPath, effectId, mediaItemId, ex); } break; } case OP_AUDIO_TRACK_ADD: { if (finalize) { finalizeRequest(intent); } final MovieAudioTrack movieAudioTrack = (MovieAudioTrack)result; final VideoEditorProject videoProject = getProject(projectPath); if (videoProject != null) { if (ex == null) { videoProject.addAudioTrack(movieAudioTrack); } } for (ApiServiceListener listener : mListeners) { listener.onAudioTrackAdded(projectPath, movieAudioTrack, ex); } break; } case OP_AUDIO_TRACK_REMOVE: { if (finalize) { finalizeRequest(intent); } final String audioTrackId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); final VideoEditorProject videoProject = getProject(projectPath); if (videoProject != null) { if (ex == null) { videoProject.removeAudioTrack(audioTrackId); } } for (ApiServiceListener listener : mListeners) { listener.onAudioTrackRemoved(projectPath, audioTrackId, ex); } break; } case OP_AUDIO_TRACK_SET_BOUNDARIES: { if (finalize) { finalizeRequest(intent); } final String audioTrackId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); final long beginBoundary = intent.getLongExtra(PARAM_BEGIN_BOUNDARY, 0); final long endBoundary = intent.getLongExtra(PARAM_END_BOUNDARY, 0); final VideoEditorProject videoProject = getProject(projectPath); if (videoProject != null) { if (ex == null) { final MovieAudioTrack audioTrack = videoProject.getAudioTrack(audioTrackId); if (audioTrack != null) { videoProject.setClean(false); audioTrack.setExtractBoundaries(beginBoundary, endBoundary); } } } for (ApiServiceListener listener : mListeners) { listener.onAudioTrackBoundariesSet(projectPath, audioTrackId, beginBoundary, endBoundary, ex); } break; } case OP_AUDIO_TRACK_SET_LOOP: { if (finalize) { finalizeRequest(intent); } final VideoEditorProject videoProject = getProject(projectPath); if (videoProject != null) { final String audioTrackId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); final MovieAudioTrack audioTrack = videoProject.getAudioTrack(audioTrackId); if (audioTrack != null) { videoProject.setClean(false); if (ex == null) { audioTrack.enableLoop(intent.getBooleanExtra(PARAM_LOOP, false)); } else { audioTrack.enableAppLoop(audioTrack.isLooping()); } } } break; } case OP_AUDIO_TRACK_SET_DUCK: { if (finalize) { finalizeRequest(intent); } final VideoEditorProject videoProject = getProject(projectPath); if (videoProject != null) { final String audioTrackId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); final MovieAudioTrack audioTrack = videoProject.getAudioTrack(audioTrackId); if (audioTrack != null) { videoProject.setClean(false); if (ex == null) { audioTrack.enableDucking(intent.getBooleanExtra(PARAM_DUCK, false)); } else { audioTrack.enableAppDucking(audioTrack.isDuckingEnabled()); } } } break; } case OP_AUDIO_TRACK_SET_VOLUME: { if (finalize) { finalizeRequest(intent); } final VideoEditorProject videoProject = getProject(projectPath); if (videoProject != null) { final String audioTrackId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); final MovieAudioTrack audioTrack = videoProject.getAudioTrack(audioTrackId); if (audioTrack != null) { videoProject.setClean(false); if (ex == null) { audioTrack.setVolume(intent.getIntExtra(PARAM_VOLUME, 0)); } else { audioTrack.setAppVolume(audioTrack.getVolume()); } } } break; } case OP_AUDIO_TRACK_SET_MUTE: { if (finalize) { finalizeRequest(intent); } final VideoEditorProject videoProject = getProject(projectPath); if (videoProject != null) { final String audioTrackId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); final MovieAudioTrack audioTrack = videoProject.getAudioTrack(audioTrackId); if (audioTrack != null) { videoProject.setClean(false); if (ex == null) { audioTrack.setMute(intent.getBooleanExtra(PARAM_MUTE, false)); } else { audioTrack.setAppMute(audioTrack.isMuted()); } } } break; } case OP_AUDIO_TRACK_EXTRACT_AUDIO_WAVEFORM_STATUS: { if (finalize) { finalizeRequest(intent); } final String audioTrackId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); final int progress = intent.getIntExtra(PARAM_PROGRESS_VALUE, 0); for (ApiServiceListener listener : mListeners) { listener.onAudioTrackExtractAudioWaveformProgress(projectPath, audioTrackId, progress); } break; } case OP_AUDIO_TRACK_EXTRACT_AUDIO_WAVEFORM: { if (finalize) { finalizeRequest(intent); } final String audioTrackId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); final VideoEditorProject videoProject = getProject(projectPath); if (ex == null && videoProject != null) { if (result != null) { final MovieAudioTrack audioTrack = videoProject.getAudioTrack(audioTrackId); if (audioTrack != null) { videoProject.setClean(false); audioTrack.setWaveformData((WaveformData)result); } } } for (ApiServiceListener listener : mListeners) { listener.onAudioTrackExtractAudioWaveformComplete(projectPath, audioTrackId, ex); } break; } default: { if (finalize) { finalizeRequest(intent); } break; } } if (finalize) { mIntentPool.put(intent); } } /** * Finalizes a request. Calls the listeners that are interested in project status * change and stops this service if there are no more pending intents. * * @param intent The intent that just completed */ private void finalizeRequest(Intent intent) { mPendingIntents.remove(intent.getStringExtra(PARAM_REQUEST_ID)); final String projectPath = intent.getStringExtra(PARAM_PROJECT_PATH); if (projectPath != null) { final boolean projectEdited = isProjectBeingEdited(projectPath); if (projectEdited == false) { for (ApiServiceListener listener : mListeners) { listener.onProjectEditState(projectPath, projectEdited); } } } if (mPendingIntents.size() == 0) { // Cancel the current timer if any. Extend the timeout by 5000 ms. mHandler.removeCallbacks(mStopRunnable); // Start a timer which will stop the service if the queue of // pending intent will be empty at that time. // This prevents the service from starting & stopping too often. mHandler.postDelayed(mStopRunnable, 5000); logd("completeRequest: Stopping service in 5000 ms"); } } /** * Checks if the current project is the project specified by the specified path. * * @param projectPath The project path * * @return The video editor project */ private VideoEditorProject getProject(String projectPath) { if (mVideoProject != null) { if (mVideoProject.getPath().equals(projectPath)) { return mVideoProject; } } return null; } /** * Check if the current editor is the project specified by the specified path * * @param projectPath The project path * * @return The video editor */ private synchronized VideoEditor getVideoEditor(String projectPath) { if (mVideoEditor != null) { if (mVideoEditor.getPath().equals(projectPath)) { return mVideoEditor; } } return null; } /** * Release the editor */ private synchronized void releaseEditor() { if (mVideoEditor != null) { logd("releaseEditor (current): " + mVideoEditor.getPath()); mVideoEditor.release(); mVideoEditor = null; mGeneratePreviewListener = null; System.gc(); } } /** * Release the specified editor * * @param projectPath The project path */ private synchronized void releaseEditor(String projectPath) { if (mVideoEditor != null) { if (mVideoEditor.getPath().equals(projectPath)) { logd("releaseEditor: " + projectPath); mVideoEditor.release(); mVideoEditor = null; mGeneratePreviewListener = null; System.gc(); } } } /** * Release the current editor if it is *not* the current editor * * @param projectPath The project path * * @return The current video editor */ private synchronized VideoEditor releaseEditorNot(String projectPath) { if (mVideoEditor != null) { if (!mVideoEditor.getPath().equals(projectPath)) { logd("releaseEditorNot: " + mVideoEditor.getPath()); mVideoEditor.release(); mVideoEditor = null; mGeneratePreviewListener = null; System.gc(); } } return mVideoEditor; } /** * Generate the preview * * @param videoEditor The video editor * @param updatePreviewFrame true to show preview frame when done */ private void generatePreview(VideoEditor videoEditor, boolean updatePreviewFrame) { try { videoEditor.generatePreview(mGeneratePreviewListener); if (mGeneratePreviewListener != null) { // This is the last callback which is always fired last to // let the UI know that generate preview completed mGeneratePreviewListener.onProgress(null, updatePreviewFrame ? ACTION_UPDATE_FRAME : ACTION_NO_FRAME_UPDATE, 100); } } catch (Exception ex) { ex.printStackTrace(); } } /** * Exports a movie in a distinct worker thread. * * @param videoEditor The video editor * @param intent The intent */ private void exportMovie(final VideoEditor videoEditor, final Intent intent) { mExportCancelled = false; new Thread() { @Override public void run() { final String filename = intent.getStringExtra(PARAM_FILENAME); final int height = intent.getIntExtra(PARAM_HEIGHT, -1); final int bitrate = intent.getIntExtra(PARAM_BITRATE, -1); // Create the export status Intent final Intent statusIntent = mIntentPool.get(); statusIntent.putExtra(PARAM_OP, OP_VIDEO_EDITOR_EXPORT_STATUS); statusIntent.putExtra(PARAM_PROJECT_PATH, intent.getStringExtra( PARAM_PROJECT_PATH)); statusIntent.putExtra(PARAM_FILENAME, filename); statusIntent.putExtra(PARAM_INTENT, intent); Exception resultException = null; try { videoEditor.export(filename, height, bitrate, new ExportProgressListener() { @Override public void onProgress(VideoEditor videoEditor, String filename, int progress) { final Intent progressIntent = mIntentPool.get(); progressIntent.putExtra(PARAM_OP, OP_VIDEO_EDITOR_EXPORT_STATUS); progressIntent.putExtra(PARAM_PROJECT_PATH, intent.getStringExtra( PARAM_PROJECT_PATH)); progressIntent.putExtra(PARAM_FILENAME, filename); progressIntent.putExtra(PARAM_INTENT, intent); progressIntent.putExtra(PARAM_PROGRESS_VALUE, progress); mVideoThread.submit(progressIntent); logv("Export progress: " + progress + " for: " + filename); } }); statusIntent.putExtra(PARAM_CANCELLED, mExportCancelled); if (!mExportCancelled) { if (new File(filename).exists()) { statusIntent.putExtra(PARAM_MOVIE_URI, exportToGallery(filename)); } else { resultException = new IllegalStateException("Export file does not exist: " + filename); } logv("Export complete for: " + filename); } else { logv("Export cancelled by user, file name: " + filename); } } catch (Exception ex) { logv("Export error for: " + filename); ex.printStackTrace(); resultException = ex; } // Complete the request statusIntent.putExtra(PARAM_EXCEPTION, resultException); mVideoThread.submit(statusIntent); } }.start(); } /** * Extract the audio waveform of a media item * * @param intent The original Intent * @param videoEditor The video editor * @param mediaItem The media item */ private void extractMediaItemAudioWaveform(final Intent intent, final VideoEditor videoEditor, final MediaVideoItem mediaItem) throws IOException { mediaItem.extractAudioWaveform( new ExtractAudioWaveformProgressListener() { @Override public void onProgress(int progress) { final Intent progressIntent = mIntentPool.get(); progressIntent.putExtra(PARAM_OP, OP_MEDIA_ITEM_EXTRACT_AUDIO_WAVEFORM_STATUS); progressIntent.putExtra(PARAM_PROJECT_PATH, intent.getStringExtra(PARAM_PROJECT_PATH)); progressIntent.putExtra(PARAM_INTENT, intent); progressIntent.putExtra(PARAM_STORYBOARD_ITEM_ID, mediaItem.getId()); progressIntent.putExtra(PARAM_PROGRESS_VALUE, progress); completeRequest(progressIntent, videoEditor, null, null, null, true); } }); } /** * Extract the audio waveform of an AudioTrack * * @param intent The original Intent * @param videoEditor The video editor * @param audioTrack The audio track */ private void extractAudioTrackAudioWaveform(final Intent intent, final VideoEditor videoEditor, final AudioTrack audioTrack) throws IOException { audioTrack.extractAudioWaveform( new ExtractAudioWaveformProgressListener() { @Override public void onProgress(int progress) { final Intent progressIntent = mIntentPool.get(); progressIntent.putExtra(PARAM_OP, OP_AUDIO_TRACK_EXTRACT_AUDIO_WAVEFORM_STATUS); progressIntent.putExtra(PARAM_PROJECT_PATH, intent.getStringExtra(PARAM_PROJECT_PATH)); progressIntent.putExtra(PARAM_INTENT, intent); progressIntent.putExtra(PARAM_STORYBOARD_ITEM_ID, audioTrack.getId()); progressIntent.putExtra(PARAM_PROGRESS_VALUE, progress); completeRequest(progressIntent, videoEditor, null, null, null, true); } }); } /** * Get the media item following the specified media item * * @param videoEditor The video editor * @param mediaItemId The media item id * @return The next media item */ private static MediaItem nextMediaItem(VideoEditor videoEditor, String mediaItemId) { final List<MediaItem> mediaItems = videoEditor.getAllMediaItems(); if (mediaItemId == null) { if (mediaItems.size() > 0) { return mediaItems.get(0); } } else { final int mediaItemCount = mediaItems.size(); for (int i = 0; i < mediaItemCount; i++) { MediaItem mi = mediaItems.get(i); if (mi.getId().equals(mediaItemId)) { if (i < mediaItemCount - 1) { return mediaItems.get(i + 1); } else { break; } } } } return null; } /** * Export the movie to the Gallery * * @param filename The filename * @return The video MediaStore URI */ private Uri exportToGallery(String filename) { // Save the name and description of a video in a ContentValues map. final ContentValues values = new ContentValues(2); values.put(Video.Media.MIME_TYPE, "video/mp4"); values.put(Video.Media.DATA, filename); // Add a new record (identified by uri) final Uri uri = getContentResolver().insert(Video.Media.EXTERNAL_CONTENT_URI, values); sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://"+ filename))); return uri; } /** * Apply a theme to the entire movie. This method shall be used when the * theme is changing. * * @param videoEditor The video editor * @param themeId The theme id */ private void applyThemeToMovie(VideoEditor videoEditor, String themeId) throws IOException { final Context context = getApplicationContext(); final MovieTheme theme = MovieTheme.getTheme(context, themeId); final List<MediaItem> mediaItems = videoEditor.getAllMediaItems(); // Add the transitions final int mediaItemsCount = mediaItems.size(); if (mediaItemsCount > 0) { // Remove all the transitions for (int i = 0; i < mediaItemsCount; i++) { final MediaItem mi = mediaItems.get(i); if (i == 0) { final Transition beginTransition = mi.getBeginTransition(); if (beginTransition != null) { videoEditor.removeTransition(beginTransition.getId()); } } final Transition endTransition = mi.getEndTransition(); if (endTransition != null) { videoEditor.removeTransition(endTransition.getId()); } } // Add the begin transition to the first media item final MovieTransition beginMovieTransition = theme.getBeginTransition(); if (beginMovieTransition != null) { final MediaItem firstMediaItem = mediaItems.get(0); videoEditor.addTransition( beginMovieTransition.buildTransition(context, null, firstMediaItem)); } // Add the mid transitions final MovieTransition midMovieTransition = theme.getMidTransition(); if (midMovieTransition != null) { for (int i = 0; i < mediaItemsCount - 1; i++) { videoEditor.addTransition( midMovieTransition.buildTransition(context, mediaItems.get(i), mediaItems.get(i + 1))); } } // Add the end transition to the last media item final MovieTransition endMovieTransition = theme.getEndTransition(); if (endMovieTransition != null) { final MediaItem lastMediaItem = mediaItems.get(mediaItemsCount - 1); videoEditor.addTransition( endMovieTransition.buildTransition(context, lastMediaItem, null)); } } // Add the overlay final MovieOverlay movieOverlay = theme.getOverlay(); if (movieOverlay != null && mediaItemsCount > 0) { // Remove all the overlay for the first media item final MediaItem mediaItem = mediaItems.get(0); final List<Overlay> overlays = mediaItem.getAllOverlays(); if (overlays.size() > 0) { mediaItem.removeOverlay(overlays.get(0).getId()); } // Add the new overlay final int scaledWidth, scaledHeight; if (mediaItem instanceof MediaVideoItem) { scaledWidth = ((MediaVideoItem)mediaItem).getWidth(); scaledHeight = ((MediaVideoItem)mediaItem).getHeight(); } else { scaledWidth = ((MediaImageItem)mediaItem).getScaledWidth(); scaledHeight = ((MediaImageItem)mediaItem).getScaledHeight(); } final Overlay overlay = new OverlayFrame(mediaItem, generateId(), ImageUtils.buildOverlayBitmap(getApplicationContext(), null, movieOverlay.getType(), movieOverlay.getTitle(), movieOverlay.getSubtitle(), scaledWidth, scaledHeight), movieOverlay.getStartTime(), movieOverlay.getDuration()); // Set the user attributes final Bundle userAttributes = movieOverlay.buildUserAttributes(); for (String name : userAttributes.keySet()) { if (MovieOverlay.getAttributeType(name).equals(Integer.class)) { overlay.setUserAttribute(name, Integer.toString(userAttributes.getInt(name))); } else { // Strings overlay.setUserAttribute(name, userAttributes.getString(name)); } } mediaItem.addOverlay(overlay); } final MovieAudioTrack at = theme.getAudioTrack(); if (at != null) { // Remove all audio tracks final List<AudioTrack> audioTracks = videoEditor.getAllAudioTracks(); while (audioTracks.size() > 0) { videoEditor.removeAudioTrack(audioTracks.get(0).getId()); } // Add the new audio track final AudioTrack audioTrack = new AudioTrack(videoEditor, generateId(), FileUtils.getAudioTrackFilename(context, at.getRawResourceId())); // Enable looping if necessary if (at.isLooping()) { audioTrack.enableLoop(); } // Enable ducking audioTrack.enableDucking(DUCK_THRESHOLD, DUCK_TRACK_VOLUME); audioTrack.setVolume(DEFAULT_AUDIO_TRACK_VOLUME); videoEditor.addAudioTrack(audioTrack); } } /** * Apply a theme * * @param videoEditor The video editor * @param themeId The theme id * @param mediaItem The mediaItem */ private void applyThemeToMediaItem(VideoEditor videoEditor, String themeId, MediaItem mediaItem) throws IOException { final List<MediaItem> mediaItems = videoEditor.getAllMediaItems(); final int mediaItemsCount = mediaItems.size(); if (mediaItemsCount == 0) { return; } // We would only add transitions if the transitions don't exist final Transition beginTransition = mediaItem.getBeginTransition(); final Transition endTransition = mediaItem.getEndTransition(); final Context context = getApplicationContext(); final MovieTheme theme = MovieTheme.getTheme(context, themeId); final MediaItem firstMediaItem = mediaItems.get(0); if (beginTransition == null) { // Add the begin transition final MovieTransition beginMovieTransition = theme.getBeginTransition(); if (beginMovieTransition != null) { if (firstMediaItem == mediaItem) { videoEditor.addTransition( beginMovieTransition.buildTransition(context, null, mediaItem)); } } } // Add the mid transitions final MovieTransition midMovieTransition = theme.getMidTransition(); if (midMovieTransition != null) { for (int i = 0; i < mediaItemsCount; i++) { final MediaItem mi = mediaItems.get(i); if (mi == mediaItem) { if (i > 0) { // Not the first one if (beginTransition == null) { // Add transition before this media item videoEditor.addTransition(midMovieTransition.buildTransition(context, mediaItems.get(i - 1), mi)); } } if (i < mediaItemsCount - 1) { // Not the last one if (endTransition == null) { // Add the transition after this media item videoEditor.addTransition(midMovieTransition.buildTransition(context, mi, mediaItems.get(i + 1))); } } break; } } } if (endTransition == null) { // Add the end transition to the last media item final MovieTransition endMovieTransition = theme.getEndTransition(); final MediaItem lastMediaItem = mediaItems.get(mediaItemsCount - 1); if (endMovieTransition != null && lastMediaItem == mediaItem) { videoEditor.addTransition( endMovieTransition.buildTransition(context, lastMediaItem, null)); } } // Add the overlay final MovieOverlay movieOverlay = theme.getOverlay(); if (movieOverlay != null) { if (firstMediaItem == mediaItem) { // Remove the overlay final List<Overlay> overlays = mediaItem.getAllOverlays(); if (overlays.size() > 0) { mediaItem.removeOverlay(overlays.get(0).getId()); } // Add the new overlay final int scaledWidth, scaledHeight; if (mediaItem instanceof MediaVideoItem) { scaledWidth = ((MediaVideoItem)mediaItem).getWidth(); scaledHeight = ((MediaVideoItem)mediaItem).getHeight(); } else { scaledWidth = ((MediaImageItem)mediaItem).getScaledWidth(); scaledHeight = ((MediaImageItem)mediaItem).getScaledHeight(); } final Overlay overlay = new OverlayFrame(mediaItem, generateId(), ImageUtils.buildOverlayBitmap(getApplicationContext(), null, movieOverlay.getType(), movieOverlay.getTitle(), movieOverlay.getSubtitle(), scaledWidth, scaledHeight), movieOverlay.getStartTime(), Math.min(movieOverlay.getDuration(), mediaItem.getDuration() - movieOverlay.getStartTime())); // Set the user attributes final Bundle userAttributes = movieOverlay.buildUserAttributes(); for (String name : userAttributes.keySet()) { if (MovieOverlay.getAttributeType(name).equals(Integer.class)) { overlay.setUserAttribute(name, Integer.toString(userAttributes.getInt(name))); } else { // Strings overlay.setUserAttribute(name, userAttributes.getString(name)); } } mediaItem.addOverlay(overlay); } } } /** * Apply theme transitions after an item was removed * * @param videoEditor The video editor * @param themeId The theme id * @param removedItemPosition The position of the removed item * @param beginTransition The removed item begin transition * @param endTransition The removed item end transition * * @return The transition that was added */ private Transition applyThemeAfterRemove(VideoEditor videoEditor, String themeId, int removedItemPosition, Transition beginTransition, Transition endTransition) throws IOException { final List<MediaItem> mediaItems = videoEditor.getAllMediaItems(); final int mediaItemCount = mediaItems.size(); if (mediaItemCount == 0) { return null; } final Context context = getApplicationContext(); final MovieTheme theme = MovieTheme.getTheme(context, themeId); Transition transition = null; if (removedItemPosition == 0) { // First item removed if (theme.getBeginTransition() != null && beginTransition != null) { transition = theme.getBeginTransition().buildTransition(context, null, mediaItems.get(0)); videoEditor.addTransition(transition); } } else if (removedItemPosition == mediaItemCount) { // Last item removed if (theme.getEndTransition() != null && endTransition != null) { transition = theme.getEndTransition().buildTransition(context, mediaItems.get(mediaItemCount - 1), null); videoEditor.addTransition(transition); } } else { // Mid item removed if (theme.getMidTransition() != null && beginTransition != null) { transition = theme.getMidTransition().buildTransition(context, mediaItems.get(removedItemPosition - 1), mediaItems.get(removedItemPosition)); videoEditor.addTransition(transition); } } return transition; } /** * Apply theme transitions after an item was moved * * @param videoEditor The video editor * @param themeId The theme id * @param movedMediaItem The moved media item * @param originalItemPosition The original media item position * @param beginTransition The moved item begin transition * @param endTransition The moved item end transition */ private void applyThemeAfterMove(VideoEditor videoEditor, String themeId, MediaItem movedMediaItem, int originalItemPosition, Transition beginTransition, Transition endTransition) throws IOException { final List<MediaItem> mediaItems = videoEditor.getAllMediaItems(); final int mediaItemCount = mediaItems.size(); if (mediaItemCount == 0) { return; } final Context context = getApplicationContext(); final MovieTheme theme = MovieTheme.getTheme(context, themeId); if (originalItemPosition == 0) { // First item moved if (theme.getBeginTransition() != null && beginTransition != null) { final Transition transition = theme.getBeginTransition().buildTransition(context, null, mediaItems.get(0)); videoEditor.addTransition(transition); } } else if (originalItemPosition == mediaItemCount - 1) { // Last item moved if (theme.getEndTransition() != null && endTransition != null) { final Transition transition = theme.getEndTransition().buildTransition(context, mediaItems.get(mediaItemCount - 1), null); videoEditor.addTransition(transition); } } else { // Mid item moved final int newPosition = mediaItems.indexOf(movedMediaItem); if (newPosition > originalItemPosition) { // Moved forward if (endTransition != null && theme.getMidTransition() != null) { final Transition transition = theme.getMidTransition().buildTransition( context, mediaItems.get(originalItemPosition - 1), mediaItems.get(originalItemPosition)); videoEditor.addTransition(transition); } } else { // Moved backward if (beginTransition != null && theme.getMidTransition() != null) { final Transition transition = theme.getMidTransition().buildTransition( context, mediaItems.get(originalItemPosition), mediaItems.get(originalItemPosition + 1)); videoEditor.addTransition(transition); } } } // Apply the theme at the new position applyThemeToMediaItem(videoEditor, themeId, movedMediaItem); } /** * Copy the media items * * @param mediaItems The media items * * @return The list of media items */ private List<MovieMediaItem> copyMediaItems(List<MediaItem> mediaItems) { final List<MovieMediaItem> movieMediaItems = new ArrayList<MovieMediaItem>(mediaItems.size()); MovieMediaItem prevMediaItem = null; for (MediaItem mediaItem : mediaItems) { final MovieTransition prevTransition; if (prevMediaItem != null) { prevTransition = prevMediaItem.getEndTransition(); } else if (mediaItem.getBeginTransition() != null) { prevTransition = new MovieTransition(mediaItem.getBeginTransition()); } else { prevTransition = null; } final MovieMediaItem movieMediaItem = new MovieMediaItem(mediaItem, prevTransition); movieMediaItems.add(movieMediaItem); prevMediaItem = movieMediaItem; } return movieMediaItems; } /** * Copy the audio tracks * * @param audioTracks The audio tracks * * @return The list of audio tracks */ private List<MovieAudioTrack> copyAudioTracks(List<AudioTrack> audioTracks) { final List<MovieAudioTrack> movieAudioTracks = new ArrayList<MovieAudioTrack>(audioTracks.size()); for (AudioTrack audioTrack : audioTracks) { movieAudioTracks.add(new MovieAudioTrack(audioTrack)); } return movieAudioTracks; } private static void logd(String message) { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, message); } } private static void logv(String message) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, message); } } /** * Worker thread that processes intents and maintains its own intent queue. */ private class IntentProcessor extends Thread { private final BlockingQueue<Intent> mIntentQueue; public IntentProcessor(String threadName) { super("IntentProcessor-" + threadName); mIntentQueue = new LinkedBlockingQueue<Intent>(); } @Override public void run() { try { while(true) { processIntent(mIntentQueue.take()); } } catch (InterruptedException e) { Log.e(TAG, "Terminating " + getName()); } } /** * Submits a new intent for processing. * * @param intent The intent to be processed */ public void submit(Intent intent) { if (isAlive()) { mIntentQueue.add(intent); } else { Log.e(TAG, getName() + " should be started before submitting tasks."); } } /** * Removes an intent from the queue. * * @param intent The intent to be removed * * @return true if the intent is removed */ public boolean cancel(Intent intent) { return mIntentQueue.remove(intent); } public Iterator<Intent> getIntentQueueIterator() { return mIntentQueue.iterator(); } public void quit() { // Display an error if the queue is not empty and clear it. final int queueSize = mIntentQueue.size(); if (queueSize > 0) { Log.e(TAG, "Thread queue is not empty. Size: " + queueSize); mIntentQueue.clear(); } interrupt(); } } }