package com.teamluper.luper;
/*
* Copyright (C) 2011 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.
*/
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.Shader;
import android.graphics.drawable.BitmapDrawable;
import android.media.MediaPlayer;
import android.media.MediaRecorder;
import android.os.Environment;
import android.os.Looper;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
import android.widget.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Random;
import com.androidlearner.widget.DragThing;
import com.androidlearner.widget.DragThingPlayhead;
import com.googlecode.androidannotations.annotations.Background;
import com.googlecode.androidannotations.annotations.EView;
import com.googlecode.androidannotations.annotations.UiThread;
import com.teamluper.luper.AudioRecorderTestActivity.PlayButton;
import com.teamluper.luper.AudioRecorderTestActivity.playTrackButton;
import android.os.Bundle;
import com.actionbarsherlock.app.SherlockActivity;
import com.androidlearner.widget.DragThingPlayhead;
import com.googlecode.androidannotations.annotations.EActivity;
/**
* A custom view for a color chip for an event that can be drawn differently
* according to the event's status.
*
*/
@EView
public class TrackView extends RelativeLayout {
private static final String LOG_TAG = "TrackView";
public static final float PIXELS_PER_MILLISECOND = 0.3f;
public static final int LEFT_MARGIN = 100;
DragThing deMovingTxt;
int [] paramz;
private String lastRecordedFileName = null;
private AudioFile lastRecordedFile = null;
private RecordButton mRecordButton = null;
private MediaRecorder mRecorder = null;
private PlayButton mPlayButton = null;
private MediaPlayer mPlayer = null;
private Button mBrowseButton;
private TextView fileSelected;
private DragThingPlayhead playhead;
private int MediaFetchResultCode = 11;
private SQLiteDataSource dataSource;
private Clip newClip;
private Clip preparedClip = null;
Track associated;
public int startTimeSetter;
private LuperProjectEditorActivity editorActivity;
//the track that will be associated with this TrackView
//Track associated;
//constructor
public TrackView(LuperProjectEditorActivity context, Track track, SQLiteDataSource dataSource) {
super(context);
editorActivity = context;
playhead = (DragThingPlayhead)editorActivity.findViewById(1337);
associated = track;
this.dataSource = dataSource;
deMovingTxt = (DragThing) findViewById(R.id.detext);
init();
}
//set a click listener for the buttons that will activate promptDialog() when clicked
OnClickListener addClipClicker = new OnClickListener(){
public void onClick(View v){
addClipPromptDialog(); // later on this will be the time corresponding to where in the empty timeline we touched.
// TODO
}
};
//TODO: Implement the listener for the play button, so we can play a track
OnClickListener playClicker = new OnClickListener(){
public void onClick(View v){
startPlayingTrack(); //need track startPlayback but track class + audio hook-up not working yet; this does work though
}
};
//listener for the browse button in the record or browse alertdialog
OnClickListener browseClicker = new OnClickListener(){
public void onClick(View v){
browsePromptDialog();
}
};
public void render() {
init();
}
@UiThread
public void init(){
mPlayer = new MediaPlayer();
//LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
// LinearLayout.LayoutParams.FILL_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
//layoutParams.setMargins(5, 0, 5, 0);
//this.setLayoutParams(layoutParams);
// add a linear layout to the left side that will have a playtrack button
// as well as a button to add a clip to this track
if(this.getChildCount() != 0) this.removeAllViews();
LinearLayout trackControl = new LinearLayout(this.getContext());
trackControl.setOrientation(LinearLayout.VERTICAL);
//wai doesnt this werrrkk???
//deMovingTxt = (DragThing) findViewById(R.id.detext);
//this.addView(deMovingTxt);
// create the addClipButton then set its image to add and add it to the trackControl
ImageButton addClipButton = new ImageButton(this.getContext());
addClipButton.setImageResource(R.drawable.add);
addClipButton.setOnClickListener(addClipClicker);
//addClipButton.setBackgroundColor(Color.parseColor("#f5f5f5"));
trackControl.addView(addClipButton);
// create the playButton then set its image to play and add it to the trackControl
ImageButton playButton = new ImageButton(this.getContext());
playButton.setImageResource(R.drawable.play);
playButton.setOnClickListener(playClicker);
//playButton.setBackgroundColor(Color.parseColor("#f5f5f5"));
trackControl.addView(playButton);
trackControl.setBackgroundColor(Color.parseColor("#f5f5f5"));
this.addView(trackControl);
// testing...
//Clip clip1 = new Clip(); clip1.begin = 100; clip1.end = 4000; clip1.duration = 3900;
//Clip clip2 = new Clip(); clip2.begin = 0; clip2.end = 450; clip2.duration = 450;
ColorChipButton chip;
//this.associated.putClip(clip1);
//this.associated.putClip(clip2);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
this.setLayoutParams(params);
for(Clip c : this.associated.clips) {
chip = new ColorChipButton(this.getContext(), c);
c.addAssociatedView(chip);
this.addView(chip);
if(c.getLoopCount() > 1) {
for(int i=1; i<=c.getLoopCount(); i++) {
ColorChipButton loopChip = new ColorChipButton(getContext(), c, i);
this.addView(loopChip);
}
}
}
}
//Creates the dialog for record or browse, for adding clips to tracks,
public void addClipPromptDialog(){
//our custom layout for inside the dialog
LinearLayout custom = new LinearLayout(this.getContext());
custom.setOrientation(LinearLayout.VERTICAL);
LinearLayout ll = new LinearLayout(this.getContext());
mRecordButton = new RecordButton(this.getContext());
ll.addView(mRecordButton,
new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
0));
LinearLayout ll2 = new LinearLayout(this.getContext());
mBrowseButton = new Button(this.getContext());
mBrowseButton.setText("Browse");
mBrowseButton.setOnClickListener(browseClicker);
fileSelected = new AutoCompleteTextView(this.getContext());
fileSelected.setHint("Select a File");
ll2.addView(mBrowseButton,
new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
0));
ll2.addView(fileSelected,
new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.FILL_PARENT,
0));
custom.addView(ll,
new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
0));
custom.addView(ll2,
new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
0));
//final int finalStartTime = startTime;
final Track finalTrack = associated;
new AlertDialog.Builder(getContext())
.setTitle("Record or Browse?")
.setView(custom)
.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
//want it to pass a new clip back to the editor panel and add it to the screen
//startTimePrompt(finalStartTime);
startTimePrompt();
}
})
.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
// Do nothing.
}
})
.show();
}
//creates a dialog for browsing a list of the user's audiofiles, for adding clips to a track with a file youve already
//recorded.
public void browsePromptDialog(){
System.out.println("Here");
final AudioFile[] audioFilesByUser = this.dataSource.getAudioFilesByUserId(this.dataSource.getActiveUser().getId());
final String[] fileNames = new String[audioFilesByUser.length];
for(int i = 0; i < audioFilesByUser.length; i++){
fileNames[i] = audioFilesByUser[i].getClientFilePath();
}
new AlertDialog.Builder(getContext())
.setTitle("Select Audio File for New Clip Homie")
.setItems(fileNames, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
// The 'which' argument contains the index position
// of the selected item
lastRecordedFile = audioFilesByUser[which];
fileSelected.setText(lastRecordedFile.getClientFilePath());
}
})
.show();
}
public void setStart(int time){
this.startTimeSetter = time;
}
public static boolean isNumeric(String s) {
return s.matches("[-+]?\\d*\\.?\\d+");
}
public void startTimePrompt(){
DialogFactory.prompt(getContext(), "Enter a start time or add it to the end of the track by entering nothing.", "",
new Lambda.StringCallback() {
public void go(String value) {
if (value.isEmpty()) {
int val = associated.findLastClipTime() + 25;
clipMaker(associated, lastRecordedFile, val);
} else if (isNumeric(value)) {
int val = Integer.parseInt(value);
clipMaker(associated, lastRecordedFile, val);
} else {
DialogFactory.alert(getContext(), "Oops!", "That isn't a valid start time.");
}
}
}
);
}
public void clipMaker(Track track, AudioFile file, int startTime) {
Random rnd = new Random();
int newColor = Color.argb(168, rnd.nextInt(256), rnd.nextInt(256), rnd.nextInt(256));
Clip newClip = dataSource.createClip(track, file, startTime, newColor);
newClip.parentTrack = track;
track.clips.add(newClip);
ColorChipButton newButton = new ColorChipButton(this.getContext(), newClip);
newClip.addAssociatedView(newButton);
this.addView(newButton);
this.invalidateSafely();
}
class RecordButton extends Button {
boolean mStartRecording = true;
OnClickListener clicker = new OnClickListener() {
public void onClick(View v) {
onRecord(mStartRecording);
if (mStartRecording) {
setText("Stop recording");
} else {
setText("Start recording");
}
mStartRecording = !mStartRecording;
}
};
public RecordButton(Context ctx) {
super(ctx);
setText("Start recording");
setOnClickListener(clicker);
}
}
private void onRecord(boolean start) {
if (start) {
startRecording();
} else {
stopRecording();
}
}
private void startRecording() {
//Sets the name of the file when you start recording as opposed to when you click "Audio Record Test" from the main screen
lastRecordedFileName = Environment.getExternalStorageDirectory()+"/LuperApp/Clips";
lastRecordedFileName += "/clip_" + System.currentTimeMillis() +".3gp";
mRecorder = new MediaRecorder();
mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
mRecorder.setOutputFile(lastRecordedFileName);
mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
try {
mRecorder.prepare();
} catch (IOException e) {
Log.e(LOG_TAG, "prepare() failed", e);
}
mRecorder.start();
}
private void stopRecording() {
mRecorder.stop();
mRecorder.release();
lastRecordedFile = dataSource.createAudioFile(dataSource.getActiveUser(), lastRecordedFileName);
lastRecordedFile.setReadyOnClient(true);
lastRecordedFile.setDurationMS(lastRecordedFile.calcDuration());
fileSelected.setText(lastRecordedFileName);
mRecorder = null;
}
/*private void startPlaying() {
mPlayer = new MediaPlayer();
try {
mPlayer.setDataSource(lastRecordedFileName);
mPlayer.prepare();
//playFrom(1000);
mPlayer.start();
editorActivity.playhead.moveHead(mPlayer);
} catch (IOException e) {
Log.e(LOG_TAG, "prepare() failed1");
}
}
private void stopPlaying() {
if(mPlayer != null) {
mPlayer.release();
mPlayer = null;
}
}*/
@Background
public void playPreparedClip() {
playPreparedClip(null);
}
// plays the prepared clip immediately, without regard to timing.
// since there is no timing logic in here, we must call it exactly when we want the clip to start.
// this also sets up a listener for when the clip is finished playing, where it identifies and prepares the next clip.
@Background
public void playPreparedClip(final Clip optionalNextClip) {
try {
this.preparedClip.resetLoop();
final Track t = associated;
final Clip justPlayedClip = this.preparedClip;
mPlayer.start();
mPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
public void onCompletion(MediaPlayer mp) {
// when a clip finishes playing, we check conditions to do a number of things:
if(justPlayedClip.getLoopCount() > 1 && justPlayedClip.remainingLoops > 0) {
// if this is a loop which has more loops to go, decrement and start playing it again.+
justPlayedClip.remainingLoops--;
mp.start();
} else if(optionalNextClip == null && t.nextClip != null) {
// if there is no specific clip to prepare, just progress to the track's next clip.
t.prepareNextClip(t.nextClip.getStartTime()+10);
} else if(optionalNextClip != null) {
// if this was called with a specific next clip to prepare, prepare it.
t.trackView.prepareClip(optionalNextClip);
} else {
// we've played the last clip!
if(justPlayedClip == editorActivity.veryLastClip) {
((Playhead) editorActivity.findViewById(1337)).stopPlayback(0);
editorActivity.supportInvalidateOptionsMenu();
}
}
}
});
} catch(Exception e) {
//handle interrupted exceptions in a different way
Log.e(LOG_TAG, "CLIP PREPARE FAILED", e);
}
}
@UiThread
public void startPlayingTrack() {
startPlayingTrackInBackground();
}
@Background // doesn't actually run them in parallel - might be related to the sleep call in track startPlayback
public void startPlayingTrackInBackground() {
//mPlayer = new MediaPlayer();
ArrayList<Clip> clips = associated.getClips();
for(int i=0; i<clips.size(); i++) {
Clip c = clips.get(i);
prepareClip(c);
try {
Thread.sleep(c.getDurationMS());
} catch(InterruptedException e) {
Log.e("luper", "THREAD.SLEEP WAS INTERRUPTED WHILE PLAYING INDIVIDUAL TRACK");
}
playPreparedClip(i+1 < clips.size() ? clips.get(i+1) : null);
}
}
@Background
public void stopPlaying() {
if(mPlayer.isPlaying()) mPlayer.stop();
}
@Background
public void prepareClip(Clip c) {
this.preparedClip = c;
if(mPlayer != null) {
mPlayer.reset();
} else {
mPlayer = new MediaPlayer();
}
// mPlayer is now idle
String clipFileName = c.getAudioFile().getClientFilePath();
try {
mPlayer.setDataSource(clipFileName);
mPlayer.prepare();
// TODO make an onPrepared and check if we're behind playhead
} catch(Exception e) {
//handle interrupted exceptions in a different way
Log.e(LOG_TAG, "CLIP PREPARE FAILED", e);
}
}
public void invalidateSafely() {
this.requestLayout();
if (Looper.myLooper() != null && Looper.myLooper() == Looper.getMainLooper()) {
// we're in the main-thread / UI Thread.
this.invalidate();
} else {
// we're in a background thread.
this.postInvalidate();
}
}
private void stopPlayingTrack() {
if(mPlayer != null) {
mPlayer.release();
mPlayer = null;
}
}
public MediaPlayer getMediaPlayer() {
return this.mPlayer;
}
}