| /* |
| * Copyright (C) 2010 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.videoeditor.widgets; |
| |
| import java.util.List; |
| |
| import android.app.Activity; |
| import android.app.Dialog; |
| import android.content.Context; |
| import android.content.DialogInterface; |
| import android.os.Bundle; |
| import android.util.AttributeSet; |
| import android.util.Log; |
| import android.view.ActionMode; |
| import android.view.Display; |
| import android.view.Menu; |
| import android.view.MenuItem; |
| import android.view.MotionEvent; |
| import android.view.View; |
| import android.widget.LinearLayout; |
| import android.widget.RelativeLayout; |
| import android.widget.SeekBar; |
| import android.widget.TextView; |
| |
| import com.android.videoeditor.AlertDialogs; |
| import com.android.videoeditor.VideoEditorActivity; |
| import com.android.videoeditor.service.ApiService; |
| import com.android.videoeditor.service.MovieAudioTrack; |
| import com.android.videoeditor.service.VideoEditorProject; |
| import com.android.videoeditor.util.FileUtils; |
| import com.android.videoeditor.R; |
| |
| /** |
| * The LinearLayout which displays audio tracks |
| */ |
| public class AudioTrackLinearLayout extends LinearLayout { |
| // Logging |
| private static final String TAG = "AudioTrackLinearLayout"; |
| |
| // Dialog parameter ids |
| private static final String PARAM_DIALOG_AUDIO_TRACK_ID = "audio_track_id"; |
| |
| // Instance variables |
| private final ItemSimpleGestureListener mAudioTrackGestureListener; |
| private final int mAudioTrackHeight; |
| private final int mHalfParentWidth; |
| private final View mAddAudioTrackButtonView; |
| private final int mAddAudioTrackButtonWidth; |
| private AudioTracksLayoutListener mListener; |
| private ActionMode mAudioTrackActionMode; |
| private VideoEditorProject mProject; |
| private boolean mPlaybackInProgress; |
| private long mTimelineDurationMs; |
| |
| /** |
| * Activity listener |
| */ |
| public interface AudioTracksLayoutListener { |
| /** |
| * Add an audio track |
| */ |
| public void onAddAudioTrack(); |
| } |
| |
| /** |
| * The audio track action mode handler |
| */ |
| private class AudioTrackActionModeCallback implements ActionMode.Callback, |
| SeekBar.OnSeekBarChangeListener { |
| // Instance variables |
| private final MovieAudioTrack mAudioTrack; |
| private int mProgress; |
| |
| /** |
| * Constructor |
| * |
| * @param audioTrack The audio track |
| */ |
| public AudioTrackActionModeCallback(MovieAudioTrack audioTrack) { |
| mAudioTrack = audioTrack; |
| } |
| |
| @Override |
| public boolean onCreateActionMode(ActionMode mode, Menu menu) { |
| mAudioTrackActionMode = mode; |
| |
| mode.getMenuInflater().inflate(R.menu.audio_mode_menu, menu); |
| |
| final View titleBarView = inflate(getContext(), R.layout.audio_track_action_bar, null); |
| |
| mode.setCustomView(titleBarView); |
| |
| final TextView titleView = (TextView)titleBarView.findViewById(R.id.action_bar_title); |
| titleView.setText(FileUtils.getSimpleName(mAudioTrack.getFilename())); |
| |
| final SeekBar seekBar = |
| ((SeekBar)titleBarView.findViewById(R.id.action_volume)); |
| seekBar.setOnSeekBarChangeListener(this); |
| mProgress = mAudioTrack.getAppVolume(); |
| seekBar.setProgress(mProgress); |
| |
| return true; |
| } |
| |
| @Override |
| public boolean onPrepareActionMode(ActionMode mode, Menu menu) { |
| MenuItem duckItem = menu.findItem(R.id.action_duck); |
| duckItem.setChecked(mAudioTrack.isAppDuckingEnabled()); |
| return true; |
| } |
| |
| @Override |
| public boolean onActionItemClicked(ActionMode mode, MenuItem item) { |
| switch (item.getItemId()) { |
| case R.id.action_duck: { |
| final boolean duck = !mAudioTrack.isAppDuckingEnabled(); |
| mAudioTrack.enableAppDucking(duck); |
| ApiService.setAudioTrackDuck(getContext(), mProject.getPath(), |
| mAudioTrack.getId(), duck); |
| item.setChecked(duck); |
| break; |
| } |
| |
| case R.id.action_remove_audio_track: { |
| final Bundle bundle = new Bundle(); |
| bundle.putString(PARAM_DIALOG_AUDIO_TRACK_ID, mAudioTrack.getId()); |
| ((Activity)getContext()).showDialog( |
| VideoEditorActivity.DIALOG_REMOVE_AUDIO_TRACK_ID, bundle); |
| break; |
| } |
| } |
| return true; |
| } |
| |
| @Override |
| public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { |
| if (fromUser) { |
| mProgress = progress; |
| mAudioTrack.setAppVolume(mProgress); |
| } |
| } |
| |
| @Override |
| public void onStartTrackingTouch(SeekBar seekBar) { |
| } |
| |
| @Override |
| public void onStopTrackingTouch(SeekBar seekBar) { |
| ApiService.setAudioTrackVolume(getContext(), mProject.getPath(), |
| mAudioTrack.getId(), mProgress); |
| } |
| |
| @Override |
| public void onDestroyActionMode(ActionMode mode) { |
| final View audioTrackView = getAudioTrackView(mAudioTrack.getId()); |
| if (audioTrackView != null) { |
| selectView(audioTrackView, false); |
| } |
| |
| mAudioTrackActionMode = null; |
| } |
| } |
| |
| public AudioTrackLinearLayout(Context context, AttributeSet attrs, int defStyle) { |
| super(context, attrs, defStyle); |
| |
| mAudioTrackGestureListener = new ItemSimpleGestureListener() { |
| @Override |
| public boolean onSingleTapConfirmed(View view, int area, MotionEvent e) { |
| if (mPlaybackInProgress) { |
| return false; |
| } |
| |
| if (!view.isSelected()) { |
| selectView(view, true); |
| } |
| |
| return true; |
| } |
| |
| @Override |
| public void onLongPress(View view, MotionEvent e) { |
| if (mPlaybackInProgress) { |
| return; |
| } |
| |
| if (!view.isSelected()) { |
| selectView(view, true); |
| } |
| |
| if (mAudioTrackActionMode == null) { |
| startActionMode(new AudioTrackActionModeCallback( |
| (MovieAudioTrack)view.getTag())); |
| } |
| } |
| }; |
| |
| // Add the beginning timeline item |
| final View beginView = inflate(getContext(), R.layout.empty_timeline_item, null); |
| beginView.setOnClickListener(new View.OnClickListener() { |
| @Override |
| public void onClick(View view) { |
| unselectAllViews(); |
| } |
| }); |
| addView(beginView); |
| |
| // Add the end timeline item |
| final View endView = inflate(context, R.layout.empty_timeline_item, null); |
| endView.setOnClickListener(new View.OnClickListener() { |
| @Override |
| public void onClick(View view) { |
| unselectAllViews(); |
| } |
| }); |
| addView(endView); |
| |
| // Add the audio track button |
| mAddAudioTrackButtonView = inflate(getContext(), R.layout.add_audio_track_button, null); |
| addView(mAddAudioTrackButtonView, 1); |
| mAddAudioTrackButtonView.setOnClickListener(new View.OnClickListener() { |
| @Override |
| public void onClick(View view) { |
| if (mListener != null) { |
| mListener.onAddAudioTrack(); |
| } |
| } |
| }); |
| mAddAudioTrackButtonWidth = (int)context.getResources().getDimension( |
| R.dimen.add_audio_track_button_width); |
| |
| // Compute half the width of the screen (and therefore the parent view) |
| final Display display = ((Activity)context).getWindowManager().getDefaultDisplay(); |
| mHalfParentWidth = display.getWidth() / 2; |
| |
| // Get the layout height |
| mAudioTrackHeight = (int)context.getResources().getDimension(R.dimen.audio_layout_height); |
| |
| setMotionEventSplittingEnabled(false); |
| } |
| |
| public AudioTrackLinearLayout(Context context, AttributeSet attrs) { |
| this(context, attrs, 0); |
| } |
| |
| public AudioTrackLinearLayout(Context context) { |
| this(context, null, 0); |
| } |
| |
| /** |
| * The activity was resumed |
| */ |
| public void onResume() { |
| final int childrenCount = getChildCount(); |
| for (int i = 0; i < childrenCount; i++) { |
| final View childView = getChildAt(i); |
| final Object tag = childView.getTag(); |
| if (tag != null) { // This view represents an audio track |
| final AudioTrackView audioTrackView = (AudioTrackView)childView; |
| if (audioTrackView.getWaveformData() == null) { |
| final MovieAudioTrack audioTrack = (MovieAudioTrack)tag; |
| if (audioTrack.getWaveformData() != null) { |
| audioTrackView.setWaveformData(audioTrack.getWaveformData()); |
| audioTrackView.invalidate(); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * @param listener The listener |
| */ |
| public void setListener(AudioTracksLayoutListener listener) { |
| mListener = listener; |
| } |
| |
| /** |
| * @param project The project |
| */ |
| public void setProject(VideoEditorProject project) { |
| // Close the contextual action bar |
| if (mAudioTrackActionMode != null) { |
| mAudioTrackActionMode.finish(); |
| mAudioTrackActionMode = null; |
| } |
| |
| mProject = project; |
| |
| updateAddAudioTrackButton(); |
| |
| removeAudioTrackViews(); |
| } |
| |
| /** |
| * @param inProgress true if playback is in progress |
| */ |
| public void setPlaybackInProgress(boolean inProgress) { |
| mPlaybackInProgress = inProgress; |
| |
| // Don't allow the user to interact with the audio tracks while playback |
| // is in progress |
| if (inProgress && mAudioTrackActionMode != null) { |
| mAudioTrackActionMode.finish(); |
| mAudioTrackActionMode = null; |
| } |
| } |
| |
| /** |
| * Add the audio tracks |
| * |
| * @param audioTracks The audio tracks |
| */ |
| public void addAudioTracks(List<MovieAudioTrack> audioTracks) { |
| if (mAudioTrackActionMode != null) { |
| mAudioTrackActionMode.finish(); |
| mAudioTrackActionMode = null; |
| } |
| |
| updateAddAudioTrackButton(); |
| |
| removeAudioTrackViews(); |
| |
| mTimelineDurationMs = mProject.computeDuration(); |
| |
| for (MovieAudioTrack audioTrack : audioTracks) { |
| addAudioTrack(audioTrack); |
| } |
| } |
| |
| /** |
| * Add a new audio track |
| * |
| * @param audioTrack The audio track |
| * |
| * @return The view that was added |
| */ |
| public View addAudioTrack(MovieAudioTrack audioTrack) { |
| updateAddAudioTrackButton(); |
| |
| final AudioTrackView audioTrackView = (AudioTrackView)inflate(getContext(), |
| R.layout.audio_track_item, null); |
| |
| audioTrackView.setTag(audioTrack); |
| |
| audioTrackView.setGestureListener(mAudioTrackGestureListener); |
| |
| audioTrackView.updateTimelineDuration(mTimelineDurationMs); |
| |
| if (audioTrack.getWaveformData() != null) { |
| audioTrackView.setWaveformData(audioTrack.getWaveformData()); |
| } else { |
| ApiService.extractAudioTrackAudioWaveform(getContext(), mProject.getPath(), |
| audioTrack.getId()); |
| } |
| |
| final LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( |
| LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.FILL_PARENT); |
| addView(audioTrackView, getChildCount() - 1, lp); |
| |
| if (mAudioTrackActionMode != null) { |
| mAudioTrackActionMode.invalidate(); |
| } |
| |
| requestLayout(); |
| return audioTrackView; |
| } |
| |
| /** |
| * Remove an audio track |
| * |
| * @param audioTrackId The audio track id |
| * @return The view which was removed |
| */ |
| public View removeAudioTrack(String audioTrackId) { |
| final int childrenCount = getChildCount(); |
| for (int i = 0; i < childrenCount; i++) { |
| final View childView = getChildAt(i); |
| final MovieAudioTrack audioTrack = (MovieAudioTrack)childView.getTag(); |
| if (audioTrack != null && audioTrack.getId().equals(audioTrackId)) { |
| removeViewAt(i); |
| |
| updateAddAudioTrackButton(); |
| |
| requestLayout(); |
| return childView; |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Update the audio track item |
| * |
| * @param audioTrackId The audio track id |
| */ |
| public void updateAudioTrack(String audioTrackId) { |
| final AudioTrackView audioTrackView = (AudioTrackView)getAudioTrackView(audioTrackId); |
| if (audioTrackView == null) { |
| Log.e(TAG, "updateAudioTrack: audio track view not found: " + audioTrackId); |
| return; |
| } |
| |
| if (mAudioTrackActionMode != null) { |
| mAudioTrackActionMode.invalidate(); |
| } |
| |
| requestLayout(); |
| invalidate(); |
| } |
| |
| /** |
| * An audio track is being decoded |
| * |
| * @param audioTrackId The audio track id |
| * @param action The action |
| * @param progress The progress |
| */ |
| public void onGeneratePreviewProgress(String audioTrackId, int action, int progress) { |
| final AudioTrackView audioTrackView = (AudioTrackView)getAudioTrackView(audioTrackId); |
| if (audioTrackView == null) { |
| Log.e(TAG, "onGeneratePreviewProgress: audio track view not found: " + audioTrackId); |
| return; |
| } |
| |
| audioTrackView.setProgress(progress); |
| } |
| |
| /** |
| * Set the waveform progress |
| * |
| * @param audioTrackId The audio track id |
| * @param progress The progress |
| */ |
| public void setWaveformExtractionProgress(String audioTrackId, int progress) { |
| final AudioTrackView audioTrackView = (AudioTrackView)getAudioTrackView(audioTrackId); |
| if (audioTrackView == null) { |
| Log.e(TAG, "setWaveformExtractionProgress: audio track view not found: " |
| + audioTrackId); |
| return; |
| } |
| |
| audioTrackView.setProgress(progress); |
| } |
| |
| /** |
| * The waveform extraction is complete |
| * |
| * @param audioTrackId The audio track id |
| */ |
| public void setWaveformExtractionComplete(String audioTrackId) { |
| final AudioTrackView audioTrackView = (AudioTrackView)getAudioTrackView(audioTrackId); |
| if (audioTrackView == null) { |
| Log.e(TAG, "setWaveformExtractionComplete: audio track view not found: " |
| + audioTrackId); |
| return; |
| } |
| |
| audioTrackView.setProgress(-1); |
| |
| final MovieAudioTrack audioTrack = (MovieAudioTrack)audioTrackView.getTag(); |
| if (audioTrack.getWaveformData() != null) { |
| audioTrackView.setWaveformData(audioTrack.getWaveformData()); |
| } |
| |
| requestLayout(); |
| invalidate(); |
| } |
| |
| /** |
| * The timeline duration has changed. Refresh the view. |
| */ |
| public void updateTimelineDuration() { |
| mTimelineDurationMs = mProject.computeDuration(); |
| |
| // Media items may had been added or removed |
| updateAddAudioTrackButton(); |
| |
| // Update the project duration for all views |
| final int childrenCount = getChildCount(); |
| for (int i = 0; i < childrenCount; i++) { |
| final View childView = getChildAt(i); |
| final MovieAudioTrack audioTrack = (MovieAudioTrack)childView.getTag(); |
| if (audioTrack != null) { |
| ((AudioTrackView)childView).updateTimelineDuration(mTimelineDurationMs); |
| } |
| } |
| |
| requestLayout(); |
| invalidate(); |
| } |
| |
| @Override |
| protected void onLayout(boolean changed, int l, int t, int r, int b) { |
| final int childrenCount = getChildCount(); |
| if (mTimelineDurationMs == 0) { |
| int left = 0; |
| for (int i = 0; i < childrenCount; i++) { |
| final View childView = getChildAt(i); |
| final MovieAudioTrack audioTrack = (MovieAudioTrack)childView.getTag(); |
| if (audioTrack != null) { |
| // Audio tracks are not visible |
| childView.layout(left, 0, left, mAudioTrackHeight); |
| } else { // Beginning and end views |
| childView.layout(left, 0, left + mHalfParentWidth, mAudioTrackHeight); |
| left += mHalfParentWidth; |
| } |
| } |
| } else { |
| final int viewWidth = getWidth() - (2 * mHalfParentWidth); |
| int left = 0; |
| |
| final int leftViewWidth = (Integer)((View)getParent().getParent()).getTag( |
| R.id.left_view_width); |
| |
| for (int i = 0; i < childrenCount; i++) { |
| final View childView = getChildAt(i); |
| final int id = childView.getId(); |
| final MovieAudioTrack audioTrack = (MovieAudioTrack)childView.getTag(); |
| if (audioTrack != null) { // Audio track views |
| final int width; |
| if (audioTrack.isAppLooping()) { |
| width = (int)((mTimelineDurationMs - |
| audioTrack.getAppStartTime()) * viewWidth / mTimelineDurationMs); |
| } else { |
| if (audioTrack.getAppStartTime() + audioTrack.getTimelineDuration() > |
| mTimelineDurationMs) { |
| width = (int)((mTimelineDurationMs - |
| audioTrack.getAppStartTime()) * viewWidth / mTimelineDurationMs); |
| } else { |
| width = (int)(audioTrack.getTimelineDuration() * viewWidth / |
| mTimelineDurationMs); |
| } |
| } |
| |
| final int trackLeft = |
| (int)((audioTrack.getAppStartTime() * viewWidth) / mTimelineDurationMs) + |
| leftViewWidth; |
| childView.layout(trackLeft, 0, trackLeft + width, mAudioTrackHeight); |
| left = trackLeft + width; |
| } else if (id == R.id.add_audio_track_button) { |
| if (childView.getVisibility() == View.VISIBLE) { |
| childView.layout(left, 0, left + mAddAudioTrackButtonWidth, |
| mAudioTrackHeight); |
| left += mAddAudioTrackButtonWidth; |
| } |
| } else if (i == 0) { // Begin view |
| childView.layout(left, 0, left + leftViewWidth, mAudioTrackHeight); |
| left += leftViewWidth; |
| } else { // End view |
| childView.layout(left, 0, getWidth(), mAudioTrackHeight); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Create a new dialog |
| * |
| * @param id The dialog id |
| * @param bundle The dialog bundle |
| * |
| * @return The dialog |
| */ |
| public Dialog onCreateDialog(int id, final Bundle bundle) { |
| // If the project is not yet loaded do nothing. |
| if (mProject == null) { |
| return null; |
| } |
| |
| switch (id) { |
| case VideoEditorActivity.DIALOG_REMOVE_AUDIO_TRACK_ID: { |
| final MovieAudioTrack audioTrack = mProject.getAudioTrack( |
| bundle.getString(PARAM_DIALOG_AUDIO_TRACK_ID)); |
| if (audioTrack == null) { |
| return null; |
| } |
| |
| final Activity activity = (Activity)getContext(); |
| return AlertDialogs.createAlert(activity, |
| FileUtils.getSimpleName(audioTrack.getFilename()), 0, |
| activity.getString(R.string.editor_remove_audio_track_question), |
| activity.getString(R.string.yes), |
| new DialogInterface.OnClickListener() { |
| @Override |
| public void onClick(DialogInterface dialog, int which) { |
| if (mAudioTrackActionMode != null) { |
| mAudioTrackActionMode.finish(); |
| mAudioTrackActionMode = null; |
| } |
| activity.removeDialog(VideoEditorActivity.DIALOG_REMOVE_AUDIO_TRACK_ID); |
| |
| ApiService.removeAudioTrack(activity, mProject.getPath(), |
| audioTrack.getId()); |
| } |
| }, activity.getString(R.string.no), new DialogInterface.OnClickListener() { |
| @Override |
| public void onClick(DialogInterface dialog, int which) { |
| activity.removeDialog(VideoEditorActivity.DIALOG_REMOVE_AUDIO_TRACK_ID); |
| } |
| }, new DialogInterface.OnCancelListener() { |
| @Override |
| public void onCancel(DialogInterface dialog) { |
| activity.removeDialog(VideoEditorActivity.DIALOG_REMOVE_AUDIO_TRACK_ID); |
| } |
| }, true); |
| } |
| |
| default: { |
| return null; |
| } |
| } |
| } |
| |
| /** |
| * Find the audio track view with the specified id |
| * |
| * @param audioTrackId The audio track id |
| * @return The audio track view |
| */ |
| private View getAudioTrackView(String audioTrackId) { |
| final int childrenCount = getChildCount(); |
| for (int i = 0; i < childrenCount; i++) { |
| final View childView = getChildAt(i); |
| final MovieAudioTrack audioTrack = (MovieAudioTrack)childView.getTag(); |
| if (audioTrack != null && audioTrackId.equals(audioTrack.getId())) { |
| return childView; |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Remove all audio track views (leave the beginning and end views) |
| */ |
| private void removeAudioTrackViews() { |
| int index = 0; |
| while (index < getChildCount()) { |
| final Object tag = getChildAt(index).getTag(); |
| if (tag != null) { |
| removeViewAt(index); |
| } else { |
| index++; |
| } |
| } |
| |
| requestLayout(); |
| } |
| |
| /** |
| * Set the background of the begin view |
| */ |
| private void updateAddAudioTrackButton() { |
| if (mProject == null) { // No project |
| mAddAudioTrackButtonView.setVisibility(View.GONE); |
| } else if (mProject.getMediaItemCount() > 0) { |
| if (mProject.getAudioTracks().size() > 0) { |
| mAddAudioTrackButtonView.setVisibility(View.GONE); |
| } else { |
| mAddAudioTrackButtonView.setVisibility(View.VISIBLE); |
| } |
| } else { // No media items |
| mAddAudioTrackButtonView.setVisibility(View.GONE); |
| } |
| } |
| |
| @Override |
| public void setSelected(boolean selected) { |
| if (selected == false) { |
| // Close the contextual action bar |
| if (mAudioTrackActionMode != null) { |
| mAudioTrackActionMode.finish(); |
| mAudioTrackActionMode = null; |
| } |
| } |
| |
| final int childrenCount = getChildCount(); |
| for (int i = 0; i < childrenCount; i++) { |
| final View childView = getChildAt(i); |
| childView.setSelected(false); |
| } |
| } |
| |
| /** |
| * Select a view and unselect any view that is selected. |
| * |
| * @param selectedView The view to select |
| * @param selected true if selected |
| */ |
| private void selectView(View selectedView, boolean selected) { |
| // Check if the selection has changed |
| if (selectedView.isSelected() == selected) { |
| return; |
| } |
| |
| if (selected) { |
| unselectAllViews(); |
| } |
| |
| if (selected && mAudioTrackActionMode == null) { |
| startActionMode(new AudioTrackActionModeCallback( |
| (MovieAudioTrack)selectedView.getTag())); |
| } |
| |
| // Select the new view |
| selectedView.setSelected(selected); |
| } |
| |
| /** |
| * Unselect all views |
| */ |
| private void unselectAllViews() { |
| ((RelativeLayout)getParent()).setSelected(false); |
| } |
| } |