blob: 9b60962ff674e4831940fa2f06ef5657600ab68f [file] [log] [blame]
/*
* 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);
}
}