Back to project page PlayTunes.
The source code is released under:
Copyright (c) 2014, Impeccable Labs, LLC. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions ar...
If you think the Android project PlayTunes listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.
// // CustomMediaPlayer adds looping, shuffling, and playlist handling ///*from w w w . j a va 2 s . c o m*/ package com.ideabag.playtunes.media; import java.util.Random; import com.ideabag.playtunes.database.MediaQuery; import android.content.Context; import android.database.Cursor; import android.media.AudioManager; import android.media.MediaPlayer; import android.os.PowerManager; import android.provider.MediaStore; public class PlaylistMediaPlayer { public static final String TAG = "PlaylistMediaPlayer"; protected boolean isShuffling, isPlaying; protected Cursor mPlaylistCursor; // Local copy of the playlist Cursor that we keep in case the cursor is closed or changed protected MediaQuery mMediaQuery = null; protected int mPlaylistPosition = -1; protected int mPlaylistSize = -1; protected boolean isPrepared = false; private int[] mShuffledPlaylist; protected int mLoopState; protected MediaPlayer mMediaPlayer; private boolean isDucking = false; private PlaybackListener PlaybackChanged = null; protected Context mContext; private long shuffleRandomNumberSeed = -1; //private PowerManager pm; //private PowerManager.WakeLock wl; // // // public interface PlaybackListener { void onTrackChanged( String media_id ); void onPlay(); void onPause(); void onPlaylistDone(); void onLoopingChanged( int loop ); void onShuffleChanged( boolean isShuffling ); void onDurationChanged( int position, int duration ); } // Loop states public static final int LOOP_NO = 0; public static final int LOOP_ALL = 1; public static final int LOOP_ONE = 2; public PlaylistMediaPlayer( Context context ) { super(); //( ( TelephonyManager ) context.getSystemService( Context.TELEPHONY_SERVICE ) ).listen( phoneListener, PhoneStateListener.LISTEN_CALL_STATE ); mContext = context; mMediaPlayer = new MediaPlayer(); mMediaPlayer.setAudioStreamType( AudioManager.STREAM_MUSIC ); mMediaPlayer.setWakeMode( mContext, PowerManager.PARTIAL_WAKE_LOCK ); mMediaPlayer.setOnCompletionListener( loopOnCompletionListener ); mMediaPlayer.setOnPreparedListener( onPreparedListener ); mLoopState = LOOP_NO; // // Intents for loss of connection to media // //hardwareStopIntents.addAction( Intent.ACTION_MEDIA_EJECT ); //hardwareStopIntents.addAction( Intent.ACTION_MEDIA_UNMOUNTED ); //hardwareStopIntents.addAction( Intent.ACTION_MEDIA_REMOVED ); //mContext.registerReceiver(HardwareStopReceiver, hardwareStopIntents); //pm = ( PowerManager ) mContext.getSystemService( Context.POWER_SERVICE ); //wl = pm.newWakeLock( PowerManager.PARTIAL_WAKE_LOCK, TAG ); } public void setPlaybackListener( PlaybackListener pbl ) { this.PlaybackChanged = pbl; } MediaPlayer.OnCompletionListener loopOnCompletionListener = new MediaPlayer.OnCompletionListener() { public void onCompletion( MediaPlayer mp ) { if ( null != mPlaylistCursor ) { if ( mLoopState == LOOP_ALL ) { //android.util.Log.i( TAG, "LOOP_ALL"); setPlaylistPosition( ( mPlaylistPosition + 1 ) % mPlaylistSize ); // Will always loop the whole thing } else if ( mLoopState == LOOP_NO ) { //android.util.Log.i( TAG, "LOOP_NO"); setPlaylistPosition( mPlaylistPosition + 1 ); } else if ( mLoopState == LOOP_ONE ) { // We don't need to change the media on LOOP_ONE, but we need to alert the client //android.util.Log.i( TAG, "LOOP_ONE"); setPlaylistPosition( mPlaylistPosition ); } } } }; MediaPlayer.OnPreparedListener onPreparedListener = new MediaPlayer.OnPreparedListener() { @Override public void onPrepared( MediaPlayer mp ) { isPrepared = true; if ( PlaybackChanged != null ) { PlaybackChanged.onDurationChanged( mp.getCurrentPosition(), mp.getDuration() ); } if ( isPlaying ) { if ( null != PlaybackChanged ) { PlaybackChanged.onPlay(); } mp.start(); } else { if ( null != PlaybackChanged ) { PlaybackChanged.onPause(); } } } }; // // Public Methods // // // // Clean up the media player and associated acts when you're done // public void destroy() { mMediaPlayer.reset(); mMediaPlayer.release(); /* if ( wl.isHeld() ) { wl.release(); } */ //mContext.unregisterReceiver( HardwareStopReceiver ); } public void setPlaylistQuery( MediaQuery query ) { if ( null == mMediaQuery || !mMediaQuery.equals( query ) ) { mMediaQuery = query; if ( mPlaylistCursor != null && !mPlaylistCursor.isClosed() ) { mPlaylistCursor.close(); } mPlaylistCursor = MediaQuery.execute( mContext, mMediaQuery ); //mPlaylistCursor.registerDataSetObserver( mPlaylistCursorObserver ); if ( mPlaylistCursor != null ) { mPlaylistSize = mPlaylistCursor.getCount(); mPlaylistPosition = 0; /* if ( isShuffling ) { generateShuffledPlaylist(); } */ setShuffle( false ); } else { mMediaQuery = null; } } } public MediaQuery getPlaylistQuery() { return mMediaQuery; } public boolean isPlaying() { return isPlaying; } public void play() { if ( null != mPlaylistCursor && !mPlaylistCursor.isBeforeFirst() && !mPlaylistCursor.isAfterLast() ) { isPlaying = true; if ( null != PlaybackChanged ) { PlaybackChanged.onPlay(); } if ( isPrepared ) { mMediaPlayer.start(); } /* if ( !wl.isHeld() ) { wl.acquire(); // Wake lock acquired } */ } } public void pause() { if ( mMediaPlayer.isPlaying() ) { isPlaying = false; mMediaPlayer.pause(); if ( null != PlaybackChanged ) { PlaybackChanged.onPause(); } } } public void back() { if ( null != mPlaylistCursor && null != mMediaPlayer ) { int position = mMediaPlayer.getCurrentPosition(); int duration = mMediaPlayer.getDuration(); if ( position >= ( 0.08 * duration ) ) { setSeekPosition( 0 ); } else { previousTrack(); } } } public void previousTrack() { if ( null != mPlaylistCursor ) { setPlaylistPosition( mPlaylistPosition - 1 ); } } public void nextTrack() { if ( null != mPlaylistCursor ) { setPlaylistPosition( mPlaylistPosition + 1 ); } } public boolean hasNextTrack() { return ( mLoopState != LOOP_NO || mPlaylistPosition < mPlaylistCursor.getCount() - 1 ); } public boolean hasPreviousTrack() { return ( mLoopState != LOOP_NO || mPlaylistPosition > 0 ); } // // Abstracting out collecting information on the current song // // NOTE: PlaylistMediaPlayer expects column 0 of the Cursor to be the ID of the media // public boolean isPlaylistReady() { return !( mPlaylistCursor == null || mPlaylistPosition < 0 || mPlaylistPosition >= mPlaylistSize //|| mPlaylistCursor.isAfterLast() //|| mPlaylistCursor.isBeforeFirst() || mPlaylistCursor.isClosed() ); } public String getCurrentMediaID() { String media_id = null; if ( isPlaylistReady() ) { if ( isShuffling ) { int shufflePosition = mShuffledPlaylist[ mPlaylistPosition ]; mPlaylistCursor.moveToPosition( shufflePosition ); media_id = mPlaylistCursor.getString( 0 ); } else { mPlaylistCursor.moveToPosition( mPlaylistPosition ); media_id = mPlaylistCursor.getString( 0 ); } } return media_id; } public int getTrackPosition() { return mMediaPlayer.getCurrentPosition(); } public int getTrackDuration() { return mMediaPlayer.getDuration(); } public void setPlaylistPosition( int position ) { try { mPlaylistPosition = position; // Is the new position out of bounds? if ( mPlaylistPosition < 0 || mPlaylistPosition >= mPlaylistSize) { // Out of bounds, but looping, so we bring the position back if ( mLoopState == LOOP_ALL ) { mPlaylistPosition = mPlaylistPosition % mPlaylistSize; } else { // Not looping and out of bounds pause(); // Send stopped event if ( null != PlaybackChanged ) { PlaybackChanged.onPlaylistDone(); } return; } } // // NOTE: Calling reset clears the looping, this only affects the LOOP_ONE state. // Nonetheless, we need to set the loop state again after calling reset() // mMediaPlayer.reset(); isPrepared = false; //setLooping( mLoopState ); if ( !isShuffling ) { mPlaylistCursor.moveToPosition( mPlaylistPosition ); mMediaPlayer.setDataSource( mPlaylistCursor.getString( mPlaylistCursor.getColumnIndexOrThrow( MediaStore.Audio.Media.DATA ) ) ); if ( null != PlaybackChanged ) { PlaybackChanged.onTrackChanged( getCurrentMediaID() ); } } else { // Shuffle! int nextInt = mShuffledPlaylist[ mPlaylistPosition ]; mPlaylistCursor.moveToPosition( nextInt ); mMediaPlayer.setDataSource( mPlaylistCursor.getString( mPlaylistCursor.getColumnIndexOrThrow( MediaStore.Audio.Media.DATA ) ) ); if ( null != PlaybackChanged ) { PlaybackChanged.onTrackChanged( getCurrentMediaID() ); } } mMediaPlayer.prepareAsync(); } catch ( Exception e ) { e.printStackTrace(); } } public int getPlaylistPosition() { return mPlaylistPosition; } public void setSeekPosition( int msecs ) { if ( this.isPlaying && mMediaPlayer.isPlaying() ) { mMediaPlayer.pause(); } mMediaPlayer.seekTo( msecs ); if ( isPlaying ) { play(); } } // // Looping / Repeating // // The standard media player has a built-in setLooping function that sets whether the current media will loop or not. // We want to be able to loop the entire playlist. To represent this third state of looping, the LoopState enum // is used. MediaPlayer's setLooping() method still accepts a boolean and PlaylistMediaPlayer's mLoopState is update // with the appropriate LoopState.LOOP_NO or LoopState.LOOP_ONE value. The setLooping() method is overloaded with a // version that accepts a LoopState enum and through this you can set any of the three loop states. // public void setLooping( int state ) { mLoopState = state; if ( null != PlaybackChanged ) { PlaybackChanged.onLoopingChanged( mLoopState ); } } public int getLoopState() { return mLoopState; } // // Shuffling // // To avoid altering the structure of the playlist Cursor, we generate // a list of numbers where the numbers represent the indices of songs // in the cursor. // // For shuffling, we generate a seed number whenever shuffle is turned on // That seed number is used by the algorithm to // // // // Updated: Should not change the play position when turning shuffle on or off // public void setShuffle( boolean shouldShuffle ) { isShuffling = shouldShuffle; if ( isShuffling ) { generateShuffledPlaylist(); } else { mShuffledPlaylist = null; } if ( null != PlaybackChanged ) { PlaybackChanged.onShuffleChanged( shouldShuffle );//( mMediaPlayer.getCurrentPosition() ); } } public boolean isShuffling() { return isShuffling; } private void generateShuffledPlaylist() { int size = mPlaylistSize; mShuffledPlaylist = new int[ mPlaylistSize ]; for ( int i = 0; i < size; i++ ) { mShuffledPlaylist[ i ] = i; } shuffleArray( mShuffledPlaylist ); } // // Implementing Fisher?Yates shuffle void shuffleArray( int[] ar ) { Random rnd = new Random(); for ( int i = ar.length - 1; i > 0; i-- ) { if ( i == mPlaylistPosition) { continue; } int index = rnd.nextInt( i + 1 ); while ( index == mPlaylistPosition) { index = rnd.nextInt( i + 1 ); } // Simple swap int a = ar[ index ]; ar[ index ] = ar[ i ]; ar[ i ] = a; } } // Volume Ducking public void startVolumeDucking() { if ( !isDucking ) { mMediaPlayer.setVolume( 0.4f, 0.4f ); isDucking = true; } } public void stopVolumeDucking() { if ( isDucking ) { mMediaPlayer.setVolume( 1.0f, 1.0f ); } } }