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.widgets; 18 19 import java.util.List; 20 21 import android.app.Activity; 22 import android.app.AlertDialog; 23 import android.app.Dialog; 24 import android.content.ClipData; 25 import android.content.Context; 26 import android.content.DialogInterface; 27 import android.content.Intent; 28 import android.graphics.Bitmap; 29 import android.graphics.Rect; 30 import android.media.videoeditor.EffectColor; 31 import android.media.videoeditor.MediaItem; 32 import android.media.videoeditor.Transition; 33 import android.media.videoeditor.TransitionSliding; 34 import android.os.Bundle; 35 import android.os.Handler; 36 import android.util.AttributeSet; 37 import android.util.Log; 38 import android.view.ActionMode; 39 import android.view.Display; 40 import android.view.DragEvent; 41 import android.view.Menu; 42 import android.view.MenuItem; 43 import android.view.MotionEvent; 44 import android.view.View; 45 import android.widget.ImageButton; 46 import android.widget.LinearLayout; 47 import android.widget.RelativeLayout; 48 import android.widget.Toast; 49 50 import com.android.videoeditor.AlertDialogs; 51 import com.android.videoeditor.EffectType; 52 import com.android.videoeditor.KenBurnsActivity; 53 import com.android.videoeditor.OverlayTitleEditor; 54 import com.android.videoeditor.TransitionType; 55 import com.android.videoeditor.TransitionsActivity; 56 import com.android.videoeditor.VideoEditorActivity; 57 import com.android.videoeditor.service.ApiService; 58 import com.android.videoeditor.service.MovieEffect; 59 import com.android.videoeditor.service.MovieMediaItem; 60 import com.android.videoeditor.service.MovieOverlay; 61 import com.android.videoeditor.service.MovieTransition; 62 import com.android.videoeditor.service.VideoEditorProject; 63 import com.android.videoeditor.util.FileUtils; 64 import com.android.videoeditor.util.MediaItemUtils; 65 import com.android.videoeditor.R; 66 67 /** 68 * LinearLayout which holds media items and transitions. 69 */ 70 public class MediaLinearLayout extends LinearLayout { 71 // Logging 72 private static final String TAG = "MediaLinearLayout"; 73 74 // Dialog parameter ids 75 private static final String PARAM_DIALOG_MEDIA_ITEM_ID = "media_item_id"; 76 private static final String PARAM_DIALOG_CURRENT_RENDERING_MODE = "rendering_mode"; 77 private static final String PARAM_DIALOG_TRANSITION_ID = "transition_id"; 78 79 // Transition duration limits 80 private static final long MAXIMUM_IMAGE_DURATION = 6000; 81 private static final long MAXIMUM_TRANSITION_DURATION = 3000; 82 private static final long MINIMUM_TRANSITION_DURATION = 250; 83 84 private static final long TIME_TOLERANCE = 30; 85 86 // Instance variables 87 private final ItemSimpleGestureListener mMediaItemGestureListener; 88 private final ItemSimpleGestureListener mTransitionGestureListener; 89 private final Handler mHandler; 90 private final int mHalfParentWidth; 91 private final int mHandleWidth; 92 private final int mTransitionVerticalInset; 93 private final ImageButton mLeftAddClipButton, mRightAddClipButton; 94 private MediaLinearLayoutListener mListener; 95 private ActionMode mMediaItemActionMode; 96 private ActionMode mTransitionActionMode; 97 private VideoEditorProject mProject; 98 private boolean mPlaybackInProgress; 99 private HandleView mLeftHandle, mRightHandle; 100 private boolean mIsTrimming; // Indicates if some media item is being trimmed. 101 private boolean mMoveLayoutPending; 102 private View mScrollView; // Convenient handle to the parent scroll view. 103 private View mSelectedView; 104 private String mDragMediaItemId; 105 private float mPrevDragPosition; 106 private long mPrevDragScrollTime; 107 private MovieMediaItem mDropAfterMediaItem; 108 private int mDropIndex; 109 private boolean mFirstEntered; 110 111 /** 112 * The media item action mode handler. 113 */ 114 private class MediaItemActionModeCallback implements ActionMode.Callback { 115 // Media item associated with this callback. 116 private final MovieMediaItem mMediaItem; 117 118 public MediaItemActionModeCallback(MovieMediaItem mediaItem) { 119 mMediaItem = mediaItem; 120 } 121 122 @Override 123 public boolean onCreateActionMode(ActionMode mode, Menu menu) { 124 mMediaItemActionMode = mode; 125 126 final Activity activity = (Activity) getContext(); 127 activity.getMenuInflater().inflate(R.menu.media_item_mode_menu, menu); 128 129 return true; 130 } 131 132 @Override 133 public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 134 final boolean enable = !ApiService.isProjectBeingEdited(mProject.getPath()) && 135 !mPlaybackInProgress; 136 137 // Pan zoom effect is only for images. Hide it from video clips. 138 MenuItem item; 139 if (!mMediaItem.isImage()) { 140 item = menu.findItem(R.id.action_pan_zoom_effect); 141 item.setVisible(false); 142 item.setEnabled(false); 143 } 144 145 // If the selected media item already has an effect applied on it, check the 146 // corresponding effect menu item. 147 MovieEffect effect = mMediaItem.getEffect(); 148 if (effect != null) { 149 switch (mMediaItem.getEffect().getType()) { 150 case EffectType.EFFECT_KEN_BURNS: 151 item = menu.findItem(R.id.action_pan_zoom_effect); 152 break; 153 case EffectType.EFFECT_COLOR_GRADIENT: 154 item = menu.findItem(R.id.action_gradient_effect); 155 break; 156 case EffectType.EFFECT_COLOR_SEPIA: 157 item = menu.findItem(R.id.action_sepia_effect); 158 break; 159 case EffectType.EFFECT_COLOR_NEGATIVE: 160 item = menu.findItem(R.id.action_negative_effect); 161 break; 162 default: 163 item = menu.findItem(R.id.action_no_effect); 164 break; 165 } 166 } else { 167 item = menu.findItem(R.id.action_no_effect); 168 } 169 item.setChecked(true); 170 menu.findItem(R.id.media_item_effect_menu).setEnabled(enable); 171 172 // Menu item for adding a new overlay. It is also used to edit 173 // existing overlay. We change the displayed text accordingly. 174 final MenuItem aomi = menu.findItem(R.id.action_add_overlay); 175 aomi.setTitle((mMediaItem.getOverlay() == null) ? 176 R.string.editor_add_overlay : R.string.editor_edit_overlay); 177 aomi.setEnabled(enable); 178 179 final MenuItem romi = menu.findItem(R.id.action_remove_overlay); 180 romi.setVisible(mMediaItem.getOverlay() != null); 181 romi.setEnabled(enable); 182 183 final MenuItem btmi = menu.findItem(R.id.action_add_begin_transition); 184 btmi.setVisible(mMediaItem.getBeginTransition() == null); 185 btmi.setEnabled(enable); 186 187 final MenuItem etmi = menu.findItem(R.id.action_add_end_transition); 188 etmi.setVisible(mMediaItem.getEndTransition() == null); 189 etmi.setEnabled(enable); 190 191 final MenuItem rmmi = menu.findItem(R.id.action_rendering_mode); 192 rmmi.setVisible(mProject.hasMultipleAspectRatios()); 193 rmmi.setEnabled(enable); 194 195 return true; 196 } 197 198 @Override 199 public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 200 switch (item.getItemId()) { 201 case R.id.action_add_overlay: { 202 editOverlay(mMediaItem); 203 break; 204 } 205 206 case R.id.action_remove_overlay: { 207 removeOverlay(mMediaItem); 208 break; 209 } 210 211 case R.id.action_add_begin_transition: { 212 final MovieMediaItem prevMediaItem = mProject.getPreviousMediaItem( 213 mMediaItem.getId()); 214 pickTransition(prevMediaItem); 215 break; 216 } 217 218 case R.id.action_add_end_transition: { 219 pickTransition(mMediaItem); 220 break; 221 } 222 223 case R.id.action_gradient_effect: 224 case R.id.action_sepia_effect: 225 case R.id.action_negative_effect: 226 case R.id.action_pan_zoom_effect: { 227 applyEffect(item); 228 break; 229 } 230 231 case R.id.action_no_effect: { 232 if (!item.isChecked()) { 233 final Bundle bundle = new Bundle(); 234 bundle.putString(PARAM_DIALOG_MEDIA_ITEM_ID, mMediaItem.getId()); 235 ((Activity) getContext()).showDialog( 236 VideoEditorActivity.DIALOG_REMOVE_EFFECT_ID, bundle); 237 } 238 break; 239 } 240 241 case R.id.action_rendering_mode: { 242 final Bundle bundle = new Bundle(); 243 bundle.putString(PARAM_DIALOG_MEDIA_ITEM_ID, mMediaItem.getId()); 244 bundle.putInt(PARAM_DIALOG_CURRENT_RENDERING_MODE, 245 mMediaItem.getAppRenderingMode()); 246 ((Activity) getContext()).showDialog( 247 VideoEditorActivity.DIALOG_CHANGE_RENDERING_MODE_ID, bundle); 248 break; 249 } 250 251 case R.id.action_delete_media_item: { 252 final Bundle bundle = new Bundle(); 253 bundle.putString(PARAM_DIALOG_MEDIA_ITEM_ID, mMediaItem.getId()); 254 ((Activity) getContext()).showDialog( 255 VideoEditorActivity.DIALOG_REMOVE_MEDIA_ITEM_ID, bundle); 256 break; 257 } 258 259 default: { 260 break; 261 } 262 } 263 264 return true; 265 } 266 267 @Override 268 public void onDestroyActionMode(ActionMode mode) { 269 final View mediaItemView = getMediaItemView(mMediaItem.getId()); 270 if (mSelectedView != null) { 271 mLeftHandle.endMove(); 272 mRightHandle.endMove(); 273 } 274 unSelect(mediaItemView); 275 showAddMediaItemButtons(true); 276 mMediaItemActionMode = null; 277 } 278 279 private void applyEffect(MenuItem clickedItem) { 280 if (!clickedItem.isChecked()) { 281 switch(clickedItem.getItemId()) { 282 case R.id.action_gradient_effect: 283 addEffect(EffectType.EFFECT_COLOR_GRADIENT, 284 mMediaItem.getId(), null, null); 285 clickedItem.setChecked(true); 286 break; 287 case R.id.action_sepia_effect: 288 addEffect(EffectType.EFFECT_COLOR_SEPIA, 289 mMediaItem.getId(), null, null); 290 clickedItem.setChecked(true); 291 break; 292 case R.id.action_negative_effect: 293 addEffect(EffectType.EFFECT_COLOR_NEGATIVE, 294 mMediaItem.getId(), null, null); 295 clickedItem.setChecked(true); 296 break; 297 case R.id.action_pan_zoom_effect: { 298 // Note that we don't check the pan zoom checkbox here. 299 // Because pan zoom effect will start a new activity and users 300 // could cancel applying the effect. Once pan zoom effect has 301 // really been applied. The action mode will be invalidated in 302 // onActivityResult() method and the checkbox is then checked. 303 final Intent intent = new Intent(getContext(), KenBurnsActivity.class); 304 intent.putExtra(KenBurnsActivity.PARAM_MEDIA_ITEM_ID, mMediaItem.getId()); 305 intent.putExtra(KenBurnsActivity.PARAM_FILENAME, mMediaItem.getFilename()); 306 intent.putExtra(KenBurnsActivity.PARAM_WIDTH, mMediaItem.getWidth()); 307 intent.putExtra(KenBurnsActivity.PARAM_HEIGHT, mMediaItem.getHeight()); 308 ((Activity) getContext()).startActivityForResult(intent, 309 VideoEditorActivity.REQUEST_CODE_KEN_BURNS); 310 break; 311 } 312 default: 313 break; 314 } 315 } 316 } 317 } 318 319 /** 320 * The transition action mode handler. 321 */ 322 private class TransitionActionModeCallback implements ActionMode.Callback { 323 private final MovieTransition mTransition; 324 325 public TransitionActionModeCallback(MovieTransition transition) { 326 mTransition = transition; 327 } 328 329 @Override 330 public boolean onCreateActionMode(ActionMode mode, Menu menu) { 331 mTransitionActionMode = mode; 332 333 final Activity activity = (Activity) getContext(); 334 activity.getMenuInflater().inflate(R.menu.transition_mode_menu, menu); 335 mode.setTitle(activity.getString(R.string.editor_transition_title)); 336 337 return true; 338 } 339 340 @Override 341 public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 342 final boolean enable = !ApiService.isProjectBeingEdited(mProject.getPath()) && 343 !mPlaybackInProgress; 344 345 final MenuItem etmi = menu.findItem(R.id.action_change_transition); 346 etmi.setEnabled(enable); 347 348 final MenuItem rtmi = menu.findItem(R.id.action_remove_transition); 349 rtmi.setEnabled(enable); 350 351 return true; 352 } 353 354 @Override 355 public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 356 switch (item.getItemId()) { 357 case R.id.action_remove_transition: { 358 final Bundle bundle = new Bundle(); 359 bundle.putString(PARAM_DIALOG_TRANSITION_ID, mTransition.getId()); 360 ((Activity) getContext()).showDialog( 361 VideoEditorActivity.DIALOG_REMOVE_TRANSITION_ID, bundle); 362 break; 363 } 364 365 case R.id.action_change_transition: { 366 editTransition(mTransition); 367 break; 368 } 369 370 default: { 371 break; 372 } 373 } 374 375 return true; 376 } 377 378 @Override 379 public void onDestroyActionMode(ActionMode mode) { 380 final View transitionView = getTransitionView(mTransition.getId()); 381 unSelect(transitionView); 382 showAddMediaItemButtons(true); 383 mTransitionActionMode = null; 384 } 385 } 386 387 public MediaLinearLayout(Context context, AttributeSet attrs, int defStyle) { 388 super(context, attrs, defStyle); 389 390 mMediaItemGestureListener = new ItemSimpleGestureListener() { 391 @Override 392 public boolean onSingleTapConfirmed(View view, int area, MotionEvent e) { 393 if (mPlaybackInProgress) { 394 return false; 395 } 396 397 switch (area) { 398 case ItemSimpleGestureListener.LEFT_AREA: { 399 if (view.isSelected()) { 400 final MovieMediaItem mediaItem = (MovieMediaItem) view.getTag(); 401 final MovieMediaItem prevMediaItem = mProject.getPreviousMediaItem( 402 mediaItem.getId()); 403 pickTransition(prevMediaItem); 404 } 405 break; 406 } 407 408 case ItemSimpleGestureListener.CENTER_AREA: { 409 break; 410 } 411 412 case ItemSimpleGestureListener.RIGHT_AREA: { 413 if (view.isSelected()) { 414 pickTransition((MovieMediaItem) view.getTag()); 415 } 416 break; 417 } 418 } 419 select(view); 420 421 return true; 422 } 423 424 @Override 425 public void onLongPress(View view, MotionEvent e) { 426 if (mPlaybackInProgress) { 427 return; 428 } 429 430 final MovieMediaItem mediaItem = (MovieMediaItem)view.getTag(); 431 if (mProject.getMediaItemCount() > 1) { 432 view.startDrag(ClipData.newPlainText("File", mediaItem.getFilename()), 433 ((MediaItemView)view).getShadowBuilder(), mediaItem.getId(), 0); 434 } 435 436 select(view); 437 438 if (mMediaItemActionMode == null) { 439 startActionMode(new MediaItemActionModeCallback(mediaItem)); 440 } 441 } 442 }; 443 444 mTransitionGestureListener = new ItemSimpleGestureListener() { 445 @Override 446 public boolean onSingleTapConfirmed(View view, int area, MotionEvent e) { 447 if (mPlaybackInProgress) { 448 return false; 449 } 450 select(view); 451 return true; 452 } 453 454 @Override 455 public void onLongPress(View view, MotionEvent e) { 456 if (mPlaybackInProgress) { 457 return; 458 } 459 460 select(view); 461 462 if (mTransitionActionMode == null) { 463 startActionMode(new TransitionActionModeCallback( 464 (MovieTransition) view.getTag())); 465 } 466 } 467 }; 468 469 // Add the beginning timeline item 470 final View beginView = inflate(getContext(), R.layout.empty_left_timeline_item, null); 471 beginView.setOnClickListener(new View.OnClickListener() { 472 @Override 473 public void onClick(View view) { 474 unselectAllTimelineViews(); 475 } 476 }); 477 478 mLeftAddClipButton = (ImageButton) beginView.findViewById( 479 R.id.add_left_media_item_button); 480 mLeftAddClipButton.setVisibility(View.GONE); 481 mLeftAddClipButton.setOnClickListener(new View.OnClickListener() { 482 @Override 483 public void onClick(View view) { 484 if (mProject != null && mProject.getMediaItemCount() > 0) { 485 unselectAllTimelineViews(); 486 // Add a clip at the beginning of the movie. 487 mListener.onAddMediaItem(null); 488 } 489 } 490 }); 491 addView(beginView); 492 493 // Add the end timeline item 494 final View endView = inflate(getContext(), R.layout.empty_right_timeline_item, null); 495 endView.setOnClickListener(new View.OnClickListener() { 496 @Override 497 public void onClick(View view) { 498 unselectAllTimelineViews(); 499 } 500 }); 501 502 mRightAddClipButton = (ImageButton) endView.findViewById( 503 R.id.add_right_media_item_button); 504 mRightAddClipButton.setOnClickListener(new View.OnClickListener() { 505 @Override 506 public void onClick(View view) { 507 if (mProject != null) { 508 unselectAllTimelineViews(); 509 // Add a clip at the end of the movie. 510 final MovieMediaItem lastMediaItem = mProject.getLastMediaItem(); 511 if (lastMediaItem != null) { 512 mListener.onAddMediaItem(lastMediaItem.getId()); 513 } else { 514 mListener.onAddMediaItem(null); 515 } 516 } 517 } 518 }); 519 addView(endView); 520 521 mLeftHandle = (HandleView)inflate(getContext(), R.layout.left_handle_view, null); 522 addView(mLeftHandle); 523 524 mRightHandle = (HandleView)inflate(getContext(), R.layout.right_handle_view, null); 525 addView(mRightHandle); 526 527 mHandleWidth = (int) context.getResources().getDimension(R.dimen.handle_width); 528 529 mTransitionVerticalInset = (int) context.getResources().getDimension( 530 R.dimen.timelime_transition_vertical_inset); 531 532 // Compute half the width of the screen (and therefore the parent view). 533 final Display display = ((Activity) context).getWindowManager().getDefaultDisplay(); 534 mHalfParentWidth = display.getWidth() / 2; 535 536 mHandler = new Handler(); 537 538 setMotionEventSplittingEnabled(false); 539 } 540 541 public MediaLinearLayout(Context context, AttributeSet attrs) { 542 this(context, attrs, 0); 543 } 544 545 public MediaLinearLayout(Context context) { 546 this(context, null, 0); 547 } 548 549 public void setParentTimelineScrollView(View scrollView) { 550 mScrollView = scrollView; 551 } 552 553 /** 554 * Called when the containing activity is resumed. 555 */ 556 public void onResume() { 557 // Invalidate all progress in case the transition generation or 558 // Ken Burns effect completed while the activity was being paused. 559 final int childrenCount = getChildCount(); 560 for (int i = 0; i < childrenCount; i++) { 561 final View childView = getChildAt(i); 562 final Object item = childView.getTag(); 563 if (item != null) { 564 if (item instanceof MovieMediaItem) { 565 ((MediaItemView) childView).resetGeneratingEffectProgress(); 566 } else if (item instanceof MovieTransition) { 567 ((TransitionView) childView).resetGeneratingTransitionProgress(); 568 } 569 } 570 } 571 } 572 573 public void setListener(MediaLinearLayoutListener listener) { 574 mListener = listener; 575 } 576 577 public void setProject(VideoEditorProject project) { 578 closeActionBars(); 579 clearAndHideTrimHandles(); 580 removeAllMediaItemAndTransitionViews(); 581 582 mProject = project; 583 } 584 585 /** 586 * @param inProgress {@code true} if playback is in progress, false otherwise 587 */ 588 public void setPlaybackInProgress(boolean inProgress) { 589 mPlaybackInProgress = inProgress; 590 setPlaybackState(inProgress); 591 // Don't allow the user to interact with media items or 592 // transitions while the playback is in progress. 593 closeActionBars(); 594 } 595 596 /** 597 * Returns selected view's position on the timeline; -1 if none. 598 */ 599 public int getSelectedViewPos() { 600 return indexOfChild(mSelectedView); 601 } 602 603 /** 604 * Selects the view at the specified position; null if it does not exist. 605 */ 606 public void setSelectedView(int pos) { 607 if (pos < 0) { 608 return; 609 } 610 mSelectedView = getChildAt(pos); 611 if (mSelectedView != null) { 612 select(mSelectedView); 613 } 614 } 615 616 /** 617 * Clears existing media or transition items and adds all given media items. 618 * 619 * @param mediaItems The list of media items 620 */ 621 public void addMediaItems(List<MovieMediaItem> mediaItems) { 622 closeActionBars(); 623 removeAllMediaItemAndTransitionViews(); 624 625 for (MovieMediaItem mediaItem : mediaItems) { 626 addMediaItem(mediaItem); 627 } 628 } 629 630 /** 631 * Adds a new media item at the end of the timeline. 632 * 633 * @param mediaItem The media item 634 */ 635 private void addMediaItem(MovieMediaItem mediaItem) { 636 final View mediaItemView = inflate(getContext(), R.layout.media_item, null); 637 ((MediaItemView) mediaItemView).setGestureListener(mMediaItemGestureListener); 638 ((MediaItemView) mediaItemView).setProjectPath(mProject.getPath()); 639 mediaItemView.setTag(mediaItem); 640 641 // Add the new view 642 final LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( 643 LinearLayout.LayoutParams.WRAP_CONTENT, 644 LinearLayout.LayoutParams.FILL_PARENT); 645 // Add the view before the end view, left handle and right handle views. 646 addView(mediaItemView, getChildCount() - 3, lp); 647 648 // If the new media item has beginning and end transitions, add them. 649 final MovieTransition beginTransition = mediaItem.getBeginTransition(); 650 if (beginTransition != null) { 651 final int cc = getChildCount(); 652 // Account for the beginning and end views and the trim handles 653 if (cc > 5) { // There is a previous view (transition or media item) 654 final View view = getChildAt(cc - 5); 655 final Object tag = view.getTag(); 656 // Do not add transition if it already exists 657 if (tag != null && tag instanceof MovieMediaItem) { 658 final MovieMediaItem prevMediaItem = (MovieMediaItem)tag; 659 addTransition(beginTransition, prevMediaItem.getId()); 660 } 661 } else { // This is the first media item 662 addTransition(beginTransition, null); 663 } 664 } 665 666 final MovieTransition endTransition = mediaItem.getEndTransition(); 667 if (endTransition != null) { 668 addTransition(endTransition, mediaItem.getId()); 669 } 670 671 requestLayout(); 672 673 if (mMediaItemActionMode != null) { 674 mMediaItemActionMode.invalidate(); 675 } 676 677 // Now we can add clips by tapping the beginning view 678 mLeftAddClipButton.setVisibility(View.VISIBLE); 679 } 680 681 /** 682 * Inserts a new media item after the specified media item id. 683 * 684 * @param mediaItem The media item 685 * @param afterMediaItemId The id of the media item preceding the media item 686 */ 687 public void insertMediaItem(MovieMediaItem mediaItem, String afterMediaItemId) { 688 final View mediaItemView = inflate(getContext(), R.layout.media_item, null); 689 ((MediaItemView)mediaItemView).setGestureListener(mMediaItemGestureListener); 690 ((MediaItemView)mediaItemView).setProjectPath(mProject.getPath()); 691 692 mediaItemView.setTag(mediaItem); 693 694 int insertViewIndex; 695 if (afterMediaItemId != null) { 696 if ((insertViewIndex = getMediaItemViewIndex(afterMediaItemId)) == -1) { 697 Log.e(TAG, "Media item not found: " + afterMediaItemId); 698 return; 699 } 700 701 insertViewIndex++; 702 703 if (insertViewIndex < getChildCount()) { 704 final Object tag = getChildAt(insertViewIndex).getTag(); 705 if (tag != null && tag instanceof MovieTransition) { 706 // Remove the transition following the media item 707 removeViewAt(insertViewIndex); 708 } 709 } 710 } else { // Insert at the beginning 711 // If we have a transition at the beginning remove it 712 final Object tag = getChildAt(1).getTag(); 713 if (tag != null && tag instanceof MovieTransition) { 714 removeViewAt(1); 715 } 716 717 insertViewIndex = 1; 718 } 719 720 // Add the new view 721 final LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( 722 LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.FILL_PARENT); 723 addView(mediaItemView, insertViewIndex, lp); 724 725 // If the new media item has beginning and end transitions add them 726 final MovieTransition beginTransition = mediaItem.getBeginTransition(); 727 if (beginTransition != null) { 728 if (insertViewIndex > 1) { // There is a previous view (transition or media item) 729 final View view = getChildAt(insertViewIndex - 1); 730 final Object tag = view.getTag(); 731 // Do not add transition if it already exists 732 if (tag != null && tag instanceof MovieMediaItem) { 733 final MovieMediaItem prevMediaItem = (MovieMediaItem)tag; 734 addTransition(beginTransition, prevMediaItem.getId()); 735 } 736 } else { // This is the first media item 737 addTransition(beginTransition, null); 738 } 739 } 740 741 final MovieTransition endTransition = mediaItem.getEndTransition(); 742 if (endTransition != null) { 743 addTransition(endTransition, mediaItem.getId()); 744 } 745 746 requestLayout(); 747 748 if (mMediaItemActionMode != null) { 749 mMediaItemActionMode.invalidate(); 750 } 751 752 // Now we can add clips by tapping the beginning view 753 mLeftAddClipButton.setVisibility(View.VISIBLE); 754 } 755 756 /** 757 * Updates the specified media item. 758 * 759 * @param mediaItem The media item to be updated 760 */ 761 public void updateMediaItem(MovieMediaItem mediaItem) { 762 final String mediaItemId = mediaItem.getId(); 763 final int childrenCount = getChildCount(); 764 for (int i = 0; i < childrenCount; i++) { 765 final View childView = getChildAt(i); 766 final Object tag = childView.getTag(); 767 if (tag != null && tag instanceof MovieMediaItem) { 768 final MovieMediaItem mi = (MovieMediaItem) tag; 769 if (mediaItemId.equals(mi.getId())) { 770 if (mediaItem != mi) { 771 // The media item is a new instance of the media item 772 childView.setTag(mediaItem); 773 if (mediaItem.getBeginTransition() != null) { 774 if (i > 0) { 775 final View tView = getChildAt(i - 1); 776 final Object tagT = tView.getTag(); 777 if (tagT != null && tagT instanceof MovieTransition) { 778 tView.setTag(mediaItem.getBeginTransition()); 779 } 780 } 781 } 782 783 if (mediaItem.getEndTransition() != null) { 784 if (i < childrenCount - 1) { 785 final View tView = getChildAt(i + 1); 786 final Object tagT = tView.getTag(); 787 if (tagT != null && tagT instanceof MovieTransition) { 788 tView.setTag(mediaItem.getEndTransition()); 789 } 790 } 791 } 792 } 793 794 if (childView.isSelected()) { 795 mLeftHandle.setEnabled(true); 796 mRightHandle.setEnabled(true); 797 } 798 799 break; 800 } 801 } 802 } 803 804 requestLayout(); 805 806 if (mMediaItemActionMode != null) { 807 mMediaItemActionMode.invalidate(); 808 } 809 } 810 811 /** 812 * Removes a media item view. 813 * 814 * @param mediaItemId The media item id 815 * @param transition The transition inserted at the removal position 816 * if a theme is in use. 817 * 818 * @return The view which was removed 819 */ 820 public View removeMediaItem(String mediaItemId, MovieTransition transition) { 821 final int childrenCount = getChildCount(); 822 MovieMediaItem prevMediaItem = null; 823 for (int i = 0; i < childrenCount; i++) { 824 final View childView = getChildAt(i); 825 final Object tag = childView.getTag(); 826 if (tag != null && tag instanceof MovieMediaItem) { 827 final MovieMediaItem mi = (MovieMediaItem)tag; 828 if (mediaItemId.equals(mi.getId())) { 829 int mediaItemViewIndex = i; 830 831 // Remove the before transition 832 if (mediaItemViewIndex > 0) { 833 final Object beforeTag = getChildAt(mediaItemViewIndex - 1).getTag(); 834 if (beforeTag != null && beforeTag instanceof MovieTransition) { 835 // Remove the transition view 836 removeViewAt(mediaItemViewIndex - 1); 837 mediaItemViewIndex--; 838 } 839 } 840 841 // Remove the after transition view 842 if (mediaItemViewIndex < getChildCount() - 1) { 843 final Object afterTag = getChildAt(mediaItemViewIndex + 1).getTag(); 844 if (afterTag != null && afterTag instanceof MovieTransition) { 845 // Remove the transition view 846 removeViewAt(mediaItemViewIndex + 1); 847 } 848 } 849 850 // Remove the media item view 851 removeViewAt(mediaItemViewIndex); 852 853 if (transition != null) { 854 addTransition(transition, 855 prevMediaItem != null ? prevMediaItem.getId() : null); 856 } 857 858 if (mMediaItemActionMode != null) { 859 mMediaItemActionMode.invalidate(); 860 } 861 862 if (mProject.getMediaItemCount() == 0) { 863 // We cannot add clips by tapping the beginning view 864 mLeftAddClipButton.setVisibility(View.GONE); 865 } 866 return childView; 867 } 868 869 prevMediaItem = mi; 870 } 871 } 872 873 return null; 874 } 875 876 /** 877 * Creates a new transition. 878 * 879 * @param afterMediaItemId Insert the transition after this media item id 880 * @param transitionType The transition type 881 * @param transitionDurationMs The transition duration in ms 882 */ 883 public void addTransition(String afterMediaItemId, int transitionType, 884 long transitionDurationMs) { 885 unselectAllTimelineViews(); 886 887 final MovieMediaItem afterMediaItem; 888 if (afterMediaItemId != null) { 889 afterMediaItem = mProject.getMediaItem(afterMediaItemId); 890 if (afterMediaItem == null) { 891 return; 892 } 893 } else { 894 afterMediaItem = null; 895 } 896 897 final String id = ApiService.generateId(); 898 switch (transitionType) { 899 case TransitionType.TRANSITION_TYPE_ALPHA_CONTOUR: { 900 ApiService.insertAlphaTransition(getContext(), mProject.getPath(), 901 afterMediaItemId, id, transitionDurationMs, Transition.BEHAVIOR_LINEAR, 902 R.raw.mask_contour, 50, false); 903 break; 904 } 905 906 case TransitionType.TRANSITION_TYPE_ALPHA_DIAGONAL: { 907 ApiService.insertAlphaTransition(getContext(), mProject.getPath(), 908 afterMediaItemId, id, transitionDurationMs, Transition.BEHAVIOR_LINEAR, 909 R.raw.mask_diagonal, 50, false); 910 break; 911 } 912 913 case TransitionType.TRANSITION_TYPE_CROSSFADE: { 914 ApiService.insertCrossfadeTransition(getContext(), mProject.getPath(), 915 afterMediaItemId, id, transitionDurationMs, Transition.BEHAVIOR_LINEAR); 916 break; 917 } 918 919 case TransitionType.TRANSITION_TYPE_FADE_BLACK: { 920 ApiService.insertFadeBlackTransition(getContext(), mProject.getPath(), 921 afterMediaItemId, id, transitionDurationMs, Transition.BEHAVIOR_LINEAR); 922 break; 923 } 924 925 case TransitionType.TRANSITION_TYPE_SLIDING_RIGHT_OUT_LEFT_IN: { 926 ApiService.insertSlidingTransition(getContext(), mProject.getPath(), 927 afterMediaItemId, id, transitionDurationMs, Transition.BEHAVIOR_LINEAR, 928 TransitionSliding.DIRECTION_RIGHT_OUT_LEFT_IN); 929 break; 930 } 931 932 case TransitionType.TRANSITION_TYPE_SLIDING_LEFT_OUT_RIGHT_IN: { 933 ApiService.insertSlidingTransition(getContext(), mProject.getPath(), 934 afterMediaItemId, id, transitionDurationMs, Transition.BEHAVIOR_LINEAR, 935 TransitionSliding.DIRECTION_LEFT_OUT_RIGHT_IN); 936 break; 937 } 938 939 case TransitionType.TRANSITION_TYPE_SLIDING_TOP_OUT_BOTTOM_IN: { 940 ApiService.insertSlidingTransition(getContext(), mProject.getPath(), 941 afterMediaItemId, id, transitionDurationMs, Transition.BEHAVIOR_LINEAR, 942 TransitionSliding.DIRECTION_TOP_OUT_BOTTOM_IN); 943 break; 944 } 945 946 case TransitionType.TRANSITION_TYPE_SLIDING_BOTTOM_OUT_TOP_IN: { 947 ApiService.insertSlidingTransition(getContext(), mProject.getPath(), 948 afterMediaItemId, id, transitionDurationMs, Transition.BEHAVIOR_LINEAR, 949 TransitionSliding.DIRECTION_BOTTOM_OUT_TOP_IN); 950 break; 951 } 952 953 default: { 954 break; 955 } 956 } 957 958 if (mMediaItemActionMode != null) { 959 mMediaItemActionMode.invalidate(); 960 } 961 } 962 963 /** 964 * Edits a transition. 965 * 966 * @param afterMediaItemId Insert the transition after this media item id 967 * @param transitionId The transition id 968 * @param transitionType The transition type 969 * @param transitionDurationMs The transition duration in ms 970 */ 971 public void editTransition(String afterMediaItemId, String transitionId, int transitionType, 972 long transitionDurationMs) { 973 final MovieTransition transition = mProject.getTransition(transitionId); 974 if (transition == null) { 975 return; 976 } 977 978 // Check if the type or duration had changed 979 if (transition.getType() != transitionType) { 980 // Remove the transition and add it again 981 ApiService.removeTransition(getContext(), mProject.getPath(), transitionId); 982 addTransition(afterMediaItemId, transitionType, transitionDurationMs); 983 } else if (transition.getAppDuration() != transitionDurationMs) { 984 transition.setAppDuration(transitionDurationMs); 985 ApiService.setTransitionDuration(getContext(), mProject.getPath(), transitionId, 986 transitionDurationMs); 987 } 988 989 if (mMediaItemActionMode != null) { 990 mMediaItemActionMode.invalidate(); 991 } 992 } 993 994 /** 995 * Adds a new transition after the specified media id. This method assumes that a 996 * transition does not exist at the insertion point. 997 * 998 * @param transition The transition to be added 999 * @param afterMediaItemId After the specified media item id 1000 * 1001 * @return The transition view that was added, {@code null} upon errors. 1002 */ 1003 public View addTransition(MovieTransition transition, String afterMediaItemId) { 1004 // Determine the insert position 1005 int index; 1006 if (afterMediaItemId != null) { 1007 index = -1; 1008 final int childrenCount = getChildCount(); 1009 for (int i = 0; i < childrenCount; i++) { 1010 final Object tag = getChildAt(i).getTag(); 1011 if (tag != null && tag instanceof MovieMediaItem) { 1012 final MovieMediaItem mi = (MovieMediaItem) tag; 1013 if (afterMediaItemId.equals(mi.getId())) { 1014 index = i + 1; 1015 break; 1016 } 1017 } 1018 } 1019 1020 if (index < 0) { 1021 Log.e(TAG, "addTransition media item not found: " + afterMediaItemId); 1022 return null; 1023 } 1024 } else { 1025 index = 1; 1026 } 1027 1028 final View transitionView = inflate(getContext(), R.layout.transition_view, null); 1029 ((TransitionView) transitionView).setGestureListener(mTransitionGestureListener); 1030 ((TransitionView) transitionView).setProjectPath(mProject.getPath()); 1031 transitionView.setTag(transition); 1032 1033 final LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( 1034 LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.FILL_PARENT); 1035 addView(transitionView, index, lp); 1036 1037 // Adjust the size of all the views 1038 requestLayout(); 1039 1040 // If this transition was added by the user invalidate the menu item 1041 if (mMediaItemActionMode != null) { 1042 mMediaItemActionMode.invalidate(); 1043 } 1044 1045 return transitionView; 1046 } 1047 1048 /** 1049 * Updates a transition. 1050 * 1051 * @param transitionId The transition id 1052 */ 1053 public void updateTransition(String transitionId) { 1054 requestLayout(); 1055 invalidate(); 1056 } 1057 1058 /** 1059 * Removes a transition with the specified id. 1060 * 1061 * @param transitionId The transition id 1062 */ 1063 public void removeTransition(String transitionId) { 1064 final int childrenCount = getChildCount(); 1065 for (int i = 0; i < childrenCount; i++) { 1066 final Object tag = getChildAt(i).getTag(); 1067 if (tag != null && tag instanceof MovieTransition) { 1068 final MovieTransition transition = (MovieTransition)tag; 1069 if (transitionId.equals(transition.getId())) { 1070 // Remove the view 1071 removeViewAt(i); 1072 1073 // Adjust the size of all the views 1074 requestLayout(); 1075 1076 // If this transition was removed by the user invalidate the menu item 1077 if (mMediaItemActionMode != null) { 1078 mMediaItemActionMode.invalidate(); 1079 } 1080 1081 return; 1082 } 1083 } 1084 } 1085 } 1086 1087 /** 1088 * Invalidates the available action modes. Used to refresh menu contents. 1089 */ 1090 public void invalidateActionBar() { 1091 if (mMediaItemActionMode != null) { 1092 mMediaItemActionMode.invalidate(); 1093 } 1094 if (mTransitionActionMode != null) { 1095 mTransitionActionMode.invalidate(); 1096 } 1097 } 1098 1099 /** 1100 * A Ken Burns movie is encoded for an MediaImageItem. 1101 * 1102 * @param mediaItemId The media item id 1103 * @param action The action 1104 * @param progress Progress value (between 0..100) 1105 */ 1106 public void onGeneratePreviewMediaItemProgress(String mediaItemId, int action, int progress) { 1107 // Display the progress while generating the Ken Burns video clip 1108 final MediaItemView view = (MediaItemView) getMediaItemView(mediaItemId); 1109 if (view != null) { 1110 view.setGeneratingEffectProgress(progress); 1111 1112 if (view.isSelected()) { 1113 if (progress == 0) { 1114 mLeftHandle.setEnabled(false); 1115 mRightHandle.setEnabled(false); 1116 } else if (progress == 100) { 1117 mLeftHandle.setEnabled(true); 1118 mRightHandle.setEnabled(true); 1119 } 1120 } 1121 } 1122 } 1123 1124 /** 1125 * A transition is being encoded. 1126 * 1127 * @param transitionId The transition id 1128 * @param action The action 1129 * @param progress The progress 1130 */ 1131 public void onGeneratePreviewTransitionProgress(String transitionId, int action, 1132 int progress) { 1133 // Display the progress while generating the transition 1134 final TransitionView view = (TransitionView) getTransitionView(transitionId); 1135 if (view != null) { 1136 view.setGeneratingTransitionProgress(progress); 1137 1138 if (view.isSelected()) { 1139 if (progress == 0) { 1140 mLeftHandle.setEnabled(false); 1141 mRightHandle.setEnabled(false); 1142 } else if (progress == 100) { 1143 mLeftHandle.setEnabled(true); 1144 mRightHandle.setEnabled(true); 1145 } 1146 } 1147 } 1148 } 1149 1150 /** 1151 * Creates a new effect on the specified media item. 1152 * 1153 * @param effectType The effect type 1154 * @param mediaItemId Add the effect for this media item id 1155 * @param startRect The start rectangle 1156 * @param endRect The end rectangle 1157 */ 1158 public void addEffect(int effectType, String mediaItemId, Rect startRect, Rect endRect) { 1159 final MovieMediaItem mediaItem = mProject.getMediaItem(mediaItemId); 1160 if (mediaItem == null) { 1161 Log.e(TAG, "addEffect media item not found: " + mediaItemId); 1162 return; 1163 } 1164 1165 final String id = ApiService.generateId(); 1166 switch (effectType) { 1167 case EffectType.EFFECT_KEN_BURNS: { 1168 ApiService.addEffectKenBurns(getContext(), mProject.getPath(), mediaItemId, 1169 id, 0, mediaItem.getDuration(), startRect, endRect); 1170 break; 1171 } 1172 1173 case EffectType.EFFECT_COLOR_GRADIENT: { 1174 ApiService.addEffectColor(getContext(), mProject.getPath(), mediaItemId, id, 0, 1175 mediaItem.getDuration(), EffectColor.TYPE_GRADIENT, 1176 EffectColor.GRAY); 1177 break; 1178 } 1179 1180 case EffectType.EFFECT_COLOR_SEPIA: { 1181 ApiService.addEffectColor(getContext(), mProject.getPath(), mediaItemId, id, 0, 1182 mediaItem.getDuration(), EffectColor.TYPE_SEPIA, 0); 1183 break; 1184 } 1185 1186 case EffectType.EFFECT_COLOR_NEGATIVE: { 1187 ApiService.addEffectColor(getContext(), mProject.getPath(), mediaItemId, id, 0, 1188 mediaItem.getDuration(), EffectColor.TYPE_NEGATIVE, 0); 1189 break; 1190 } 1191 1192 default: { 1193 break; 1194 } 1195 } 1196 1197 if (mMediaItemActionMode != null) { 1198 mMediaItemActionMode.invalidate(); 1199 } 1200 } 1201 1202 /** 1203 * Set the media item thumbnail. 1204 * 1205 * @param mediaItemId The media item id 1206 * @param bitmap The bitmap 1207 * @param index The index of the bitmap 1208 * @param token The token given in the original request 1209 * 1210 * @return true if the bitmap is used 1211 */ 1212 public boolean setMediaItemThumbnail( 1213 String mediaItemId, Bitmap bitmap, int index, int token) { 1214 final int childrenCount = getChildCount(); 1215 for (int i = 0; i < childrenCount; i++) { 1216 final Object tag = getChildAt(i).getTag(); 1217 if (tag != null && tag instanceof MovieMediaItem) { 1218 final MovieMediaItem mi = (MovieMediaItem)tag; 1219 if (mediaItemId.equals(mi.getId())) { 1220 return ((MediaItemView)getChildAt(i)).setBitmap( 1221 bitmap, index, token); 1222 } 1223 } 1224 } 1225 1226 return false; 1227 } 1228 1229 /** 1230 * Sets the transition thumbnails. 1231 * 1232 * @param transitionId The transition id 1233 * @param bitmaps The bitmaps array 1234 * 1235 * @return true if the bitmaps were used 1236 */ 1237 public boolean setTransitionThumbnails(String transitionId, Bitmap[] bitmaps) { 1238 final int childrenCount = getChildCount(); 1239 for (int i = 0; i < childrenCount; i++) { 1240 final Object tag = getChildAt(i).getTag(); 1241 if (tag != null && tag instanceof MovieTransition) { 1242 final MovieTransition transition = (MovieTransition)tag; 1243 if (transitionId.equals(transition.getId())) { 1244 return ((TransitionView)getChildAt(i)).setBitmaps(bitmaps); 1245 } 1246 } 1247 } 1248 1249 return false; 1250 } 1251 1252 @Override 1253 protected void onLayout(boolean changed, int l, int t, int r, int b) { 1254 // Compute the total duration of the project. 1255 final long totalDurationMs = mProject.computeDuration(); 1256 1257 // Total available width for putting media items and transitions. 1258 // We subtract 2 half screen widths from the width because we put 1259 // 2 empty view at the beginning and end of the timeline, each with 1260 // half screen width. We then layout each child view into the 1261 // available width. 1262 final int viewWidth = getWidth() - (2 * mHalfParentWidth); 1263 1264 // If we are in trimming mode, the left view width might be different 1265 // due to trimming; otherwise it equals half of screen width. 1266 final int leftViewWidth = (mSelectedView != null) ? 1267 (Integer) mScrollView.getTag(R.id.left_view_width) : mHalfParentWidth; 1268 1269 // Top and bottom position are fixed for media item views. For transition views, 1270 // there is additional inset which makes them smaller. See below. 1271 final int top = getPaddingTop(); 1272 final int bottom = b - t; 1273 1274 long startMs = 0; 1275 int left = 0; 1276 1277 final int childrenCount = getChildCount(); 1278 for (int i = 0; i < childrenCount; i++) { 1279 final View view = getChildAt(i); 1280 final Object tag = view.getTag(); 1281 if (tag != null) { 1282 final long durationMs = computeViewDuration(view); 1283 1284 final int right = (int)((float)((startMs + durationMs) * viewWidth) / 1285 (float)totalDurationMs) + leftViewWidth; 1286 1287 if (tag instanceof MovieMediaItem) { 1288 if (left != view.getLeft() || right != view.getRight()) { 1289 final int oldLeft = view.getLeft(); 1290 final int oldRight = view.getRight(); 1291 view.layout(left, top, right, bottom); 1292 ((MediaItemView) view).onLayoutPerformed(oldLeft, oldRight); 1293 } else { 1294 view.layout(left, top, right, bottom); 1295 } 1296 } else { // Transition view. 1297 // Note that we set additional inset so it looks smaller 1298 // than media item views on the timeline. 1299 view.layout(left, 1300 top + mTransitionVerticalInset, 1301 right, 1302 bottom - mTransitionVerticalInset); 1303 } 1304 1305 startMs += durationMs; 1306 left = right; 1307 } else if (view == mLeftHandle && mSelectedView != null) { 1308 // We are in trimming mode, the left handle must be shown. 1309 view.layout(mSelectedView.getLeft() - mHandleWidth, 1310 top + mSelectedView.getPaddingTop(), 1311 mSelectedView.getLeft(), 1312 bottom - mSelectedView.getPaddingBottom()); 1313 } else if (view == mRightHandle && mSelectedView != null) { 1314 // We are in trimming mode, the right handle must be shown. 1315 view.layout(mSelectedView.getRight(), 1316 top + mSelectedView.getPaddingTop(), 1317 mSelectedView.getRight() + mHandleWidth, 1318 bottom - mSelectedView.getPaddingBottom()); 1319 } else if (i == 0) { // Begin view 1320 view.layout(0, top, leftViewWidth, bottom); 1321 left += leftViewWidth; 1322 } else { // End view 1323 view.layout(left, top, getWidth(), bottom); 1324 } 1325 } 1326 mMoveLayoutPending = false; 1327 } 1328 1329 /** 1330 * Computes the duration of the specified view. 1331 * 1332 * @param view The specified view 1333 * 1334 * @return The duration in milliseconds, 0 if the specified view is not a media item view 1335 * or a transition view 1336 */ 1337 private long computeViewDuration(View view) { 1338 long durationMs; 1339 final Object tag = view.getTag(); 1340 if (tag != null) { 1341 if (tag instanceof MovieMediaItem) { 1342 final MovieMediaItem mediaItem = (MovieMediaItem) view.getTag(); 1343 durationMs = mediaItem.getAppTimelineDuration(); 1344 if (mediaItem.getBeginTransition() != null) { 1345 durationMs -= mediaItem.getBeginTransition().getAppDuration(); 1346 } 1347 1348 if (mediaItem.getEndTransition() != null) { 1349 durationMs -= mediaItem.getEndTransition().getAppDuration(); 1350 } 1351 } else { // Transition 1352 final MovieTransition transition = (MovieTransition) tag; 1353 durationMs = transition.getAppDuration(); 1354 } 1355 } else { 1356 durationMs = 0; 1357 } 1358 1359 return durationMs; 1360 } 1361 1362 /** 1363 * Creates a new dialog. 1364 * 1365 * @param id The dialog id 1366 * @param bundle The dialog bundle 1367 * 1368 * @return The dialog 1369 */ 1370 public Dialog onCreateDialog(int id, final Bundle bundle) { 1371 // If the project is not yet loaded do nothing. 1372 if (mProject == null) { 1373 return null; 1374 } 1375 1376 switch (id) { 1377 case VideoEditorActivity.DIALOG_REMOVE_MEDIA_ITEM_ID: { 1378 final MovieMediaItem mediaItem = mProject.getMediaItem( 1379 bundle.getString(PARAM_DIALOG_MEDIA_ITEM_ID)); 1380 if (mediaItem == null) { 1381 return null; 1382 } 1383 1384 final Activity activity = (Activity) getContext(); 1385 return AlertDialogs.createAlert(activity, 1386 FileUtils.getSimpleName(mediaItem.getFilename()), 1387 0, mediaItem.isVideoClip() ? 1388 activity.getString(R.string.editor_remove_video_question) : 1389 activity.getString(R.string.editor_remove_image_question), 1390 activity.getString(R.string.yes), 1391 new DialogInterface.OnClickListener() { 1392 @Override 1393 public void onClick(DialogInterface dialog, int which) { 1394 if (mMediaItemActionMode != null) { 1395 mMediaItemActionMode.finish(); 1396 mMediaItemActionMode = null; 1397 } 1398 unselectAllTimelineViews(); 1399 1400 activity.removeDialog(VideoEditorActivity.DIALOG_REMOVE_MEDIA_ITEM_ID); 1401 1402 ApiService.removeMediaItem(activity, mProject.getPath(), mediaItem.getId(), 1403 mProject.getTheme()); 1404 } 1405 }, activity.getString(R.string.no), new DialogInterface.OnClickListener() { 1406 @Override 1407 public void onClick(DialogInterface dialog, int which) { 1408 activity.removeDialog(VideoEditorActivity.DIALOG_REMOVE_MEDIA_ITEM_ID); 1409 } 1410 }, new DialogInterface.OnCancelListener() { 1411 @Override 1412 public void onCancel(DialogInterface dialog) { 1413 activity.removeDialog(VideoEditorActivity.DIALOG_REMOVE_MEDIA_ITEM_ID); 1414 } 1415 }, true); 1416 } 1417 1418 case VideoEditorActivity.DIALOG_CHANGE_RENDERING_MODE_ID: { 1419 final MovieMediaItem mediaItem = mProject.getMediaItem( 1420 bundle.getString(PARAM_DIALOG_MEDIA_ITEM_ID)); 1421 if (mediaItem == null) { 1422 return null; 1423 } 1424 1425 final Activity activity = (Activity)getContext(); 1426 final AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); 1427 builder.setTitle(activity.getString(R.string.editor_change_rendering_mode)); 1428 final CharSequence[] renderingModeStrings = new CharSequence[3]; 1429 renderingModeStrings[0] = getContext().getString(R.string.rendering_mode_black_borders); 1430 renderingModeStrings[1] = getContext().getString(R.string.rendering_mode_stretch); 1431 renderingModeStrings[2] = getContext().getString(R.string.rendering_mode_crop); 1432 1433 final int currentRenderingMode = bundle.getInt(PARAM_DIALOG_CURRENT_RENDERING_MODE); 1434 final int currentRenderingModeIndex; 1435 switch (currentRenderingMode) { 1436 case MediaItem.RENDERING_MODE_CROPPING: { 1437 currentRenderingModeIndex = 2; 1438 break; 1439 } 1440 1441 case MediaItem.RENDERING_MODE_STRETCH: { 1442 currentRenderingModeIndex = 1; 1443 break; 1444 } 1445 1446 case MediaItem.RENDERING_MODE_BLACK_BORDER: 1447 default: { 1448 currentRenderingModeIndex = 0; 1449 break; 1450 } 1451 } 1452 1453 builder.setSingleChoiceItems(renderingModeStrings, currentRenderingModeIndex, 1454 new DialogInterface.OnClickListener() { 1455 @Override 1456 public void onClick(DialogInterface dialog, int which) { 1457 switch (which) { 1458 case 0: { 1459 mediaItem.setAppRenderingMode(MediaItem.RENDERING_MODE_BLACK_BORDER); 1460 ApiService.setMediaItemRenderingMode(getContext(), 1461 mProject.getPath(), mediaItem.getId(), 1462 MediaItem.RENDERING_MODE_BLACK_BORDER); 1463 break; 1464 } 1465 1466 case 1: { 1467 mediaItem.setAppRenderingMode(MediaItem.RENDERING_MODE_STRETCH); 1468 ApiService.setMediaItemRenderingMode(getContext(), 1469 mProject.getPath(), 1470 mediaItem.getId(), MediaItem.RENDERING_MODE_STRETCH); 1471 break; 1472 } 1473 1474 case 2: { 1475 mediaItem.setAppRenderingMode(MediaItem.RENDERING_MODE_CROPPING); 1476 ApiService.setMediaItemRenderingMode(getContext(), 1477 mProject.getPath(), 1478 mediaItem.getId(), MediaItem.RENDERING_MODE_CROPPING); 1479 break; 1480 } 1481 1482 default: { 1483 break; 1484 } 1485 } 1486 activity.removeDialog(VideoEditorActivity.DIALOG_CHANGE_RENDERING_MODE_ID); 1487 } 1488 }); 1489 builder.setCancelable(true); 1490 builder.setOnCancelListener(new DialogInterface.OnCancelListener() { 1491 @Override 1492 public void onCancel(DialogInterface dialog) { 1493 activity.removeDialog(VideoEditorActivity.DIALOG_CHANGE_RENDERING_MODE_ID); 1494 } 1495 }); 1496 return builder.create(); 1497 } 1498 1499 case VideoEditorActivity.DIALOG_REMOVE_TRANSITION_ID: { 1500 final MovieTransition transition = mProject.getTransition( 1501 bundle.getString(PARAM_DIALOG_TRANSITION_ID)); 1502 if (transition == null) { 1503 return null; 1504 } 1505 1506 final Activity activity = (Activity) getContext(); 1507 return AlertDialogs.createAlert(activity, 1508 activity.getString(R.string.remove), 1509 0, activity.getString(R.string.editor_remove_transition_question), 1510 activity.getString(R.string.yes), 1511 new DialogInterface.OnClickListener() { 1512 @Override 1513 public void onClick(DialogInterface dialog, int which) { 1514 if (mTransitionActionMode != null) { 1515 mTransitionActionMode.finish(); 1516 mTransitionActionMode = null; 1517 } 1518 unselectAllTimelineViews(); 1519 activity.removeDialog(VideoEditorActivity.DIALOG_REMOVE_TRANSITION_ID); 1520 1521 ApiService.removeTransition(activity, mProject.getPath(), 1522 transition.getId()); 1523 } 1524 }, activity.getString(R.string.no), new DialogInterface.OnClickListener() { 1525 @Override 1526 public void onClick(DialogInterface dialog, int which) { 1527 activity.removeDialog(VideoEditorActivity.DIALOG_REMOVE_TRANSITION_ID); 1528 } 1529 }, new DialogInterface.OnCancelListener() { 1530 @Override 1531 public void onCancel(DialogInterface dialog) { 1532 activity.removeDialog(VideoEditorActivity.DIALOG_REMOVE_TRANSITION_ID); 1533 } 1534 }, true); 1535 } 1536 1537 case VideoEditorActivity.DIALOG_REMOVE_EFFECT_ID: { 1538 final MovieMediaItem mediaItem = mProject.getMediaItem( 1539 bundle.getString(PARAM_DIALOG_MEDIA_ITEM_ID)); 1540 if (mediaItem == null) { 1541 return null; 1542 } 1543 1544 final Activity activity = (Activity) getContext(); 1545 return AlertDialogs.createAlert(activity, 1546 FileUtils.getSimpleName(mediaItem.getFilename()), 1547 0, activity.getString(R.string.editor_remove_effect_question), 1548 activity.getString(R.string.yes), 1549 new DialogInterface.OnClickListener() { 1550 @Override 1551 public void onClick(DialogInterface dialog, int which) { 1552 activity.removeDialog(VideoEditorActivity.DIALOG_REMOVE_EFFECT_ID); 1553 1554 ApiService.removeEffect(activity, mProject.getPath(), 1555 mediaItem.getId(), mediaItem.getEffect().getId()); 1556 1557 if (mMediaItemActionMode != null) { 1558 mMediaItemActionMode.invalidate(); 1559 } 1560 } 1561 }, activity.getString(R.string.no), new DialogInterface.OnClickListener() { 1562 @Override 1563 public void onClick(DialogInterface dialog, int which) { 1564 activity.removeDialog(VideoEditorActivity.DIALOG_REMOVE_EFFECT_ID); 1565 } 1566 }, new DialogInterface.OnCancelListener() { 1567 @Override 1568 public void onCancel(DialogInterface dialog) { 1569 activity.removeDialog(VideoEditorActivity.DIALOG_REMOVE_EFFECT_ID); 1570 } 1571 }, true); 1572 } 1573 1574 default: { 1575 return null; 1576 } 1577 } 1578 } 1579 1580 @Override 1581 public boolean onDragEvent(DragEvent event) { 1582 boolean result = false; 1583 switch (event.getAction()) { 1584 case DragEvent.ACTION_DRAG_STARTED: { 1585 // Claim to accept any dragged content 1586 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1587 Log.v(TAG, "ACTION_DRAG_STARTED: " + event); 1588 } 1589 1590 mDragMediaItemId = (String)event.getLocalState(); 1591 1592 // Hide the handles while dragging 1593 mLeftHandle.setVisibility(View.GONE); 1594 mRightHandle.setVisibility(View.GONE); 1595 1596 mDropAfterMediaItem = null; 1597 mDropIndex = -1; 1598 1599 mFirstEntered = true; 1600 // This view accepts drag 1601 result = true; 1602 break; 1603 } 1604 1605 case DragEvent.ACTION_DRAG_ENTERED: { 1606 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1607 Log.v(TAG, "ACTION_DRAG_ENTERED: " + event); 1608 } 1609 1610 if (!mFirstEntered && mDropIndex >= 0) { 1611 mScrollView.setTag(R.id.playhead_type, 1612 TimelineHorizontalScrollView.PLAYHEAD_MOVE_OK); 1613 } else { 1614 mScrollView.setTag(R.id.playhead_type, 1615 TimelineHorizontalScrollView.PLAYHEAD_MOVE_NOT_OK); 1616 } 1617 mScrollView.invalidate(); 1618 1619 mFirstEntered = false; 1620 break; 1621 } 1622 1623 case DragEvent.ACTION_DRAG_EXITED: { 1624 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1625 Log.v(TAG, "ACTION_DRAG_EXITED: " + event); 1626 } 1627 1628 // Redraw the "normal playhead" 1629 mScrollView.setTag(R.id.playhead_type, TimelineHorizontalScrollView.PLAYHEAD_NORMAL); 1630 mScrollView.invalidate(); 1631 break; 1632 } 1633 1634 case DragEvent.ACTION_DRAG_ENDED: { 1635 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1636 Log.v(TAG, "ACTION_DRAG_ENDED: " + event); 1637 } 1638 1639 mDragMediaItemId = null; 1640 mDropIndex = -1; 1641 1642 // Hide the handles while dragging 1643 mLeftHandle.setVisibility(View.VISIBLE); 1644 mRightHandle.setVisibility(View.VISIBLE); 1645 1646 // Redraw the "normal playhead" 1647 mScrollView.setTag(R.id.playhead_type, TimelineHorizontalScrollView.PLAYHEAD_NORMAL); 1648 mScrollView.invalidate(); 1649 1650 requestLayout(); 1651 break; 1652 } 1653 1654 case DragEvent.ACTION_DRAG_LOCATION: { 1655 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1656 Log.v(TAG, "ACTION_DRAG_LOCATION: " + event); 1657 } 1658 1659 moveToPosition(event.getX()); 1660 // We returned true to DRAG_STARTED, so return true here 1661 result = true; 1662 break; 1663 } 1664 1665 case DragEvent.ACTION_DROP: { 1666 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1667 Log.v(TAG, "ACTION_DROP: " + event); 1668 } 1669 1670 if (mDropIndex >= 0) { 1671 final String afterMediaItemId = 1672 mDropAfterMediaItem != null ? mDropAfterMediaItem.getId() : null; 1673 if (Log.isLoggable(TAG, Log.DEBUG)) { 1674 Log.d(TAG, "ACTION_DROP: Index: " + mDropIndex + " | " + afterMediaItemId); 1675 } 1676 ApiService.moveMediaItem(getContext(), mProject.getPath(), mDragMediaItemId, 1677 afterMediaItemId, null); 1678 } 1679 result = true; 1680 break; 1681 } 1682 1683 1684 default: { 1685 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1686 Log.v(TAG, "Other drag event: " + event); 1687 } 1688 result = true; 1689 break; 1690 } 1691 } 1692 1693 return result; 1694 } 1695 1696 /** 1697 * Move the playhead during a move operation 1698 * 1699 * @param eventX The event horizontal position 1700 */ 1701 private void moveToPosition(float eventX) { 1702 final int x = (int)eventX - mScrollView.getScrollX(); 1703 final long now = System.currentTimeMillis(); 1704 if (now - mPrevDragScrollTime > 300) { 1705 if (x < mPrevDragPosition - 42) { // Backwards 1706 final long positionMs = getLeftDropPosition(); 1707 if (mDropIndex >= 0) { 1708 // Redraw the "move ok playhead" 1709 mScrollView.setTag(R.id.playhead_type, 1710 TimelineHorizontalScrollView.PLAYHEAD_MOVE_OK); 1711 } else { 1712 // Redraw the "move not ok playhead" 1713 mScrollView.setTag(R.id.playhead_type, 1714 TimelineHorizontalScrollView.PLAYHEAD_MOVE_NOT_OK); 1715 } 1716 1717 mListener.onRequestMovePlayhead(positionMs, true); 1718 mScrollView.invalidate(); 1719 1720 mPrevDragPosition = x; 1721 mPrevDragScrollTime = now; 1722 } else if (x > mPrevDragPosition + 42) { // Forward 1723 final long positionMs = getRightDropPosition(); 1724 if (mDropIndex >= 0) { 1725 // Redraw the "move ok playhead" 1726 mScrollView.setTag(R.id.playhead_type, 1727 TimelineHorizontalScrollView.PLAYHEAD_MOVE_OK); 1728 } else { 1729 // Redraw the "move not ok playhead" 1730 mScrollView.setTag(R.id.playhead_type, 1731 TimelineHorizontalScrollView.PLAYHEAD_MOVE_NOT_OK); 1732 } 1733 1734 mListener.onRequestMovePlayhead(positionMs, true); 1735 mScrollView.invalidate(); 1736 1737 mPrevDragPosition = x; 1738 mPrevDragScrollTime = now; 1739 } 1740 } else { 1741 mPrevDragPosition = x; 1742 } 1743 } 1744 1745 // Returns the begin time of a media item (exclude transition). 1746 private long getBeginTime(MovieMediaItem item) { 1747 final List<MovieMediaItem> mediaItems = mProject.getMediaItems(); 1748 long beginMs = 0; 1749 final int mediaItemsCount = mediaItems.size(); 1750 for (int i = 0; i < mediaItemsCount; i++) { 1751 final MovieMediaItem mediaItem = mediaItems.get(i); 1752 final MovieTransition beginTransition = mediaItem.getBeginTransition(); 1753 final MovieTransition endTransition = mediaItem.getEndTransition(); 1754 1755 if (item.getId().equals(mediaItem.getId())) { 1756 if (beginTransition != null) { 1757 beginMs += beginTransition.getAppDuration(); 1758 } 1759 return beginMs; 1760 } 1761 1762 beginMs += mediaItem.getAppTimelineDuration(); 1763 1764 if (endTransition != null) { 1765 beginMs -= endTransition.getAppDuration(); 1766 } 1767 } 1768 1769 return 0; 1770 } 1771 1772 // Returns the end time of a media item (exclude transition) 1773 private long getEndTime(MovieMediaItem item) { 1774 final List<MovieMediaItem> mediaItems = mProject.getMediaItems(); 1775 long endMs = 0; 1776 final int mediaItemsCount = mediaItems.size(); 1777 for (int i = 0; i < mediaItemsCount; i++) { 1778 final MovieMediaItem mediaItem = mediaItems.get(i); 1779 final MovieTransition beginTransition = mediaItem.getBeginTransition(); 1780 final MovieTransition endTransition = mediaItem.getEndTransition(); 1781 1782 endMs += mediaItem.getAppTimelineDuration(); 1783 1784 if (endTransition != null) { 1785 endMs -= endTransition.getAppDuration(); 1786 } 1787 1788 if (item.getId().equals(mediaItem.getId())) { 1789 return endMs; 1790 } 1791 1792 } 1793 1794 return 0; 1795 } 1796 1797 /** 1798 * @return The valid time location of the drop (-1 if none) 1799 */ 1800 private long getLeftDropPosition() { 1801 final List<MovieMediaItem> mediaItems = mProject.getMediaItems(); 1802 long beginMs = 0; 1803 long endMs = 0; 1804 long timeMs = mProject.getPlayheadPos(); 1805 1806 final int mediaItemsCount = mediaItems.size(); 1807 for (int i = 0; i < mediaItemsCount; i++) { 1808 final MovieMediaItem mediaItem = mediaItems.get(i); 1809 1810 endMs = beginMs + mediaItem.getAppTimelineDuration(); 1811 1812 if (mediaItem.getEndTransition() != null) { 1813 if (i < mediaItemsCount - 1) { 1814 endMs -= mediaItem.getEndTransition().getAppDuration(); 1815 } 1816 } 1817 1818 if (timeMs > beginMs && timeMs <= endMs) { 1819 if (mediaItem.getBeginTransition() != null) { 1820 beginMs += mediaItem.getBeginTransition().getAppDuration(); 1821 } 1822 1823 if (!mDragMediaItemId.equals(mediaItem.getId())) { 1824 if (i > 0) { 1825 // Check if the previous item is the drag item 1826 final MovieMediaItem prevMediaItem = mediaItems.get(i - 1); 1827 if (!mDragMediaItemId.equals(prevMediaItem.getId())) { 1828 mDropAfterMediaItem = prevMediaItem; 1829 mDropIndex = i; 1830 return beginMs; 1831 } else { 1832 mDropAfterMediaItem = null; 1833 mDropIndex = -1; 1834 return beginMs; 1835 } 1836 } else { 1837 mDropAfterMediaItem = null; 1838 mDropIndex = 0; 1839 return 0; 1840 } 1841 } else { 1842 mDropAfterMediaItem = null; 1843 mDropIndex = -1; 1844 return beginMs; 1845 } 1846 } 1847 1848 beginMs = endMs; 1849 } 1850 1851 return timeMs; 1852 } 1853 1854 /** 1855 * @return The valid time location of the drop (-1 if none) 1856 */ 1857 private long getRightDropPosition() { 1858 final List<MovieMediaItem> mediaItems = mProject.getMediaItems(); 1859 long beginMs = 0; 1860 long endMs = 0; 1861 long timeMs = mProject.getPlayheadPos(); 1862 1863 final int mediaItemsCount = mediaItems.size(); 1864 for (int i = 0; i < mediaItemsCount; i++) { 1865 final MovieMediaItem mediaItem = mediaItems.get(i); 1866 1867 endMs = beginMs + mediaItem.getAppTimelineDuration(); 1868 1869 if (mediaItem.getEndTransition() != null) { 1870 if (i < mediaItemsCount - 1) { 1871 endMs -= mediaItem.getEndTransition().getAppDuration(); 1872 } 1873 } 1874 1875 if (timeMs >= beginMs && timeMs < endMs) { 1876 if (!mDragMediaItemId.equals(mediaItem.getId())) { 1877 if (i < mediaItemsCount - 1) { 1878 // Check if the next item is the drag item 1879 final MovieMediaItem nextMediaItem = mediaItems.get(i + 1); 1880 if (!mDragMediaItemId.equals(nextMediaItem.getId())) { 1881 mDropAfterMediaItem = mediaItem; 1882 mDropIndex = i; 1883 return endMs; 1884 } else { 1885 mDropAfterMediaItem = null; 1886 mDropIndex = -1; 1887 return endMs; 1888 } 1889 } else { 1890 mDropAfterMediaItem = mediaItem; 1891 mDropIndex = i; 1892 return endMs; 1893 } 1894 } else { 1895 mDropAfterMediaItem = null; 1896 mDropIndex = -1; 1897 return endMs; 1898 } 1899 } 1900 1901 beginMs = endMs; 1902 } 1903 1904 return timeMs; 1905 } 1906 1907 1908 /** 1909 * Adds/edits title overlay of the specified media item. 1910 */ 1911 private void editOverlay(MovieMediaItem mediaItem) { 1912 final Intent intent = new Intent(getContext(), OverlayTitleEditor.class); 1913 intent.putExtra(OverlayTitleEditor.PARAM_MEDIA_ITEM_ID, mediaItem.getId()); 1914 1915 // Determine if user wants to edit an existing title overlay or add a new one. 1916 // Add overlay id and attributes bundle to the extra if the overlay already exists. 1917 final MovieOverlay overlay = mediaItem.getOverlay(); 1918 if (overlay != null) { 1919 final String overlayId = mediaItem.getOverlay().getId(); 1920 intent.putExtra(OverlayTitleEditor.PARAM_OVERLAY_ID, overlayId); 1921 final Bundle attributes = MovieOverlay.buildUserAttributes( 1922 overlay.getType(), overlay.getTitle(), overlay.getSubtitle()); 1923 intent.putExtra(OverlayTitleEditor.PARAM_OVERLAY_ATTRIBUTES, 1924 attributes); 1925 } 1926 ((Activity) getContext()).startActivityForResult(intent, 1927 VideoEditorActivity.REQUEST_CODE_PICK_OVERLAY); 1928 } 1929 1930 /** 1931 * Removes the overlay of the specified media item. 1932 */ 1933 private void removeOverlay(MovieMediaItem mediaItem) { 1934 final Bundle bundle = new Bundle(); 1935 bundle.putString(PARAM_DIALOG_MEDIA_ITEM_ID, mediaItem.getId()); 1936 ((Activity) getContext()).showDialog( 1937 VideoEditorActivity.DIALOG_REMOVE_OVERLAY_ID, bundle); 1938 } 1939 1940 /** 1941 * Picks a transition. 1942 * 1943 * @param afterMediaItem After the media item 1944 * 1945 * @return true if the transition can be inserted 1946 */ 1947 private boolean pickTransition(MovieMediaItem afterMediaItem) { 1948 // Check if the transition would be too short 1949 final long transitionDurationMs = getTransitionDuration(afterMediaItem); 1950 if (transitionDurationMs < MINIMUM_TRANSITION_DURATION) { 1951 Toast.makeText(getContext(), 1952 getContext().getString(R.string.editor_transition_too_short), 1953 Toast.LENGTH_SHORT).show(); 1954 return false; 1955 } 1956 1957 final String afterMediaId = afterMediaItem != null ? afterMediaItem.getId() : null; 1958 final Intent intent = new Intent(getContext(), TransitionsActivity.class); 1959 intent.putExtra(TransitionsActivity.PARAM_AFTER_MEDIA_ITEM_ID, afterMediaId); 1960 intent.putExtra(TransitionsActivity.PARAM_MINIMUM_DURATION, MINIMUM_TRANSITION_DURATION); 1961 intent.putExtra(TransitionsActivity.PARAM_DEFAULT_DURATION, transitionDurationMs); 1962 intent.putExtra(TransitionsActivity.PARAM_MAXIMUM_DURATION, 1963 getMaxTransitionDuration(afterMediaItem)); 1964 ((Activity) getContext()).startActivityForResult(intent, 1965 VideoEditorActivity.REQUEST_CODE_PICK_TRANSITION); 1966 return true; 1967 } 1968 1969 /** 1970 * Edits a transition. 1971 * 1972 * @param transition The transition 1973 */ 1974 private void editTransition(MovieTransition transition) { 1975 final MovieMediaItem afterMediaItem = mProject.getPreviousMediaItem(transition); 1976 final String afterMediaItemId = afterMediaItem != null ? afterMediaItem.getId() : null; 1977 1978 final Intent intent = new Intent(getContext(), TransitionsActivity.class); 1979 intent.putExtra(TransitionsActivity.PARAM_AFTER_MEDIA_ITEM_ID, afterMediaItemId); 1980 intent.putExtra(TransitionsActivity.PARAM_TRANSITION_ID, transition.getId()); 1981 intent.putExtra(TransitionsActivity.PARAM_TRANSITION_TYPE, transition.getType()); 1982 intent.putExtra(TransitionsActivity.PARAM_MINIMUM_DURATION, MINIMUM_TRANSITION_DURATION); 1983 intent.putExtra(TransitionsActivity.PARAM_DEFAULT_DURATION, transition.getAppDuration()); 1984 intent.putExtra(TransitionsActivity.PARAM_MAXIMUM_DURATION, 1985 getMaxTransitionDuration(afterMediaItem)); 1986 ((Activity)getContext()).startActivityForResult(intent, 1987 VideoEditorActivity.REQUEST_CODE_EDIT_TRANSITION); 1988 } 1989 1990 /** 1991 * Finds the media item view with the specified id. 1992 * 1993 * @param mediaItemId The media item id 1994 * @return The found media item view; null if not found 1995 */ 1996 private View getMediaItemView(String mediaItemId) { 1997 final int childrenCount = getChildCount(); 1998 for (int i = 0; i < childrenCount; i++) { 1999 final View childView = getChildAt(i); 2000 final Object tag = childView.getTag(); 2001 if (tag != null && tag instanceof MovieMediaItem) { 2002 final MovieMediaItem mediaItem = (MovieMediaItem)tag; 2003 if (mediaItemId.equals(mediaItem.getId())) { 2004 return childView; 2005 } 2006 } 2007 } 2008 2009 return null; 2010 } 2011 2012 /** 2013 * Finds the media item view index with the specified id. 2014 * 2015 * @param mediaItemId The media item id 2016 * @return The media item view index; -1 if not found 2017 */ 2018 private int getMediaItemViewIndex(String mediaItemId) { 2019 final int childrenCount = getChildCount(); 2020 for (int i = 0; i < childrenCount; i++) { 2021 final View childView = getChildAt(i); 2022 final Object tag = childView.getTag(); 2023 if (tag != null && tag instanceof MovieMediaItem) { 2024 final MovieMediaItem mediaItem = (MovieMediaItem)tag; 2025 if (mediaItemId.equals(mediaItem.getId())) { 2026 return i; 2027 } 2028 } 2029 } 2030 2031 return -1; 2032 } 2033 2034 /** 2035 * Finds the transition view with the specified id. 2036 * 2037 * @param transitionId The transition id 2038 * 2039 * @return The found transition view; null if not found 2040 */ 2041 private View getTransitionView(String transitionId) { 2042 final int childrenCount = getChildCount(); 2043 for (int i = 0; i < childrenCount; i++) { 2044 final View childView = getChildAt(i); 2045 final Object tag = childView.getTag(); 2046 if (tag != null && tag instanceof MovieTransition) { 2047 final MovieTransition transition = (MovieTransition)tag; 2048 if (transitionId.equals(transition.getId())) { 2049 return childView; 2050 } 2051 } 2052 } 2053 2054 return null; 2055 } 2056 2057 /** 2058 * Removes a transition. 2059 * 2060 * @param transitionId The id of the transition to be removed 2061 */ 2062 public void removeTransitionView(String transitionId) { 2063 final int childrenCount = getChildCount(); 2064 for (int i = 0; i < childrenCount; i++) { 2065 final Object tag = getChildAt(i).getTag(); 2066 if (tag != null && tag instanceof MovieTransition) { 2067 final MovieTransition transition = (MovieTransition)tag; 2068 if (transitionId.equals(transition.getId())) { 2069 // Remove the view 2070 removeViewAt(i); 2071 2072 // Adjust the size of all the views 2073 requestLayout(); 2074 2075 // If this transition was removed by the user invalidate the menu item 2076 if (mMediaItemActionMode != null) { 2077 mMediaItemActionMode.invalidate(); 2078 } 2079 return; 2080 } 2081 } 2082 } 2083 } 2084 2085 /** 2086 * Removes all media item and transition views but leave the beginning, end views, and handles. 2087 */ 2088 private void removeAllMediaItemAndTransitionViews() { 2089 int index = 0; 2090 while (index < getChildCount()) { 2091 final Object tag = getChildAt(index).getTag(); 2092 // Media item view or transition view is associated with a media item or transition 2093 // attached as a tag. We can thus check the nullity of the tag to determine if it is 2094 // media item view or transition view. 2095 if (tag != null) { 2096 removeViewAt(index); 2097 } else { 2098 index++; 2099 } 2100 } 2101 requestLayout(); 2102 2103 // We cannot add clips by tapping the beginning view. 2104 mLeftAddClipButton.setVisibility(View.GONE); 2105 } 2106 2107 /** 2108 * Computes the transition duration. 2109 * 2110 * @param afterMediaItem The position of the transition 2111 * 2112 * @return The transition duration 2113 */ 2114 private long getTransitionDuration(MovieMediaItem afterMediaItem) { 2115 if (afterMediaItem == null) { 2116 final MovieMediaItem firstMediaItem = mProject.getFirstMediaItem(); 2117 return Math.min(MAXIMUM_TRANSITION_DURATION / 2, 2118 firstMediaItem.getAppTimelineDuration() / 4); 2119 } else if (mProject.isLastMediaItem(afterMediaItem.getId())) { 2120 return Math.min(MAXIMUM_TRANSITION_DURATION / 2, 2121 afterMediaItem.getAppTimelineDuration() / 4); 2122 } else { 2123 final MovieMediaItem beforeMediaItem = 2124 mProject.getNextMediaItem(afterMediaItem.getId()); 2125 final long minDurationMs = Math.min(afterMediaItem.getAppTimelineDuration(), 2126 beforeMediaItem.getAppTimelineDuration()); 2127 return Math.min(MAXIMUM_TRANSITION_DURATION / 2, minDurationMs / 4); 2128 } 2129 } 2130 2131 /** 2132 * Computes the maximum transition duration. 2133 * 2134 * @param afterMediaItem The position of the transition 2135 * 2136 * @return The transition duration 2137 */ 2138 private long getMaxTransitionDuration(MovieMediaItem afterMediaItem) { 2139 if (afterMediaItem == null) { 2140 final MovieMediaItem firstMediaItem = mProject.getFirstMediaItem(); 2141 return Math.min(MAXIMUM_TRANSITION_DURATION, 2142 firstMediaItem.getAppTimelineDuration() / 4); 2143 } else if (mProject.isLastMediaItem(afterMediaItem.getId())) { 2144 return Math.min(MAXIMUM_TRANSITION_DURATION, 2145 afterMediaItem.getAppTimelineDuration() / 4); 2146 } else { 2147 final MovieMediaItem beforeMediaItem = 2148 mProject.getNextMediaItem(afterMediaItem.getId()); 2149 final long minDurationMs = Math.min(afterMediaItem.getAppTimelineDuration(), 2150 beforeMediaItem.getAppTimelineDuration()); 2151 return Math.min(MAXIMUM_TRANSITION_DURATION, minDurationMs / 4); 2152 } 2153 } 2154 2155 @Override 2156 public void setSelected(boolean selected) { 2157 // We only care about when this layout is unselected, which means all children are 2158 // unselected. Clients should never call setSelected(true) since it is no-op here. 2159 if (selected == false) { 2160 closeActionBars(); 2161 clearAndHideTrimHandles(); 2162 mSelectedView = null; 2163 showAddMediaItemButtons(true); 2164 } 2165 dispatchSetSelected(false); 2166 } 2167 2168 /** 2169 * Returns true if some view item on the timeline is selected. 2170 */ 2171 public boolean hasItemSelected() { 2172 return (mSelectedView != null); 2173 } 2174 2175 /** 2176 * Returns true if some media item is being trimmed by user. 2177 */ 2178 public boolean isTrimming() { 2179 return mIsTrimming; 2180 } 2181 2182 /** 2183 * Closes all contextual action bars. 2184 */ 2185 private void closeActionBars() { 2186 if (mMediaItemActionMode != null) { 2187 mMediaItemActionMode.finish(); 2188 mMediaItemActionMode = null; 2189 } 2190 2191 if (mTransitionActionMode != null) { 2192 mTransitionActionMode.finish(); 2193 mTransitionActionMode = null; 2194 } 2195 } 2196 2197 /** 2198 * Hides left and right trim handles and unregisters their listeners. 2199 */ 2200 private void clearAndHideTrimHandles() { 2201 mLeftHandle.setVisibility(View.GONE); 2202 mLeftHandle.setListener(null); 2203 mRightHandle.setVisibility(View.GONE); 2204 mRightHandle.setListener(null); 2205 } 2206 2207 /** 2208 * Unselects the specified view. No-op if the specified view is already unselected. 2209 */ 2210 private void unSelect(View view) { 2211 // Return early if the specified view is already unselected or null. 2212 if (view == null || !view.isSelected()) { 2213 return; 2214 } 2215 2216 mSelectedView = null; 2217 view.setSelected(false); 2218 // Need to redraw other children as well because they had dimmed themselves. 2219 invalidateAllChildren(); 2220 clearAndHideTrimHandles(); 2221 } 2222 2223 /** 2224 * Selects the specified view and un-selects all others. 2225 * No-op if the specified view is already selected. 2226 * The selected view will stand out and all other views on the 2227 * timeline are dimmed. 2228 */ 2229 private void select(View selectedView) { 2230 // Return early if the view is already selected. 2231 if (selectedView.isSelected()) { 2232 return; 2233 } 2234 2235 unselectAllTimelineViews(); 2236 mSelectedView = selectedView; 2237 mSelectedView.setSelected(true); 2238 showAddMediaItemButtons(false); 2239 2240 final Object tag = mSelectedView.getTag(); 2241 if (tag instanceof MovieMediaItem) { 2242 final MediaItemView mediaItemView = (MediaItemView) mSelectedView; 2243 if (mediaItemView.isGeneratingEffect()) { 2244 mLeftHandle.setEnabled(false); 2245 mRightHandle.setEnabled(false); 2246 } else { 2247 mLeftHandle.setEnabled(true); 2248 mRightHandle.setEnabled(true); 2249 } 2250 2251 final MovieMediaItem mi = (MovieMediaItem) tag; 2252 if (mMediaItemActionMode == null) { 2253 startActionMode(new MediaItemActionModeCallback(mi)); 2254 } 2255 2256 final boolean videoClip = mi.isVideoClip(); 2257 if (videoClip) { 2258 mLeftHandle.setVisibility(View.VISIBLE); 2259 mLeftHandle.bringToFront(); 2260 mLeftHandle.setLimitReached(mi.getAppBoundaryBeginTime() <= 0, 2261 mi.getAppTimelineDuration() <= 2262 MediaItemUtils.getMinimumVideoItemDuration()); 2263 mLeftHandle.setListener(new HandleView.MoveListener() { 2264 private View mTrimmedView; 2265 private MovieMediaItem mMediaItem; 2266 private long mTransitionsDurationMs; 2267 private long mOriginalBeginMs, mOriginalEndMs; 2268 private long mMinimumDurationMs; 2269 private int mOriginalWidth; 2270 private int mMovePosition; 2271 2272 @Override 2273 public void onMoveBegin(HandleView view) { 2274 mMediaItem = (MovieMediaItem)mediaItemView.getTag(); 2275 mTransitionsDurationMs = (mMediaItem.getBeginTransition() != null ? 2276 mMediaItem.getBeginTransition().getAppDuration() : 0) 2277 + (mMediaItem.getEndTransition() != null ? 2278 mMediaItem.getEndTransition().getAppDuration() : 0); 2279 mOriginalBeginMs = mMediaItem.getAppBoundaryBeginTime(); 2280 mOriginalEndMs = mMediaItem.getAppBoundaryEndTime(); 2281 mOriginalWidth = mediaItemView.getWidth(); 2282 mMinimumDurationMs = MediaItemUtils.getMinimumVideoItemDuration(); 2283 setIsTrimming(true); 2284 invalidateAllChildren(); 2285 mTrimmedView = mediaItemView; 2286 2287 mListener.onTrimMediaItemBegin(mMediaItem); 2288 if (videoClip) { // Video clip 2289 mListener.onTrimMediaItem(mMediaItem, 2290 mMediaItem.getAppBoundaryBeginTime()); 2291 } else { 2292 mListener.onTrimMediaItem(mMediaItem, 0); 2293 } 2294 // Move the playhead 2295 mScrollView.setTag(R.id.playhead_offset, view.getRight()); 2296 mScrollView.invalidate(); 2297 } 2298 2299 @Override 2300 public boolean onMove(HandleView view, int left, int delta) { 2301 if (mMoveLayoutPending) { 2302 return false; 2303 } 2304 2305 int position = left + delta; 2306 mMovePosition = position; 2307 // Compute what will become the width of the view 2308 int newWidth = mTrimmedView.getRight() - position; 2309 if (newWidth == mTrimmedView.getWidth()) { 2310 return false; 2311 } 2312 2313 // Compute the new duration 2314 long newDurationMs = mTransitionsDurationMs + 2315 (newWidth * mProject.computeDuration()) / 2316 (getWidth() - (2 * mHalfParentWidth)); 2317 if (Math.abs(mMediaItem.getAppTimelineDuration() - newDurationMs) < 2318 TIME_TOLERANCE) { 2319 return false; 2320 } else if (newDurationMs < Math.max(2 * mTransitionsDurationMs, 2321 mMinimumDurationMs)) { 2322 newDurationMs = Math.max(2 * mTransitionsDurationMs, 2323 mMinimumDurationMs); 2324 newWidth = (int)(((newDurationMs - mTransitionsDurationMs) * 2325 (getWidth() - (2 * mHalfParentWidth)) / 2326 mProject.computeDuration())); 2327 position = mTrimmedView.getRight() - newWidth; 2328 } else if (mMediaItem.getAppBoundaryEndTime() - newDurationMs < 0) { 2329 newDurationMs = mMediaItem.getAppBoundaryEndTime(); 2330 newWidth = (int)(((newDurationMs - mTransitionsDurationMs) * 2331 (getWidth() - (2 * mHalfParentWidth)) / 2332 mProject.computeDuration())); 2333 position = mTrimmedView.getRight() - newWidth; 2334 } 2335 2336 // Return early if the new duration has not changed. We don't have to 2337 // adjust the layout. 2338 if (newDurationMs == mMediaItem.getAppTimelineDuration()) { 2339 return false; 2340 } 2341 2342 mMediaItem.setAppExtractBoundaries( 2343 mMediaItem.getAppBoundaryEndTime() - newDurationMs, 2344 mMediaItem.getAppBoundaryEndTime()); 2345 2346 mLeftHandle.setLimitReached(mMediaItem.getAppBoundaryBeginTime() <= 0, 2347 mMediaItem.getAppTimelineDuration() <= mMinimumDurationMs); 2348 mMoveLayoutPending = true; 2349 mScrollView.setTag(R.id.left_view_width, 2350 mHalfParentWidth - (newWidth - mOriginalWidth)); 2351 mScrollView.setTag(R.id.playhead_offset, position); 2352 requestLayout(); 2353 2354 mListener.onTrimMediaItem(mMediaItem, 2355 mMediaItem.getAppBoundaryBeginTime()); 2356 return true; 2357 } 2358 2359 @Override 2360 public void onMoveEnd(final HandleView view, final int left, final int delta) { 2361 final int position = left + delta; 2362 if (mMoveLayoutPending || (position != mMovePosition)) { 2363 mHandler.post(new Runnable() { 2364 @Override 2365 public void run() { 2366 if (mMoveLayoutPending) { 2367 mHandler.post(this); 2368 } else if (position != mMovePosition) { 2369 if (onMove(view, left, delta)) { 2370 mHandler.post(this); 2371 } else { 2372 moveDone(); 2373 } 2374 } else { 2375 moveDone(); 2376 } 2377 } 2378 }); 2379 } else { 2380 moveDone(); 2381 } 2382 } 2383 2384 /** 2385 * The move is complete 2386 */ 2387 private void moveDone() { 2388 mScrollView.setTag(R.id.left_view_width, mHalfParentWidth); 2389 mScrollView.setTag(R.id.playhead_offset, -1); 2390 2391 mListener.onTrimMediaItemEnd(mMediaItem, 2392 mMediaItem.getAppBoundaryBeginTime()); 2393 mListener.onRequestMovePlayhead(getBeginTime(mMediaItem), false); 2394 2395 if (Math.abs(mOriginalBeginMs - mMediaItem.getAppBoundaryBeginTime()) > 2396 TIME_TOLERANCE 2397 || Math.abs(mOriginalEndMs - mMediaItem.getAppBoundaryEndTime()) > 2398 TIME_TOLERANCE) { 2399 2400 if (videoClip) { // Video clip 2401 ApiService.setMediaItemBoundaries(getContext(), mProject.getPath(), 2402 mMediaItem.getId(), mMediaItem.getAppBoundaryBeginTime(), 2403 mMediaItem.getAppBoundaryEndTime()); 2404 } else { // Image 2405 ApiService.setMediaItemDuration(getContext(), mProject.getPath(), 2406 mMediaItem.getId(), mMediaItem.getAppTimelineDuration()); 2407 } 2408 2409 final long durationMs = mMediaItem.getAppTimelineDuration(); 2410 mRightHandle.setLimitReached(durationMs <= 2411 MediaItemUtils.getMinimumMediaItemDuration(mMediaItem), 2412 videoClip ? (mMediaItem.getAppBoundaryEndTime() >= 2413 mMediaItem.getDuration()) : durationMs >= 2414 MAXIMUM_IMAGE_DURATION); 2415 2416 mLeftHandle.setEnabled(false); 2417 mRightHandle.setEnabled(false); 2418 } 2419 setIsTrimming(false); 2420 mScrollView.invalidate(); 2421 invalidateAllChildren(); 2422 } 2423 }); 2424 } 2425 2426 mRightHandle.setVisibility(View.VISIBLE); 2427 mRightHandle.bringToFront(); 2428 final long durationMs = mi.getAppTimelineDuration(); 2429 mRightHandle.setLimitReached( 2430 durationMs <= MediaItemUtils.getMinimumMediaItemDuration(mi), 2431 videoClip ? (mi.getAppBoundaryEndTime() >= mi.getDuration()) : 2432 durationMs >= MAXIMUM_IMAGE_DURATION); 2433 mRightHandle.setListener(new HandleView.MoveListener() { 2434 private View mTrimmedView; 2435 private MovieMediaItem mMediaItem; 2436 private long mTransitionsDurationMs; 2437 private long mOriginalBeginMs, mOriginalEndMs; 2438 private long mMinimumItemDurationMs; 2439 private int mMovePosition; 2440 2441 @Override 2442 public void onMoveBegin(HandleView view) { 2443 mMediaItem = (MovieMediaItem)mediaItemView.getTag(); 2444 mTransitionsDurationMs = (mMediaItem.getBeginTransition() != null ? 2445 mMediaItem.getBeginTransition().getAppDuration() : 0) 2446 + (mMediaItem.getEndTransition() != null ? 2447 mMediaItem.getEndTransition().getAppDuration() : 0); 2448 mOriginalBeginMs = mMediaItem.getAppBoundaryBeginTime(); 2449 mOriginalEndMs = mMediaItem.getAppBoundaryEndTime(); 2450 mMinimumItemDurationMs = MediaItemUtils.getMinimumMediaItemDuration(mMediaItem); 2451 setIsTrimming(true); 2452 invalidateAllChildren(); 2453 mTrimmedView = mediaItemView; 2454 2455 mListener.onTrimMediaItemBegin(mMediaItem); 2456 if (videoClip) { // Video clip 2457 mListener.onTrimMediaItem(mMediaItem, mMediaItem.getAppBoundaryEndTime()); 2458 } else { 2459 mListener.onTrimMediaItem(mMediaItem, 0); 2460 } 2461 2462 // Move the playhead 2463 mScrollView.setTag(R.id.playhead_offset, view.getLeft()); 2464 mScrollView.invalidate(); 2465 } 2466 2467 @Override 2468 public boolean onMove(HandleView view, int left, int delta) { 2469 if (mMoveLayoutPending) { 2470 return false; 2471 } 2472 2473 int position = left + delta; 2474 mMovePosition = position; 2475 2476 long newDurationMs; 2477 // Compute what will become the width of the view 2478 int newWidth = position - mTrimmedView.getLeft(); 2479 if (newWidth == mTrimmedView.getWidth()) { 2480 return false; 2481 } 2482 2483 // Compute the new duration 2484 newDurationMs = mTransitionsDurationMs + 2485 (newWidth * mProject.computeDuration()) / 2486 (getWidth() - (2 * mHalfParentWidth)); 2487 if (Math.abs(mMediaItem.getAppTimelineDuration() - newDurationMs) < 2488 TIME_TOLERANCE) { 2489 return false; 2490 } 2491 2492 if (videoClip) { // Video clip 2493 if (newDurationMs < Math.max(2 * mTransitionsDurationMs, 2494 mMinimumItemDurationMs)) { 2495 newDurationMs = Math.max(2 * mTransitionsDurationMs, 2496 mMinimumItemDurationMs); 2497 newWidth = (int)(((newDurationMs - mTransitionsDurationMs) * 2498 (getWidth() - (2 * mHalfParentWidth)) / 2499 mProject.computeDuration())); 2500 position = newWidth + mTrimmedView.getLeft(); 2501 } else if (mMediaItem.getAppBoundaryBeginTime() + newDurationMs > 2502 mMediaItem.getDuration()) { 2503 newDurationMs = mMediaItem.getDuration() - 2504 mMediaItem.getAppBoundaryBeginTime(); 2505 newWidth = (int)(((newDurationMs - mTransitionsDurationMs) * 2506 (getWidth() - (2 * mHalfParentWidth)) / 2507 mProject.computeDuration())); 2508 position = newWidth + mTrimmedView.getLeft(); 2509 } 2510 2511 if (newDurationMs == mMediaItem.getAppTimelineDuration()) { 2512 return false; 2513 } 2514 2515 mMediaItem.setAppExtractBoundaries(mMediaItem.getAppBoundaryBeginTime(), 2516 mMediaItem.getAppBoundaryBeginTime() + newDurationMs); 2517 mListener.onTrimMediaItem(mMediaItem, mMediaItem.getAppBoundaryEndTime()); 2518 } else { // Image 2519 if (newDurationMs < Math.max(mMinimumItemDurationMs, 2520 2 * mTransitionsDurationMs)) { 2521 newDurationMs = Math.max(mMinimumItemDurationMs, 2522 2 * mTransitionsDurationMs); 2523 newWidth = (int)(((newDurationMs - mTransitionsDurationMs) * 2524 (getWidth() - (2 * mHalfParentWidth)) / 2525 mProject.computeDuration())); 2526 position = newWidth + mTrimmedView.getLeft(); 2527 } else if (newDurationMs > MAXIMUM_IMAGE_DURATION) { 2528 newDurationMs = MAXIMUM_IMAGE_DURATION; 2529 newWidth = (int)(((newDurationMs - mTransitionsDurationMs) * 2530 (getWidth() - (2 * mHalfParentWidth)) / 2531 mProject.computeDuration())); 2532 position = newWidth + mTrimmedView.getLeft(); 2533 } 2534 2535 // Check if the duration would change 2536 if (newDurationMs == mMediaItem.getAppTimelineDuration()) { 2537 return false; 2538 } 2539 2540 mMediaItem.setAppExtractBoundaries(0, newDurationMs); 2541 mListener.onTrimMediaItem(mMediaItem, 0); 2542 } 2543 2544 mScrollView.setTag(R.id.playhead_offset, position); 2545 mRightHandle.setLimitReached( 2546 newDurationMs <= mMinimumItemDurationMs, 2547 videoClip ? (mMediaItem.getAppBoundaryEndTime() >= 2548 mMediaItem.getDuration()) : newDurationMs >= 2549 MAXIMUM_IMAGE_DURATION); 2550 2551 mMoveLayoutPending = true; 2552 requestLayout(); 2553 2554 return true; 2555 } 2556 2557 @Override 2558 public void onMoveEnd(final HandleView view, final int left, final int delta) { 2559 final int position = left + delta; 2560 if (mMoveLayoutPending || (position != mMovePosition)) { 2561 mHandler.post(new Runnable() { 2562 @Override 2563 public void run() { 2564 if (mMoveLayoutPending) { 2565 mHandler.post(this); 2566 } else if (position != mMovePosition) { 2567 if (onMove(view, left, delta)) { 2568 mHandler.post(this); 2569 } else { 2570 moveDone(); 2571 } 2572 } else { 2573 moveDone(); 2574 } 2575 } 2576 }); 2577 } else { 2578 moveDone(); 2579 } 2580 } 2581 2582 /** 2583 * The move is complete 2584 */ 2585 private void moveDone() { 2586 mScrollView.setTag(R.id.playhead_offset, -1); 2587 2588 mListener.onTrimMediaItemEnd(mMediaItem, 2589 mMediaItem.getAppBoundaryEndTime()); 2590 mListener.onRequestMovePlayhead(getEndTime(mMediaItem), false); 2591 2592 if (Math.abs(mOriginalBeginMs - mMediaItem.getAppBoundaryBeginTime()) > 2593 TIME_TOLERANCE || 2594 Math.abs(mOriginalEndMs - mMediaItem.getAppBoundaryEndTime()) > 2595 TIME_TOLERANCE) { 2596 if (videoClip) { // Video clip 2597 ApiService.setMediaItemBoundaries(getContext(), mProject.getPath(), 2598 mMediaItem.getId(), mMediaItem.getAppBoundaryBeginTime(), 2599 mMediaItem.getAppBoundaryEndTime()); 2600 } else { // Image 2601 ApiService.setMediaItemDuration(getContext(), mProject.getPath(), 2602 mMediaItem.getId(), mMediaItem.getAppTimelineDuration()); 2603 } 2604 2605 if (videoClip) { 2606 mLeftHandle.setLimitReached(mMediaItem.getAppBoundaryBeginTime() <= 0, 2607 mMediaItem.getAppTimelineDuration() <= mMinimumItemDurationMs); 2608 } 2609 2610 mLeftHandle.setEnabled(false); 2611 mRightHandle.setEnabled(false); 2612 } 2613 setIsTrimming(false); 2614 mScrollView.invalidate(); 2615 invalidateAllChildren(); 2616 } 2617 }); 2618 } else if (tag instanceof MovieTransition) { 2619 if (mTransitionActionMode == null) { 2620 startActionMode(new TransitionActionModeCallback((MovieTransition) tag)); 2621 } 2622 } 2623 } 2624 2625 /** 2626 * Indicates if any media item is being trimmed or no. 2627 */ 2628 private void setIsTrimming(boolean isTrimming) { 2629 mIsTrimming = isTrimming; 2630 } 2631 2632 /** 2633 * Sets the playback state for all media item views. 2634 * 2635 * @param playback indicates if the playback is ongoing 2636 */ 2637 private void setPlaybackState(boolean playback) { 2638 final int childrenCount = getChildCount(); 2639 for (int i = 0; i < childrenCount; i++) { 2640 final View childView = getChildAt(i); 2641 final Object tag = childView.getTag(); 2642 if (tag != null) { 2643 if (tag instanceof MovieMediaItem) { 2644 ((MediaItemView) childView).setPlaybackMode(playback); 2645 } else if (tag instanceof MovieTransition) { 2646 ((TransitionView) childView).setPlaybackMode(playback); 2647 } 2648 } 2649 } 2650 } 2651 2652 /** 2653 * Un-selects all views in the timeline relative layout, including playhead view and 2654 * the ones in audio track layout and transition layout. 2655 */ 2656 private void unselectAllTimelineViews() { 2657 ((RelativeLayout) getParent()).setSelected(false); 2658 invalidateAllChildren(); 2659 } 2660 2661 /** 2662 * Invalidates all children. Note that invalidating the parent does not invalidate its children. 2663 */ 2664 private void invalidateAllChildren() { 2665 final int childrenCount = getChildCount(); 2666 for (int i = 0; i < childrenCount; i++) { 2667 final View childView = getChildAt(i); 2668 childView.invalidate(); 2669 } 2670 } 2671 2672 /** 2673 * Shows or hides "add media buttons" on both sides of the timeline. 2674 * 2675 * @param show {@code true} to show the "Add media item" buttons, {@code false} to hide them 2676 */ 2677 private void showAddMediaItemButtons(boolean show) { 2678 if (show) { 2679 // Shows left add button iff there is at least one media item on the timeline. 2680 if (mProject.getMediaItemCount() > 0) { 2681 mLeftAddClipButton.setVisibility(View.VISIBLE); 2682 } 2683 mRightAddClipButton.setVisibility(View.VISIBLE); 2684 } else { 2685 mLeftAddClipButton.setVisibility(View.GONE); 2686 mRightAddClipButton.setVisibility(View.GONE); 2687 } 2688 } 2689 } 2690