1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.videoeditor.service; 18 19 import java.io.File; 20 import java.io.FileNotFoundException; 21 import java.io.FileOutputStream; 22 import java.io.IOException; 23 import java.io.InputStream; 24 import java.util.ArrayList; 25 import java.util.Collections; 26 import java.util.Comparator; 27 import java.util.HashMap; 28 import java.util.Iterator; 29 import java.util.List; 30 import java.util.Map; 31 import java.util.concurrent.BlockingQueue; 32 import java.util.concurrent.LinkedBlockingQueue; 33 34 import android.app.Service; 35 import android.content.ContentValues; 36 import android.content.Context; 37 import android.content.Intent; 38 import android.database.Cursor; 39 import android.graphics.Bitmap; 40 import android.graphics.Rect; 41 import android.media.videoeditor.AudioTrack; 42 import android.media.videoeditor.Effect; 43 import android.media.videoeditor.EffectColor; 44 import android.media.videoeditor.EffectKenBurns; 45 import android.media.videoeditor.ExtractAudioWaveformProgressListener; 46 import android.media.videoeditor.MediaImageItem; 47 import android.media.videoeditor.MediaItem; 48 import android.media.videoeditor.MediaProperties; 49 import android.media.videoeditor.MediaVideoItem; 50 import android.media.videoeditor.Overlay; 51 import android.media.videoeditor.OverlayFrame; 52 import android.media.videoeditor.Transition; 53 import android.media.videoeditor.TransitionAlpha; 54 import android.media.videoeditor.TransitionCrossfade; 55 import android.media.videoeditor.TransitionFadeBlack; 56 import android.media.videoeditor.TransitionSliding; 57 import android.media.videoeditor.VideoEditor; 58 import android.media.videoeditor.VideoEditorFactory; 59 import android.media.videoeditor.WaveformData; 60 import android.media.videoeditor.MediaItem.GetThumbnailListCallback; 61 import android.media.videoeditor.VideoEditor.ExportProgressListener; 62 import android.media.videoeditor.VideoEditor.MediaProcessingProgressListener; 63 import android.net.Uri; 64 import android.os.Bundle; 65 import android.os.Handler; 66 import android.os.IBinder; 67 import android.os.Looper; 68 import android.provider.MediaStore; 69 import android.provider.MediaStore.Audio; 70 import android.provider.MediaStore.Images; 71 import android.provider.MediaStore.Video; 72 import android.util.Log; 73 74 import com.android.videoeditor.R; 75 import com.android.videoeditor.util.FileUtils; 76 import com.android.videoeditor.util.ImageUtils; 77 import com.android.videoeditor.util.MediaItemUtils; 78 import com.android.videoeditor.util.StringUtils; 79 80 /** 81 * VideoEditor service API 82 */ 83 public class ApiService extends Service { 84 // Logging 85 private static final String TAG = "VEApiService"; 86 87 // Additional updates 88 public static final int ACTION_UPDATE_FRAME = MediaProcessingProgressListener.ACTION_DECODE + 100; 89 public static final int ACTION_NO_FRAME_UPDATE = MediaProcessingProgressListener.ACTION_DECODE + 101; 90 91 // Parameters 92 private static final String PARAM_OP = "op"; 93 private static final String PARAM_REQUEST_ID = "rid"; 94 private static final String PARAM_PROJECT_PATH = "project"; 95 private static final String PARAM_FILENAME = "filename"; 96 private static final String PARAM_STORYBOARD_ITEM_ID = "item_id"; 97 private static final String PARAM_RELATIVE_STORYBOARD_ITEM_ID = "r_item_id"; 98 private static final String PARAM_PROGRESS_VALUE = "prog_value"; 99 private static final String PARAM_EXCEPTION = "ex"; 100 private static final String PARAM_START_TIME = "s_time"; 101 private static final String PARAM_END_TIME = "e_time"; 102 private static final String PARAM_DURATION = "duration"; 103 private static final String PARAM_WIDTH = "width"; 104 private static final String PARAM_HEIGHT = "height"; 105 private static final String PARAM_BITRATE = "bitrate"; 106 private static final String PARAM_MEDIA_ITEM_RENDERING_MODE = "rm"; 107 private static final String PARAM_MEDIA_ITEM_START_RECT = "start_rect"; 108 private static final String PARAM_MEDIA_ITEM_END_RECT = "end_rect"; 109 private static final String PARAM_EFFECT_TYPE = "e_type"; 110 private static final String PARAM_EFFECT_PARAM = "e_param"; 111 private static final String PARAM_TRANSITION_BEHAVIOR = "behavior"; 112 private static final String PARAM_TRANSITION_MASK = "t_mask"; 113 private static final String PARAM_TRANSITION_BLENDING = "t_blending"; 114 private static final String PARAM_TRANSITION_INVERT = "t_invert"; 115 private static final String PARAM_TRANSITION_DIRECTION = "t_dir"; 116 private static final String PARAM_INTENT = "req_intent"; 117 private static final String PARAM_PROJECT_NAME = "name"; 118 private static final String PARAM_MOVIES_FILENAMES = "movies"; 119 private static final String PARAM_PHOTOS_FILENAMES = "images"; 120 private static final String PARAM_ASPECT_RATIO = "aspect_ratio"; 121 private static final String PARAM_BEGIN_BOUNDARY = "b_boundary"; 122 private static final String PARAM_END_BOUNDARY = "e_boundary"; 123 private static final String PARAM_ATTRIBUTES = "attributes"; 124 private static final String PARAM_VOLUME = "volume"; 125 private static final String PARAM_LOOP = "loop"; 126 private static final String PARAM_MUTE = "mute"; 127 private static final String PARAM_DUCK = "duck"; 128 private static final String PARAM_MOVIE_URI = "uri"; 129 private static final String PARAM_THEME = "theme"; 130 private static final String PARAM_ACTION = "action"; 131 private static final String PARAM_COUNT = "count"; 132 private static final String PARAM_TOKEN = "token"; 133 private static final String PARAM_INDICES = "indices"; 134 private static final String PARAM_CANCELLED = "cancelled"; 135 136 // Operations 137 private static final int OP_VIDEO_EDITOR_CREATE = 1; 138 private static final int OP_VIDEO_EDITOR_LOAD = 2; 139 private static final int OP_VIDEO_EDITOR_SAVE = 3; 140 private static final int OP_VIDEO_EDITOR_EXPORT = 4; 141 private static final int OP_VIDEO_EDITOR_CANCEL_EXPORT = 5; 142 private static final int OP_VIDEO_EDITOR_EXPORT_STATUS = 6; 143 private static final int OP_VIDEO_EDITOR_RELEASE = 8; 144 private static final int OP_VIDEO_EDITOR_DELETE = 9; 145 private static final int OP_VIDEO_EDITOR_SET_ASPECT_RATIO = 10; 146 private static final int OP_VIDEO_EDITOR_APPLY_THEME = 11; 147 private static final int OP_VIDEO_EDITOR_GENERATE_PREVIEW_PROGRESS = 12; 148 private static final int OP_VIDEO_EDITOR_LOAD_PROJECTS = 13; 149 150 private static final int OP_MEDIA_ITEM_ADD_VIDEO_URI = 100; 151 private static final int OP_MEDIA_ITEM_ADD_IMAGE_URI = 101; 152 private static final int OP_MEDIA_ITEM_MOVE = 102; 153 private static final int OP_MEDIA_ITEM_REMOVE = 103; 154 private static final int OP_MEDIA_ITEM_SET_RENDERING_MODE = 104; 155 private static final int OP_MEDIA_ITEM_SET_DURATION = 105; 156 private static final int OP_MEDIA_ITEM_SET_BOUNDARIES = 106; 157 private static final int OP_MEDIA_ITEM_SET_VOLUME = 107; 158 private static final int OP_MEDIA_ITEM_SET_MUTE = 108; 159 private static final int OP_MEDIA_ITEM_EXTRACT_AUDIO_WAVEFORM = 109; 160 private static final int OP_MEDIA_ITEM_EXTRACT_AUDIO_WAVEFORM_STATUS = 110; 161 private static final int OP_MEDIA_ITEM_GET_THUMBNAILS = 112; 162 private static final int OP_MEDIA_ITEM_LOAD = 113; 163 private static final int OP_MEDIA_ITEM_LOAD_STATUS = 114; 164 165 private static final int OP_EFFECT_ADD_COLOR = 200; 166 private static final int OP_EFFECT_ADD_IMAGE_KEN_BURNS = 201; 167 private static final int OP_EFFECT_REMOVE = 202; 168 169 private static final int OP_TRANSITION_INSERT_ALPHA = 300; 170 private static final int OP_TRANSITION_INSERT_CROSSFADE = 301; 171 private static final int OP_TRANSITION_INSERT_FADE_BLACK = 302; 172 private static final int OP_TRANSITION_INSERT_SLIDING = 303; 173 private static final int OP_TRANSITION_REMOVE = 304; 174 private static final int OP_TRANSITION_SET_DURATION = 305; 175 private static final int OP_TRANSITION_GET_THUMBNAIL = 306; 176 177 private static final int OP_OVERLAY_ADD = 400; 178 private static final int OP_OVERLAY_REMOVE = 401; 179 private static final int OP_OVERLAY_SET_START_TIME = 402; 180 private static final int OP_OVERLAY_SET_DURATION = 403; 181 private static final int OP_OVERLAY_SET_ATTRIBUTES = 404; 182 183 private static final int OP_AUDIO_TRACK_ADD = 500; 184 private static final int OP_AUDIO_TRACK_REMOVE = 501; 185 private static final int OP_AUDIO_TRACK_SET_VOLUME = 502; 186 private static final int OP_AUDIO_TRACK_SET_MUTE = 503; 187 private static final int OP_AUDIO_TRACK_SET_BOUNDARIES = 505; 188 private static final int OP_AUDIO_TRACK_SET_LOOP = 506; 189 private static final int OP_AUDIO_TRACK_SET_DUCK = 507; 190 private static final int OP_AUDIO_TRACK_EXTRACT_AUDIO_WAVEFORM = 508; 191 private static final int OP_AUDIO_TRACK_EXTRACT_AUDIO_WAVEFORM_STATUS = 509; 192 193 private static final int DUCK_THRESHOLD = 20; 194 private static final int DUCK_TRACK_VOLUME = 65; 195 // The default audio track volume 196 private static final int DEFAULT_AUDIO_TRACK_VOLUME = 50; 197 198 // Static member variables 199 private static final Map<String, Intent> mPendingIntents = new HashMap<String, Intent>(); 200 private static final List<ApiServiceListener> mListeners = new ArrayList<ApiServiceListener>(); 201 private static final IntentPool mIntentPool = new IntentPool(8); 202 private static VideoEditorProject mVideoProject; 203 private static VideoEditor mVideoEditor; 204 private static ServiceMediaProcessingProgressListener mGeneratePreviewListener; 205 private static volatile boolean mExportCancelled; 206 207 private IntentProcessor mVideoThread; 208 private IntentProcessor mAudioThread; 209 private IntentProcessor mThumbnailThread; 210 private Handler mHandler; 211 212 private final Runnable mStopRunnable = new Runnable() { 213 @Override 214 public void run() { 215 if (mPendingIntents.size() == 0) { 216 logd("Stop runnable: Stopping service"); 217 stopSelf(); 218 } 219 } 220 }; 221 222 /** 223 * Generate preview listener 224 */ 225 private final class ServiceMediaProcessingProgressListener 226 implements VideoEditor.MediaProcessingProgressListener { 227 // Instance variables 228 private final String mProjectPath; 229 230 /** 231 * Constructor 232 * 233 * @param projectPath The project path 234 */ 235 public ServiceMediaProcessingProgressListener(String projectPath) { 236 mProjectPath = projectPath; 237 } 238 239 @Override 240 public void onProgress(Object item, int action, int progress) { 241 final Intent intent = mIntentPool.get(); 242 intent.putExtra(PARAM_OP, OP_VIDEO_EDITOR_GENERATE_PREVIEW_PROGRESS); 243 intent.putExtra(PARAM_PROJECT_PATH, mProjectPath); 244 intent.putExtra(PARAM_ACTION, action); 245 intent.putExtra(PARAM_PROGRESS_VALUE, progress); 246 247 if (item == null) { // Last callback uses null 248 } else if (item instanceof MediaItem) { 249 intent.putExtra(PARAM_STORYBOARD_ITEM_ID, ((MediaItem)item).getId()); 250 intent.putExtra(PARAM_ATTRIBUTES, MediaItem.class.getCanonicalName()); 251 } else if (item instanceof Transition) { 252 intent.putExtra(PARAM_STORYBOARD_ITEM_ID, ((Transition)item).getId()); 253 intent.putExtra(PARAM_ATTRIBUTES, Transition.class.getCanonicalName()); 254 } else if (item instanceof AudioTrack) { 255 intent.putExtra(PARAM_STORYBOARD_ITEM_ID, ((AudioTrack)item).getId()); 256 intent.putExtra(PARAM_ATTRIBUTES, AudioTrack.class.getCanonicalName()); 257 } else { 258 Log.w(TAG, "Unsupported storyboard item type: " + item.getClass()); 259 return; 260 } 261 262 completeRequest(intent, null, null, null, null, true); 263 } 264 } 265 266 /** 267 * @return A unique id 268 */ 269 public static String generateId() { 270 return StringUtils.randomString(6); 271 } 272 273 /** 274 * Register a listener 275 * 276 * @param listener The listener 277 */ 278 public static void registerListener(ApiServiceListener listener) { 279 mListeners.add(listener); 280 } 281 282 /** 283 * Unregister a listener 284 * 285 * @param listener The listener 286 */ 287 public static void unregisterListener(ApiServiceListener listener) { 288 mListeners.remove(listener); 289 } 290 291 /** 292 * Load the projects 293 * 294 * @param context The context 295 */ 296 public static void loadProjects(Context context) { 297 final Intent intent = mIntentPool.get(context, ApiService.class); 298 intent.putExtra(PARAM_OP, OP_VIDEO_EDITOR_LOAD_PROJECTS); 299 300 startCommand(context, intent); 301 } 302 303 /** 304 * Create a new VideoEditor project 305 * 306 * @param context The context 307 * @param projectPath The project path 308 * @param projectName The project name 309 * @param movies The array of movie file names to add to the newly 310 * created project 311 * @param photos The array of photo file names to add to the newly 312 * created project 313 * @param themeType The theme type 314 */ 315 public static void createVideoEditor(Context context, String projectPath, String projectName, 316 String[] movies, String[] photos, String themeType) { 317 final Intent intent = mIntentPool.get(context, ApiService.class); 318 intent.putExtra(PARAM_OP, OP_VIDEO_EDITOR_CREATE); 319 intent.putExtra(PARAM_PROJECT_PATH, projectPath); 320 intent.putExtra(PARAM_PROJECT_NAME, projectName); 321 intent.putExtra(PARAM_MOVIES_FILENAMES, movies); 322 intent.putExtra(PARAM_PHOTOS_FILENAMES, photos); 323 intent.putExtra(PARAM_THEME, themeType); 324 325 startCommand(context, intent); 326 } 327 328 /** 329 * Create a new VideoEditor project 330 * 331 * @param context The context 332 * @param projectPath The project path 333 */ 334 public static void loadVideoEditor(Context context, String projectPath) { 335 final Intent intent = mIntentPool.get(context, ApiService.class); 336 intent.putExtra(PARAM_OP, OP_VIDEO_EDITOR_LOAD); 337 intent.putExtra(PARAM_PROJECT_PATH, projectPath); 338 339 startCommand(context, intent); 340 } 341 342 /** 343 * Export the VideoEditor movie 344 * 345 * @param context The context 346 * @param projectPath The project path 347 * @param filename The export filename 348 * @param height The output movie height 349 * @param bitrate The output movie bitrate 350 */ 351 public static void exportVideoEditor(Context context, String projectPath, String filename, 352 int height, int bitrate) { 353 final Intent intent = mIntentPool.get(context, ApiService.class); 354 intent.putExtra(PARAM_OP, OP_VIDEO_EDITOR_EXPORT); 355 intent.putExtra(PARAM_PROJECT_PATH, projectPath); 356 intent.putExtra(PARAM_FILENAME, filename); 357 intent.putExtra(PARAM_HEIGHT, height); 358 intent.putExtra(PARAM_BITRATE, bitrate); 359 360 startCommand(context, intent); 361 } 362 363 /** 364 * Check if export is pending 365 * 366 * @param projectPath The project path 367 * @param filename The export filename 368 * 369 * @return true if the export is pending 370 */ 371 public static boolean isVideoEditorExportPending(String projectPath, String filename) { 372 for (Intent intent : mPendingIntents.values()) { 373 final int op = intent.getIntExtra(PARAM_OP, -1); 374 if (op == OP_VIDEO_EDITOR_EXPORT) { 375 String pp = intent.getStringExtra(PARAM_PROJECT_PATH); 376 if (pp.equals(projectPath)) { 377 String fn = intent.getStringExtra(PARAM_FILENAME); 378 if (fn.equals(filename)) { 379 return true; 380 } 381 } 382 } 383 } 384 385 return false; 386 } 387 388 /** 389 * Cancel the export of the specified VideoEditor movie 390 * 391 * @param context The context 392 * @param projectPath The project path 393 * @param filename The export filename 394 */ 395 public static void cancelExportVideoEditor(Context context, String projectPath, 396 String filename) { 397 mExportCancelled = true; 398 final Intent intent = mIntentPool.get(context, ApiService.class); 399 intent.putExtra(PARAM_OP, OP_VIDEO_EDITOR_CANCEL_EXPORT); 400 intent.putExtra(PARAM_PROJECT_PATH, projectPath); 401 intent.putExtra(PARAM_FILENAME, filename); 402 403 startCommand(context, intent); 404 } 405 406 /** 407 * Change the aspect ratio 408 * 409 * @param context The context 410 * @param projectPath The project path 411 * @param aspectRatio The aspect ratio 412 */ 413 public static void setAspectRatio(Context context, String projectPath, int aspectRatio) { 414 final Intent intent = mIntentPool.get(context, ApiService.class); 415 intent.putExtra(PARAM_OP, OP_VIDEO_EDITOR_SET_ASPECT_RATIO); 416 intent.putExtra(PARAM_PROJECT_PATH, projectPath); 417 intent.putExtra(PARAM_ASPECT_RATIO, aspectRatio); 418 419 startCommand(context, intent); 420 } 421 422 /** 423 * Apply a theme 424 * 425 * @param context The context 426 * @param projectPath The project path 427 * @param theme The theme 428 */ 429 public static void applyTheme(Context context, String projectPath, String theme) { 430 final Intent intent = mIntentPool.get(context, ApiService.class); 431 intent.putExtra(PARAM_OP, OP_VIDEO_EDITOR_APPLY_THEME); 432 intent.putExtra(PARAM_PROJECT_PATH, projectPath); 433 intent.putExtra(PARAM_THEME, theme); 434 435 startCommand(context, intent); 436 } 437 438 /** 439 * Checks if the service is busy modifying the timeline. While 440 * the video editor is busy the application should not attempt 441 * to preview the movie. 442 * 443 * @param projectPath The project path 444 * 445 * @return {@code true} if the video editor is modifying the timeline 446 */ 447 public static boolean isProjectBeingEdited(String projectPath) { 448 for (Intent intent : mPendingIntents.values()) { 449 final int op = intent.getIntExtra(PARAM_OP, -1); 450 switch (op) { 451 // When these operations are pending the video editor is not busy. 452 case OP_VIDEO_EDITOR_LOAD_PROJECTS: 453 case OP_VIDEO_EDITOR_SAVE: 454 case OP_MEDIA_ITEM_SET_VOLUME: 455 case OP_MEDIA_ITEM_SET_MUTE: 456 case OP_MEDIA_ITEM_GET_THUMBNAILS: 457 case OP_MEDIA_ITEM_LOAD: 458 case OP_TRANSITION_GET_THUMBNAIL: 459 case OP_AUDIO_TRACK_SET_VOLUME: 460 case OP_AUDIO_TRACK_SET_MUTE: { 461 break; 462 } 463 464 default: { 465 final String pp = intent.getStringExtra(PARAM_PROJECT_PATH); 466 if (pp != null && pp.equals(projectPath)) { 467 return true; 468 } 469 break; 470 } 471 } 472 } 473 474 return false; 475 } 476 477 /** 478 * Save the VideoEditor project 479 * 480 * @param context The context 481 * @param projectPath The project path 482 */ 483 public static void saveVideoEditor(Context context, String projectPath) { 484 final Intent intent = mIntentPool.get(context, ApiService.class); 485 intent.putExtra(PARAM_OP, OP_VIDEO_EDITOR_SAVE); 486 intent.putExtra(PARAM_PROJECT_PATH, projectPath); 487 488 startCommand(context, intent); 489 } 490 491 /** 492 * Release the VideoEditor project 493 * 494 * @param context The context 495 * @param projectPath The project path 496 */ 497 public static void releaseVideoEditor(Context context, String projectPath) { 498 final Intent intent = mIntentPool.get(context, ApiService.class); 499 intent.putExtra(PARAM_OP, OP_VIDEO_EDITOR_RELEASE); 500 intent.putExtra(PARAM_PROJECT_PATH, projectPath); 501 502 startCommand(context, intent); 503 } 504 505 /** 506 * Delete the project specified by the project path 507 * 508 * @param context The context 509 * @param projectPath The project path 510 */ 511 public static void deleteProject(Context context, String projectPath) { 512 final Intent intent = mIntentPool.get(context, ApiService.class); 513 intent.putExtra(PARAM_OP, OP_VIDEO_EDITOR_DELETE); 514 intent.putExtra(PARAM_PROJECT_PATH, projectPath); 515 516 startCommand(context, intent); 517 } 518 519 /** 520 * Add a new video media item after the specified media item id 521 * 522 * @param context The context 523 * @param projectPath The project path 524 * @param mediaItemId The mediaItem id 525 * @param afterMediaItemId The id of the media item preceding the media item 526 * @param uri The media item URI 527 * @param renderingMode The rendering mode 528 * @param themeId The theme id 529 */ 530 public static void addMediaItemVideoUri(Context context, String projectPath, 531 String mediaItemId, String afterMediaItemId, Uri uri, int renderingMode, 532 String themeId) { 533 final Intent intent = mIntentPool.get(context, ApiService.class); 534 intent.putExtra(PARAM_OP, OP_MEDIA_ITEM_ADD_VIDEO_URI); 535 intent.putExtra(PARAM_PROJECT_PATH, projectPath); 536 intent.putExtra(PARAM_STORYBOARD_ITEM_ID, mediaItemId); 537 intent.putExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID, afterMediaItemId); 538 intent.putExtra(PARAM_FILENAME, uri); 539 intent.putExtra(PARAM_MEDIA_ITEM_RENDERING_MODE, renderingMode); 540 intent.putExtra(PARAM_THEME, themeId); 541 542 startCommand(context, intent); 543 } 544 545 /** 546 * Add a new image media item after the specified media item id 547 * 548 * @param context The context 549 * @param projectPath The project path 550 * @param mediaItemId The mediaItem id 551 * @param afterMediaItemId The id of the media item preceding the media item 552 * @param uri The media item URI 553 * @param renderingMode The rendering mode 554 * @param durationMs The duration of the item (for images only, ignored for videos) 555 * @param themeId The theme id 556 */ 557 public static void addMediaItemImageUri(Context context, String projectPath, 558 String mediaItemId, String afterMediaItemId, Uri uri, int renderingMode, 559 long durationMs, String themeId) { 560 final Intent intent = mIntentPool.get(context, ApiService.class); 561 intent.putExtra(PARAM_OP, OP_MEDIA_ITEM_ADD_IMAGE_URI); 562 intent.putExtra(PARAM_PROJECT_PATH, projectPath); 563 intent.putExtra(PARAM_STORYBOARD_ITEM_ID, mediaItemId); 564 intent.putExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID, afterMediaItemId); 565 intent.putExtra(PARAM_FILENAME, uri); 566 intent.putExtra(PARAM_MEDIA_ITEM_RENDERING_MODE, renderingMode); 567 intent.putExtra(PARAM_DURATION, durationMs); 568 intent.putExtra(PARAM_THEME, themeId); 569 570 startCommand(context, intent); 571 } 572 573 /** 574 * Download or make a copy of an image from the specified URI 575 * 576 * @param context The context 577 * @param projectPath The project path 578 * @param uri The media item URI 579 * @param mimeType The MIME type 580 */ 581 public static void loadMediaItem(Context context, String projectPath, Uri uri, 582 String mimeType) { 583 final Intent intent = mIntentPool.get(context, ApiService.class); 584 intent.putExtra(PARAM_OP, OP_MEDIA_ITEM_LOAD); 585 intent.putExtra(PARAM_PROJECT_PATH, projectPath); 586 intent.putExtra(PARAM_FILENAME, uri); 587 intent.putExtra(PARAM_ATTRIBUTES, mimeType); 588 589 startCommand(context, intent); 590 } 591 592 /** 593 * Move a media item after the specified media id 594 * 595 * @param context The context 596 * @param projectPath The project path 597 * @param mediaItemId The id of the media item to move 598 * @param afterMediaItemId The id of the relative media item 599 * @param themeId The theme id 600 */ 601 public static void moveMediaItem(Context context, String projectPath, 602 String mediaItemId, String afterMediaItemId, String themeId) { 603 final Intent intent = mIntentPool.get(context, ApiService.class); 604 intent.putExtra(PARAM_OP, OP_MEDIA_ITEM_MOVE); 605 intent.putExtra(PARAM_PROJECT_PATH, projectPath); 606 intent.putExtra(PARAM_STORYBOARD_ITEM_ID, mediaItemId); 607 intent.putExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID, afterMediaItemId); 608 intent.putExtra(PARAM_THEME, themeId); 609 610 startCommand(context, intent); 611 } 612 613 /** 614 * Remove a media item 615 * 616 * @param context The context 617 * @param projectPath The project path 618 * @param mediaItemId The id of the media item to remove 619 * @param themeId The theme id 620 */ 621 public static void removeMediaItem(Context context, String projectPath, String mediaItemId, 622 String themeId) { 623 final Intent intent = mIntentPool.get(context, ApiService.class); 624 intent.putExtra(PARAM_OP, OP_MEDIA_ITEM_REMOVE); 625 intent.putExtra(PARAM_PROJECT_PATH, projectPath); 626 intent.putExtra(PARAM_STORYBOARD_ITEM_ID, mediaItemId); 627 intent.putExtra(PARAM_THEME, themeId); 628 629 startCommand(context, intent); 630 } 631 632 /** 633 * Set the rendering mode for a media item 634 * 635 * @param context The context 636 * @param projectPath The project path 637 * @param mediaItemId The id of the media item 638 */ 639 public static void setMediaItemRenderingMode(Context context, String projectPath, 640 String mediaItemId, int renderingMode) { 641 final Intent intent = mIntentPool.get(context, ApiService.class); 642 intent.putExtra(PARAM_OP, OP_MEDIA_ITEM_SET_RENDERING_MODE); 643 intent.putExtra(PARAM_PROJECT_PATH, projectPath); 644 intent.putExtra(PARAM_STORYBOARD_ITEM_ID, mediaItemId); 645 intent.putExtra(PARAM_MEDIA_ITEM_RENDERING_MODE, renderingMode); 646 647 startCommand(context, intent); 648 } 649 650 /** 651 * Get the thumbnails of the specified size 652 * 653 * @param context The context 654 * @param projectPath The project path 655 * @param mediaItemId The id of the media item 656 * @param width The width 657 * @param height The height 658 * @param startMs The start time in milliseconds 659 * @param endMs The end time in milliseconds 660 * @param count The number of thumbnails 661 */ 662 public static void getMediaItemThumbnails(Context context, 663 String projectPath, String mediaItemId, int width, int height, 664 long startMs, long endMs, int count, int token, int[] indices) { 665 final Intent intent = mIntentPool.get(context, ApiService.class); 666 intent.putExtra(PARAM_OP, OP_MEDIA_ITEM_GET_THUMBNAILS); 667 intent.putExtra(PARAM_PROJECT_PATH, projectPath); 668 intent.putExtra(PARAM_STORYBOARD_ITEM_ID, mediaItemId); 669 intent.putExtra(PARAM_WIDTH, width); 670 intent.putExtra(PARAM_HEIGHT, height); 671 intent.putExtra(PARAM_START_TIME, startMs); 672 intent.putExtra(PARAM_END_TIME, endMs); 673 intent.putExtra(PARAM_COUNT, count); 674 intent.putExtra(PARAM_TOKEN, token); 675 intent.putExtra(PARAM_INDICES, indices); 676 677 startCommand(context, intent); 678 } 679 680 /** 681 * Set the media item duration 682 * 683 * @param context The context 684 * @param projectPath The project path 685 * @param mediaItemId The id of the media item 686 * @param durationMs The media item duration 687 */ 688 public static void setMediaItemDuration(Context context, String projectPath, 689 String mediaItemId, long durationMs) { 690 final Intent intent = mIntentPool.get(context, ApiService.class); 691 intent.putExtra(PARAM_OP, OP_MEDIA_ITEM_SET_DURATION); 692 intent.putExtra(PARAM_PROJECT_PATH, projectPath); 693 intent.putExtra(PARAM_STORYBOARD_ITEM_ID, mediaItemId); 694 intent.putExtra(PARAM_DURATION, durationMs); 695 696 startCommand(context, intent); 697 } 698 699 /** 700 * Set the media item boundaries 701 * 702 * @param context The context 703 * @param projectPath The project path 704 * @param mediaItemId The id of the media item 705 * @param beginBoundaryMs The media item begin boundary 706 * @param endBoundaryMs The media item end boundary 707 */ 708 public static void setMediaItemBoundaries(Context context, String projectPath, 709 String mediaItemId, long beginBoundaryMs, long endBoundaryMs) { 710 final Intent intent = mIntentPool.get(context, ApiService.class); 711 intent.putExtra(PARAM_OP, OP_MEDIA_ITEM_SET_BOUNDARIES); 712 intent.putExtra(PARAM_PROJECT_PATH, projectPath); 713 intent.putExtra(PARAM_STORYBOARD_ITEM_ID, mediaItemId); 714 intent.putExtra(PARAM_BEGIN_BOUNDARY, beginBoundaryMs); 715 intent.putExtra(PARAM_END_BOUNDARY, endBoundaryMs); 716 717 startCommand(context, intent); 718 } 719 720 /** 721 * Set the media item volume (MediaVideoItem only) 722 * 723 * @param context The context 724 * @param projectPath The project path 725 * @param mediaItemId The id of the media item 726 * @param volumePercentage The volume 727 */ 728 public static void setMediaItemVolume(Context context, String projectPath, 729 String mediaItemId, int volumePercentage) { 730 final Intent intent = mIntentPool.get(context, ApiService.class); 731 intent.putExtra(PARAM_OP, OP_MEDIA_ITEM_SET_VOLUME); 732 intent.putExtra(PARAM_PROJECT_PATH, projectPath); 733 intent.putExtra(PARAM_STORYBOARD_ITEM_ID, mediaItemId); 734 intent.putExtra(PARAM_VOLUME, volumePercentage); 735 736 startCommand(context, intent); 737 } 738 739 /** 740 * Mute/unmute the media item (MediaVideoItem only) 741 * 742 * @param context The context 743 * @param projectPath The project path 744 * @param mediaItemId The id of the media item 745 * @param muted true to mute the media item 746 */ 747 public static void setMediaItemMute(Context context, String projectPath, String mediaItemId, 748 boolean muted) { 749 final Intent intent = mIntentPool.get(context, ApiService.class); 750 intent.putExtra(PARAM_OP, OP_MEDIA_ITEM_SET_MUTE); 751 intent.putExtra(PARAM_PROJECT_PATH, projectPath); 752 intent.putExtra(PARAM_STORYBOARD_ITEM_ID, mediaItemId); 753 intent.putExtra(PARAM_MUTE, muted); 754 755 startCommand(context, intent); 756 } 757 758 /** 759 * Extract the media item audio waveform 760 * 761 * @param context The context 762 * @param projectPath The project path 763 * @param mediaItemId The id of the media item 764 */ 765 public static void extractMediaItemAudioWaveform(Context context, String projectPath, 766 String mediaItemId) { 767 if (isMediaItemAudioWaveformPending(projectPath, mediaItemId)) { 768 return; 769 } 770 771 final Intent intent = mIntentPool.get(context, ApiService.class); 772 intent.putExtra(PARAM_OP, OP_MEDIA_ITEM_EXTRACT_AUDIO_WAVEFORM); 773 intent.putExtra(PARAM_PROJECT_PATH, projectPath); 774 intent.putExtra(PARAM_STORYBOARD_ITEM_ID, mediaItemId); 775 776 startCommand(context, intent); 777 } 778 779 /** 780 * Check if extract audio waveform is pending for the specified MediaItem 781 * 782 * @param projectPath The project path 783 * @param mediaItemId The MediaItem id 784 * 785 * @return true if the extract audio waveform is pending 786 */ 787 public static boolean isMediaItemAudioWaveformPending(String projectPath, String mediaItemId) { 788 for (Intent intent : mPendingIntents.values()) { 789 int op = intent.getIntExtra(PARAM_OP, -1); 790 if (op == OP_MEDIA_ITEM_EXTRACT_AUDIO_WAVEFORM) { 791 String pp = intent.getStringExtra(PARAM_PROJECT_PATH); 792 if (pp.equals(projectPath)) { 793 String mid = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); 794 if (mid.equals(mediaItemId)) { 795 return true; 796 } 797 } 798 } 799 } 800 801 return false; 802 } 803 804 /** 805 * Insert an alpha transition after the specified media item 806 * 807 * @param context The context 808 * @param projectPath The project path 809 * @param afterMediaItemId Insert the transition after the media item with this id 810 * @param transitionId The transition id 811 * @param durationMs The duration in milliseconds 812 * @param behavior The transition behavior 813 * @param maskRawResourceId The mask raw resource id 814 * @param blending The transition blending 815 * @param invert The transition invert 816 */ 817 public static void insertAlphaTransition(Context context, String projectPath, 818 String afterMediaItemId, String transitionId, long durationMs, int behavior, 819 int maskRawResourceId, int blending, boolean invert) { 820 final Intent intent = mIntentPool.get(context, ApiService.class); 821 intent.putExtra(PARAM_OP, OP_TRANSITION_INSERT_ALPHA); 822 intent.putExtra(PARAM_PROJECT_PATH, projectPath); 823 intent.putExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID, afterMediaItemId); 824 intent.putExtra(PARAM_STORYBOARD_ITEM_ID, transitionId); 825 intent.putExtra(PARAM_DURATION, durationMs); 826 intent.putExtra(PARAM_TRANSITION_BEHAVIOR, behavior); 827 intent.putExtra(PARAM_TRANSITION_MASK, maskRawResourceId); 828 intent.putExtra(PARAM_TRANSITION_BLENDING, blending); 829 intent.putExtra(PARAM_TRANSITION_INVERT, invert); 830 831 startCommand(context, intent); 832 } 833 834 /** 835 * Insert an crossfade transition after the specified media item 836 * 837 * @param context The context 838 * @param projectPath The project path 839 * @param afterMediaItemId Insert the transition after the media item with this id 840 * @param transitionId The transition id 841 * @param durationMs The duration in milliseconds 842 * @param behavior The transition behavior 843 */ 844 public static void insertCrossfadeTransition(Context context, String projectPath, 845 String afterMediaItemId, String transitionId, long durationMs, int behavior) { 846 final Intent intent = mIntentPool.get(context, ApiService.class); 847 intent.putExtra(PARAM_OP, OP_TRANSITION_INSERT_CROSSFADE); 848 intent.putExtra(PARAM_PROJECT_PATH, projectPath); 849 intent.putExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID, afterMediaItemId); 850 intent.putExtra(PARAM_STORYBOARD_ITEM_ID, transitionId); 851 intent.putExtra(PARAM_DURATION, durationMs); 852 intent.putExtra(PARAM_TRANSITION_BEHAVIOR, behavior); 853 854 startCommand(context, intent); 855 } 856 857 /** 858 * Insert a fade-to-black transition after the specified media item 859 * 860 * @param context The context 861 * @param projectPath The project path 862 * @param afterMediaItemId Insert the transition after the media item with this id 863 * @param transitionId The transition id 864 * @param durationMs The duration in milliseconds 865 * @param behavior The transition behavior 866 */ 867 public static void insertFadeBlackTransition(Context context, String projectPath, 868 String afterMediaItemId, String transitionId, long durationMs, int behavior) { 869 final Intent intent = mIntentPool.get(context, ApiService.class); 870 intent.putExtra(PARAM_OP, OP_TRANSITION_INSERT_FADE_BLACK); 871 intent.putExtra(PARAM_PROJECT_PATH, projectPath); 872 intent.putExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID, afterMediaItemId); 873 intent.putExtra(PARAM_STORYBOARD_ITEM_ID, transitionId); 874 intent.putExtra(PARAM_DURATION, durationMs); 875 intent.putExtra(PARAM_TRANSITION_BEHAVIOR, behavior); 876 877 startCommand(context, intent); 878 } 879 880 /** 881 * Insert a sliding transition after the specified media item 882 * 883 * @param context The context 884 * @param projectPath The project path 885 * @param afterMediaItemId Insert the transition after the media item with this id 886 * @param transitionId The transition id 887 * @param durationMs The duration in milliseconds 888 * @param behavior The transition behavior 889 * @param direction The slide direction 890 */ 891 public static void insertSlidingTransition(Context context, String projectPath, 892 String afterMediaItemId, String transitionId, long durationMs, int behavior, 893 int direction) { 894 final Intent intent = mIntentPool.get(context, ApiService.class); 895 intent.putExtra(PARAM_OP, OP_TRANSITION_INSERT_SLIDING); 896 intent.putExtra(PARAM_PROJECT_PATH, projectPath); 897 intent.putExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID, afterMediaItemId); 898 intent.putExtra(PARAM_DURATION, durationMs); 899 intent.putExtra(PARAM_STORYBOARD_ITEM_ID, transitionId); 900 intent.putExtra(PARAM_TRANSITION_BEHAVIOR, behavior); 901 intent.putExtra(PARAM_TRANSITION_DIRECTION, direction); 902 903 startCommand(context, intent); 904 } 905 906 /** 907 * Remove a transition 908 * 909 * @param context The context 910 * @param projectPath The project path 911 * @param transitionId The id of the transition to remove 912 */ 913 public static void removeTransition(Context context, String projectPath, String transitionId) { 914 final Intent intent = mIntentPool.get(context, ApiService.class); 915 intent.putExtra(PARAM_OP, OP_TRANSITION_REMOVE); 916 intent.putExtra(PARAM_PROJECT_PATH, projectPath); 917 intent.putExtra(PARAM_STORYBOARD_ITEM_ID, transitionId); 918 919 startCommand(context, intent); 920 } 921 922 /** 923 * Set a transition duration 924 * 925 * @param context The context 926 * @param projectPath The project path 927 * @param transitionId The id of the transition 928 * @param durationMs The transition duration 929 */ 930 public static void setTransitionDuration(Context context, String projectPath, 931 String transitionId, long durationMs) { 932 final Intent intent = mIntentPool.get(context, ApiService.class); 933 intent.putExtra(PARAM_OP, OP_TRANSITION_SET_DURATION); 934 intent.putExtra(PARAM_PROJECT_PATH, projectPath); 935 intent.putExtra(PARAM_STORYBOARD_ITEM_ID, transitionId); 936 intent.putExtra(PARAM_DURATION, durationMs); 937 938 startCommand(context, intent); 939 } 940 941 /** 942 * Get the thumbnail of the specified height 943 * 944 * @param context The context 945 * @param projectPath The project path 946 * @param transitionId The id of the transition 947 * @param height The height 948 */ 949 public static void getTransitionThumbnails(Context context, String projectPath, 950 String transitionId, int height) { 951 final Intent intent = mIntentPool.get(context, ApiService.class); 952 intent.putExtra(PARAM_OP, OP_TRANSITION_GET_THUMBNAIL); 953 intent.putExtra(PARAM_PROJECT_PATH, projectPath); 954 intent.putExtra(PARAM_STORYBOARD_ITEM_ID, transitionId); 955 intent.putExtra(PARAM_HEIGHT, height); 956 957 startCommand(context, intent); 958 } 959 960 /** 961 * Check if the transition thumbnailing is in progress 962 * 963 * @param projectPath The project path 964 * @param transitionId The id of the transition 965 * 966 * @return true if the transition thumbnailing is in progress 967 */ 968 public static boolean isTransitionThumbnailsPending(String projectPath, String transitionId) { 969 for (Intent intent : mPendingIntents.values()) { 970 int op = intent.getIntExtra(PARAM_OP, -1); 971 if (op == OP_TRANSITION_GET_THUMBNAIL) { 972 String pp = intent.getStringExtra(PARAM_PROJECT_PATH); 973 if (pp.equals(projectPath)) { 974 String mid = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); 975 if (mid.equals(transitionId)) { 976 return true; 977 } 978 } 979 } 980 } 981 982 return false; 983 } 984 985 /** 986 * Add a color effect 987 * 988 * @param context The context 989 * @param projectPath The project path 990 * @param mediaItemId The media item id 991 * @param effectId The effect id 992 * @param startTimeMs The start time in milliseconds 993 * @param durationMs The duration in milliseconds 994 * @param type The effect type 995 * @param param The effect param (if any) 996 */ 997 public static void addEffectColor(Context context, String projectPath, String mediaItemId, 998 String effectId, long startTimeMs, long durationMs, int type, int param) { 999 final Intent intent = mIntentPool.get(context, ApiService.class); 1000 intent.putExtra(PARAM_OP, OP_EFFECT_ADD_COLOR); 1001 intent.putExtra(PARAM_PROJECT_PATH, projectPath); 1002 intent.putExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID, mediaItemId); 1003 intent.putExtra(PARAM_STORYBOARD_ITEM_ID, effectId); 1004 intent.putExtra(PARAM_START_TIME, startTimeMs); 1005 intent.putExtra(PARAM_DURATION, durationMs); 1006 intent.putExtra(PARAM_EFFECT_TYPE, type); 1007 intent.putExtra(PARAM_EFFECT_PARAM, param); 1008 1009 startCommand(context, intent); 1010 } 1011 1012 /** 1013 * Add a Ken Burns effect 1014 * 1015 * @param context The context 1016 * @param projectPath The project path 1017 * @param mediaItemId The media item id 1018 * @param effectId The effect id 1019 * @param startTimeMs The start time 1020 * @param durationMs The duration of the item 1021 * @param startRect The start rectangle for the Ken Burns effect 1022 * @param endRect The end rectangle for the Ken Burns effect 1023 */ 1024 public static void addEffectKenBurns(Context context, String projectPath, 1025 String mediaItemId, String effectId, long startTimeMs, long durationMs, 1026 Rect startRect, Rect endRect) { 1027 final Intent intent = mIntentPool.get(context, ApiService.class); 1028 intent.putExtra(PARAM_OP, OP_EFFECT_ADD_IMAGE_KEN_BURNS); 1029 intent.putExtra(PARAM_PROJECT_PATH, projectPath); 1030 intent.putExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID, mediaItemId); 1031 intent.putExtra(PARAM_STORYBOARD_ITEM_ID, effectId); 1032 intent.putExtra(PARAM_START_TIME, startTimeMs); 1033 intent.putExtra(PARAM_DURATION, durationMs); 1034 intent.putExtra(PARAM_MEDIA_ITEM_START_RECT, startRect); 1035 intent.putExtra(PARAM_MEDIA_ITEM_END_RECT, endRect); 1036 1037 startCommand(context, intent); 1038 } 1039 1040 /** 1041 * Remove an effect 1042 * 1043 * @param context The context 1044 * @param projectPath The project path 1045 * @param mediaItemId The media item id 1046 * @param effectId The id of the effect to remove 1047 */ 1048 public static void removeEffect(Context context, String projectPath, String mediaItemId, 1049 String effectId) { 1050 final Intent intent = mIntentPool.get(context, ApiService.class); 1051 intent.putExtra(PARAM_OP, OP_EFFECT_REMOVE); 1052 intent.putExtra(PARAM_PROJECT_PATH, projectPath); 1053 intent.putExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID, mediaItemId); 1054 intent.putExtra(PARAM_STORYBOARD_ITEM_ID, effectId); 1055 1056 startCommand(context, intent); 1057 } 1058 1059 /** 1060 * Add an overlay 1061 * 1062 * @param context The context 1063 * @param projectPath The project path 1064 * @param mediaItemId The media item id 1065 * @param userAttributes The overlay user attributes 1066 * @param startTimeMs The start time in milliseconds 1067 * @param durationMs The duration in milliseconds 1068 */ 1069 public static void addOverlay(Context context, String projectPath, String mediaItemId, 1070 String overlayId, Bundle userAttributes, long startTimeMs, long durationMs) { 1071 final Intent intent = mIntentPool.get(context, ApiService.class); 1072 intent.putExtra(PARAM_OP, OP_OVERLAY_ADD); 1073 intent.putExtra(PARAM_PROJECT_PATH, projectPath); 1074 intent.putExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID, mediaItemId); 1075 intent.putExtra(PARAM_STORYBOARD_ITEM_ID, overlayId); 1076 intent.putExtra(PARAM_START_TIME, startTimeMs); 1077 intent.putExtra(PARAM_DURATION, durationMs); 1078 intent.putExtra(PARAM_ATTRIBUTES, userAttributes); 1079 1080 startCommand(context, intent); 1081 } 1082 1083 /** 1084 * Remove an overlay 1085 * 1086 * @param context The context 1087 * @param projectPath The project path 1088 * @param mediaItemId The media item id 1089 * @param overlayId The id of the overlay to remove 1090 */ 1091 public static void removeOverlay(Context context, String projectPath, String mediaItemId, 1092 String overlayId) { 1093 final Intent intent = mIntentPool.get(context, ApiService.class); 1094 intent.putExtra(PARAM_OP, OP_OVERLAY_REMOVE); 1095 intent.putExtra(PARAM_PROJECT_PATH, projectPath); 1096 intent.putExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID, mediaItemId); 1097 intent.putExtra(PARAM_STORYBOARD_ITEM_ID, overlayId); 1098 1099 startCommand(context, intent); 1100 } 1101 1102 /** 1103 * Set the start time of an overlay 1104 * 1105 * @param context The context 1106 * @param projectPath The project path 1107 * @param mediaItemId The media item id 1108 * @param overlayId The id of the overlay 1109 * @param startTimeMs The start time in milliseconds 1110 */ 1111 public static void setOverlayStartTime(Context context, String projectPath, String mediaItemId, 1112 String overlayId, long startTimeMs) { 1113 final Intent intent = mIntentPool.get(context, ApiService.class); 1114 intent.putExtra(PARAM_OP, OP_OVERLAY_SET_START_TIME); 1115 intent.putExtra(PARAM_PROJECT_PATH, projectPath); 1116 intent.putExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID, mediaItemId); 1117 intent.putExtra(PARAM_STORYBOARD_ITEM_ID, overlayId); 1118 intent.putExtra(PARAM_START_TIME, startTimeMs); 1119 1120 startCommand(context, intent); 1121 } 1122 1123 /** 1124 * Set the duration of an overlay 1125 * 1126 * @param context The context 1127 * @param projectPath The project path 1128 * @param mediaItemId The media item id 1129 * @param overlayId The id of the overlay 1130 * @param durationMs The duration in milliseconds 1131 */ 1132 public static void setOverlayDuration(Context context, String projectPath, String mediaItemId, 1133 String overlayId, long durationMs) { 1134 final Intent intent = mIntentPool.get(context, ApiService.class); 1135 intent.putExtra(PARAM_OP, OP_OVERLAY_SET_DURATION); 1136 intent.putExtra(PARAM_PROJECT_PATH, projectPath); 1137 intent.putExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID, mediaItemId); 1138 intent.putExtra(PARAM_STORYBOARD_ITEM_ID, overlayId); 1139 intent.putExtra(PARAM_DURATION, durationMs); 1140 1141 startCommand(context, intent); 1142 } 1143 1144 /** 1145 * Set the user attributes of an overlay 1146 * 1147 * @param context The context 1148 * @param projectPath The project path 1149 * @param mediaItemId The media item id 1150 * @param overlayId The id of the overlay 1151 * @param userAttributes The user attributes 1152 */ 1153 public static void setOverlayUserAttributes(Context context, String projectPath, 1154 String mediaItemId, String overlayId, Bundle userAttributes) { 1155 final Intent intent = mIntentPool.get(context, ApiService.class); 1156 intent.putExtra(PARAM_OP, OP_OVERLAY_SET_ATTRIBUTES); 1157 intent.putExtra(PARAM_PROJECT_PATH, projectPath); 1158 intent.putExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID, mediaItemId); 1159 intent.putExtra(PARAM_STORYBOARD_ITEM_ID, overlayId); 1160 intent.putExtra(PARAM_ATTRIBUTES, userAttributes); 1161 1162 startCommand(context, intent); 1163 } 1164 1165 /** 1166 * Add an audio track 1167 * 1168 * @param context The context 1169 * @param projectPath The project path 1170 * @param id The audio track id 1171 * @param uri The audio track URI 1172 * @param loop true to loop the audio track 1173 */ 1174 public static void addAudioTrack(Context context, String projectPath, String id, Uri uri, 1175 boolean loop) { 1176 final Intent intent = mIntentPool.get(context, ApiService.class); 1177 intent.putExtra(PARAM_OP, OP_AUDIO_TRACK_ADD); 1178 intent.putExtra(PARAM_PROJECT_PATH, projectPath); 1179 intent.putExtra(PARAM_STORYBOARD_ITEM_ID, id); 1180 intent.putExtra(PARAM_FILENAME, uri); 1181 intent.putExtra(PARAM_LOOP, loop); 1182 1183 startCommand(context, intent); 1184 } 1185 1186 /** 1187 * Remove an audio track from the storyboard timeline 1188 * 1189 * @param context The context 1190 * @param projectPath The project path 1191 * @param audioTrackId The id of the audio track to remove 1192 */ 1193 public static void removeAudioTrack(Context context, String projectPath, String audioTrackId) { 1194 final Intent intent = mIntentPool.get(context, ApiService.class); 1195 intent.putExtra(PARAM_OP, OP_AUDIO_TRACK_REMOVE); 1196 intent.putExtra(PARAM_PROJECT_PATH, projectPath); 1197 intent.putExtra(PARAM_STORYBOARD_ITEM_ID, audioTrackId); 1198 1199 startCommand(context, intent); 1200 } 1201 1202 /** 1203 * Set the audio track boundaries 1204 * 1205 * @param context The context 1206 * @param projectPath The project path 1207 * @param audioTrackId The id of the audio track 1208 * @param beginBoundaryMs The audio track begin boundary 1209 * @param endBoundaryMs The audio track end boundary 1210 */ 1211 public static void setAudioTrackBoundaries(Context context, String projectPath, 1212 String audioTrackId, long beginBoundaryMs, long endBoundaryMs) { 1213 final Intent intent = mIntentPool.get(context, ApiService.class); 1214 intent.putExtra(PARAM_OP, OP_AUDIO_TRACK_SET_BOUNDARIES); 1215 intent.putExtra(PARAM_PROJECT_PATH, projectPath); 1216 intent.putExtra(PARAM_STORYBOARD_ITEM_ID, audioTrackId); 1217 intent.putExtra(PARAM_BEGIN_BOUNDARY, beginBoundaryMs); 1218 intent.putExtra(PARAM_END_BOUNDARY, endBoundaryMs); 1219 1220 startCommand(context, intent); 1221 } 1222 1223 /** 1224 * Set the loop flag for an audio track 1225 * 1226 * @param context The context 1227 * @param projectPath The project path 1228 * @param audioTrackId The id of the audio track 1229 * @param loop true to loop audio 1230 */ 1231 public static void setAudioTrackLoop(Context context, String projectPath, String audioTrackId, 1232 boolean loop) { 1233 final Intent intent = mIntentPool.get(context, ApiService.class); 1234 intent.putExtra(PARAM_OP, OP_AUDIO_TRACK_SET_LOOP); 1235 intent.putExtra(PARAM_PROJECT_PATH, projectPath); 1236 intent.putExtra(PARAM_STORYBOARD_ITEM_ID, audioTrackId); 1237 intent.putExtra(PARAM_LOOP, loop); 1238 1239 startCommand(context, intent); 1240 } 1241 1242 /** 1243 * Set the duck flag for an audio track 1244 * 1245 * @param context The context 1246 * @param projectPath The project path 1247 * @param audioTrackId The id of the audio track 1248 * @param duck true to enable ducking 1249 */ 1250 public static void setAudioTrackDuck(Context context, String projectPath, String audioTrackId, 1251 boolean duck) { 1252 final Intent intent = mIntentPool.get(context, ApiService.class); 1253 intent.putExtra(PARAM_OP, OP_AUDIO_TRACK_SET_DUCK); 1254 intent.putExtra(PARAM_PROJECT_PATH, projectPath); 1255 intent.putExtra(PARAM_STORYBOARD_ITEM_ID, audioTrackId); 1256 intent.putExtra(PARAM_DUCK, duck); 1257 1258 startCommand(context, intent); 1259 } 1260 1261 /** 1262 * Set the audio track volume 1263 * 1264 * @param context The context 1265 * @param projectPath The project path 1266 * @param audioTrackId The id of the audio track 1267 * @param volumePercentage The audio track volume (in percentage) 1268 */ 1269 public static void setAudioTrackVolume(Context context, String projectPath, 1270 String audioTrackId, int volumePercentage) { 1271 final Intent intent = mIntentPool.get(context, ApiService.class); 1272 intent.putExtra(PARAM_OP, OP_AUDIO_TRACK_SET_VOLUME); 1273 intent.putExtra(PARAM_PROJECT_PATH, projectPath); 1274 intent.putExtra(PARAM_STORYBOARD_ITEM_ID, audioTrackId); 1275 intent.putExtra(PARAM_VOLUME, volumePercentage); 1276 1277 startCommand(context, intent); 1278 } 1279 1280 /** 1281 * Mute/unmute the audio track 1282 * 1283 * @param context The context 1284 * @param projectPath The project path 1285 * @param audioTrackId The id of the audio track 1286 * @param muted true to mute the audio track 1287 */ 1288 public static void setAudioTrackMute(Context context, String projectPath, String audioTrackId, 1289 boolean muted) { 1290 final Intent intent = mIntentPool.get(context, ApiService.class); 1291 intent.putExtra(PARAM_OP, OP_AUDIO_TRACK_SET_MUTE); 1292 intent.putExtra(PARAM_PROJECT_PATH, projectPath); 1293 intent.putExtra(PARAM_STORYBOARD_ITEM_ID, audioTrackId); 1294 intent.putExtra(PARAM_MUTE, muted); 1295 1296 startCommand(context, intent); 1297 } 1298 1299 /** 1300 * Extract the audio track audio waveform 1301 * 1302 * @param context The context 1303 * @param projectPath The project path 1304 * @param audioTrackId The id of the audio track 1305 */ 1306 public static void extractAudioTrackAudioWaveform(Context context, String projectPath, 1307 String audioTrackId) { 1308 if (isAudioTrackAudioWaveformPending(projectPath, audioTrackId)) { 1309 return; 1310 } 1311 final Intent intent = mIntentPool.get(context, ApiService.class); 1312 intent.putExtra(PARAM_OP, OP_AUDIO_TRACK_EXTRACT_AUDIO_WAVEFORM); 1313 intent.putExtra(PARAM_PROJECT_PATH, projectPath); 1314 intent.putExtra(PARAM_STORYBOARD_ITEM_ID, audioTrackId); 1315 1316 startCommand(context, intent); 1317 } 1318 1319 /** 1320 * Check if extract audio waveform is pending for the specified audio track 1321 * 1322 * @param projectPath The project path 1323 * @param audioTrackId The audio track id 1324 * 1325 * @return true if the extract audio waveform is pending 1326 */ 1327 public static boolean isAudioTrackAudioWaveformPending(String projectPath, 1328 String audioTrackId) { 1329 for (Intent intent : mPendingIntents.values()) { 1330 int op = intent.getIntExtra(PARAM_OP, -1); 1331 if (op == OP_AUDIO_TRACK_EXTRACT_AUDIO_WAVEFORM) { 1332 String pp = intent.getStringExtra(PARAM_PROJECT_PATH); 1333 if (pp.equals(projectPath)) { 1334 String mid = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); 1335 if (mid.equals(audioTrackId)) { 1336 return true; 1337 } 1338 } 1339 } 1340 } 1341 1342 return false; 1343 } 1344 1345 /** 1346 * Start the service (if it is not running) with the specified Intent 1347 * 1348 * @param context The context 1349 * @param intent The intent 1350 * 1351 * @return The request id of the pending request 1352 */ 1353 private static String startCommand(Context context, Intent intent) { 1354 final String requestId = StringUtils.randomString(8); 1355 intent.putExtra(PARAM_REQUEST_ID, requestId); 1356 mPendingIntents.put(requestId, intent); 1357 1358 context.startService(intent); 1359 1360 final String projectPath = intent.getStringExtra(PARAM_PROJECT_PATH); 1361 if (projectPath != null) { 1362 final boolean projectEdited = isProjectBeingEdited(projectPath); 1363 if (projectEdited) { 1364 for (ApiServiceListener listener : mListeners) { 1365 listener.onProjectEditState(projectPath, projectEdited); 1366 } 1367 } 1368 } 1369 1370 return requestId; 1371 } 1372 1373 @Override 1374 public void onCreate() { 1375 super.onCreate(); 1376 mHandler = new Handler(Looper.getMainLooper()); 1377 1378 mVideoThread = new IntentProcessor("VideoServiceThread"); 1379 mVideoThread.start(); 1380 1381 mAudioThread = new IntentProcessor("AudioServiceThread"); 1382 mAudioThread.start(); 1383 1384 mThumbnailThread = new IntentProcessor("ThumbnailServiceThread"); 1385 mThumbnailThread.start(); 1386 } 1387 1388 @Override 1389 public int onStartCommand(final Intent intent, int flags, int startId) { 1390 final int op = intent.getIntExtra(PARAM_OP, -1); 1391 switch(op) { 1392 case OP_VIDEO_EDITOR_LOAD_PROJECTS: 1393 case OP_VIDEO_EDITOR_CREATE: 1394 case OP_VIDEO_EDITOR_LOAD: 1395 case OP_VIDEO_EDITOR_SAVE: 1396 case OP_VIDEO_EDITOR_RELEASE: 1397 case OP_VIDEO_EDITOR_DELETE: 1398 case OP_VIDEO_EDITOR_SET_ASPECT_RATIO: 1399 case OP_VIDEO_EDITOR_APPLY_THEME: 1400 case OP_VIDEO_EDITOR_EXPORT: 1401 case OP_VIDEO_EDITOR_CANCEL_EXPORT: 1402 case OP_VIDEO_EDITOR_EXPORT_STATUS: 1403 1404 case OP_MEDIA_ITEM_ADD_VIDEO_URI: 1405 case OP_MEDIA_ITEM_ADD_IMAGE_URI: 1406 case OP_MEDIA_ITEM_MOVE: 1407 case OP_MEDIA_ITEM_REMOVE: 1408 case OP_MEDIA_ITEM_SET_RENDERING_MODE: 1409 case OP_MEDIA_ITEM_SET_DURATION: 1410 case OP_MEDIA_ITEM_SET_BOUNDARIES: 1411 case OP_MEDIA_ITEM_EXTRACT_AUDIO_WAVEFORM: 1412 case OP_MEDIA_ITEM_EXTRACT_AUDIO_WAVEFORM_STATUS: 1413 case OP_MEDIA_ITEM_LOAD: 1414 case OP_MEDIA_ITEM_LOAD_STATUS: 1415 1416 case OP_EFFECT_ADD_COLOR: 1417 case OP_EFFECT_ADD_IMAGE_KEN_BURNS: 1418 case OP_EFFECT_REMOVE: 1419 1420 case OP_TRANSITION_INSERT_ALPHA: 1421 case OP_TRANSITION_INSERT_CROSSFADE: 1422 case OP_TRANSITION_INSERT_FADE_BLACK: 1423 case OP_TRANSITION_INSERT_SLIDING: 1424 case OP_TRANSITION_REMOVE: 1425 case OP_TRANSITION_SET_DURATION: 1426 1427 case OP_OVERLAY_ADD: 1428 case OP_OVERLAY_REMOVE: 1429 case OP_OVERLAY_SET_START_TIME: 1430 case OP_OVERLAY_SET_DURATION: 1431 case OP_OVERLAY_SET_ATTRIBUTES: 1432 1433 case OP_AUDIO_TRACK_ADD: 1434 case OP_AUDIO_TRACK_REMOVE: 1435 case OP_AUDIO_TRACK_SET_BOUNDARIES: 1436 case OP_AUDIO_TRACK_EXTRACT_AUDIO_WAVEFORM: 1437 case OP_AUDIO_TRACK_EXTRACT_AUDIO_WAVEFORM_STATUS: { 1438 mVideoThread.submit(intent); 1439 break; 1440 } 1441 1442 case OP_TRANSITION_GET_THUMBNAIL: { 1443 mThumbnailThread.submit(intent); 1444 break; 1445 } 1446 1447 case OP_MEDIA_ITEM_GET_THUMBNAILS: { 1448 final String projectPath = intent.getStringExtra(PARAM_PROJECT_PATH); 1449 final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); 1450 final int token = intent.getIntExtra(PARAM_TOKEN, 0); 1451 // Cancel any pending thumbnail request for the same media item 1452 // but with a different token 1453 Iterator<Intent> intentQueueIterator = mThumbnailThread.getIntentQueueIterator(); 1454 while (intentQueueIterator.hasNext()) { 1455 Intent qIntent = intentQueueIterator.next(); 1456 int opi = qIntent.getIntExtra(PARAM_OP, -1); 1457 String pp = qIntent.getStringExtra(PARAM_PROJECT_PATH); 1458 String mid = qIntent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); 1459 int tk = qIntent.getIntExtra(PARAM_TOKEN, 0); 1460 if (opi == op && pp.equals(projectPath) && mid.equals(mediaItemId) 1461 && tk != token) { 1462 boolean canceled = mThumbnailThread.cancel(qIntent); 1463 if (canceled) { 1464 logd("Canceled operation: " + op + " for media item" + mediaItemId); 1465 mPendingIntents.remove(qIntent.getStringExtra(PARAM_REQUEST_ID)); 1466 mIntentPool.put(qIntent); 1467 } 1468 break; 1469 } 1470 } 1471 mThumbnailThread.submit(intent); 1472 break; 1473 } 1474 1475 case OP_MEDIA_ITEM_SET_VOLUME: 1476 case OP_MEDIA_ITEM_SET_MUTE: 1477 1478 case OP_AUDIO_TRACK_SET_VOLUME: 1479 case OP_AUDIO_TRACK_SET_MUTE: 1480 case OP_AUDIO_TRACK_SET_LOOP: 1481 case OP_AUDIO_TRACK_SET_DUCK: { 1482 mAudioThread.submit(intent); 1483 break; 1484 } 1485 1486 default: { 1487 Log.e(TAG, "No thread assigned: " + op); 1488 break; 1489 } 1490 } 1491 1492 return START_NOT_STICKY; 1493 } 1494 1495 @Override 1496 public void onDestroy() { 1497 super.onDestroy(); 1498 1499 if (mThumbnailThread != null) { 1500 mThumbnailThread.quit(); 1501 mThumbnailThread = null; 1502 } 1503 1504 if (mAudioThread != null) { 1505 mAudioThread.quit(); 1506 mAudioThread = null; 1507 } 1508 1509 if (mVideoThread != null) { 1510 mVideoThread.quit(); 1511 mVideoThread = null; 1512 } 1513 } 1514 1515 @Override 1516 public IBinder onBind(Intent intent) { 1517 return null; 1518 } 1519 1520 /** 1521 * Process the intent 1522 * 1523 * @param intent The intent 1524 */ 1525 public void processIntent(final Intent intent) { 1526 final int op = intent.getIntExtra(PARAM_OP, -1); 1527 VideoEditor videoEditor = null; 1528 try { 1529 final String projectPath = intent.getStringExtra(PARAM_PROJECT_PATH); 1530 // Check if the project path matches the current VideoEditor project 1531 switch (op) { 1532 case OP_VIDEO_EDITOR_LOAD_PROJECTS: 1533 case OP_VIDEO_EDITOR_CREATE: 1534 case OP_VIDEO_EDITOR_LOAD: 1535 case OP_VIDEO_EDITOR_DELETE: { 1536 break; 1537 } 1538 1539 default: { 1540 videoEditor = getVideoEditor(projectPath); 1541 if (videoEditor == null) { 1542 throw new IllegalArgumentException("Invalid project path: " 1543 + projectPath + " for operation: " + op); 1544 } 1545 break; 1546 } 1547 } 1548 1549 switch (op) { 1550 case OP_VIDEO_EDITOR_LOAD_PROJECTS: { 1551 logd("OP_LOAD_PROJECTS"); 1552 final List<VideoEditorProject> projects = new ArrayList<VideoEditorProject>(); 1553 final File dir = FileUtils.getProjectsRootDir(getApplicationContext()); 1554 if (dir != null) { 1555 // Collect valid projects (project with valid metadata). 1556 final File[] files = dir.listFiles(); 1557 if (files != null) { 1558 for (int i = 0; i < files.length; i++) { 1559 if (files[i].isDirectory()) { 1560 final String pp = files[i].getAbsolutePath(); 1561 try { 1562 projects.add(VideoEditorProject.fromXml(null, pp)); 1563 } catch (FileNotFoundException ex) { 1564 Log.w(TAG, "processIntent: Project file not found: " + pp); 1565 FileUtils.deleteDir(new File(pp)); 1566 } catch (Exception ex) { 1567 ex.printStackTrace(); 1568 } 1569 } 1570 } 1571 1572 if (projects.size() > 0) { 1573 // Sort the projects in order of "last saved" 1574 Collections.sort(projects, new Comparator<VideoEditorProject>() { 1575 @Override 1576 public int compare(VideoEditorProject object1, 1577 VideoEditorProject object2) { 1578 if (object1.getLastSaved() > object2.getLastSaved()) { 1579 return -1; 1580 } else if (object1.getLastSaved() == object2.getLastSaved()) { 1581 return 0; 1582 } else { 1583 return 1; 1584 } 1585 } 1586 }); 1587 } 1588 } 1589 } 1590 1591 completeRequest(intent, videoEditor, null, projects, null, true); 1592 break; 1593 } 1594 1595 case OP_VIDEO_EDITOR_CREATE: { 1596 logd("OP_VIDEO_EDITOR_CREATE: " + projectPath); 1597 1598 try { 1599 // Release the current video editor if any 1600 releaseEditor(); 1601 1602 videoEditor = VideoEditorFactory.create(projectPath); 1603 1604 // Add the movies to the timeline 1605 final String[] movies = intent.getStringArrayExtra(PARAM_MOVIES_FILENAMES); 1606 for (int i = 0; i < movies.length; i++) { 1607 final MediaItem mediaItem = new MediaVideoItem(videoEditor, 1608 generateId(), movies[i], 1609 MediaItem.RENDERING_MODE_BLACK_BORDER); 1610 videoEditor.addMediaItem(mediaItem); 1611 } 1612 1613 // Add the photos to the timeline 1614 final String[] photos = intent.getStringArrayExtra(PARAM_PHOTOS_FILENAMES); 1615 for (int i = 0; i < photos.length; i++) { 1616 final MediaItem mediaItem = new MediaImageItem(videoEditor, 1617 generateId(), photos[i], 1618 MediaItemUtils.getDefaultImageDuration(), 1619 MediaItem.RENDERING_MODE_BLACK_BORDER); 1620 videoEditor.addMediaItem(mediaItem); 1621 } 1622 1623 // Create the project 1624 final String projectName = intent.getStringExtra(PARAM_PROJECT_NAME); 1625 final String themeId = intent.getStringExtra(PARAM_THEME); 1626 if (themeId != null) { 1627 applyThemeToMovie(videoEditor, themeId); 1628 } 1629 1630 // Set the aspect ratio to the aspect ratio of the first item 1631 final List<MediaItem> mediaItems = videoEditor.getAllMediaItems(); 1632 if (mediaItems.size() > 0) { 1633 videoEditor.setAspectRatio(mediaItems.get(0).getAspectRatio()); 1634 } 1635 1636 // Create the video editor project 1637 final VideoEditorProject videoProject = new VideoEditorProject( 1638 videoEditor, projectPath, projectName, System.currentTimeMillis(), 1639 0, 0, VideoEditorProject.DEFAULT_ZOOM_LEVEL, null, themeId, null); 1640 videoProject.setMediaItems(copyMediaItems( 1641 videoEditor.getAllMediaItems())); 1642 videoProject.setAudioTracks(copyAudioTracks( 1643 videoEditor.getAllAudioTracks())); 1644 1645 // Make this project the current project 1646 mVideoEditor = videoEditor; 1647 mGeneratePreviewListener = new ServiceMediaProcessingProgressListener( 1648 projectPath); 1649 1650 completeRequest(intent, videoEditor, null, videoProject, null, false); 1651 generatePreview(videoEditor, true); 1652 completeRequest(intent); 1653 } catch (Exception ex) { 1654 if (videoEditor != null) { 1655 videoEditor.release(); 1656 videoEditor = null; 1657 } 1658 throw ex; 1659 } 1660 1661 break; 1662 } 1663 1664 case OP_VIDEO_EDITOR_LOAD: { 1665 videoEditor = releaseEditorNot(projectPath); 1666 1667 if (videoEditor == null) { // The old project was released. 1668 logd("OP_VIDEO_EDITOR_LOAD: Loading: " + projectPath); 1669 try { 1670 // Load the project 1671 videoEditor = VideoEditorFactory.load(projectPath, false); 1672 1673 // Load the video editor project 1674 final VideoEditorProject videoProject = VideoEditorProject.fromXml( 1675 videoEditor, projectPath); 1676 videoProject.setMediaItems(copyMediaItems( 1677 videoEditor.getAllMediaItems())); 1678 videoProject.setAudioTracks(copyAudioTracks( 1679 videoEditor.getAllAudioTracks())); 1680 // Make this the current project 1681 mVideoEditor = videoEditor; 1682 mGeneratePreviewListener = new ServiceMediaProcessingProgressListener( 1683 projectPath); 1684 1685 completeRequest(intent, videoEditor, null, videoProject, null, false); 1686 generatePreview(videoEditor, true); 1687 completeRequest(intent); 1688 } catch (Exception ex) { 1689 if (videoEditor != null) { 1690 videoEditor.release(); 1691 videoEditor = null; 1692 } 1693 throw ex; 1694 } 1695 } else { // The project is already loaded. 1696 logd("OP_VIDEO_EDITOR_LOAD: Was already loaded: " + projectPath); 1697 completeRequest(intent, videoEditor, null, null, null, true); 1698 } 1699 1700 break; 1701 } 1702 1703 case OP_VIDEO_EDITOR_SET_ASPECT_RATIO: { 1704 logd("OP_VIDEO_EDITOR_SET_ASPECT_RATIO"); 1705 1706 videoEditor.setAspectRatio(intent.getIntExtra(PARAM_ASPECT_RATIO, 1707 MediaProperties.ASPECT_RATIO_UNDEFINED)); 1708 1709 completeRequest(intent, videoEditor, null, null, null, false); 1710 generatePreview(videoEditor, true); 1711 completeRequest(intent); 1712 break; 1713 } 1714 1715 case OP_VIDEO_EDITOR_APPLY_THEME: { 1716 logd("OP_VIDEO_EDITOR_APPLY_THEME"); 1717 1718 // Apply the theme 1719 applyThemeToMovie(videoEditor, intent.getStringExtra(PARAM_THEME)); 1720 1721 final List<MovieMediaItem> mediaItems = 1722 copyMediaItems(videoEditor.getAllMediaItems()); 1723 final List<MovieAudioTrack> audioTracks = 1724 copyAudioTracks(videoEditor.getAllAudioTracks()); 1725 1726 completeRequest(intent, videoEditor, null, mediaItems, audioTracks, false); 1727 generatePreview(videoEditor, true); 1728 completeRequest(intent); 1729 break; 1730 } 1731 1732 case OP_VIDEO_EDITOR_EXPORT: { 1733 logd("OP_VIDEO_EDITOR_EXPORT"); 1734 exportMovie(videoEditor, intent); 1735 break; 1736 } 1737 1738 case OP_VIDEO_EDITOR_CANCEL_EXPORT: { 1739 logd("OP_VIDEO_EDITOR_CANCEL_EXPORT"); 1740 videoEditor.cancelExport(intent.getStringExtra(PARAM_FILENAME)); 1741 completeRequest(intent, videoEditor, null, null, null, true); 1742 break; 1743 } 1744 1745 case OP_VIDEO_EDITOR_EXPORT_STATUS: { 1746 logd("OP_VIDEO_EDITOR_EXPORT_STATUS"); 1747 completeRequest(intent, videoEditor, null, null, null, true); 1748 break; 1749 } 1750 1751 case OP_VIDEO_EDITOR_SAVE: { 1752 logd("OP_VIDEO_EDITOR_SAVE: " + projectPath); 1753 videoEditor.save(); 1754 1755 final VideoEditorProject videoProject = getProject(projectPath); 1756 if (videoProject != null) { 1757 videoProject.saveToXml(); 1758 } 1759 1760 completeRequest(intent, videoEditor, null, null, null, true); 1761 break; 1762 } 1763 1764 case OP_VIDEO_EDITOR_RELEASE: { 1765 logd("OP_VIDEO_EDITOR_RELEASE: " + projectPath); 1766 releaseEditor(projectPath); 1767 completeRequest(intent, videoEditor, null, null, null, true); 1768 break; 1769 } 1770 1771 case OP_VIDEO_EDITOR_DELETE: { 1772 logd("OP_VIDEO_EDITOR_DELETE: " + projectPath); 1773 releaseEditor(projectPath); 1774 // Delete all the files and the project folder. 1775 FileUtils.deleteDir(new File(projectPath)); 1776 completeRequest(intent, videoEditor, null, null, null, true); 1777 break; 1778 } 1779 1780 case OP_MEDIA_ITEM_ADD_VIDEO_URI: { 1781 logd("OP_MEDIA_ITEM_ADD_VIDEO_URI: " + 1782 intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); 1783 final Uri data = intent.getParcelableExtra(PARAM_FILENAME); 1784 String filename = null; 1785 // Get the filename 1786 Cursor cursor = null; 1787 try { 1788 cursor = getContentResolver().query(data, 1789 new String[] {Video.Media.DATA}, null, null, null); 1790 if (cursor.moveToFirst()) { 1791 filename = cursor.getString(0); 1792 } 1793 } finally { 1794 if (cursor != null) { 1795 cursor.close(); 1796 } 1797 } 1798 1799 if (filename == null) { 1800 throw new IllegalArgumentException("Media file not found: " + data); 1801 } 1802 1803 final MediaItem mediaItem = new MediaVideoItem(videoEditor, 1804 intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID), 1805 filename, 1806 intent.getIntExtra(PARAM_MEDIA_ITEM_RENDERING_MODE, 0)); 1807 1808 videoEditor.insertMediaItem(mediaItem, 1809 intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID)); 1810 1811 // If this is the first media item, change the aspect ratio 1812 final Integer aspectRatio; 1813 if (videoEditor.getAllMediaItems().size() == 1) { 1814 videoEditor.setAspectRatio(mediaItem.getAspectRatio()); 1815 aspectRatio = videoEditor.getAspectRatio(); 1816 } else { 1817 aspectRatio = null; 1818 } 1819 1820 // Apply the theme if any 1821 final String themeId = intent.getStringExtra(PARAM_THEME); 1822 if (themeId != null) { 1823 applyThemeToMediaItem(videoEditor, themeId, mediaItem); 1824 } 1825 1826 completeRequest(intent, videoEditor, null, new MovieMediaItem(mediaItem), 1827 aspectRatio, false); 1828 generatePreview(videoEditor, true); 1829 completeRequest(intent); 1830 break; 1831 } 1832 1833 case OP_MEDIA_ITEM_ADD_IMAGE_URI: { 1834 logd("OP_MEDIA_ITEM_ADD_IMAGE_URI: " 1835 + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); 1836 1837 final Uri data = intent.getParcelableExtra(PARAM_FILENAME); 1838 String filename = null; 1839 // Get the filename 1840 Cursor cursor = null; 1841 try { 1842 cursor = getContentResolver().query(data, 1843 new String[] {Images.Media.DATA, Images.Media.MIME_TYPE}, 1844 null, null, null); 1845 if (cursor.moveToFirst()) { 1846 filename = cursor.getString(0); 1847 final String mimeType = cursor.getString(1); 1848 if ("image/jpeg".equals(mimeType)) { 1849 try { 1850 final File outputFile = new File(projectPath, 1851 "gallery_image_" + generateId() + ".jpg"); 1852 if (ImageUtils.transformJpeg(filename, outputFile)) { 1853 filename = outputFile.getAbsolutePath(); 1854 } 1855 } catch (Exception ex) { 1856 // Ignore the exception and continue 1857 Log.w(TAG, "Could not transform JPEG: " + filename, ex); 1858 } 1859 } 1860 } 1861 } finally { 1862 if (cursor != null) { 1863 cursor.close(); 1864 } 1865 } 1866 1867 if (filename == null) { 1868 throw new IllegalArgumentException("Media file not found: " + data); 1869 } 1870 1871 final MediaItem mediaItem = new MediaImageItem(videoEditor, 1872 intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID), 1873 filename, 1874 intent.getLongExtra(PARAM_DURATION, 0), 1875 intent.getIntExtra(PARAM_MEDIA_ITEM_RENDERING_MODE, 0)); 1876 1877 videoEditor.insertMediaItem(mediaItem, 1878 intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID)); 1879 1880 // If this is the first media item, change the aspect ratio 1881 final Integer aspectRatio; 1882 if (videoEditor.getAllMediaItems().size() == 1) { 1883 videoEditor.setAspectRatio(mediaItem.getAspectRatio()); 1884 aspectRatio = videoEditor.getAspectRatio(); 1885 } else { 1886 aspectRatio = null; 1887 } 1888 1889 // Apply the theme if any 1890 final String themeId = intent.getStringExtra(PARAM_THEME); 1891 if (themeId != null) { 1892 applyThemeToMediaItem(videoEditor, themeId, mediaItem); 1893 } 1894 1895 completeRequest(intent, videoEditor, null, new MovieMediaItem(mediaItem), 1896 aspectRatio, false); 1897 generatePreview(videoEditor, true); 1898 completeRequest(intent); 1899 break; 1900 } 1901 1902 case OP_MEDIA_ITEM_LOAD: { 1903 final Uri data = intent.getParcelableExtra(PARAM_FILENAME); 1904 logd("OP_MEDIA_ITEM_LOAD: " + data); 1905 final Intent requestIntent = intent; 1906 new Thread() { 1907 @Override 1908 public void run() { 1909 InputStream is = null; 1910 FileOutputStream fos = null; 1911 final File file = new File(projectPath, "download_" + generateId()); 1912 1913 final Intent statusIntent = mIntentPool.get(); 1914 statusIntent.putExtra(PARAM_OP, OP_MEDIA_ITEM_LOAD_STATUS); 1915 statusIntent.putExtra(PARAM_PROJECT_PATH, 1916 requestIntent.getStringExtra(PARAM_PROJECT_PATH)); 1917 statusIntent.putExtra(PARAM_INTENT, requestIntent); 1918 try { 1919 is = getContentResolver().openInputStream(data); 1920 // Save the input stream to a file 1921 fos = new FileOutputStream(file); 1922 final byte[] readBuffer = new byte[2048]; 1923 int readBytes; 1924 while ((readBytes = is.read(readBuffer)) >= 0) { 1925 fos.write(readBuffer, 0, readBytes); 1926 } 1927 } catch (Exception ex) { 1928 Log.e(TAG, "Cannot open input stream for: " + data); 1929 statusIntent.putExtra(PARAM_EXCEPTION, ex); 1930 file.delete(); 1931 } finally { 1932 if (is != null) { 1933 try { 1934 is.close(); 1935 } catch (IOException ex) { 1936 Log.e(TAG, "Cannot close input stream for: " + data); 1937 } 1938 } 1939 1940 if (fos != null) { 1941 try { 1942 fos.flush(); 1943 fos.close(); 1944 } catch (IOException ex) { 1945 Log.e(TAG, "Cannot close output stream for: " + data); 1946 } 1947 } 1948 } 1949 1950 if (!statusIntent.hasExtra(PARAM_EXCEPTION)) { 1951 final String filename = file.getAbsolutePath(); 1952 try { 1953 final String mimeType = getContentResolver().getType(data); 1954 if ("image/jpeg".equals(mimeType)) { 1955 final File outputFile = new File(projectPath, 1956 "download_" + generateId() + ".jpg"); 1957 if (ImageUtils.transformJpeg(filename, outputFile)) { 1958 // Delete the downloaded file 1959 file.delete(); 1960 statusIntent.putExtra(PARAM_FILENAME, 1961 outputFile.getAbsolutePath()); 1962 } else { 1963 statusIntent.putExtra(PARAM_FILENAME, filename); 1964 } 1965 } else { 1966 statusIntent.putExtra(PARAM_FILENAME, filename); 1967 } 1968 } catch (Exception ex) { 1969 // Ignore the exception and continue 1970 Log.w(TAG, "Could not transform JPEG: " + filename, ex); 1971 statusIntent.putExtra(PARAM_FILENAME, filename); 1972 } 1973 } 1974 1975 mVideoThread.submit(statusIntent); 1976 } 1977 }.start(); 1978 1979 break; 1980 } 1981 1982 case OP_MEDIA_ITEM_LOAD_STATUS: { 1983 final Intent originalIntent = (Intent)intent.getParcelableExtra(PARAM_INTENT); 1984 if (intent.hasExtra(PARAM_EXCEPTION)) { // 1985 final Exception exception = 1986 (Exception)intent.getSerializableExtra(PARAM_EXCEPTION); 1987 completeRequest(intent, videoEditor, exception, null, originalIntent, 1988 true); 1989 } else { 1990 completeRequest(intent, videoEditor, null, 1991 intent.getStringExtra(PARAM_FILENAME), originalIntent, true); 1992 } 1993 break; 1994 } 1995 1996 case OP_MEDIA_ITEM_MOVE: { 1997 final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); 1998 logd("OP_MEDIA_ITEM_MOVE: " + mediaItemId); 1999 2000 // Determine the position of the media item we are moving 2001 final List<MediaItem> mediaItems = videoEditor.getAllMediaItems(); 2002 final int mediaItemsCount = mediaItems.size(); 2003 int movedItemPosition = -1; 2004 MediaItem movedMediaItem = null; 2005 for (int i = 0; i < mediaItemsCount; i++) { 2006 final MediaItem mi = mediaItems.get(i); 2007 if (mi.getId().equals(mediaItemId)) { 2008 movedMediaItem = mi; 2009 movedItemPosition = i; 2010 break; 2011 } 2012 } 2013 2014 if (movedItemPosition == -1) { 2015 throw new IllegalArgumentException("Moved MediaItem not found: " + 2016 mediaItemId); 2017 } 2018 2019 final Transition beginTransition = movedMediaItem.getBeginTransition(); 2020 final Transition endTransition = movedMediaItem.getEndTransition(); 2021 2022 final String afterMediaItemId = intent.getStringExtra( 2023 PARAM_RELATIVE_STORYBOARD_ITEM_ID); 2024 videoEditor.moveMediaItem(mediaItemId, afterMediaItemId); 2025 2026 // Apply the theme if any 2027 final String themeId = intent.getStringExtra(PARAM_THEME); 2028 if (themeId != null) { 2029 // Apply the theme at the removed position 2030 applyThemeAfterMove(videoEditor, themeId, movedMediaItem, 2031 movedItemPosition, beginTransition, endTransition); 2032 } 2033 2034 final List<MovieMediaItem> mediaItemsCopy = copyMediaItems(mediaItems); 2035 completeRequest(intent, videoEditor, null, mediaItemsCopy, null, false); 2036 generatePreview(videoEditor, true); 2037 completeRequest(intent); 2038 break; 2039 } 2040 2041 case OP_MEDIA_ITEM_REMOVE: { 2042 final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); 2043 logd("OP_MEDIA_ITEM_REMOVE: " + mediaItemId); 2044 2045 // Determine the position of the media item we are removing 2046 final List<MediaItem> mediaItems = videoEditor.getAllMediaItems(); 2047 final int mediaItemsCount = mediaItems.size(); 2048 int removedItemPosition = -1; 2049 MediaItem removedMediaItem = null; 2050 for (int i = 0; i < mediaItemsCount; i++) { 2051 if (mediaItems.get(i).getId().equals(mediaItemId)) { 2052 removedMediaItem = mediaItems.get(i); 2053 removedItemPosition = i; 2054 break; 2055 } 2056 } 2057 2058 if (removedMediaItem == null) { 2059 throw new IllegalArgumentException("MediaItem not found: " + mediaItemId); 2060 } 2061 2062 final Transition beginTransition = removedMediaItem.getBeginTransition(); 2063 final Transition endTransition = removedMediaItem.getEndTransition(); 2064 2065 videoEditor.removeMediaItem(mediaItemId); 2066 2067 // Apply the theme if any 2068 MovieTransition movieTransition = null; 2069 final String themeId = intent.getStringExtra(PARAM_THEME); 2070 if (themeId != null && mediaItems.size() > 0) { 2071 final Transition transition = applyThemeAfterRemove(videoEditor, themeId, 2072 removedItemPosition, beginTransition, endTransition); 2073 if (transition != null) { 2074 movieTransition = new MovieTransition(transition); 2075 } 2076 } 2077 2078 completeRequest(intent, videoEditor, null, movieTransition, null, false); 2079 generatePreview(videoEditor, true); 2080 completeRequest(intent); 2081 break; 2082 } 2083 2084 case OP_MEDIA_ITEM_SET_RENDERING_MODE: { 2085 final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); 2086 logd("OP_MEDIA_ITEM_SET_RENDERING_MODE: " + mediaItemId); 2087 2088 final MediaItem mediaItem = videoEditor.getMediaItem(mediaItemId); 2089 if (mediaItem == null) { 2090 throw new IllegalArgumentException("MediaItem not found: " + mediaItemId); 2091 } 2092 mediaItem.setRenderingMode(intent.getIntExtra(PARAM_MEDIA_ITEM_RENDERING_MODE, 2093 MediaItem.RENDERING_MODE_BLACK_BORDER)); 2094 2095 completeRequest(intent, videoEditor, null, null, null, false); 2096 generatePreview(videoEditor, true); 2097 completeRequest(intent); 2098 break; 2099 } 2100 2101 case OP_MEDIA_ITEM_SET_DURATION: { 2102 final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); 2103 logd("OP_MEDIA_ITEM_SET_DURATION: " + mediaItemId); 2104 2105 final MediaImageItem mediaItem = 2106 (MediaImageItem)videoEditor.getMediaItem(mediaItemId); 2107 if (mediaItem == null) { 2108 throw new IllegalArgumentException("MediaItem not found: " + mediaItemId); 2109 } 2110 2111 final long durationMs = intent.getLongExtra(PARAM_DURATION, 0); 2112 mediaItem.setDuration(durationMs); 2113 // Adjust all effects to the new duration 2114 final List<Effect> effects = mediaItem.getAllEffects(); 2115 for (Effect effect : effects) { 2116 effect.setDuration(durationMs); 2117 } 2118 2119 completeRequest(intent, videoEditor, null, new MovieMediaItem(mediaItem), null, 2120 false); 2121 generatePreview(videoEditor, true); 2122 completeRequest(intent); 2123 break; 2124 } 2125 2126 case OP_MEDIA_ITEM_SET_BOUNDARIES: { 2127 final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); 2128 final MediaVideoItem mediaItem = 2129 (MediaVideoItem)videoEditor.getMediaItem(mediaItemId); 2130 if (mediaItem == null) { 2131 throw new IllegalArgumentException("MediaItem not found: " + mediaItemId); 2132 } 2133 2134 mediaItem.setExtractBoundaries(intent.getLongExtra(PARAM_BEGIN_BOUNDARY, 0), 2135 intent.getLongExtra(PARAM_END_BOUNDARY, 0)); 2136 2137 final List<Overlay> overlays = mediaItem.getAllOverlays(); 2138 if (overlays.size() > 0) { 2139 // Adjust the overlay 2140 final Overlay overlay = overlays.get(0); 2141 if (overlay.getStartTime() < mediaItem.getBoundaryBeginTime()) { 2142 overlay.setStartTime(mediaItem.getBoundaryBeginTime()); 2143 overlay.setDuration(Math.min(overlay.getDuration(), 2144 mediaItem.getTimelineDuration())); 2145 } else if (overlay.getStartTime() + overlay.getDuration() > 2146 mediaItem.getBoundaryEndTime()) { 2147 overlay.setStartTime(Math.max(mediaItem.getBoundaryBeginTime(), 2148 mediaItem.getBoundaryEndTime() - overlay.getDuration())); 2149 overlay.setDuration(mediaItem.getBoundaryEndTime() - 2150 overlay.getStartTime()); 2151 } 2152 } 2153 2154 completeRequest(intent, videoEditor, null, new MovieMediaItem(mediaItem), null, 2155 false); 2156 generatePreview(videoEditor, true); 2157 completeRequest(intent); 2158 break; 2159 } 2160 2161 case OP_MEDIA_ITEM_GET_THUMBNAILS: { 2162 // Note that this command is executed in the thumbnail thread 2163 final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); 2164 logd("OP_MEDIA_ITEM_GET_THUMBNAILS: " + mediaItemId); 2165 2166 final MediaItem mediaItem = videoEditor.getMediaItem(mediaItemId); 2167 if (mediaItem == null) { 2168 throw new IllegalArgumentException("MediaItem not found: " + mediaItemId); 2169 } 2170 2171 final VideoEditor ve = videoEditor; // Just to make it "final" 2172 mediaItem.getThumbnailList( 2173 intent.getIntExtra(PARAM_WIDTH, 0), 2174 intent.getIntExtra(PARAM_HEIGHT, 0), 2175 intent.getLongExtra(PARAM_START_TIME, 0), 2176 intent.getLongExtra(PARAM_END_TIME, 0), 2177 intent.getIntExtra(PARAM_COUNT, 0), 2178 intent.getIntArrayExtra(PARAM_INDICES), 2179 new GetThumbnailListCallback() { 2180 public void onThumbnail(Bitmap bitmap, int index) { 2181 completeRequest( 2182 intent, ve, null, bitmap, 2183 Integer.valueOf(index), false); 2184 } 2185 } 2186 ); 2187 2188 completeRequest(intent, videoEditor, null, null, null, true); 2189 break; 2190 } 2191 2192 case OP_MEDIA_ITEM_SET_VOLUME: { 2193 final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); 2194 logd("OP_MEDIA_ITEM_SET_VOLUME: " + mediaItemId); 2195 2196 final MediaItem mediaItem = videoEditor.getMediaItem(mediaItemId); 2197 if (mediaItem != null && mediaItem instanceof MediaVideoItem) { 2198 ((MediaVideoItem)mediaItem).setVolume(intent.getIntExtra(PARAM_VOLUME, 0)); 2199 2200 completeRequest(intent, videoEditor, null, null, null, false); 2201 generatePreview(videoEditor, false); 2202 completeRequest(intent); 2203 } else { 2204 throw new IllegalArgumentException("MediaItem not found: " + mediaItemId); 2205 } 2206 break; 2207 } 2208 2209 case OP_MEDIA_ITEM_SET_MUTE: { 2210 final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); 2211 logd("OP_MEDIA_ITEM_SET_MUTE: " + mediaItemId); 2212 2213 final MediaItem mediaItem = videoEditor.getMediaItem(mediaItemId); 2214 if (mediaItem != null && mediaItem instanceof MediaVideoItem) { 2215 ((MediaVideoItem)mediaItem).setMute(intent.getBooleanExtra(PARAM_MUTE, 2216 false)); 2217 2218 completeRequest(intent, videoEditor, null, null, null, false); 2219 generatePreview(videoEditor, false); 2220 completeRequest(intent); 2221 } else { 2222 throw new IllegalArgumentException("MediaItem not found: " + mediaItemId); 2223 } 2224 break; 2225 } 2226 2227 case OP_MEDIA_ITEM_EXTRACT_AUDIO_WAVEFORM: { 2228 final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); 2229 logd("OP_MEDIA_ITEM_EXTRACT_AUDIO_WAVEFORM: " + mediaItemId); 2230 2231 final MediaItem mediaItem = videoEditor.getMediaItem(mediaItemId); 2232 if (mediaItem != null && mediaItem instanceof MediaVideoItem) { 2233 final MediaVideoItem movieMediaItem = ((MediaVideoItem)mediaItem); 2234 final WaveformData waveformData = movieMediaItem.getWaveformData(); 2235 if (waveformData == null) { 2236 extractMediaItemAudioWaveform(intent, videoEditor, movieMediaItem); 2237 completeRequest(intent, videoEditor, null, 2238 movieMediaItem.getWaveformData(), null, true); 2239 } else { 2240 completeRequest(intent, videoEditor, null, waveformData, null, true); 2241 } 2242 } else { 2243 throw new IllegalArgumentException("MediaItem not found: " + mediaItemId); 2244 } 2245 break; 2246 } 2247 2248 case OP_TRANSITION_INSERT_ALPHA: { 2249 logd("OP_TRANSITION_INSERT_ALPHA: " 2250 + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); 2251 2252 final String afterMediaItemId = 2253 intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID); 2254 final MediaItem afterMediaItem; 2255 if (afterMediaItemId != null) { 2256 afterMediaItem = videoEditor.getMediaItem(afterMediaItemId); 2257 } else { 2258 afterMediaItem = null; 2259 } 2260 2261 final int maskRawResourceId = intent.getIntExtra(PARAM_TRANSITION_MASK, 2262 R.raw.mask_contour); 2263 2264 final MediaItem beforeMediaItem = nextMediaItem(videoEditor, afterMediaItemId); 2265 final Transition transition = new TransitionAlpha( 2266 intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID), 2267 afterMediaItem, beforeMediaItem, 2268 intent.getLongExtra(PARAM_DURATION, 0), 2269 intent.getIntExtra(PARAM_TRANSITION_BEHAVIOR, 2270 Transition.BEHAVIOR_LINEAR), 2271 FileUtils.getMaskFilename(getApplicationContext(), 2272 maskRawResourceId), 2273 intent.getIntExtra(PARAM_TRANSITION_BLENDING, 100), 2274 intent.getBooleanExtra(PARAM_TRANSITION_INVERT, false)); 2275 videoEditor.addTransition(transition); 2276 2277 completeRequest(intent, videoEditor, null, transition, null, false); 2278 generatePreview(videoEditor, true); 2279 completeRequest(intent); 2280 break; 2281 } 2282 2283 case OP_TRANSITION_INSERT_CROSSFADE: { 2284 logd("OP_TRANSITION_INSERT_CROSSFADE: " 2285 + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); 2286 2287 final String afterMediaItemId = 2288 intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID); 2289 final MediaItem afterMediaItem; 2290 if (afterMediaItemId != null) { 2291 afterMediaItem = videoEditor.getMediaItem(afterMediaItemId); 2292 } else { 2293 afterMediaItem = null; 2294 } 2295 2296 final MediaItem beforeMediaItem = nextMediaItem(videoEditor, afterMediaItemId); 2297 final Transition transition = new TransitionCrossfade( 2298 intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID), 2299 afterMediaItem, beforeMediaItem, 2300 intent.getLongExtra(PARAM_DURATION, 0), 2301 intent.getIntExtra(PARAM_TRANSITION_BEHAVIOR, 2302 Transition.BEHAVIOR_LINEAR)); 2303 videoEditor.addTransition(transition); 2304 2305 completeRequest(intent, videoEditor, null, transition, null, false); 2306 generatePreview(videoEditor, true); 2307 completeRequest(intent); 2308 break; 2309 } 2310 2311 case OP_TRANSITION_INSERT_FADE_BLACK: { 2312 logd("OP_TRANSITION_INSERT_FADE_TO_BLACK: " 2313 + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); 2314 2315 final String afterMediaItemId = 2316 intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID); 2317 final MediaItem afterMediaItem; 2318 if (afterMediaItemId != null) { 2319 afterMediaItem = videoEditor.getMediaItem(afterMediaItemId); 2320 } else { 2321 afterMediaItem = null; 2322 } 2323 2324 final MediaItem beforeMediaItem = nextMediaItem(videoEditor, afterMediaItemId); 2325 final Transition transition = new TransitionFadeBlack( 2326 intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID), 2327 afterMediaItem, beforeMediaItem, 2328 intent.getLongExtra(PARAM_DURATION, 0), 2329 intent.getIntExtra(PARAM_TRANSITION_BEHAVIOR, 2330 Transition.BEHAVIOR_LINEAR)); 2331 videoEditor.addTransition(transition); 2332 2333 completeRequest(intent, videoEditor, null, transition, null, false); 2334 generatePreview(videoEditor, true); 2335 completeRequest(intent); 2336 break; 2337 } 2338 2339 case OP_TRANSITION_INSERT_SLIDING: { 2340 logd("OP_TRANSITION_INSERT_SLIDING: " 2341 + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); 2342 2343 final String afterMediaItemId = 2344 intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID); 2345 final MediaItem afterMediaItem; 2346 if (afterMediaItemId != null) { 2347 afterMediaItem = videoEditor.getMediaItem(afterMediaItemId); 2348 } else { 2349 afterMediaItem = null; 2350 } 2351 2352 final MediaItem beforeMediaItem = nextMediaItem(videoEditor, afterMediaItemId); 2353 final Transition transition = new TransitionSliding( 2354 intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID), 2355 afterMediaItem, beforeMediaItem, 2356 intent.getLongExtra(PARAM_DURATION, 0), 2357 intent.getIntExtra(PARAM_TRANSITION_BEHAVIOR, 2358 Transition.BEHAVIOR_LINEAR), 2359 intent.getIntExtra(PARAM_TRANSITION_DIRECTION, 2360 TransitionSliding.DIRECTION_RIGHT_OUT_LEFT_IN)); 2361 videoEditor.addTransition(transition); 2362 2363 completeRequest(intent, videoEditor, null, transition, null, false); 2364 generatePreview(videoEditor, true); 2365 completeRequest(intent); 2366 break; 2367 } 2368 2369 case OP_TRANSITION_REMOVE: { 2370 logd("OP_TRANSITION_REMOVE: " 2371 + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); 2372 2373 videoEditor.removeTransition(intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); 2374 2375 completeRequest(intent, videoEditor, null, null, null, false); 2376 generatePreview(videoEditor, true); 2377 completeRequest(intent); 2378 break; 2379 } 2380 2381 case OP_TRANSITION_SET_DURATION: { 2382 final String transitionId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); 2383 logd("OP_TRANSITION_SET_DURATION: " + transitionId); 2384 2385 final Transition transition = videoEditor.getTransition(transitionId); 2386 if (transition == null) { 2387 throw new IllegalArgumentException("Transition not found: " + 2388 transitionId); 2389 } 2390 transition.setDuration(intent.getLongExtra(PARAM_DURATION, 0)); 2391 2392 completeRequest(intent, videoEditor, null, null, null, false); 2393 generatePreview(videoEditor, true); 2394 completeRequest(intent); 2395 break; 2396 } 2397 2398 case OP_TRANSITION_GET_THUMBNAIL: { 2399 final String transitionId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); 2400 logd("OP_TRANSITION_GET_THUMBNAIL: " + transitionId); 2401 2402 final Transition transition = videoEditor.getTransition(transitionId); 2403 if (transition == null) { 2404 throw new IllegalArgumentException("Transition not found: " + 2405 transitionId); 2406 } 2407 2408 final int height = intent.getIntExtra(PARAM_HEIGHT, 0); 2409 final MediaItem afterMediaItem = transition.getAfterMediaItem(); 2410 final Bitmap[] thumbnails = new Bitmap[2]; 2411 if (afterMediaItem != null) { 2412 thumbnails[0] = afterMediaItem.getThumbnail( 2413 (afterMediaItem.getWidth() * height) / afterMediaItem.getHeight(), 2414 height, afterMediaItem.getTimelineDuration()); 2415 } else { 2416 thumbnails[0] = null; 2417 } 2418 2419 final MediaItem beforeMediaItem = transition.getBeforeMediaItem(); 2420 if (beforeMediaItem != null) { 2421 thumbnails[1] = beforeMediaItem.getThumbnail( 2422 (beforeMediaItem.getWidth() * height) / beforeMediaItem.getHeight(), 2423 height, 0); 2424 } else { 2425 thumbnails[1] = null; 2426 } 2427 2428 completeRequest(intent, videoEditor, null, thumbnails, null, true); 2429 break; 2430 } 2431 2432 case OP_EFFECT_ADD_COLOR: { 2433 logd("OP_EFFECT_ADD_COLOR: " 2434 + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); 2435 2436 final MediaItem mediaItem = videoEditor.getMediaItem( 2437 intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID)); 2438 if (mediaItem == null) { 2439 throw new IllegalArgumentException("MediaItem not found: " + 2440 intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID)); 2441 } 2442 2443 // Remove any existing effect 2444 final List<Effect> effects = mediaItem.getAllEffects(); 2445 for (Effect effect : effects) { 2446 mediaItem.removeEffect(effect.getId()); 2447 } 2448 2449 final Effect effect = new EffectColor(mediaItem, 2450 intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID), 2451 intent.getLongExtra(PARAM_START_TIME, -1), 2452 intent.getLongExtra(PARAM_DURATION, 0), 2453 intent.getIntExtra(PARAM_EFFECT_TYPE, -1), 2454 intent.getIntExtra(PARAM_EFFECT_PARAM, -1)); 2455 mediaItem.addEffect(effect); 2456 2457 completeRequest(intent, videoEditor, null, new MovieEffect(effect), null, 2458 false); 2459 generatePreview(videoEditor, true); 2460 completeRequest(intent); 2461 break; 2462 } 2463 2464 case OP_EFFECT_ADD_IMAGE_KEN_BURNS: { 2465 logd("OP_EFFECT_ADD_IMAGE_KEN_BURNS: " 2466 + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); 2467 2468 final MediaItem mediaItem = videoEditor.getMediaItem( 2469 intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID)); 2470 if (mediaItem == null) { 2471 throw new IllegalArgumentException("MediaItem not found: " + 2472 intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID)); 2473 } 2474 2475 // Remove any existing effect 2476 final List<Effect> effects = mediaItem.getAllEffects(); 2477 for (Effect effect : effects) { 2478 mediaItem.removeEffect(effect.getId()); 2479 } 2480 2481 final Effect effect = new EffectKenBurns(mediaItem, 2482 intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID), 2483 (Rect)intent.getParcelableExtra(PARAM_MEDIA_ITEM_START_RECT), 2484 (Rect)intent.getParcelableExtra(PARAM_MEDIA_ITEM_END_RECT), 2485 intent.getLongExtra(PARAM_START_TIME, 0), 2486 intent.getLongExtra(PARAM_DURATION, 0)); 2487 mediaItem.addEffect(effect); 2488 2489 completeRequest(intent, videoEditor, null, new MovieEffect(effect), null, 2490 false); 2491 generatePreview(videoEditor, true); 2492 completeRequest(intent); 2493 break; 2494 } 2495 2496 case OP_EFFECT_REMOVE: { 2497 logd("OP_EFFECT_REMOVE: " + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); 2498 2499 final MediaItem mediaItem = videoEditor.getMediaItem( 2500 intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID)); 2501 if (mediaItem == null) { 2502 throw new IllegalArgumentException("MediaItem not found: " + 2503 intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID)); 2504 } 2505 2506 mediaItem.removeEffect(intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); 2507 2508 completeRequest(intent, videoEditor, null, null, null, false); 2509 generatePreview(videoEditor, true); 2510 completeRequest(intent); 2511 break; 2512 } 2513 2514 case OP_OVERLAY_ADD: { 2515 logd("OP_OVERLAY_ADD: " + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); 2516 2517 final MediaItem mediaItem = videoEditor.getMediaItem( 2518 intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID)); 2519 if (mediaItem == null) { 2520 throw new IllegalArgumentException("MediaItem not found: " + 2521 intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID)); 2522 } 2523 2524 // Remove any existing overlays 2525 final List<Overlay> overlays = mediaItem.getAllOverlays(); 2526 for (Overlay overlay : overlays) { 2527 mediaItem.removeOverlay(overlay.getId()); 2528 } 2529 2530 final int scaledWidth, scaledHeight; 2531 if (mediaItem instanceof MediaVideoItem) { 2532 scaledWidth = ((MediaVideoItem)mediaItem).getWidth(); 2533 scaledHeight = ((MediaVideoItem)mediaItem).getHeight(); 2534 } else { 2535 scaledWidth = ((MediaImageItem)mediaItem).getScaledWidth(); 2536 scaledHeight = ((MediaImageItem)mediaItem).getScaledHeight(); 2537 } 2538 2539 final Bundle userAttributes = intent.getBundleExtra(PARAM_ATTRIBUTES); 2540 2541 final int overlayType = MovieOverlay.getType(userAttributes); 2542 final String title = MovieOverlay.getTitle(userAttributes); 2543 final String subTitle = MovieOverlay.getSubtitle(userAttributes); 2544 2545 final OverlayFrame overlay = new OverlayFrame(mediaItem, 2546 intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID), 2547 ImageUtils.buildOverlayBitmap(getApplicationContext(), null, 2548 overlayType, title, subTitle, scaledWidth, scaledHeight), 2549 intent.getLongExtra(PARAM_START_TIME, -1), 2550 intent.getLongExtra(PARAM_DURATION, 0)); 2551 2552 // Set the user attributes 2553 for (String name : userAttributes.keySet()) { 2554 if (MovieOverlay.getAttributeType(name).equals(Integer.class)) { 2555 overlay.setUserAttribute(name, 2556 Integer.toString(userAttributes.getInt(name))); 2557 } else { // Strings 2558 overlay.setUserAttribute(name, userAttributes.getString(name)); 2559 } 2560 } 2561 mediaItem.addOverlay(overlay); 2562 2563 completeRequest(intent, videoEditor, null, new MovieOverlay(overlay), null, 2564 false); 2565 generatePreview(videoEditor, true); 2566 completeRequest(intent); 2567 break; 2568 } 2569 2570 case OP_OVERLAY_REMOVE: { 2571 logd("OP_OVERLAY_REMOVE: " + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); 2572 2573 final MediaItem mediaItem = videoEditor.getMediaItem( 2574 intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID)); 2575 if (mediaItem == null) { 2576 throw new IllegalArgumentException("MediaItem not found: " + 2577 intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID)); 2578 } 2579 2580 mediaItem.removeOverlay(intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); 2581 2582 completeRequest(intent, videoEditor, null, null, null, false); 2583 generatePreview(videoEditor, true); 2584 completeRequest(intent); 2585 break; 2586 } 2587 2588 case OP_OVERLAY_SET_START_TIME: { 2589 logd("OP_OVERLAY_SET_START_TIME: " 2590 + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); 2591 2592 final MediaItem mediaItem = videoEditor.getMediaItem( 2593 intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID)); 2594 if (mediaItem == null) { 2595 throw new IllegalArgumentException("MediaItem not found: " + 2596 intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID)); 2597 } 2598 2599 final Overlay overlay = mediaItem.getOverlay( 2600 intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); 2601 if (overlay == null) { 2602 throw new IllegalArgumentException("Overlay not found: " + 2603 intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); 2604 } 2605 2606 overlay.setStartTime(intent.getLongExtra(PARAM_START_TIME, 0)); 2607 2608 completeRequest(intent, videoEditor, null, null, null, false); 2609 generatePreview(videoEditor, true); 2610 completeRequest(intent); 2611 break; 2612 } 2613 2614 case OP_OVERLAY_SET_DURATION: { 2615 logd("OP_OVERLAY_SET_DURATION: " 2616 + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); 2617 2618 final MediaItem mediaItem = videoEditor.getMediaItem( 2619 intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID)); 2620 if (mediaItem == null) { 2621 throw new IllegalArgumentException("MediaItem not found: " + 2622 intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID)); 2623 } 2624 2625 final Overlay overlay = mediaItem.getOverlay( 2626 intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); 2627 if (overlay == null) { 2628 throw new IllegalArgumentException("Overlay not found: " + 2629 intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); 2630 } 2631 2632 overlay.setDuration(intent.getLongExtra(PARAM_DURATION, 0)); 2633 2634 completeRequest(intent, videoEditor, null, null, null, false); 2635 generatePreview(videoEditor, true); 2636 completeRequest(intent); 2637 break; 2638 } 2639 2640 case OP_OVERLAY_SET_ATTRIBUTES: { 2641 logd("OP_OVERLAY_SET_ATTRIBUTES: " 2642 + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); 2643 2644 final MediaItem mediaItem = videoEditor.getMediaItem( 2645 intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID)); 2646 if (mediaItem == null) { 2647 throw new IllegalArgumentException("MediaItem not found: " + 2648 intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID)); 2649 } 2650 2651 final Overlay overlay = mediaItem.getOverlay( 2652 intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); 2653 if (overlay == null) { 2654 throw new IllegalArgumentException("Overlay not found: " + 2655 intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); 2656 } 2657 2658 final int scaledWidth, scaledHeight; 2659 if (mediaItem instanceof MediaVideoItem) { 2660 scaledWidth = ((MediaVideoItem)mediaItem).getWidth(); 2661 scaledHeight = ((MediaVideoItem)mediaItem).getHeight(); 2662 } else { 2663 scaledWidth = ((MediaImageItem)mediaItem).getScaledWidth(); 2664 scaledHeight = ((MediaImageItem)mediaItem).getScaledHeight(); 2665 } 2666 2667 final Bundle userAttributes = intent.getBundleExtra(PARAM_ATTRIBUTES); 2668 final int overlayType = MovieOverlay.getType(userAttributes); 2669 final String title = MovieOverlay.getTitle(userAttributes); 2670 final String subTitle = MovieOverlay.getSubtitle(userAttributes); 2671 2672 ((OverlayFrame)overlay).setBitmap( 2673 ImageUtils.buildOverlayBitmap(getApplicationContext(), null, 2674 overlayType, title, subTitle, scaledWidth, scaledHeight)); 2675 2676 for (String name : userAttributes.keySet()) { 2677 if (MovieOverlay.getAttributeType(name).equals(Integer.class)) { 2678 overlay.setUserAttribute(name, 2679 Integer.toString(userAttributes.getInt(name))); 2680 } else { // Strings 2681 overlay.setUserAttribute(name, userAttributes.getString(name)); 2682 } 2683 } 2684 2685 completeRequest(intent, videoEditor, null, null, null, false); 2686 generatePreview(videoEditor, true); 2687 completeRequest(intent); 2688 break; 2689 } 2690 2691 case OP_AUDIO_TRACK_ADD: { 2692 logd("OP_AUDIO_TRACK_ADD: " + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); 2693 2694 final Uri data = intent.getParcelableExtra(PARAM_FILENAME); 2695 String filename = null; 2696 // Get the filename 2697 Cursor cursor = null; 2698 try { 2699 cursor = getContentResolver().query(data, 2700 new String[] {Audio.Media.DATA}, null, null, null); 2701 if (cursor.moveToFirst()) { 2702 filename = cursor.getString(0); 2703 } 2704 } finally { 2705 if (cursor != null) { 2706 cursor.close(); 2707 } 2708 } 2709 2710 if (filename == null) { 2711 throw new IllegalArgumentException("Media file not found: " + data); 2712 } 2713 2714 final AudioTrack audioTrack = new AudioTrack(videoEditor, 2715 intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID), filename); 2716 audioTrack.enableDucking(DUCK_THRESHOLD, DUCK_TRACK_VOLUME); 2717 audioTrack.setVolume(DEFAULT_AUDIO_TRACK_VOLUME); 2718 if (intent.getBooleanExtra(PARAM_LOOP, false)) { 2719 audioTrack.enableLoop(); 2720 } else { 2721 audioTrack.disableLoop(); 2722 } 2723 2724 videoEditor.addAudioTrack(audioTrack); 2725 2726 completeRequest(intent, videoEditor, null, new MovieAudioTrack(audioTrack), 2727 null, false); 2728 // This is needed to decode the audio file into a PCM file 2729 generatePreview(videoEditor, false); 2730 completeRequest(intent); 2731 break; 2732 } 2733 2734 case OP_AUDIO_TRACK_REMOVE: { 2735 logd("OP_AUDIO_TRACK_REMOVE: " 2736 + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); 2737 2738 videoEditor.removeAudioTrack(intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID)); 2739 2740 completeRequest(intent, videoEditor, null, null, null, false); 2741 generatePreview(videoEditor, false); 2742 completeRequest(intent); 2743 break; 2744 } 2745 2746 case OP_AUDIO_TRACK_SET_BOUNDARIES: { 2747 final String audioTrackId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); 2748 logd("OP_AUDIO_TRACK_SET_BOUNDARIES: " + audioTrackId); 2749 2750 final AudioTrack audioTrack = videoEditor.getAudioTrack(audioTrackId); 2751 if (audioTrack == null) { 2752 throw new IllegalArgumentException("AudioTrack not found: " + 2753 audioTrackId); 2754 } 2755 2756 audioTrack.setExtractBoundaries(intent.getLongExtra(PARAM_BEGIN_BOUNDARY, 0), 2757 intent.getLongExtra(PARAM_END_BOUNDARY, 0)); 2758 2759 completeRequest(intent, videoEditor, null, null, null, false); 2760 generatePreview(videoEditor, false); 2761 completeRequest(intent); 2762 break; 2763 } 2764 2765 case OP_AUDIO_TRACK_SET_LOOP: { 2766 final String audioTrackId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); 2767 logd("OP_AUDIO_TRACK_SET_LOOP: " + audioTrackId); 2768 2769 final AudioTrack audioTrack = videoEditor.getAudioTrack(audioTrackId); 2770 if (audioTrack == null) { 2771 throw new IllegalArgumentException("AudioTrack not found: " + 2772 audioTrackId); 2773 } 2774 2775 if (intent.getBooleanExtra(PARAM_LOOP, false)) { 2776 audioTrack.enableLoop(); 2777 } else { 2778 audioTrack.disableLoop(); 2779 } 2780 2781 completeRequest(intent, videoEditor, null, null, null, false); 2782 generatePreview(videoEditor, false); 2783 completeRequest(intent); 2784 break; 2785 } 2786 2787 case OP_AUDIO_TRACK_SET_DUCK: { 2788 final String audioTrackId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); 2789 logd("OP_AUDIO_TRACK_SET_DUCK: " + audioTrackId); 2790 2791 final AudioTrack audioTrack = videoEditor.getAudioTrack(audioTrackId); 2792 if (audioTrack == null) { 2793 throw new IllegalArgumentException("AudioTrack not found: " + 2794 audioTrackId); 2795 } 2796 2797 if (intent.getBooleanExtra(PARAM_DUCK, false)) { 2798 audioTrack.enableDucking(DUCK_THRESHOLD, DUCK_TRACK_VOLUME); 2799 } else { 2800 audioTrack.disableDucking(); 2801 } 2802 2803 completeRequest(intent, videoEditor, null, null, null, false); 2804 generatePreview(videoEditor, false); 2805 completeRequest(intent); 2806 break; 2807 } 2808 2809 case OP_AUDIO_TRACK_SET_VOLUME: { 2810 final String audioTrackId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); 2811 logd("OP_AUDIO_TRACK_SET_VOLUME: " + audioTrackId); 2812 2813 final AudioTrack audioTrack = videoEditor.getAudioTrack(audioTrackId); 2814 if (audioTrack == null) { 2815 throw new IllegalArgumentException("AudioTrack not found: " + 2816 audioTrackId); 2817 } 2818 2819 audioTrack.setVolume(intent.getIntExtra(PARAM_VOLUME, 0)); 2820 2821 completeRequest(intent, videoEditor, null, null, null, false); 2822 generatePreview(videoEditor, false); 2823 completeRequest(intent); 2824 break; 2825 } 2826 2827 case OP_AUDIO_TRACK_SET_MUTE: { 2828 final String audioTrackId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); 2829 logd("OP_AUDIO_TRACK_SET_MUTE: " + audioTrackId); 2830 2831 final AudioTrack audioTrack = videoEditor.getAudioTrack(audioTrackId); 2832 if (audioTrack == null) { 2833 throw new IllegalArgumentException("AudioTrack not found: " + 2834 audioTrackId); 2835 } 2836 2837 audioTrack.setMute(intent.getBooleanExtra(PARAM_MUTE, false)); 2838 2839 completeRequest(intent, videoEditor, null, null, null, false); 2840 generatePreview(videoEditor, false); 2841 completeRequest(intent); 2842 break; 2843 } 2844 2845 case OP_AUDIO_TRACK_EXTRACT_AUDIO_WAVEFORM: { 2846 final String audioTrackId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); 2847 logd("OP_AUDIO_TRACK_EXTRACT_AUDIO_WAVEFORM: " + audioTrackId); 2848 2849 final AudioTrack audioTrack = videoEditor.getAudioTrack(audioTrackId); 2850 if (audioTrack == null) { 2851 throw new IllegalArgumentException("AudioTrack not found: " + 2852 audioTrackId); 2853 } 2854 2855 final WaveformData waveformData = audioTrack.getWaveformData(); 2856 if (waveformData == null) { 2857 extractAudioTrackAudioWaveform(intent, videoEditor, audioTrack); 2858 completeRequest(intent, videoEditor, null, audioTrack.getWaveformData(), 2859 null, true); 2860 } else { 2861 completeRequest(intent, videoEditor, null, waveformData, null, true); 2862 } 2863 break; 2864 } 2865 2866 default: { 2867 throw new IllegalArgumentException("Unhandled operation: " + op); 2868 } 2869 } 2870 } catch (Exception ex) { 2871 ex.printStackTrace(); 2872 completeRequest(intent, videoEditor, ex, null, null, true); 2873 } 2874 } 2875 2876 /** 2877 * Complete the request 2878 * 2879 * @param intent The intent 2880 * @param videoEditor The video editor 2881 * @param exception The exception 2882 * @param result The result object 2883 * @param extraResult The extra result object 2884 * @param finalize true if the request should be finalized 2885 */ 2886 private void completeRequest(final Intent intent, final VideoEditor videoEditor, 2887 final Exception exception, final Object result, final Object extraResult, 2888 final boolean finalize) { 2889 mHandler.post(new Runnable() { 2890 @Override 2891 public void run() { 2892 onIntentProcessed(intent, videoEditor, result, extraResult, exception, finalize); 2893 } 2894 }); 2895 } 2896 2897 /** 2898 * Complete the request 2899 * 2900 * @param intent The intent 2901 */ 2902 private void completeRequest(final Intent intent) { 2903 mHandler.post (new Runnable() { 2904 @Override 2905 public void run() { 2906 finalizeRequest(intent); 2907 mIntentPool.put(intent); 2908 } 2909 }); 2910 } 2911 2912 /** 2913 * Callback called after the specified intent is processed. 2914 * 2915 * @param intent The intent 2916 * @param videoEditor The VideoEditor on which the operation was performed 2917 * @param result The result object 2918 * @param extraResult The extra result object 2919 * @param ex The exception 2920 * @param finalize true if the request should be finalized 2921 */ 2922 @SuppressWarnings("unchecked") 2923 public void onIntentProcessed(final Intent intent, VideoEditor videoEditor, 2924 Object result, Object extraResult, Exception ex, boolean finalize) { 2925 2926 final String projectPath = intent.getStringExtra(PARAM_PROJECT_PATH); 2927 final int op = intent.getIntExtra(PARAM_OP, -1); 2928 switch (op) { 2929 case OP_VIDEO_EDITOR_LOAD_PROJECTS: { 2930 if (finalize) { 2931 finalizeRequest(intent); 2932 } 2933 2934 final List<VideoEditorProject> projects = (List<VideoEditorProject>)result; 2935 for (ApiServiceListener listener : mListeners) { 2936 listener.onProjectsLoaded(projects, ex); 2937 } 2938 2939 break; 2940 } 2941 2942 case OP_VIDEO_EDITOR_CREATE: { 2943 if (finalize) { 2944 finalizeRequest(intent); 2945 } 2946 2947 // Release the old project 2948 if (mVideoProject != null) { 2949 mVideoProject.release(); 2950 mVideoProject = null; 2951 } 2952 2953 if (ex != null) { 2954 FileUtils.deleteDir(new File(projectPath)); 2955 } else { 2956 mVideoProject = (VideoEditorProject)result; 2957 } 2958 2959 for (ApiServiceListener listener : mListeners) { 2960 listener.onVideoEditorCreated(projectPath, mVideoProject, 2961 videoEditor != null ? videoEditor.getAllMediaItems() : null, 2962 videoEditor != null ? videoEditor.getAllAudioTracks() : null, ex); 2963 } 2964 2965 break; 2966 } 2967 2968 case OP_VIDEO_EDITOR_LOAD: { 2969 if (finalize) { 2970 finalizeRequest(intent); 2971 } 2972 2973 if (result != null) { // A new project was created 2974 if (mVideoProject != null) { 2975 mVideoProject.release(); 2976 mVideoProject = null; 2977 } 2978 mVideoProject = (VideoEditorProject)result; 2979 } 2980 2981 for (ApiServiceListener listener : mListeners) { 2982 listener.onVideoEditorLoaded(projectPath, mVideoProject, 2983 ex == null ? videoEditor.getAllMediaItems() : null, 2984 ex == null ? videoEditor.getAllAudioTracks() : null, ex); 2985 } 2986 2987 break; 2988 } 2989 2990 case OP_VIDEO_EDITOR_SET_ASPECT_RATIO: { 2991 if (finalize) { 2992 finalizeRequest(intent); 2993 } 2994 2995 final int aspectRatio = intent.getIntExtra(PARAM_ASPECT_RATIO, 2996 MediaProperties.ASPECT_RATIO_UNDEFINED); 2997 if (ex == null) { 2998 final VideoEditorProject videoProject = getProject(projectPath); 2999 if (videoProject != null) { 3000 videoProject.setAspectRatio(aspectRatio); 3001 } 3002 } 3003 3004 for (ApiServiceListener listener : mListeners) { 3005 listener.onVideoEditorAspectRatioSet(projectPath, aspectRatio, ex); 3006 } 3007 3008 break; 3009 } 3010 3011 case OP_VIDEO_EDITOR_APPLY_THEME: { 3012 if (finalize) { 3013 finalizeRequest(intent); 3014 } 3015 3016 final String theme = intent.getStringExtra(PARAM_THEME); 3017 if (ex == null) { 3018 final VideoEditorProject videoProject = getProject(projectPath); 3019 if (videoProject != null) { 3020 videoProject.setTheme(theme); 3021 videoProject.setMediaItems((List<MovieMediaItem>)result); 3022 videoProject.setAudioTracks((List<MovieAudioTrack>)extraResult); 3023 } 3024 } 3025 3026 for (ApiServiceListener listener : mListeners) { 3027 listener.onVideoEditorThemeApplied(projectPath, theme, ex); 3028 } 3029 3030 break; 3031 } 3032 3033 case OP_VIDEO_EDITOR_GENERATE_PREVIEW_PROGRESS: { 3034 final String className = intent.getStringExtra(PARAM_ATTRIBUTES); 3035 final String itemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); 3036 final int action = intent.getIntExtra(PARAM_ACTION, 3037 VideoEditor.MediaProcessingProgressListener.ACTION_DECODE); 3038 final int progress = intent.getIntExtra(PARAM_PROGRESS_VALUE, 0); 3039 3040 for (ApiServiceListener listener : mListeners) { 3041 listener.onVideoEditorGeneratePreviewProgress(projectPath, className, itemId, 3042 action, progress); 3043 } 3044 3045 break; 3046 } 3047 3048 case OP_VIDEO_EDITOR_EXPORT: { 3049 // The finalizeRequest() call and listener callbacks are done in 3050 // OP_VIDEO_EDITOR_EXPORT_STATUS intent handling (where we are 3051 // called originalIntent). 3052 break; 3053 } 3054 3055 case OP_VIDEO_EDITOR_CANCEL_EXPORT: { 3056 if (finalize) { 3057 finalizeRequest(intent); 3058 } 3059 3060 for (ApiServiceListener listener : mListeners) { 3061 listener.onVideoEditorExportCanceled(projectPath, 3062 intent.getStringExtra(PARAM_FILENAME)); 3063 } 3064 break; 3065 } 3066 3067 case OP_VIDEO_EDITOR_EXPORT_STATUS: { 3068 // This operation is for the service internal use only 3069 if (finalize) { 3070 finalizeRequest(intent); 3071 } 3072 3073 final String filename = intent.getStringExtra(PARAM_FILENAME); 3074 if (intent.hasExtra(PARAM_EXCEPTION)) { // Complete 3075 final Intent originalIntent = (Intent)intent.getParcelableExtra(PARAM_INTENT); 3076 finalizeRequest(originalIntent); 3077 mIntentPool.put(originalIntent); 3078 3079 final Exception exception = 3080 (Exception)intent.getSerializableExtra(PARAM_EXCEPTION); 3081 final VideoEditorProject videoProject = getProject(projectPath); 3082 final boolean cancelled = intent.getBooleanExtra(PARAM_CANCELLED, false); 3083 if (!cancelled && videoProject != null && exception == null) { 3084 final Uri uri = (Uri)intent.getParcelableExtra(PARAM_MOVIE_URI); 3085 videoProject.addExportedMovieUri(uri); 3086 } 3087 3088 for (ApiServiceListener listener : mListeners) { 3089 listener.onVideoEditorExportComplete( 3090 projectPath, filename, exception, cancelled); 3091 } 3092 } else { // Progress 3093 for (ApiServiceListener listener : mListeners) { 3094 listener.onVideoEditorExportProgress(projectPath, filename, 3095 intent.getIntExtra(PARAM_PROGRESS_VALUE, -1)); 3096 } 3097 3098 // The original request is still pending 3099 } 3100 break; 3101 } 3102 3103 case OP_VIDEO_EDITOR_SAVE: { 3104 if (finalize) { 3105 finalizeRequest(intent); 3106 } 3107 3108 for (ApiServiceListener listener : mListeners) { 3109 listener.onVideoEditorSaved(projectPath, ex); 3110 } 3111 break; 3112 } 3113 3114 case OP_VIDEO_EDITOR_RELEASE: { 3115 if (finalize) { 3116 finalizeRequest(intent); 3117 } 3118 3119 final VideoEditorProject videoProject = getProject(projectPath); 3120 if (videoProject != null) { 3121 videoProject.release(); 3122 if (mVideoProject == videoProject) { 3123 mVideoProject = null; 3124 } 3125 } 3126 3127 for (ApiServiceListener listener : mListeners) { 3128 listener.onVideoEditorReleased(projectPath, ex); 3129 } 3130 3131 break; 3132 } 3133 3134 case OP_VIDEO_EDITOR_DELETE: { 3135 if (finalize) { 3136 finalizeRequest(intent); 3137 } 3138 3139 final VideoEditorProject videoProject = getProject(projectPath); 3140 if (videoProject != null) { 3141 videoProject.release(); 3142 if (mVideoProject == videoProject) { 3143 mVideoProject = null; 3144 } 3145 } 3146 3147 for (ApiServiceListener listener : mListeners) { 3148 listener.onVideoEditorDeleted(projectPath, ex); 3149 } 3150 3151 break; 3152 } 3153 3154 case OP_MEDIA_ITEM_ADD_VIDEO_URI: { 3155 if (finalize) { 3156 finalizeRequest(intent); 3157 } 3158 3159 final String afterMediaItemId = 3160 intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID); 3161 3162 final MovieMediaItem movieMediaItem = (MovieMediaItem)result; 3163 final VideoEditorProject videoProject = getProject(projectPath); 3164 if (videoProject != null) { 3165 if (ex == null && extraResult != null) { 3166 // The aspect ratio has changed 3167 videoProject.setAspectRatio((Integer)extraResult); 3168 } 3169 3170 if (ex == null) { 3171 videoProject.insertMediaItem(movieMediaItem, afterMediaItemId); 3172 } 3173 } 3174 3175 for (ApiServiceListener listener : mListeners) { 3176 listener.onMediaItemAdded(projectPath, 3177 intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID), movieMediaItem, 3178 afterMediaItemId, MediaVideoItem.class, (Integer)extraResult, ex); 3179 } 3180 3181 break; 3182 } 3183 3184 case OP_MEDIA_ITEM_ADD_IMAGE_URI: { 3185 if (finalize) { 3186 finalizeRequest(intent); 3187 } 3188 3189 final String afterMediaItemId = 3190 intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID); 3191 3192 final MovieMediaItem movieMediaItem = (MovieMediaItem)result; 3193 final VideoEditorProject videoProject = getProject(projectPath); 3194 if (videoProject != null) { 3195 if (ex == null && extraResult != null) { 3196 // The aspect ratio has changed 3197 videoProject.setAspectRatio((Integer)extraResult); 3198 } 3199 3200 if (ex == null) { 3201 videoProject.insertMediaItem(movieMediaItem, afterMediaItemId); 3202 } 3203 } 3204 3205 for (ApiServiceListener listener : mListeners) { 3206 listener.onMediaItemAdded(projectPath, 3207 intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID), movieMediaItem, 3208 afterMediaItemId, MediaImageItem.class, (Integer)extraResult, ex); 3209 } 3210 3211 break; 3212 } 3213 3214 case OP_MEDIA_ITEM_LOAD: { 3215 // Note that this message is handled only if the download 3216 // cannot start. 3217 final Uri data = (Uri)intent.getParcelableExtra(PARAM_FILENAME); 3218 final String mimeType = intent.getStringExtra(PARAM_ATTRIBUTES); 3219 if (finalize) { 3220 finalizeRequest(intent); 3221 } 3222 3223 for (ApiServiceListener listener : mListeners) { 3224 listener.onMediaLoaded(projectPath, data, mimeType, null, ex); 3225 } 3226 break; 3227 } 3228 3229 case OP_MEDIA_ITEM_LOAD_STATUS: { 3230 if (finalize) { 3231 finalizeRequest(intent); 3232 } 3233 3234 final Intent originalIntent = (Intent)intent.getParcelableExtra(PARAM_INTENT); 3235 final Uri data = (Uri)originalIntent.getParcelableExtra(PARAM_FILENAME); 3236 final String mimeType = originalIntent.getStringExtra(PARAM_ATTRIBUTES); 3237 3238 finalizeRequest(originalIntent); 3239 mIntentPool.put(originalIntent); 3240 3241 final String filename = intent.getStringExtra(PARAM_FILENAME); 3242 3243 if (ex == null && filename != null) { 3244 final VideoEditorProject videoProject = getProject(projectPath); 3245 videoProject.addDownload(data.toString(), mimeType, filename); 3246 } 3247 3248 for (ApiServiceListener listener : mListeners) { 3249 listener.onMediaLoaded(projectPath, data, mimeType, filename, ex); 3250 } 3251 break; 3252 } 3253 3254 case OP_MEDIA_ITEM_MOVE: { 3255 if (finalize) { 3256 finalizeRequest(intent); 3257 } 3258 3259 if (ex == null) { 3260 final VideoEditorProject videoProject = getProject(projectPath); 3261 if (videoProject != null) { 3262 videoProject.setMediaItems((List<MovieMediaItem>)result); 3263 } 3264 } 3265 3266 for (ApiServiceListener listener : mListeners) { 3267 listener.onMediaItemMoved(projectPath, 3268 intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID), 3269 intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID), ex); 3270 } 3271 3272 break; 3273 } 3274 3275 case OP_MEDIA_ITEM_REMOVE: { 3276 if (finalize) { 3277 finalizeRequest(intent); 3278 } 3279 3280 final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); 3281 final MovieTransition transition = (MovieTransition)result; 3282 if (ex == null) { 3283 final VideoEditorProject videoProject = getProject(projectPath); 3284 if (videoProject != null) { 3285 videoProject.removeMediaItem(mediaItemId, transition); 3286 } 3287 } 3288 3289 for (ApiServiceListener listener : mListeners) { 3290 listener.onMediaItemRemoved(projectPath, mediaItemId, transition, ex); 3291 } 3292 3293 break; 3294 } 3295 3296 case OP_MEDIA_ITEM_SET_RENDERING_MODE: { 3297 if (finalize) { 3298 finalizeRequest(intent); 3299 } 3300 3301 final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); 3302 final int renderingMode = intent.getIntExtra(PARAM_MEDIA_ITEM_RENDERING_MODE, 3303 MediaItem.RENDERING_MODE_BLACK_BORDER); 3304 3305 final VideoEditorProject videoProject = getProject(projectPath); 3306 if (videoProject != null) { 3307 final MovieMediaItem mediaItem = videoProject.getMediaItem(mediaItemId); 3308 if (mediaItem != null) { 3309 videoProject.setClean(false); 3310 if (ex == null) { 3311 mediaItem.setRenderingMode(renderingMode); 3312 } else { 3313 mediaItem.setAppRenderingMode(mediaItem.getRenderingMode()); 3314 } 3315 } 3316 } 3317 3318 for (ApiServiceListener listener : mListeners) { 3319 listener.onMediaItemRenderingModeSet(projectPath, mediaItemId, renderingMode, 3320 ex); 3321 } 3322 3323 break; 3324 } 3325 3326 case OP_MEDIA_ITEM_SET_DURATION: { 3327 if (finalize) { 3328 finalizeRequest(intent); 3329 } 3330 3331 final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); 3332 3333 final VideoEditorProject videoProject = getProject(projectPath); 3334 if (videoProject != null) { 3335 if (ex == null) { 3336 videoProject.updateMediaItem((MovieMediaItem)result); 3337 } else { 3338 final MovieMediaItem oldMediaItem = videoProject.getMediaItem(mediaItemId); 3339 if (oldMediaItem != null) { 3340 videoProject.setClean(false); 3341 oldMediaItem.setAppExtractBoundaries(0, oldMediaItem.getDuration()); 3342 } 3343 } 3344 } 3345 3346 for (ApiServiceListener listener : mListeners) { 3347 listener.onMediaItemDurationSet(projectPath, mediaItemId, 3348 intent.getLongExtra(PARAM_DURATION, 0), ex); 3349 } 3350 3351 break; 3352 } 3353 3354 case OP_MEDIA_ITEM_SET_BOUNDARIES: { 3355 if (finalize) { 3356 finalizeRequest(intent); 3357 } 3358 3359 final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); 3360 3361 final VideoEditorProject videoProject = getProject(projectPath); 3362 if (videoProject != null) { 3363 if (ex == null) { 3364 final MovieMediaItem mediaItem = (MovieMediaItem)result; 3365 videoProject.updateMediaItem(mediaItem); 3366 } else { 3367 final MovieMediaItem oldMediaItem = videoProject.getMediaItem(mediaItemId); 3368 if (oldMediaItem != null) { 3369 videoProject.setClean(false); 3370 oldMediaItem.setAppExtractBoundaries( 3371 oldMediaItem.getBoundaryBeginTime(), 3372 oldMediaItem.getBoundaryEndTime()); 3373 } 3374 } 3375 } 3376 3377 for (ApiServiceListener listener : mListeners) { 3378 listener.onMediaItemBoundariesSet(projectPath, mediaItemId, 3379 intent.getLongExtra(PARAM_BEGIN_BOUNDARY, 0), 3380 intent.getLongExtra(PARAM_END_BOUNDARY, 0), ex); 3381 } 3382 3383 break; 3384 } 3385 3386 case OP_MEDIA_ITEM_GET_THUMBNAILS: { 3387 if (finalize) { 3388 finalizeRequest(intent); 3389 break; 3390 } 3391 3392 final Bitmap bitmap = (Bitmap)result; 3393 final int index = (Integer)extraResult; 3394 boolean used = false; 3395 for (ApiServiceListener listener : mListeners) { 3396 used |= listener.onMediaItemThumbnail(projectPath, 3397 intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID), 3398 bitmap, index, intent.getIntExtra(PARAM_TOKEN, 0), 3399 ex); 3400 } 3401 3402 if (used == false) { 3403 if (bitmap != null) { 3404 bitmap.recycle(); 3405 } 3406 } 3407 3408 break; 3409 } 3410 3411 case OP_MEDIA_ITEM_SET_VOLUME: { 3412 if (finalize) { 3413 finalizeRequest(intent); 3414 } 3415 3416 final VideoEditorProject videoProject = getProject(projectPath); 3417 if (videoProject != null) { 3418 final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); 3419 final MovieMediaItem mediaItem = videoProject.getMediaItem(mediaItemId); 3420 if (mediaItem != null) { 3421 videoProject.setClean(false); 3422 if (ex == null) { 3423 mediaItem.setVolume(intent.getIntExtra(PARAM_VOLUME, 0)); 3424 } else { 3425 mediaItem.setAppVolume(mediaItem.getVolume()); 3426 } 3427 } 3428 } 3429 3430 break; 3431 } 3432 3433 case OP_MEDIA_ITEM_SET_MUTE: { 3434 if (finalize) { 3435 finalizeRequest(intent); 3436 } 3437 3438 final VideoEditorProject videoProject = getProject(projectPath); 3439 if (videoProject != null) { 3440 final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); 3441 final MovieMediaItem mediaItem = videoProject.getMediaItem(mediaItemId); 3442 if (mediaItem != null) { 3443 videoProject.setClean(false); 3444 if (ex == null) { 3445 mediaItem.setMute(intent.getBooleanExtra(PARAM_MUTE, false)); 3446 } else { 3447 mediaItem.setAppMute(mediaItem.isMuted()); 3448 } 3449 } 3450 } 3451 3452 break; 3453 } 3454 3455 case OP_MEDIA_ITEM_EXTRACT_AUDIO_WAVEFORM_STATUS: { 3456 if (finalize) { 3457 finalizeRequest(intent); 3458 } 3459 3460 final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); 3461 final int progress = intent.getIntExtra(PARAM_PROGRESS_VALUE, 0); 3462 3463 for (ApiServiceListener listener : mListeners) { 3464 listener.onMediaItemExtractAudioWaveformProgress(projectPath, mediaItemId, 3465 progress); 3466 } 3467 3468 break; 3469 } 3470 3471 case OP_MEDIA_ITEM_EXTRACT_AUDIO_WAVEFORM: { 3472 if (finalize) { 3473 finalizeRequest(intent); 3474 } 3475 3476 final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); 3477 3478 final VideoEditorProject videoProject = getProject(projectPath); 3479 if (ex == null && videoProject != null) { 3480 if (result != null) { 3481 final MovieMediaItem mediaItem = videoProject.getMediaItem(mediaItemId); 3482 if (mediaItem != null) { 3483 videoProject.setClean(false); 3484 mediaItem.setWaveformData((WaveformData)result); 3485 } 3486 } 3487 } 3488 3489 for (ApiServiceListener listener : mListeners) { 3490 listener.onMediaItemExtractAudioWaveformComplete(projectPath, mediaItemId, ex); 3491 } 3492 3493 break; 3494 } 3495 3496 case OP_TRANSITION_INSERT_ALPHA: 3497 case OP_TRANSITION_INSERT_CROSSFADE: 3498 case OP_TRANSITION_INSERT_FADE_BLACK: 3499 case OP_TRANSITION_INSERT_SLIDING: { 3500 if (finalize) { 3501 finalizeRequest(intent); 3502 } 3503 3504 final String afterMediaItemId = intent.getStringExtra( 3505 PARAM_RELATIVE_STORYBOARD_ITEM_ID); 3506 3507 final MovieTransition movieTransition; 3508 final VideoEditorProject videoProject = getProject(projectPath); 3509 if (videoProject != null) { 3510 final Transition transition = (Transition)result; 3511 // This is null for start transitions 3512 if (ex == null) { 3513 movieTransition = new MovieTransition(transition); 3514 videoProject.addTransition(movieTransition, afterMediaItemId); 3515 } else { 3516 movieTransition = null; 3517 } 3518 } else { 3519 movieTransition = null; 3520 } 3521 3522 for (ApiServiceListener listener : mListeners) { 3523 listener.onTransitionInserted(projectPath, movieTransition, 3524 afterMediaItemId, ex); 3525 } 3526 3527 break; 3528 } 3529 3530 case OP_TRANSITION_REMOVE: { 3531 if (finalize) { 3532 finalizeRequest(intent); 3533 } 3534 3535 final String transitionId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); 3536 3537 final VideoEditorProject videoProject = getProject(projectPath); 3538 if (videoProject != null) { 3539 if (ex == null) { 3540 videoProject.removeTransition(transitionId); 3541 } 3542 } 3543 3544 for (ApiServiceListener listener : mListeners) { 3545 listener.onTransitionRemoved(projectPath, transitionId, ex); 3546 } 3547 3548 break; 3549 } 3550 3551 case OP_TRANSITION_SET_DURATION: { 3552 if (finalize) { 3553 finalizeRequest(intent); 3554 } 3555 3556 final String transitionId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); 3557 final long durationMs = intent.getLongExtra(PARAM_DURATION, 0); 3558 3559 final VideoEditorProject videoProject = getProject(projectPath); 3560 if (videoProject != null) { 3561 final MovieTransition transition = videoProject.getTransition(transitionId); 3562 if (transition != null) { 3563 videoProject.setClean(false); 3564 if (ex == null) { 3565 transition.setDuration(durationMs); 3566 } else { 3567 transition.setAppDuration(transition.getDuration()); 3568 } 3569 } 3570 } 3571 3572 for (ApiServiceListener listener : mListeners) { 3573 listener.onTransitionDurationSet(projectPath, transitionId, durationMs, ex); 3574 } 3575 3576 break; 3577 } 3578 3579 case OP_TRANSITION_GET_THUMBNAIL: { 3580 if (finalize) { 3581 finalizeRequest(intent); 3582 } 3583 3584 final Bitmap[] bitmaps = (Bitmap[])result; 3585 boolean used = false; 3586 for (ApiServiceListener listener : mListeners) { 3587 used |= listener.onTransitionThumbnails(projectPath, 3588 intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID), 3589 bitmaps, ex); 3590 } 3591 3592 if (used == false) { 3593 if (bitmaps != null) { 3594 for (int i = 0; i < bitmaps.length; i++) { 3595 if (bitmaps[i] != null) { 3596 bitmaps[i].recycle(); 3597 } 3598 } 3599 } 3600 } 3601 3602 break; 3603 } 3604 3605 case OP_OVERLAY_ADD: { 3606 if (finalize) { 3607 finalizeRequest(intent); 3608 } 3609 3610 final String mediaItemId = intent.getStringExtra( 3611 PARAM_RELATIVE_STORYBOARD_ITEM_ID); 3612 3613 final MovieOverlay movieOverlay = (MovieOverlay)result; 3614 final VideoEditorProject videoProject = getProject(projectPath); 3615 if (videoProject != null) { 3616 if (ex == null) { 3617 videoProject.addOverlay(mediaItemId, movieOverlay); 3618 } 3619 } 3620 3621 for (ApiServiceListener listener : mListeners) { 3622 listener.onOverlayAdded(projectPath, movieOverlay, mediaItemId, ex); 3623 } 3624 3625 break; 3626 } 3627 3628 case OP_OVERLAY_REMOVE: { 3629 if (finalize) { 3630 finalizeRequest(intent); 3631 } 3632 3633 final String mediaItemId = intent.getStringExtra( 3634 PARAM_RELATIVE_STORYBOARD_ITEM_ID); 3635 final String overlayId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); 3636 3637 final VideoEditorProject videoProject = getProject(projectPath); 3638 if (videoProject != null) { 3639 if (ex == null) { 3640 videoProject.removeOverlay(mediaItemId, overlayId); 3641 } 3642 } 3643 3644 for (ApiServiceListener listener : mListeners) { 3645 listener.onOverlayRemoved(projectPath, overlayId, mediaItemId, ex); 3646 } 3647 3648 break; 3649 } 3650 3651 case OP_OVERLAY_SET_START_TIME: { 3652 if (finalize) { 3653 finalizeRequest(intent); 3654 } 3655 3656 final String mediaItemId = intent.getStringExtra( 3657 PARAM_RELATIVE_STORYBOARD_ITEM_ID); 3658 final String overlayId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); 3659 final long startTimeMs = intent.getLongExtra(PARAM_START_TIME, 0); 3660 3661 final VideoEditorProject videoProject = getProject(projectPath); 3662 if (videoProject != null) { 3663 final MovieOverlay overlay = videoProject.getOverlay(mediaItemId, overlayId); 3664 if (overlay != null) { 3665 videoProject.setClean(false); 3666 if (ex == null) { 3667 overlay.setStartTime(startTimeMs); 3668 } else { 3669 overlay.setAppStartTime(overlay.getStartTime()); 3670 } 3671 } 3672 } 3673 3674 for (ApiServiceListener listener : mListeners) { 3675 listener.onOverlayStartTimeSet(projectPath, overlayId, mediaItemId, 3676 startTimeMs, ex); 3677 } 3678 3679 break; 3680 } 3681 3682 case OP_OVERLAY_SET_DURATION: { 3683 if (finalize) { 3684 finalizeRequest(intent); 3685 } 3686 3687 final String mediaItemId = intent.getStringExtra( 3688 PARAM_RELATIVE_STORYBOARD_ITEM_ID); 3689 final String overlayId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); 3690 final long durationMs = intent.getLongExtra(PARAM_DURATION, 0); 3691 3692 final VideoEditorProject videoProject = getProject(projectPath); 3693 if (videoProject != null) { 3694 final MovieOverlay overlay = videoProject.getOverlay(mediaItemId, overlayId); 3695 if (overlay != null) { 3696 videoProject.setClean(false); 3697 if (ex == null) { 3698 overlay.setDuration(durationMs); 3699 } else { 3700 overlay.setAppDuration(overlay.getDuration()); 3701 } 3702 } 3703 } 3704 3705 for (ApiServiceListener listener : mListeners) { 3706 listener.onOverlayDurationSet(projectPath, overlayId, mediaItemId, 3707 durationMs, ex); 3708 } 3709 3710 break; 3711 } 3712 3713 case OP_OVERLAY_SET_ATTRIBUTES: { 3714 if (finalize) { 3715 finalizeRequest(intent); 3716 } 3717 3718 final String mediaItemId = intent.getStringExtra( 3719 PARAM_RELATIVE_STORYBOARD_ITEM_ID); 3720 final String overlayId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); 3721 final Bundle userAttributes = intent.getBundleExtra(PARAM_ATTRIBUTES); 3722 3723 final VideoEditorProject videoProject = getProject(projectPath); 3724 if (videoProject != null) { 3725 if (ex == null) { 3726 final MovieOverlay overlay = videoProject.getOverlay(mediaItemId, 3727 overlayId); 3728 if (overlay != null) { 3729 videoProject.setClean(false); 3730 overlay.updateUserAttributes(userAttributes); 3731 } 3732 } 3733 } 3734 3735 for (ApiServiceListener listener : mListeners) { 3736 listener.onOverlayUserAttributesSet(projectPath, overlayId, mediaItemId, 3737 userAttributes, ex); 3738 } 3739 3740 break; 3741 } 3742 3743 case OP_EFFECT_ADD_COLOR: 3744 case OP_EFFECT_ADD_IMAGE_KEN_BURNS:{ 3745 if (finalize) { 3746 finalizeRequest(intent); 3747 } 3748 3749 final String mediaItemId = intent.getStringExtra( 3750 PARAM_RELATIVE_STORYBOARD_ITEM_ID); 3751 3752 final MovieEffect movieEffect = (MovieEffect)result; 3753 final VideoEditorProject videoProject = getProject(projectPath); 3754 if (videoProject != null) { 3755 if (ex == null) { 3756 videoProject.addEffect(mediaItemId, movieEffect); 3757 } 3758 } 3759 3760 for (ApiServiceListener listener : mListeners) { 3761 listener.onEffectAdded(projectPath, movieEffect, mediaItemId, ex); 3762 } 3763 3764 break; 3765 } 3766 3767 case OP_EFFECT_REMOVE: { 3768 if (finalize) { 3769 finalizeRequest(intent); 3770 } 3771 3772 final String mediaItemId = intent.getStringExtra( 3773 PARAM_RELATIVE_STORYBOARD_ITEM_ID); 3774 final String effectId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); 3775 3776 final VideoEditorProject videoProject = getProject(projectPath); 3777 if (videoProject != null) { 3778 if (ex == null) { 3779 videoProject.removeEffect(mediaItemId, effectId); 3780 } 3781 } 3782 3783 for (ApiServiceListener listener : mListeners) { 3784 listener.onEffectRemoved(projectPath, effectId, mediaItemId, ex); 3785 } 3786 3787 break; 3788 } 3789 3790 case OP_AUDIO_TRACK_ADD: { 3791 if (finalize) { 3792 finalizeRequest(intent); 3793 } 3794 3795 final MovieAudioTrack movieAudioTrack = (MovieAudioTrack)result; 3796 final VideoEditorProject videoProject = getProject(projectPath); 3797 if (videoProject != null) { 3798 if (ex == null) { 3799 videoProject.addAudioTrack(movieAudioTrack); 3800 } 3801 } 3802 3803 for (ApiServiceListener listener : mListeners) { 3804 listener.onAudioTrackAdded(projectPath, movieAudioTrack, ex); 3805 } 3806 3807 break; 3808 } 3809 3810 case OP_AUDIO_TRACK_REMOVE: { 3811 if (finalize) { 3812 finalizeRequest(intent); 3813 } 3814 3815 final String audioTrackId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); 3816 3817 final VideoEditorProject videoProject = getProject(projectPath); 3818 if (videoProject != null) { 3819 if (ex == null) { 3820 videoProject.removeAudioTrack(audioTrackId); 3821 } 3822 } 3823 3824 for (ApiServiceListener listener : mListeners) { 3825 listener.onAudioTrackRemoved(projectPath, audioTrackId, ex); 3826 } 3827 3828 break; 3829 } 3830 3831 case OP_AUDIO_TRACK_SET_BOUNDARIES: { 3832 if (finalize) { 3833 finalizeRequest(intent); 3834 } 3835 3836 final String audioTrackId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); 3837 final long beginBoundary = intent.getLongExtra(PARAM_BEGIN_BOUNDARY, 0); 3838 final long endBoundary = intent.getLongExtra(PARAM_END_BOUNDARY, 0); 3839 3840 final VideoEditorProject videoProject = getProject(projectPath); 3841 if (videoProject != null) { 3842 if (ex == null) { 3843 final MovieAudioTrack audioTrack = 3844 videoProject.getAudioTrack(audioTrackId); 3845 if (audioTrack != null) { 3846 videoProject.setClean(false); 3847 audioTrack.setExtractBoundaries(beginBoundary, endBoundary); 3848 } 3849 } 3850 } 3851 3852 for (ApiServiceListener listener : mListeners) { 3853 listener.onAudioTrackBoundariesSet(projectPath, audioTrackId, 3854 beginBoundary, endBoundary, ex); 3855 } 3856 3857 break; 3858 } 3859 3860 case OP_AUDIO_TRACK_SET_LOOP: { 3861 if (finalize) { 3862 finalizeRequest(intent); 3863 } 3864 3865 final VideoEditorProject videoProject = getProject(projectPath); 3866 if (videoProject != null) { 3867 final String audioTrackId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); 3868 final MovieAudioTrack audioTrack = videoProject.getAudioTrack(audioTrackId); 3869 if (audioTrack != null) { 3870 videoProject.setClean(false); 3871 if (ex == null) { 3872 audioTrack.enableLoop(intent.getBooleanExtra(PARAM_LOOP, false)); 3873 } else { 3874 audioTrack.enableAppLoop(audioTrack.isLooping()); 3875 } 3876 } 3877 } 3878 3879 break; 3880 } 3881 3882 case OP_AUDIO_TRACK_SET_DUCK: { 3883 if (finalize) { 3884 finalizeRequest(intent); 3885 } 3886 3887 final VideoEditorProject videoProject = getProject(projectPath); 3888 if (videoProject != null) { 3889 final String audioTrackId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); 3890 final MovieAudioTrack audioTrack = videoProject.getAudioTrack(audioTrackId); 3891 if (audioTrack != null) { 3892 videoProject.setClean(false); 3893 if (ex == null) { 3894 audioTrack.enableDucking(intent.getBooleanExtra(PARAM_DUCK, false)); 3895 } else { 3896 audioTrack.enableAppDucking(audioTrack.isDuckingEnabled()); 3897 } 3898 } 3899 } 3900 3901 break; 3902 } 3903 3904 case OP_AUDIO_TRACK_SET_VOLUME: { 3905 if (finalize) { 3906 finalizeRequest(intent); 3907 } 3908 3909 final VideoEditorProject videoProject = getProject(projectPath); 3910 if (videoProject != null) { 3911 final String audioTrackId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); 3912 final MovieAudioTrack audioTrack = videoProject.getAudioTrack(audioTrackId); 3913 if (audioTrack != null) { 3914 videoProject.setClean(false); 3915 if (ex == null) { 3916 audioTrack.setVolume(intent.getIntExtra(PARAM_VOLUME, 0)); 3917 } else { 3918 audioTrack.setAppVolume(audioTrack.getVolume()); 3919 } 3920 } 3921 } 3922 3923 break; 3924 } 3925 3926 case OP_AUDIO_TRACK_SET_MUTE: { 3927 if (finalize) { 3928 finalizeRequest(intent); 3929 } 3930 3931 final VideoEditorProject videoProject = getProject(projectPath); 3932 if (videoProject != null) { 3933 final String audioTrackId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); 3934 final MovieAudioTrack audioTrack = videoProject.getAudioTrack(audioTrackId); 3935 if (audioTrack != null) { 3936 videoProject.setClean(false); 3937 if (ex == null) { 3938 audioTrack.setMute(intent.getBooleanExtra(PARAM_MUTE, false)); 3939 } else { 3940 audioTrack.setAppMute(audioTrack.isMuted()); 3941 } 3942 } 3943 } 3944 3945 break; 3946 } 3947 3948 case OP_AUDIO_TRACK_EXTRACT_AUDIO_WAVEFORM_STATUS: { 3949 if (finalize) { 3950 finalizeRequest(intent); 3951 } 3952 3953 final String audioTrackId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); 3954 final int progress = intent.getIntExtra(PARAM_PROGRESS_VALUE, 0); 3955 3956 for (ApiServiceListener listener : mListeners) { 3957 listener.onAudioTrackExtractAudioWaveformProgress(projectPath, audioTrackId, 3958 progress); 3959 } 3960 3961 break; 3962 } 3963 3964 case OP_AUDIO_TRACK_EXTRACT_AUDIO_WAVEFORM: { 3965 if (finalize) { 3966 finalizeRequest(intent); 3967 } 3968 3969 final String audioTrackId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID); 3970 3971 final VideoEditorProject videoProject = getProject(projectPath); 3972 if (ex == null && videoProject != null) { 3973 if (result != null) { 3974 final MovieAudioTrack audioTrack = 3975 videoProject.getAudioTrack(audioTrackId); 3976 if (audioTrack != null) { 3977 videoProject.setClean(false); 3978 audioTrack.setWaveformData((WaveformData)result); 3979 } 3980 } 3981 } 3982 3983 for (ApiServiceListener listener : mListeners) { 3984 listener.onAudioTrackExtractAudioWaveformComplete(projectPath, 3985 audioTrackId, ex); 3986 } 3987 3988 break; 3989 } 3990 3991 default: { 3992 if (finalize) { 3993 finalizeRequest(intent); 3994 } 3995 break; 3996 } 3997 } 3998 3999 if (finalize) { 4000 mIntentPool.put(intent); 4001 } 4002 } 4003 4004 /** 4005 * Finalizes a request. Calls the listeners that are interested in project status 4006 * change and stops this service if there are no more pending intents. 4007 * 4008 * @param intent The intent that just completed 4009 */ 4010 private void finalizeRequest(Intent intent) { 4011 mPendingIntents.remove(intent.getStringExtra(PARAM_REQUEST_ID)); 4012 4013 final String projectPath = intent.getStringExtra(PARAM_PROJECT_PATH); 4014 if (projectPath != null) { 4015 final boolean projectEdited = isProjectBeingEdited(projectPath); 4016 if (projectEdited == false) { 4017 for (ApiServiceListener listener : mListeners) { 4018 listener.onProjectEditState(projectPath, projectEdited); 4019 } 4020 } 4021 } 4022 4023 if (mPendingIntents.size() == 0) { 4024 // Cancel the current timer if any. Extend the timeout by 5000 ms. 4025 mHandler.removeCallbacks(mStopRunnable); 4026 4027 // Start a timer which will stop the service if the queue of 4028 // pending intent will be empty at that time. 4029 // This prevents the service from starting & stopping too often. 4030 mHandler.postDelayed(mStopRunnable, 5000); 4031 logd("completeRequest: Stopping service in 5000 ms"); 4032 } 4033 } 4034 4035 /** 4036 * Checks if the current project is the project specified by the specified path. 4037 * 4038 * @param projectPath The project path 4039 * 4040 * @return The video editor project 4041 */ 4042 private VideoEditorProject getProject(String projectPath) { 4043 if (mVideoProject != null) { 4044 if (mVideoProject.getPath().equals(projectPath)) { 4045 return mVideoProject; 4046 } 4047 } 4048 4049 return null; 4050 } 4051 4052 /** 4053 * Check if the current editor is the project specified by the specified path 4054 * 4055 * @param projectPath The project path 4056 * 4057 * @return The video editor 4058 */ 4059 private synchronized VideoEditor getVideoEditor(String projectPath) { 4060 if (mVideoEditor != null) { 4061 if (mVideoEditor.getPath().equals(projectPath)) { 4062 return mVideoEditor; 4063 } 4064 } 4065 4066 return null; 4067 } 4068 4069 /** 4070 * Release the editor 4071 */ 4072 private synchronized void releaseEditor() { 4073 if (mVideoEditor != null) { 4074 logd("releaseEditor (current): " + mVideoEditor.getPath()); 4075 mVideoEditor.release(); 4076 mVideoEditor = null; 4077 mGeneratePreviewListener = null; 4078 4079 System.gc(); 4080 } 4081 } 4082 4083 /** 4084 * Release the specified editor 4085 * 4086 * @param projectPath The project path 4087 */ 4088 private synchronized void releaseEditor(String projectPath) { 4089 if (mVideoEditor != null) { 4090 if (mVideoEditor.getPath().equals(projectPath)) { 4091 logd("releaseEditor: " + projectPath); 4092 mVideoEditor.release(); 4093 mVideoEditor = null; 4094 mGeneratePreviewListener = null; 4095 4096 System.gc(); 4097 } 4098 } 4099 } 4100 4101 /** 4102 * Release the current editor if it is *not* the current editor 4103 * 4104 * @param projectPath The project path 4105 * 4106 * @return The current video editor 4107 */ 4108 private synchronized VideoEditor releaseEditorNot(String projectPath) { 4109 if (mVideoEditor != null) { 4110 if (!mVideoEditor.getPath().equals(projectPath)) { 4111 logd("releaseEditorNot: " + mVideoEditor.getPath()); 4112 mVideoEditor.release(); 4113 mVideoEditor = null; 4114 mGeneratePreviewListener = null; 4115 4116 System.gc(); 4117 } 4118 } 4119 4120 return mVideoEditor; 4121 } 4122 4123 /** 4124 * Generate the preview 4125 * 4126 * @param videoEditor The video editor 4127 * @param updatePreviewFrame true to show preview frame when done 4128 */ 4129 private void generatePreview(VideoEditor videoEditor, boolean updatePreviewFrame) { 4130 try { 4131 videoEditor.generatePreview(mGeneratePreviewListener); 4132 if (mGeneratePreviewListener != null) { 4133 // This is the last callback which is always fired last to 4134 // let the UI know that generate preview completed 4135 mGeneratePreviewListener.onProgress(null, 4136 updatePreviewFrame ? ACTION_UPDATE_FRAME : ACTION_NO_FRAME_UPDATE, 100); 4137 } 4138 } catch (Exception ex) { 4139 ex.printStackTrace(); 4140 } 4141 } 4142 4143 /** 4144 * Exports a movie in a distinct worker thread. 4145 * 4146 * @param videoEditor The video editor 4147 * @param intent The intent 4148 */ 4149 private void exportMovie(final VideoEditor videoEditor, final Intent intent) { 4150 mExportCancelled = false; 4151 new Thread() { 4152 @Override 4153 public void run() { 4154 final String filename = intent.getStringExtra(PARAM_FILENAME); 4155 final int height = intent.getIntExtra(PARAM_HEIGHT, -1); 4156 final int bitrate = intent.getIntExtra(PARAM_BITRATE, -1); 4157 4158 // Create the export status Intent 4159 final Intent statusIntent = mIntentPool.get(); 4160 statusIntent.putExtra(PARAM_OP, OP_VIDEO_EDITOR_EXPORT_STATUS); 4161 statusIntent.putExtra(PARAM_PROJECT_PATH, intent.getStringExtra( 4162 PARAM_PROJECT_PATH)); 4163 statusIntent.putExtra(PARAM_FILENAME, filename); 4164 statusIntent.putExtra(PARAM_INTENT, intent); 4165 Exception resultException = null; 4166 4167 try { 4168 videoEditor.export(filename, height, bitrate, new ExportProgressListener() { 4169 @Override 4170 public void onProgress(VideoEditor videoEditor, String filename, 4171 int progress) { 4172 final Intent progressIntent = mIntentPool.get(); 4173 progressIntent.putExtra(PARAM_OP, OP_VIDEO_EDITOR_EXPORT_STATUS); 4174 progressIntent.putExtra(PARAM_PROJECT_PATH, intent.getStringExtra( 4175 PARAM_PROJECT_PATH)); 4176 progressIntent.putExtra(PARAM_FILENAME, filename); 4177 progressIntent.putExtra(PARAM_INTENT, intent); 4178 progressIntent.putExtra(PARAM_PROGRESS_VALUE, progress); 4179 mVideoThread.submit(progressIntent); 4180 logv("Export progress: " + progress + " for: " + filename); 4181 } 4182 }); 4183 4184 statusIntent.putExtra(PARAM_CANCELLED, mExportCancelled); 4185 if (!mExportCancelled) { 4186 if (new File(filename).exists()) { 4187 statusIntent.putExtra(PARAM_MOVIE_URI, exportToGallery(filename)); 4188 } else { 4189 resultException = new IllegalStateException("Export file does not exist: " + filename); 4190 } 4191 logv("Export complete for: " + filename); 4192 } else { 4193 logv("Export cancelled by user, file name: " + filename); 4194 } 4195 } catch (Exception ex) { 4196 logv("Export error for: " + filename); 4197 ex.printStackTrace(); 4198 resultException = ex; 4199 } 4200 4201 // Complete the request 4202 statusIntent.putExtra(PARAM_EXCEPTION, resultException); 4203 mVideoThread.submit(statusIntent); 4204 } 4205 }.start(); 4206 } 4207 4208 /** 4209 * Extract the audio waveform of a media item 4210 * 4211 * @param intent The original Intent 4212 * @param videoEditor The video editor 4213 * @param mediaItem The media item 4214 */ 4215 private void extractMediaItemAudioWaveform(final Intent intent, final VideoEditor videoEditor, 4216 final MediaVideoItem mediaItem) throws IOException { 4217 mediaItem.extractAudioWaveform( 4218 new ExtractAudioWaveformProgressListener() { 4219 @Override 4220 public void onProgress(int progress) { 4221 final Intent progressIntent = mIntentPool.get(); 4222 progressIntent.putExtra(PARAM_OP, OP_MEDIA_ITEM_EXTRACT_AUDIO_WAVEFORM_STATUS); 4223 progressIntent.putExtra(PARAM_PROJECT_PATH, 4224 intent.getStringExtra(PARAM_PROJECT_PATH)); 4225 progressIntent.putExtra(PARAM_INTENT, intent); 4226 progressIntent.putExtra(PARAM_STORYBOARD_ITEM_ID, mediaItem.getId()); 4227 progressIntent.putExtra(PARAM_PROGRESS_VALUE, progress); 4228 4229 completeRequest(progressIntent, videoEditor, null, null, null, true); 4230 } 4231 }); 4232 } 4233 4234 /** 4235 * Extract the audio waveform of an AudioTrack 4236 * 4237 * @param intent The original Intent 4238 * @param videoEditor The video editor 4239 * @param audioTrack The audio track 4240 */ 4241 private void extractAudioTrackAudioWaveform(final Intent intent, final VideoEditor videoEditor, 4242 final AudioTrack audioTrack) throws IOException { 4243 audioTrack.extractAudioWaveform( 4244 new ExtractAudioWaveformProgressListener() { 4245 @Override 4246 public void onProgress(int progress) { 4247 final Intent progressIntent = mIntentPool.get(); 4248 progressIntent.putExtra(PARAM_OP, 4249 OP_AUDIO_TRACK_EXTRACT_AUDIO_WAVEFORM_STATUS); 4250 progressIntent.putExtra(PARAM_PROJECT_PATH, 4251 intent.getStringExtra(PARAM_PROJECT_PATH)); 4252 progressIntent.putExtra(PARAM_INTENT, intent); 4253 progressIntent.putExtra(PARAM_STORYBOARD_ITEM_ID, audioTrack.getId()); 4254 progressIntent.putExtra(PARAM_PROGRESS_VALUE, progress); 4255 4256 completeRequest(progressIntent, videoEditor, null, null, null, true); 4257 } 4258 }); 4259 } 4260 4261 /** 4262 * Get the media item following the specified media item 4263 * 4264 * @param videoEditor The video editor 4265 * @param mediaItemId The media item id 4266 * @return The next media item 4267 */ 4268 private static MediaItem nextMediaItem(VideoEditor videoEditor, String mediaItemId) { 4269 final List<MediaItem> mediaItems = videoEditor.getAllMediaItems(); 4270 if (mediaItemId == null) { 4271 if (mediaItems.size() > 0) { 4272 return mediaItems.get(0); 4273 } 4274 } else { 4275 final int mediaItemCount = mediaItems.size(); 4276 for (int i = 0; i < mediaItemCount; i++) { 4277 MediaItem mi = mediaItems.get(i); 4278 if (mi.getId().equals(mediaItemId)) { 4279 if (i < mediaItemCount - 1) { 4280 return mediaItems.get(i + 1); 4281 } else { 4282 break; 4283 } 4284 } 4285 } 4286 } 4287 4288 return null; 4289 } 4290 4291 /** 4292 * Export the movie to the Gallery 4293 * 4294 * @param filename The filename 4295 * @return The video MediaStore URI 4296 */ 4297 private Uri exportToGallery(String filename) { 4298 // Save the name and description of a video in a ContentValues map. 4299 final ContentValues values = new ContentValues(2); 4300 values.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4"); 4301 values.put(MediaStore.Video.Media.DATA, filename); 4302 // Add a new record (identified by uri) 4303 final Uri uri = getContentResolver().insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, 4304 values); 4305 sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, 4306 Uri.parse("file://"+ filename))); 4307 return uri; 4308 } 4309 4310 /** 4311 * Apply a theme to the entire movie. This method shall be used when the 4312 * theme is changing. 4313 * 4314 * @param videoEditor The video editor 4315 * @param themeId The theme id 4316 */ 4317 private void applyThemeToMovie(VideoEditor videoEditor, String themeId) throws IOException { 4318 final Context context = getApplicationContext(); 4319 final MovieTheme theme = MovieTheme.getTheme(context, themeId); 4320 final List<MediaItem> mediaItems = videoEditor.getAllMediaItems(); 4321 4322 // Add the transitions 4323 final int mediaItemsCount = mediaItems.size(); 4324 if (mediaItemsCount > 0) { 4325 // Remove all the transitions 4326 for (int i = 0; i < mediaItemsCount; i++) { 4327 final MediaItem mi = mediaItems.get(i); 4328 if (i == 0) { 4329 final Transition beginTransition = mi.getBeginTransition(); 4330 if (beginTransition != null) { 4331 videoEditor.removeTransition(beginTransition.getId()); 4332 } 4333 } 4334 4335 final Transition endTransition = mi.getEndTransition(); 4336 if (endTransition != null) { 4337 videoEditor.removeTransition(endTransition.getId()); 4338 } 4339 } 4340 4341 // Add the begin transition to the first media item 4342 final MovieTransition beginMovieTransition = theme.getBeginTransition(); 4343 if (beginMovieTransition != null) { 4344 final MediaItem firstMediaItem = mediaItems.get(0); 4345 videoEditor.addTransition( 4346 beginMovieTransition.buildTransition(context, null, firstMediaItem)); 4347 } 4348 4349 // Add the mid transitions 4350 final MovieTransition midMovieTransition = theme.getMidTransition(); 4351 if (midMovieTransition != null) { 4352 for (int i = 0; i < mediaItemsCount - 1; i++) { 4353 videoEditor.addTransition( 4354 midMovieTransition.buildTransition(context, 4355 mediaItems.get(i), mediaItems.get(i + 1))); 4356 } 4357 } 4358 4359 // Add the end transition to the last media item 4360 final MovieTransition endMovieTransition = theme.getEndTransition(); 4361 if (endMovieTransition != null) { 4362 final MediaItem lastMediaItem = mediaItems.get(mediaItemsCount - 1); 4363 videoEditor.addTransition( 4364 endMovieTransition.buildTransition(context, lastMediaItem, null)); 4365 } 4366 } 4367 4368 // Add the overlay 4369 final MovieOverlay movieOverlay = theme.getOverlay(); 4370 if (movieOverlay != null && mediaItemsCount > 0) { 4371 // Remove all the overlay for the first media item 4372 final MediaItem mediaItem = mediaItems.get(0); 4373 final List<Overlay> overlays = mediaItem.getAllOverlays(); 4374 if (overlays.size() > 0) { 4375 mediaItem.removeOverlay(overlays.get(0).getId()); 4376 } 4377 4378 // Add the new overlay 4379 final int scaledWidth, scaledHeight; 4380 if (mediaItem instanceof MediaVideoItem) { 4381 scaledWidth = ((MediaVideoItem)mediaItem).getWidth(); 4382 scaledHeight = ((MediaVideoItem)mediaItem).getHeight(); 4383 } else { 4384 scaledWidth = ((MediaImageItem)mediaItem).getScaledWidth(); 4385 scaledHeight = ((MediaImageItem)mediaItem).getScaledHeight(); 4386 } 4387 4388 final Overlay overlay = new OverlayFrame(mediaItem, generateId(), 4389 ImageUtils.buildOverlayBitmap(getApplicationContext(), null, 4390 movieOverlay.getType(), movieOverlay.getTitle(), 4391 movieOverlay.getSubtitle(), scaledWidth, scaledHeight), 4392 movieOverlay.getStartTime(), movieOverlay.getDuration()); 4393 4394 // Set the user attributes 4395 final Bundle userAttributes = movieOverlay.buildUserAttributes(); 4396 for (String name : userAttributes.keySet()) { 4397 if (MovieOverlay.getAttributeType(name).equals(Integer.class)) { 4398 overlay.setUserAttribute(name, 4399 Integer.toString(userAttributes.getInt(name))); 4400 } else { // Strings 4401 overlay.setUserAttribute(name, userAttributes.getString(name)); 4402 } 4403 } 4404 mediaItem.addOverlay(overlay); 4405 } 4406 4407 final MovieAudioTrack at = theme.getAudioTrack(); 4408 if (at != null) { 4409 // Remove all audio tracks 4410 final List<AudioTrack> audioTracks = videoEditor.getAllAudioTracks(); 4411 while (audioTracks.size() > 0) { 4412 videoEditor.removeAudioTrack(audioTracks.get(0).getId()); 4413 } 4414 4415 // Add the new audio track 4416 final AudioTrack audioTrack = new AudioTrack(videoEditor, generateId(), 4417 FileUtils.getAudioTrackFilename(context, at.getRawResourceId())); 4418 4419 // Enable looping if necessary 4420 if (at.isLooping()) { 4421 audioTrack.enableLoop(); 4422 } 4423 4424 // Enable ducking 4425 audioTrack.enableDucking(DUCK_THRESHOLD, DUCK_TRACK_VOLUME); 4426 audioTrack.setVolume(DEFAULT_AUDIO_TRACK_VOLUME); 4427 videoEditor.addAudioTrack(audioTrack); 4428 } 4429 } 4430 4431 /** 4432 * Apply a theme 4433 * 4434 * @param videoEditor The video editor 4435 * @param themeId The theme id 4436 * @param mediaItem The mediaItem 4437 */ 4438 private void applyThemeToMediaItem(VideoEditor videoEditor, String themeId, 4439 MediaItem mediaItem) throws IOException { 4440 final List<MediaItem> mediaItems = videoEditor.getAllMediaItems(); 4441 final int mediaItemsCount = mediaItems.size(); 4442 if (mediaItemsCount == 0) { 4443 return; 4444 } 4445 4446 // We would only add transitions if the transitions don't exist 4447 final Transition beginTransition = mediaItem.getBeginTransition(); 4448 final Transition endTransition = mediaItem.getEndTransition(); 4449 4450 final Context context = getApplicationContext(); 4451 final MovieTheme theme = MovieTheme.getTheme(context, themeId); 4452 4453 final MediaItem firstMediaItem = mediaItems.get(0); 4454 if (beginTransition == null) { 4455 // Add the begin transition 4456 final MovieTransition beginMovieTransition = theme.getBeginTransition(); 4457 if (beginMovieTransition != null) { 4458 if (firstMediaItem == mediaItem) { 4459 videoEditor.addTransition( 4460 beginMovieTransition.buildTransition(context, null, mediaItem)); 4461 } 4462 } 4463 } 4464 4465 // Add the mid transitions 4466 final MovieTransition midMovieTransition = theme.getMidTransition(); 4467 if (midMovieTransition != null) { 4468 for (int i = 0; i < mediaItemsCount; i++) { 4469 final MediaItem mi = mediaItems.get(i); 4470 if (mi == mediaItem) { 4471 if (i > 0) { // Not the first one 4472 if (beginTransition == null) { 4473 // Add transition before this media item 4474 videoEditor.addTransition(midMovieTransition.buildTransition(context, 4475 mediaItems.get(i - 1), mi)); 4476 } 4477 } 4478 4479 if (i < mediaItemsCount - 1) { // Not the last one 4480 if (endTransition == null) { 4481 // Add the transition after this media item 4482 videoEditor.addTransition(midMovieTransition.buildTransition(context, 4483 mi, mediaItems.get(i + 1))); 4484 } 4485 } 4486 break; 4487 } 4488 } 4489 } 4490 4491 if (endTransition == null) { 4492 // Add the end transition to the last media item 4493 final MovieTransition endMovieTransition = theme.getEndTransition(); 4494 final MediaItem lastMediaItem = mediaItems.get(mediaItemsCount - 1); 4495 if (endMovieTransition != null && lastMediaItem == mediaItem) { 4496 videoEditor.addTransition( 4497 endMovieTransition.buildTransition(context, lastMediaItem, null)); 4498 } 4499 } 4500 4501 // Add the overlay 4502 final MovieOverlay movieOverlay = theme.getOverlay(); 4503 if (movieOverlay != null) { 4504 if (firstMediaItem == mediaItem) { 4505 // Remove the overlay 4506 final List<Overlay> overlays = mediaItem.getAllOverlays(); 4507 if (overlays.size() > 0) { 4508 mediaItem.removeOverlay(overlays.get(0).getId()); 4509 } 4510 4511 // Add the new overlay 4512 final int scaledWidth, scaledHeight; 4513 if (mediaItem instanceof MediaVideoItem) { 4514 scaledWidth = ((MediaVideoItem)mediaItem).getWidth(); 4515 scaledHeight = ((MediaVideoItem)mediaItem).getHeight(); 4516 } else { 4517 scaledWidth = ((MediaImageItem)mediaItem).getScaledWidth(); 4518 scaledHeight = ((MediaImageItem)mediaItem).getScaledHeight(); 4519 } 4520 4521 final Overlay overlay = new OverlayFrame(mediaItem, generateId(), 4522 ImageUtils.buildOverlayBitmap(getApplicationContext(), null, 4523 movieOverlay.getType(), movieOverlay.getTitle(), 4524 movieOverlay.getSubtitle(), scaledWidth, scaledHeight), 4525 movieOverlay.getStartTime(), 4526 Math.min(movieOverlay.getDuration(), 4527 mediaItem.getDuration() - movieOverlay.getStartTime())); 4528 4529 // Set the user attributes 4530 final Bundle userAttributes = movieOverlay.buildUserAttributes(); 4531 for (String name : userAttributes.keySet()) { 4532 if (MovieOverlay.getAttributeType(name).equals(Integer.class)) { 4533 overlay.setUserAttribute(name, 4534 Integer.toString(userAttributes.getInt(name))); 4535 } else { // Strings 4536 overlay.setUserAttribute(name, userAttributes.getString(name)); 4537 } 4538 } 4539 mediaItem.addOverlay(overlay); 4540 } 4541 } 4542 } 4543 4544 /** 4545 * Apply theme transitions after an item was removed 4546 * 4547 * @param videoEditor The video editor 4548 * @param themeId The theme id 4549 * @param removedItemPosition The position of the removed item 4550 * @param beginTransition The removed item begin transition 4551 * @param endTransition The removed item end transition 4552 * 4553 * @return The transition that was added 4554 */ 4555 private Transition applyThemeAfterRemove(VideoEditor videoEditor, String themeId, 4556 int removedItemPosition, Transition beginTransition, 4557 Transition endTransition) throws IOException { 4558 final List<MediaItem> mediaItems = videoEditor.getAllMediaItems(); 4559 final int mediaItemCount = mediaItems.size(); 4560 if (mediaItemCount == 0) { 4561 return null; 4562 } 4563 4564 final Context context = getApplicationContext(); 4565 final MovieTheme theme = MovieTheme.getTheme(context, themeId); 4566 4567 Transition transition = null; 4568 if (removedItemPosition == 0) { // First item removed 4569 if (theme.getBeginTransition() != null && beginTransition != null) { 4570 transition = 4571 theme.getBeginTransition().buildTransition(context, null, mediaItems.get(0)); 4572 videoEditor.addTransition(transition); 4573 } 4574 } else if (removedItemPosition == mediaItemCount) { // Last item removed 4575 if (theme.getEndTransition() != null && endTransition != null) { 4576 transition = theme.getEndTransition().buildTransition(context, 4577 mediaItems.get(mediaItemCount - 1), null); 4578 videoEditor.addTransition(transition); 4579 } 4580 } else { // Mid item removed 4581 if (theme.getMidTransition() != null && beginTransition != null) { 4582 transition = 4583 theme.getMidTransition().buildTransition(context, 4584 mediaItems.get(removedItemPosition - 1), 4585 mediaItems.get(removedItemPosition)); 4586 videoEditor.addTransition(transition); 4587 } 4588 } 4589 4590 return transition; 4591 } 4592 4593 /** 4594 * Apply theme transitions after an item was moved 4595 * 4596 * @param videoEditor The video editor 4597 * @param themeId The theme id 4598 * @param movedMediaItem The moved media item 4599 * @param originalItemPosition The original media item position 4600 * @param beginTransition The moved item begin transition 4601 * @param endTransition The moved item end transition 4602 */ 4603 private void applyThemeAfterMove(VideoEditor videoEditor, String themeId, 4604 MediaItem movedMediaItem, int originalItemPosition, Transition beginTransition, 4605 Transition endTransition) throws IOException { 4606 final List<MediaItem> mediaItems = videoEditor.getAllMediaItems(); 4607 final int mediaItemCount = mediaItems.size(); 4608 if (mediaItemCount == 0) { 4609 return; 4610 } 4611 4612 final Context context = getApplicationContext(); 4613 final MovieTheme theme = MovieTheme.getTheme(context, themeId); 4614 4615 if (originalItemPosition == 0) { // First item moved 4616 if (theme.getBeginTransition() != null && beginTransition != null) { 4617 final Transition transition = 4618 theme.getBeginTransition().buildTransition(context, null, mediaItems.get(0)); 4619 videoEditor.addTransition(transition); 4620 } 4621 } else if (originalItemPosition == mediaItemCount - 1) { // Last item moved 4622 if (theme.getEndTransition() != null && endTransition != null) { 4623 final Transition transition = theme.getEndTransition().buildTransition(context, 4624 mediaItems.get(mediaItemCount - 1), null); 4625 videoEditor.addTransition(transition); 4626 } 4627 } else { // Mid item moved 4628 final int newPosition = mediaItems.indexOf(movedMediaItem); 4629 if (newPosition > originalItemPosition) { // Moved forward 4630 if (endTransition != null && theme.getMidTransition() != null) { 4631 final Transition transition = theme.getMidTransition().buildTransition( 4632 context, mediaItems.get(originalItemPosition - 1), 4633 mediaItems.get(originalItemPosition)); 4634 videoEditor.addTransition(transition); 4635 } 4636 } else { // Moved backward 4637 if (beginTransition != null && theme.getMidTransition() != null) { 4638 final Transition transition = theme.getMidTransition().buildTransition( 4639 context, mediaItems.get(originalItemPosition), 4640 mediaItems.get(originalItemPosition + 1)); 4641 videoEditor.addTransition(transition); 4642 } 4643 } 4644 } 4645 4646 // Apply the theme at the new position 4647 applyThemeToMediaItem(videoEditor, themeId, movedMediaItem); 4648 } 4649 4650 /** 4651 * Copy the media items 4652 * 4653 * @param mediaItems The media items 4654 * 4655 * @return The list of media items 4656 */ 4657 private List<MovieMediaItem> copyMediaItems(List<MediaItem> mediaItems) { 4658 final List<MovieMediaItem> movieMediaItems 4659 = new ArrayList<MovieMediaItem>(mediaItems.size()); 4660 MovieMediaItem prevMediaItem = null; 4661 for (MediaItem mediaItem : mediaItems) { 4662 final MovieTransition prevTransition; 4663 if (prevMediaItem != null) { 4664 prevTransition = prevMediaItem.getEndTransition(); 4665 } else if (mediaItem.getBeginTransition() != null) { 4666 prevTransition = new MovieTransition(mediaItem.getBeginTransition()); 4667 } else { 4668 prevTransition = null; 4669 } 4670 4671 final MovieMediaItem movieMediaItem = new MovieMediaItem(mediaItem, prevTransition); 4672 movieMediaItems.add(movieMediaItem); 4673 prevMediaItem = movieMediaItem; 4674 } 4675 4676 return movieMediaItems; 4677 } 4678 4679 /** 4680 * Copy the audio tracks 4681 * 4682 * @param audioTracks The audio tracks 4683 * 4684 * @return The list of audio tracks 4685 */ 4686 private List<MovieAudioTrack> copyAudioTracks(List<AudioTrack> audioTracks) { 4687 final List<MovieAudioTrack> movieAudioTracks 4688 = new ArrayList<MovieAudioTrack>(audioTracks.size()); 4689 for (AudioTrack audioTrack : audioTracks) { 4690 movieAudioTracks.add(new MovieAudioTrack(audioTrack)); 4691 } 4692 return movieAudioTracks; 4693 } 4694 4695 private static void logd(String message) { 4696 if (Log.isLoggable(TAG, Log.DEBUG)) { 4697 Log.d(TAG, message); 4698 } 4699 } 4700 4701 private static void logv(String message) { 4702 if (Log.isLoggable(TAG, Log.VERBOSE)) { 4703 Log.v(TAG, message); 4704 } 4705 } 4706 4707 /** 4708 * Worker thread that processes intents and maintains its own intent queue. 4709 */ 4710 private class IntentProcessor extends Thread { 4711 private final BlockingQueue<Intent> mIntentQueue; 4712 4713 public IntentProcessor(String threadName) { 4714 super("IntentProcessor-" + threadName); 4715 mIntentQueue = new LinkedBlockingQueue<Intent>(); 4716 } 4717 4718 @Override 4719 public void run() { 4720 try { 4721 while(true) { 4722 processIntent(mIntentQueue.take()); 4723 } 4724 } catch (InterruptedException e) { 4725 Log.e(TAG, "Terminating " + getName()); 4726 } 4727 } 4728 4729 /** 4730 * Submits a new intent for processing. 4731 * 4732 * @param intent The intent to be processed 4733 */ 4734 public void submit(Intent intent) { 4735 if (isAlive()) { 4736 mIntentQueue.add(intent); 4737 } else { 4738 Log.e(TAG, getName() + " should be started before submitting tasks."); 4739 } 4740 } 4741 4742 /** 4743 * Removes an intent from the queue. 4744 * 4745 * @param intent The intent to be removed 4746 * 4747 * @return true if the intent is removed 4748 */ 4749 public boolean cancel(Intent intent) { 4750 return mIntentQueue.remove(intent); 4751 } 4752 4753 public Iterator<Intent> getIntentQueueIterator() { 4754 return mIntentQueue.iterator(); 4755 } 4756 4757 public void quit() { 4758 // Display an error if the queue is not empty and clear it. 4759 final int queueSize = mIntentQueue.size(); 4760 if (queueSize > 0) { 4761 Log.e(TAG, "Thread queue is not empty. Size: " + queueSize); 4762 mIntentQueue.clear(); 4763 } 4764 interrupt(); 4765 } 4766 } 4767 } 4768