/src/br/com/carlosrafaelgn/fplay/playback/Player.java
Java | 2954 lines | 2477 code | 192 blank | 285 comment | 735 complexity | faee75eb9cc0c9a531b46314c416bc5b MD5 | raw file
Large files files are truncated, but you can click here to view the full file
- //
- // FPlayAndroid is distributed under the FreeBSD License
- //
- // Copyright (c) 2013-2014, Carlos Rafael Gimenes das Neves
- // All rights reserved.
- //
- // Redistribution and use in source and binary forms, with or without
- // modification, are permitted provided that the following conditions are met:
- //
- // 1. Redistributions of source code must retain the above copyright notice, this
- // list of conditions and the following disclaimer.
- // 2. Redistributions in binary form must reproduce the above copyright notice,
- // this list of conditions and the following disclaimer in the documentation
- // and/or other materials provided with the distribution.
- //
- // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
- // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
- // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- //
- // The views and conclusions contained in the software and documentation are those
- // of the authors and should not be interpreted as representing official policies,
- // either expressed or implied, of the FreeBSD Project.
- //
- // https://github.com/carlosrafaelgn/FPlayAndroid
- //
- package br.com.carlosrafaelgn.fplay.playback;
- import android.Manifest;
- import android.annotation.TargetApi;
- import android.app.AlarmManager;
- import android.app.Notification;
- import android.app.NotificationManager;
- import android.app.PendingIntent;
- import android.app.Service;
- import android.content.ComponentName;
- import android.content.Context;
- import android.content.Intent;
- import android.content.IntentFilter;
- import android.content.pm.PackageManager;
- import android.media.AudioManager;
- import android.media.MediaMetadata;
- import android.media.MediaMetadataRetriever;
- import android.media.MediaPlayer;
- import android.media.MediaRouter;
- import android.media.RemoteControlClient;
- import android.media.session.MediaSession;
- import android.media.session.PlaybackState;
- import android.net.ConnectivityManager;
- import android.net.NetworkInfo;
- import android.os.Build;
- import android.os.Handler;
- import android.os.IBinder;
- import android.os.Looper;
- import android.os.Message;
- import android.os.PowerManager;
- import android.os.SystemClock;
- import android.support.annotation.NonNull;
- import android.telephony.TelephonyManager;
- import android.view.KeyEvent;
- import android.widget.RemoteViews;
- import java.io.FileNotFoundException;
- import java.io.IOException;
- import java.util.ArrayList;
- import java.util.HashSet;
- import br.com.carlosrafaelgn.fplay.ExternalReceiver;
- import br.com.carlosrafaelgn.fplay.R;
- import br.com.carlosrafaelgn.fplay.WidgetMain;
- import br.com.carlosrafaelgn.fplay.activity.ActivityHost;
- import br.com.carlosrafaelgn.fplay.activity.ClientActivity;
- import br.com.carlosrafaelgn.fplay.activity.MainHandler;
- import br.com.carlosrafaelgn.fplay.list.FileSt;
- import br.com.carlosrafaelgn.fplay.list.Song;
- import br.com.carlosrafaelgn.fplay.list.SongList;
- import br.com.carlosrafaelgn.fplay.ui.BgListView;
- import br.com.carlosrafaelgn.fplay.ui.UI;
- import br.com.carlosrafaelgn.fplay.util.ArraySorter;
- import br.com.carlosrafaelgn.fplay.util.SerializableMap;
- import br.com.carlosrafaelgn.fplay.visualizer.BluetoothVisualizerControllerJni;
- //
- //MediaPlayer CALLBACKS ARE CALLED ON THE THREAD WHERE THE PLAYER IS CREATED (WHICH
- //IS THE MAIN THREAD, IN OUR CASE) THEREFORE, THERE IS NO NEED TO SYNCHRONIZE CALLS
- //AS LONG AS previous(), play(), playPause() AND next() ARE ALWAYS CALLED FROM THE
- //MAIN THREAD!!!
- //http://stackoverflow.com/questions/3919089/do-callbacks-occur-on-the-main-ui-thread
- //http://stackoverflow.com/questions/11066186/how-does-android-perform-callbacks-on-my-thread
- //
- //
- //Android Audio: Play an MP3 file on an AudioTrack
- //http://mindtherobot.com/blog/624/android-audio-play-an-mp3-file-on-an-audiotrack/
- //
- //Android Audio: Play a WAV file on an AudioTrack
- //http://mindtherobot.com/blog/580/android-audio-play-a-wav-file-on-an-audiotrack/
- //
- //Equalizer
- //http://developer.android.com/reference/android/media/audiofx/Equalizer.html
- //
- //Media Playback
- //http://developer.android.com/guide/topics/media/mediaplayer.html
- //
- //MediaPlayer (including state diagram)
- //http://developer.android.com/reference/android/media/MediaPlayer.html
- //
- //AudioTrack
- //http://developer.android.com/reference/android/media/AudioTrack.html
- //
- //Allowing applications to play nice(r) with each other: Handling remote control buttons
- //http://android-developers.blogspot.com.br/2010/06/allowing-applications-to-play-nicer.html
- //
- //Many properties used to be private, but I decided to make them public, even though some of them
- //still have their setters, after I found out ProGuard does not inline static setters (or at least
- //I have not been able to figure out a way to do so....)
- //************************************************************************************
- //In a few devices, error -38 will happen on the MediaPlayer currently being played
- //when adding breakpoints to Player Core Thread
- //************************************************************************************
- public final class Player extends Service implements AudioManager.OnAudioFocusChangeListener, MediaPlayer.OnErrorListener, MediaPlayer.OnSeekCompleteListener, MediaPlayer.OnPreparedListener, MediaPlayer.OnCompletionListener, MediaPlayer.OnInfoListener, ArraySorter.Comparer<FileSt> {
- public interface PlayerObserver {
- void onPlayerChanged(Song currentSong, boolean songHasChanged, boolean preparingHasChanged, Throwable ex);
- void onPlayerControlModeChanged(boolean controlMode);
- void onPlayerGlobalVolumeChanged(int volume);
- void onPlayerAudioSinkChanged(int audioSink);
- void onPlayerMediaButtonPrevious();
- void onPlayerMediaButtonNext();
- }
- public interface PlayerTurnOffTimerObserver {
- void onPlayerTurnOffTimerTick();
- void onPlayerIdleTurnOffTimerTick();
- }
- public interface PlayerDestroyedObserver {
- void onPlayerDestroyed();
- }
- private static final class TimeoutException extends Exception {
- private static final long serialVersionUID = 4571328670214281144L;
- }
- private static final class MediaServerDiedException extends Exception {
- private static final long serialVersionUID = -902099312236606175L;
- }
- private static final class FocusException extends Exception {
- private static final long serialVersionUID = 6158088015763157546L;
- }
- private static final class PermissionDeniedException extends SecurityException {
- private static final long serialVersionUID = 8743650824658438278L;
- }
- private static final int MSG_UPDATE_STATE = 0x0100;
- private static final int MSG_PAUSE = 0x0101;
- private static final int MSG_PLAYPAUSE = 0x0102;
- private static final int MSG_SEEKTO = 0x0103;
- private static final int MSG_SYNC_VOLUME = 0x0104;
- private static final int MSG_PREPARE_NEXT_SONG = 0x0105;
- private static final int MSG_OVERRIDE_VOLUME_MULTIPLIER = 0x0106;
- private static final int MSG_BECOMING_NOISY = 0x0107;
- private static final int MSG_AUDIO_SINK_CHANGED = 0x0108;
- private static final int MSG_INITIALIZATION_STEP = 0x0109;
- private static final int MSG_NEXT_MAY_HAVE_CHANGED = 0x010A;
- private static final int MSG_LIST_CLEARED = 0x010B;
- private static final int MSG_FOCUS_GAIN_TIMER = 0x010C;
- private static final int MSG_FADE_IN_VOLUME_TIMER = 0x010D;
- private static final int MSG_HEADSET_HOOK_TIMER = 0x010E;
- private static final int MSG_TURN_OFF_TIMER = 0x010F;
- private static final int MSG_IDLE_TURN_OFF_TIMER = 0x0110;
- private static final int MSG_REGISTER_MEDIA_BUTTON_EVENT_RECEIVER = 0x0111;
- private static final int MSG_SONG_LIST_DESERIALIZED = 0x0112;
- private static final int MSG_PRE_PLAY = 0x0113;
- private static final int MSG_POST_PLAY = 0x0114;
- private static final int MSG_ENABLE_EFFECTS = 0x0115;
- private static final int MSG_COMMIT_EQUALIZER = 0x0116;
- private static final int MSG_COMMIT_BASS_BOOST = 0x0117;
- private static final int MSG_COMMIT_VIRTUALIZER = 0x0118;
- private static final int MSG_TURN_OFF_NOW = 0x0119;
- public static final int STATE_NEW = 0;
- public static final int STATE_INITIALIZING = 1;
- public static final int STATE_INITIALIZING_STEP2 = 2;
- public static final int STATE_ALIVE = 3;
- public static final int STATE_TERMINATING = 4;
- public static final int STATE_DEAD = 5;
- public static final int PLAYER_STATE_NEW = 0;
- public static final int PLAYER_STATE_PREPARING = 1;
- public static final int PLAYER_STATE_LOADED = 2;
- public static final int AUDIO_SINK_DEVICE = 1;
- public static final int AUDIO_SINK_WIRE = 2;
- public static final int AUDIO_SINK_BT = 4;
- public static final int VOLUME_MIN_DB = -4000;
- public static final int VOLUME_CONTROL_DB = 0;
- public static final int VOLUME_CONTROL_STREAM = 1;
- public static final int VOLUME_CONTROL_NONE = 2;
- public static final int VOLUME_CONTROL_PERCENT = 3;
- private static final int SILENCE_NORMAL = 0;
- private static final int SILENCE_FOCUS = 1;
- private static final int SILENCE_NONE = -1;
- public static final String ACTION_PREVIOUS = "br.com.carlosrafaelgn.FPlay.PREVIOUS";
- public static final String ACTION_PLAY_PAUSE = "br.com.carlosrafaelgn.FPlay.PLAY_PAUSE";
- public static final String ACTION_NEXT = "br.com.carlosrafaelgn.FPlay.NEXT";
- public static final String ACTION_EXIT = "br.com.carlosrafaelgn.FPlay.EXIT";
- public static String pathToPlayWhenStarting;
- private static String startCommand;
- public static int state;
- private static Thread thread;
- private static Looper looper;
- private static Player thePlayer;
- private static Handler handler, localHandler;
- private static AudioManager audioManager;
- private static NotificationManager notificationManager;
- private static TelephonyManager telephonyManager;
- public static final SongList songs = SongList.getInstance();
- //keep these instances here to prevent UI, MainHandler, Equalizer, BassBoost,
- //and Virtualizer classes from being garbage collected...
- public static MainHandler theMainHandler;
- public static final UI theUI = new UI();
- @SuppressWarnings("unused")
- public static final Equalizer theEqualizer = new Equalizer();
- @SuppressWarnings("unused")
- public static final BassBoost theBassBoost = new BassBoost();
- @SuppressWarnings("unused")
- public static final Virtualizer theVirtualizer = new Virtualizer();
- public static boolean hasFocus;
- public static int volumeStreamMax = 15, volumeControlType;
- private static boolean volumeDimmed;
- private static int volumeDB, volumeDBFading, silenceMode;
- private static float volumeMultiplier;
- private static int audioSink, audioSinkBeforeFocusLoss;
- private static int storedSongTime, howThePlayerStarted, playerState, nextPlayerState;
- private static boolean resumePlaybackAfterFocusGain, postPlayPending, playing, playerBuffering, playAfterSeeking, prepareNextAfterSeeking, nextAlreadySetForPlaying, reviveAlreadyTried;
- private static Song song, nextSong, songScheduledForPreparation, nextSongScheduledForPreparation, songWhenFirstErrorHappened;
- private static MediaPlayer player, nextPlayer;
- //keep these three fields here, instead of in ActivityMain/ActivityBrowser,
- //so they will survive their respective activity's destruction
- //(and even the class garbage collection)
- public static int lastCurrent = -1, listFirst = -1, listTop = 0, positionToSelect = -1;
- public static boolean isMainActiveOnTop, alreadySelected;
- //These are only set in the main thread
- private static int localVolumeStream, localPlayerState;
- public static int localVolumeDB;
- public static boolean localPlaying;
- public static Song localSong;
- public static MediaPlayer localPlayer;
- private static class CoreHandler extends Handler {
- @Override
- public void dispatchMessage(@NonNull Message msg) {
- switch (msg.what) {
- case MSG_POST_PLAY:
- _postPlay(msg.arg1, (Song[])msg.obj);
- break;
- case MSG_BECOMING_NOISY:
- if (playing)
- _playPause();
- //this cleanup must be done, as sometimes, when changing between two output types,
- //the effects are lost...
- _fullCleanup();
- _checkAudioSink(false, false);
- break;
- case MSG_FADE_IN_VOLUME_TIMER:
- _processFadeInVolumeTimer(msg.arg1);
- break;
- case MSG_AUDIO_SINK_CHANGED:
- _checkAudioSink(msg.arg1 != 0, true);
- break;
- case MSG_PAUSE:
- if (playing)
- _playPause();
- break;
- case MSG_PLAYPAUSE:
- _playPause();
- break;
- case MSG_SEEKTO:
- _seekTo(msg.arg1);
- break;
- case MSG_SYNC_VOLUME:
- _syncVolume();
- break;
- case MSG_PREPARE_NEXT_SONG:
- _prepareNextPlayer((Song)msg.obj);
- break;
- case MSG_FOCUS_GAIN_TIMER:
- _processFocusGainTimer();
- break;
- case MSG_OVERRIDE_VOLUME_MULTIPLIER:
- _overrideVolumeMultiplier(msg.arg1 != 0);
- break;
- case MSG_LIST_CLEARED:
- _fullCleanup();
- song = null;
- storedSongTime = -1;
- _updateState(false, null);
- break;
- case MSG_NEXT_MAY_HAVE_CHANGED:
- _nextMayHaveChanged((Song)msg.obj);
- break;
- case MSG_ENABLE_EFFECTS:
- _enableEffects(msg.arg1, (Runnable)msg.obj);
- break;
- case MSG_COMMIT_EQUALIZER:
- Equalizer.commit(msg.arg1);
- break;
- case MSG_COMMIT_BASS_BOOST:
- BassBoost.commit();
- break;
- case MSG_COMMIT_VIRTUALIZER:
- Virtualizer.commit();
- break;
- case MSG_SONG_LIST_DESERIALIZED:
- _songListDeserialized(msg.obj);
- break;
- }
- }
- }
- private static class CoreLocalHandler extends Handler {
- @Override
- public void dispatchMessage(@NonNull Message msg) {
- switch (msg.what) {
- case MSG_PRE_PLAY:
- prePlay(msg.arg1);
- break;
- case MSG_AUDIO_SINK_CHANGED:
- if (observer != null)
- observer.onPlayerAudioSinkChanged(audioSink);
- break;
- case MSG_UPDATE_STATE:
- updateState(msg.arg1, msg.arg2, (Song)msg.obj);
- break;
- case MSG_REGISTER_MEDIA_BUTTON_EVENT_RECEIVER:
- registerMediaButtonEventReceiver();
- break;
- case MSG_INITIALIZATION_STEP:
- switch (state) {
- case STATE_INITIALIZING:
- state = STATE_INITIALIZING_STEP2;
- break;
- case STATE_INITIALIZING_STEP2:
- state = STATE_ALIVE;
- handler.sendMessageAtTime(Message.obtain(handler, MSG_OVERRIDE_VOLUME_MULTIPLIER, (volumeControlType != VOLUME_CONTROL_DB && volumeControlType != VOLUME_CONTROL_PERCENT) ? 1 : 0, 0), SystemClock.uptimeMillis());
- setTurnOffTimer(turnOffTimerSelectedMinutes);
- setIdleTurnOffTimer(idleTurnOffTimerSelectedMinutes);
- executeStartCommand();
- break;
- }
- break;
- case MSG_HEADSET_HOOK_TIMER:
- processHeadsetHookTimer();
- break;
- case MSG_TURN_OFF_TIMER:
- processTurnOffTimer();
- break;
- case MSG_IDLE_TURN_OFF_TIMER:
- idleTurnOffTimerSent = false;
- processIdleTurnOffTimer();
- break;
- case MSG_TURN_OFF_NOW:
- stopService(false);
- break;
- }
- }
- }
- @Override
- public void onCreate() {
- thePlayer = this;
- startService(this);
- startForeground(1, getNotification());
- super.onCreate();
- }
- @Override
- public void onDestroy() {
- super.onDestroy();
- System.exit(0);
- }
- private static void executeStartCommand() {
- if (state == STATE_ALIVE) {
- if (pathToPlayWhenStarting != null) {
- if (songs.addPathAndForceScrollIntoView(pathToPlayWhenStarting, true))
- startCommand = null;
- pathToPlayWhenStarting = null;
- }
- if (startCommand != null) {
- switch (startCommand) {
- case ACTION_PREVIOUS:
- previous();
- break;
- case ACTION_PLAY_PAUSE:
- playPause();
- break;
- case ACTION_NEXT:
- next();
- break;
- case ACTION_EXIT:
- stopService(false);
- break;
- }
- startCommand = null;
- }
- }
- }
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- if (intent != null) {
- final String command = intent.getAction();
- if (command != null) {
- startCommand = command;
- executeStartCommand();
- }
- }
- return START_STICKY;
- }
- @Override
- public IBinder onBind(Intent intent) {
- return null;
- }
- public static Service getService() {
- return thePlayer;
- }
- public static boolean startService(Context context) {
- final boolean stateNew = (state == STATE_NEW);
- if (stateNew) {
- MainHandler.initialize();
- alreadySelected = true; //fix the initial selection when the app is started from the widget
- state = STATE_INITIALIZING;
- context.startService(new Intent(context, Player.class));
- theMainHandler = MainHandler.initialize();
- localHandler = new CoreLocalHandler();
- notificationManager = (NotificationManager)context.getSystemService(NOTIFICATION_SERVICE);
- audioManager = (AudioManager)context.getSystemService(AUDIO_SERVICE);
- telephonyManager = (TelephonyManager)context.getSystemService(TELEPHONY_SERVICE);
- destroyedObservers = new ArrayList<>(4);
- stickyBroadcast = new Intent();
- loadConfig(context);
- }
- //when the player starts from the widget, startService is not called twice, it is
- //called only once, from within onCreate, when thePlayer is already != null
- if (thePlayer != null && thread == null) {
- createIntents(context);
- //These broadcast actions are registered here, instead of in the manifest file,
- //because a few tests showed that some devices will produce the notifications
- //faster this way, specially AUDIO_BECOMING_NOISY. Moreover, by registering here,
- //only one BroadcastReceiver is instantiated and used throughout the entire
- //application's lifecycle, whereas when the manifest is used, a different instance
- //is created to handle every notification! :)
- //The only exception is the MEDIA_BUTTON broadcast action, which MUST BE declared
- //in the application manifest according to the documentation of the method
- //registerMediaButtonEventReceiver!!! :(
- final IntentFilter filter = new IntentFilter("android.media.AUDIO_BECOMING_NOISY");
- //filter.addAction("android.intent.action.MEDIA_BUTTON");
- filter.addAction("android.intent.action.CALL_BUTTON");
- filter.addAction("android.intent.action.HEADSET_PLUG");
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH)
- filter.addAction("android.media.ACTION_SCO_AUDIO_STATE_UPDATED");
- else
- filter.addAction("android.media.SCO_AUDIO_STATE_CHANGED");
- filter.addAction("android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED");
- filter.addAction("android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED");
- //HEADSET_STATE_CHANGED is based on: https://groups.google.com/forum/#!topic/android-developers/pN2k5_kFo4M
- filter.addAction("android.bluetooth.intent.action.HEADSET_STATE_CHANGED");
- externalReceiver = new ExternalReceiver();
- thePlayer.getApplicationContext().registerReceiver(externalReceiver, filter);
- registerMediaButtonEventReceiver();
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
- registerMediaRouter(thePlayer);
- thread = new Thread("Player Core Thread") {
- @Override
- public void run() {
- Looper.prepare();
- looper = Looper.myLooper();
- handler = new CoreHandler();
- _initializePlayers();
- Equalizer.initialize(player.getAudioSessionId());
- Equalizer.release();
- BassBoost.initialize(player.getAudioSessionId());
- BassBoost.release();
- Virtualizer.initialize(player.getAudioSessionId());
- Virtualizer.release();
- if (Equalizer.isEnabled()) {
- Equalizer.initialize(player.getAudioSessionId());
- Equalizer.setEnabled(true);
- }
- if (BassBoost.isEnabled()) {
- BassBoost.initialize(player.getAudioSessionId());
- BassBoost.setEnabled(true);
- }
- if (Virtualizer.isEnabled()) {
- Virtualizer.initialize(player.getAudioSessionId());
- Virtualizer.setEnabled(true);
- }
- _checkAudioSink(false, false);
- localHandler.sendEmptyMessageAtTime(MSG_INITIALIZATION_STEP, SystemClock.uptimeMillis());
- Looper.loop();
- if (song != null) {
- _storeSongTime();
- song = null;
- } else {
- storedSongTime = -1;
- }
- _fullCleanup();
- hasFocus = false;
- if (audioManager != null && thePlayer != null)
- audioManager.abandonAudioFocus(thePlayer);
- }
- };
- thread.start();
- while (handler == null)
- Thread.yield();
- songs.startDeserializing(thePlayer, null, true, false, false);
- }
- //fix the initial selection when the app is started from the widget
- alreadySelected = false;
- positionToSelect = songs.getCurrentPosition();
- return stateNew;
- }
- public static void stopService(boolean restart) {
- if (state != STATE_ALIVE)
- return;
- state = STATE_TERMINATING;
- looper.quit();
- try {
- thread.join();
- } catch (Throwable ex) {
- ex.printStackTrace();
- }
- if (bluetoothVisualizerController != null)
- stopBluetoothVisualizer();
- unregisterMediaButtonEventReceiver();
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && thePlayer != null)
- unregisterMediaRouter(thePlayer);
- if (destroyedObservers != null) {
- for (int i = destroyedObservers.size() - 1; i >= 0; i--)
- destroyedObservers.get(i).onPlayerDestroyed();
- destroyedObservers.clear();
- destroyedObservers = null;
- }
- if (thePlayer != null) {
- if (externalReceiver != null)
- thePlayer.getApplicationContext().unregisterReceiver(externalReceiver);
- saveConfig(thePlayer, true);
- }
- for (int i = statePlayer.length - 1; i >= 0; i--) {
- statePlayer[i] = null;
- stateEx[i] = null;
- }
- updateState(~0x27, 0, null);
- if (restart && intentActivityHost != null) {
- //http://stackoverflow.com/questions/6609414/howto-programatically-restart-android-app
- ((AlarmManager)thePlayer.getSystemService(ALARM_SERVICE)).set(AlarmManager.RTC, System.currentTimeMillis() + 300, intentActivityHost);
- }
- thread = null;
- observer = null;
- turnOffTimerObserver = null;
- theMainHandler = null;
- looper = null;
- handler = null;
- localHandler = null;
- notification = null;
- notificationRemoteViews = null;
- notificationManager = null;
- audioManager = null;
- telephonyManager = null;
- externalReceiver = null;
- stickyBroadcast = null;
- intentActivityHost = null;
- intentPrevious = null;
- intentPlayPause = null;
- intentNext = null;
- intentExit = null;
- favoriteFolders.clear();
- localSong = null;
- localPlayer = null;
- stateLastSong = null;
- state = STATE_DEAD;
- if (thePlayer != null) {
- thePlayer.stopForeground(true);
- thePlayer.stopSelf();
- thePlayer = null;
- } else {
- System.exit(0);
- }
- }
- private static void prePlay(int how) {
- if (state != STATE_ALIVE || postPlayPending)
- return;
- localSong = songs.getSongAndSetCurrent(how);
- final Song[] songArray = new Song[] { localSong, songs.possibleNextSong };
- songs.possibleNextSong = null;
- postPlayPending = true;
- handler.sendMessageAtTime(Message.obtain(handler, MSG_POST_PLAY, how, 0, songArray), SystemClock.uptimeMillis());
- }
- public static void previous() {
- prePlay(SongList.HOW_PREVIOUS);
- }
- public static void play(int index) {
- prePlay(index);
- }
- public static void pause() {
- if (state != STATE_ALIVE)
- return;
- handler.sendMessageAtTime(Message.obtain(handler, MSG_PAUSE), SystemClock.uptimeMillis());
- }
- public static void playPause() {
- if (state != STATE_ALIVE)
- return;
- handler.sendMessageAtTime(Message.obtain(handler, MSG_PLAYPAUSE), SystemClock.uptimeMillis());
- }
- public static void next() {
- prePlay(SongList.HOW_NEXT_MANUAL);
- }
- public static void seekTo(int timeMS) {
- if (state != STATE_ALIVE)
- return;
- handler.sendMessageAtTime(Message.obtain(handler, MSG_SEEKTO, timeMS, 0), SystemClock.uptimeMillis());
- }
- public static void showStreamVolumeUI() {
- try {
- if (audioManager != null)
- audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, AudioManager.ADJUST_SAME, AudioManager.FLAG_SHOW_UI);
- } catch (Throwable ex) {
- ex.printStackTrace();
- }
- }
- public static int setVolume(int volume) {
- if (state != STATE_ALIVE)
- return Integer.MIN_VALUE;
- switch (volumeControlType) {
- case VOLUME_CONTROL_STREAM:
- if (volume < 0)
- volume = 0;
- else if (volume > volumeStreamMax)
- volume = volumeStreamMax;
- localVolumeStream = volume;
- //apparently a few devices don't like to have the streamVolume changed from another thread....
- //maybe there is another reason for why it fails... I just haven't found yet :(
- try {
- if (audioManager != null)
- audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
- } catch (Throwable ex) {
- ex.printStackTrace();
- }
- return volume;
- case VOLUME_CONTROL_DB:
- case VOLUME_CONTROL_PERCENT:
- if (volume <= VOLUME_MIN_DB)
- volume = VOLUME_MIN_DB;
- else if (volume > 0)
- volume = 0;
- if (localVolumeDB == volume)
- return Integer.MIN_VALUE;
- localVolumeDB = volume;
- break;
- default:
- showStreamVolumeUI();
- return Integer.MIN_VALUE;
- }
- handler.sendEmptyMessageAtTime(MSG_SYNC_VOLUME, SystemClock.uptimeMillis());
- return volume;
- }
- public static int decreaseVolume() {
- switch (volumeControlType) {
- case VOLUME_CONTROL_STREAM:
- return setVolume(audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) - 1);
- case VOLUME_CONTROL_DB:
- case VOLUME_CONTROL_PERCENT:
- //http://stackoverflow.com/a/4413073/3569421
- //(a/b)*b+(a%b) is equal to a
- //-350 % 200 = -150
- final int leftover = (Player.volumeDB % 200);
- return setVolume(Player.volumeDB + ((leftover == 0) ? -200 : (-200 - leftover)));
- }
- showStreamVolumeUI();
- return Integer.MIN_VALUE;
- }
- public static int increaseVolume() {
- switch (volumeControlType) {
- case VOLUME_CONTROL_STREAM:
- return setVolume(audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) + 1);
- case VOLUME_CONTROL_DB:
- case VOLUME_CONTROL_PERCENT:
- //http://stackoverflow.com/a/4413073/3569421
- //(a/b)*b+(a%b) is equal to a
- //-350 % 200 = -150
- final int leftover = (Player.volumeDB % 200);
- return setVolume(Player.volumeDB + ((leftover == 0) ? 200 : -leftover));
- }
- showStreamVolumeUI();
- return Integer.MIN_VALUE;
- }
- public static int getSystemStreamVolume() {
- try {
- if (audioManager != null)
- return audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
- } catch (Throwable ex) {
- ex.printStackTrace();
- }
- return 0;
- }
- public static void setVolumeInPercentage(int percentage) {
- switch (volumeControlType) {
- case VOLUME_CONTROL_STREAM:
- setVolume((percentage * volumeStreamMax) / 100);
- break;
- case VOLUME_CONTROL_DB:
- case VOLUME_CONTROL_PERCENT:
- setVolume((percentage >= 100) ? 0 : ((percentage <= 0) ? VOLUME_MIN_DB : (((100 - percentage) * VOLUME_MIN_DB) / 100)));
- break;
- }
- }
- public static int getVolumeInPercentage() {
- switch (volumeControlType) {
- case VOLUME_CONTROL_STREAM:
- final int vol = getSystemStreamVolume();
- return ((vol <= 0) ? 0 : ((vol >= volumeStreamMax) ? 100 : ((vol * 100) / volumeStreamMax)));
- case VOLUME_CONTROL_DB:
- case VOLUME_CONTROL_PERCENT:
- return ((localVolumeDB >= 0) ? 100 : ((localVolumeDB <= VOLUME_MIN_DB) ? 0 : (((VOLUME_MIN_DB - localVolumeDB) * 100) / VOLUME_MIN_DB)));
- }
- return 0;
- }
- public static void setVolumeControlType(int volumeControlType) {
- switch (volumeControlType) {
- case VOLUME_CONTROL_DB:
- case VOLUME_CONTROL_PERCENT:
- case VOLUME_CONTROL_NONE:
- Player.volumeControlType = volumeControlType;
- if (handler != null)
- handler.sendMessageAtTime(Message.obtain(handler, MSG_OVERRIDE_VOLUME_MULTIPLIER, (volumeControlType == VOLUME_CONTROL_NONE) ? 1 : 0, 0), SystemClock.uptimeMillis());
- break;
- default:
- try {
- if (audioManager != null)
- volumeStreamMax = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
- if (volumeStreamMax < 1)
- volumeStreamMax = 1;
- if (handler != null)
- handler.sendMessageAtTime(Message.obtain(handler, MSG_OVERRIDE_VOLUME_MULTIPLIER, 1, 0), SystemClock.uptimeMillis());
- Player.volumeControlType = VOLUME_CONTROL_STREAM;
- } catch (Throwable ex) {
- Player.volumeControlType = VOLUME_CONTROL_NONE;
- ex.printStackTrace();
- }
- break;
- }
- }
- public static void enableEffects(boolean equalizer, boolean bassBoost, boolean virtualizer, Runnable callback) {
- if (state != STATE_ALIVE)
- return;
- handler.sendMessageAtTime(Message.obtain(handler, MSG_ENABLE_EFFECTS, (equalizer ? 1 : 0) | (bassBoost ? 2 : 0) | (virtualizer ? 4 : 0), 0, callback), SystemClock.uptimeMillis());
- }
- public static void commitEqualizer(int band) {
- if (state != STATE_ALIVE)
- return;
- handler.sendMessageAtTime(Message.obtain(handler, MSG_COMMIT_EQUALIZER, band, 0), SystemClock.uptimeMillis());
- }
- public static void commitBassBoost() {
- if (state != STATE_ALIVE)
- return;
- handler.sendEmptyMessageAtTime(MSG_COMMIT_BASS_BOOST, SystemClock.uptimeMillis());
- }
- public static void commitVirtualizer() {
- if (state != STATE_ALIVE)
- return;
- handler.sendEmptyMessageAtTime(MSG_COMMIT_VIRTUALIZER, SystemClock.uptimeMillis());
- }
- public static boolean isPreparing() {
- return (localPlayerState == PLAYER_STATE_PREPARING || playerBuffering);
- }
- public static int getPosition() {
- return (((localPlayer != null) && playerState == PLAYER_STATE_LOADED) ? localPlayer.getCurrentPosition() : ((localSong == null) ? -1 : storedSongTime));
- }
- public static int getAudioSessionId() {
- return ((localPlayer == null) ? -1 : localPlayer.getAudioSessionId());
- }
- private static MediaPlayer _createPlayer() {
- MediaPlayer mp = new MediaPlayer();
- mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
- mp.setOnErrorListener(thePlayer);
- mp.setOnPreparedListener(thePlayer);
- mp.setOnSeekCompleteListener(thePlayer);
- mp.setOnCompletionListener(thePlayer);
- mp.setOnInfoListener(thePlayer);
- mp.setWakeMode(thePlayer, PowerManager.PARTIAL_WAKE_LOCK);
- return mp;
- }
- private static void _startPlayer() {
- playAfterSeeking = false;
- playing = true;
- //when dimmed, decreased the volume by 20dB
- float multiplier = (volumeDimmed ? (volumeMultiplier * 0.1f) : volumeMultiplier);
- if (silenceMode != SILENCE_NONE) {
- final int increment = ((silenceMode == SILENCE_FOCUS) ? fadeInIncrementOnFocus : ((howThePlayerStarted == SongList.HOW_CURRENT) ? fadeInIncrementOnPause : fadeInIncrementOnOther));
- if (increment > 30) {
- volumeDBFading = VOLUME_MIN_DB;
- multiplier = 0;
- handler.sendMessageAtTime(Message.obtain(handler, MSG_FADE_IN_VOLUME_TIMER, increment, 0), SystemClock.uptimeMillis() + 50);
- }
- silenceMode = SILENCE_NONE;
- }
- if (player != null) {
- player.setVolume(multiplier, multiplier);
- player.start();
- }
- reviveAlreadyTried = false;
- }
- private static void _releasePlayer(MediaPlayer mediaPlayer) {
- mediaPlayer.setOnErrorListener(null);
- mediaPlayer.setOnPreparedListener(null);
- mediaPlayer.setOnSeekCompleteListener(null);
- mediaPlayer.setOnCompletionListener(null);
- mediaPlayer.setOnInfoListener(null);
- mediaPlayer.reset();
- mediaPlayer.release();
- }
- @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
- private static void _clearNextPlayer() {
- //there is no need to call setNextMediaPlayer(null) after calling reset()
- try {
- player.setNextMediaPlayer(null);
- } catch (Throwable ex) {
- ex.printStackTrace();
- }
- }
- @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
- private static void _setNextPlayer() {
- try {
- if (player != null) {
- player.setNextMediaPlayer(nextPlayer);
- nextAlreadySetForPlaying = true;
- }
- } catch (Throwable ex) {
- ex.printStackTrace();
- }
- }
- private static void _partialCleanup() {
- playerState = PLAYER_STATE_NEW;
- playerBuffering = false;
- if (player != null) {
- _releasePlayer(player);
- player = null;
- }
- nextAlreadySetForPlaying = false;
- nextPlayerState = PLAYER_STATE_NEW;
- if (nextPlayer != null) {
- _releasePlayer(nextPlayer);
- nextPlayer = null;
- }
- playing = false;
- playAfterSeeking = false;
- prepareNextAfterSeeking = false;
- resumePlaybackAfterFocusGain = false;
- songScheduledForPreparation = null;
- nextSongScheduledForPreparation = null;
- if (handler != null) {
- handler.removeMessages(MSG_FOCUS_GAIN_TIMER);
- handler.removeMessages(MSG_FADE_IN_VOLUME_TIMER);
- }
- if (localHandler != null)
- localHandler.removeMessages(MSG_HEADSET_HOOK_TIMER);
- }
- private static void _fullCleanup() {
- _partialCleanup();
- silenceMode = SILENCE_NORMAL;
- nextSong = null;
- postPlayPending = false;
- Equalizer.release();
- BassBoost.release();
- Virtualizer.release();
- }
- private static void _initializePlayers() {
- if (player == null) {
- player = _createPlayer();
- if (nextPlayer != null) {
- player.setAudioSessionId(nextPlayer.getAudioSessionId());
- } else {
- nextPlayer = _createPlayer();
- nextPlayer.setAudioSessionId(player.getAudioSessionId());
- }
- } else if (nextPlayer == null) {
- nextPlayer = _createPlayer();
- nextPlayer.setAudioSessionId(player.getAudioSessionId());
- }
- }
- private static boolean _requestFocus() {
- if (thePlayer == null || audioManager == null || audioManager.requestAudioFocus(thePlayer, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN) != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
- hasFocus = false;
- return false;
- }
- localHandler.sendEmptyMessageAtTime(MSG_REGISTER_MEDIA_BUTTON_EVENT_RECEIVER, SystemClock.uptimeMillis());
- hasFocus = true;
- volumeDimmed = false;
- return true;
- }
- private static void _storeSongTime() {
- try {
- if (song == null || song.isHttp)
- storedSongTime = -1;
- else if (player != null && playerState == PLAYER_STATE_LOADED)
- storedSongTime = player.getCurrentPosition();
- } catch (Throwable ex) {
- ex.printStackTrace();
- }
- }
- private static void _prepareNextPlayer(Song song) {
- try {
- if (song == nextSongScheduledForPreparation && nextPlayer != null) {
- //Even though it happens very rarely, a few devices will freeze and produce an ANR
- //when calling setDataSource from the main thread :(
- nextPlayer.setDataSource(nextSongScheduledForPreparation.path);
- nextPlayer.prepareAsync();
- nextPlayerState = PLAYER_STATE_PREPARING;
- }
- } catch (Throwable ex) {
- nextPlayerState = PLAYER_STATE_NEW;
- ex.printStackTrace();
- }
- }
- private static void _scheduleNextPlayerForPreparation() {
- nextAlreadySetForPlaying = false;
- prepareNextAfterSeeking = false;
- nextPlayerState = PLAYER_STATE_NEW;
- if (handler != null && song != null && nextSong != null && !nextSong.isHttp && nextPreparationEnabled && song.lengthMS > 10000 && nextSong.lengthMS > 10000) {
- handler.removeMessages(MSG_PREPARE_NEXT_SONG);
- nextSongScheduledForPreparation = nextSong;
- handler.sendMessageAtTime(Message.obtain(handler, MSG_PREPARE_NEXT_SONG, nextSong), SystemClock.uptimeMillis() + 5000);
- }
- }
- private static void _handleFailure(Throwable ex, boolean checkForPermission) {
- if (checkForPermission) {
- checkForPermission = false;
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- if (thePlayer.checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)
- checkForPermission = true;
- }
- }
- if (songWhenFirstErrorHappened == song || howThePlayerStarted != SongList.HOW_NEXT_AUTO || checkForPermission) {
- songWhenFirstErrorHappened = null;
- _updateState(false, checkForPermission ? new PermissionDeniedException() : ex);
- } else {
- if (songWhenFirstErrorHappened == null)
- songWhenFirstErrorHappened = song;
- _updateState(false, null);
- localHandler.sendMessageAtTime(Message.obtain(localHandler, MSG_PRE_PLAY, SongList.HOW_NEXT_AUTO, 0), SystemClock.uptimeMillis());
- }
- }
- private static void _postPlay(int how, Song[] songArray) {
- if (state != STATE_ALIVE)
- return;
- song = songArray[0];
- if (!hasFocus && !_requestFocus()) {
- _fullCleanup();
- _updateState(false, new FocusException());
- return;
- }
- //we must set this to false here, as the user could have manually
- //started playback before the focus timer had the chance to trigger
- resumePlaybackAfterFocusGain = false;
- if (song == null) {
- storedSongTime = -1;
- songWhenFirstErrorHappened = null;
- _fullCleanup();
- _updateState(false, null);
- return;
- }
- try {
- howThePlayerStarted = how;
- playerBuffering = false;
- if (playerState == PLAYER_STATE_PREPARING)
- //probably the user is changing songs too fast!
- _partialCleanup();
- if (player == null || nextPlayer == null) {
- _initializePlayers();
- //don't even ask.......
- //(a few devices won't disable one effect while the other effect is enabled)
- Equalizer.release();
- BassBoost.release();
- Virtualizer.release();
- final int sessionId = player.getAudioSessionId();
- if (Equalizer.isEnabled()) {
- Equalizer.initialize(sessionId);
- Equalizer.setEnabled(true);
- }
- if (BassBoost.isEnabled()) {
- BassBoost.initialize(sessionId);
- BassBoost.setEnabled(true);
- }
- if (Virtualizer.isEnabled()) {
- Virtualizer.initialize(sessionId);
- Virtualizer.setEnabled(true);
- }
- }
- player.reset();
- postPlayPending = false;
- if (nextSong == song && how != SongList.HOW_CURRENT) {
- storedSongTime = -1;
- if (nextPlayerState == PLAYER_STATE_LOADED) {
- playerState = PLAYER_STATE_LOADED;
- final MediaPlayer p = player;
- player = nextPlayer;
- nextPlayer = p;
- nextSong = songArray[1];
- if (!nextAlreadySetForPlaying || how != SongList.HOW_NEXT_AUTO) {
- _startPlayer();
- } else {
- playing = true;
- //when dimmed, decreased the volume by 20dB
- final float multiplier = (volumeDimmed ? (volumeMultiplier * 0.1f) : volumeMultiplier);
- player.setVolume(multiplier, multiplier);
- }
- songWhenFirstErrorHappened = null;
- _scheduleNextPlayerForPreparation();
- _updateState(false, null);
- return;
- } else {
- //just wait for the next song to finish preparing
- if (nextPlayerState == PLAYER_STATE_PREPARING) {
- playing = false;
- playerState = PLAYER_STATE_PREPARING;
- nextPlayerState = PLAYER_STATE_NEW;
- final MediaPlayer p = player;
- player = nextPlayer;
- nextPlayer = p;
- nextSong = songArray[1];
- _updateState(false, null);
- return;
- }
- }
- }
- playing = false;
- playerState = PLAYER_STATE_PREPARING;
- if (how != SongList.HOW_CURRENT)
- storedSongTime = -1;
- if (song.path == null || song.path.length() == 0)
- throw new IOException();
- songScheduledForPreparation = song;
- //Even though it happens very rarely, a few devices will freeze and produce an ANR
- //when calling setDataSource from the main thread :(
- player.setDataSource(song.path);
- player.prepareAsync();
- nextPlayerState = PLAYER_STATE_NEW;
- nextPlayer.reset();
- nextSong = songArray[1];
- nextSongScheduledForPreparation = null;
- if (nextPreparationEnabled)
- handler.removeMessages(MSG_PREPARE_NEXT_SONG);
- _updateState(false, null);
- } catch (Throwable ex) {
- _fullCleanup();
- //clear the flag here to allow _handleFailure to move to the next song
- _handleFailure(ex, true);
- }
- }
- private static void _playPause() {
- if (state != STATE_ALIVE || playerState == PLAYER_STATE_PREPARING)
- return;
- try {
- //we must set this to false here, as the user could have manually
- //started playback before the focus timer had the chance to trigger
- resumePlaybackAfterFocusGain = false;
- if (playing) {
- playing = false;
- player.pause();
- silenceMode = SILENCE_NORMAL;
- _storeSongTime();
- _updateState(false, null);
- } else {
- if (song == null || playerState == PLAYER_STATE_NEW || !hasFocus) {
- localHandler.sendMessageAtTime(Message.obtain(localHandler, MSG_PRE_PLAY, SongList.HOW_CURRENT, 0), SystemClock.uptimeMillis());
- } else {
- howThePlayerStarted = SongList.HOW_CURRENT;
- _startPlayer();
- _updateState(false, null);
- }
- }
- } catch (Throwable ex) {
- _fullCleanup();
- _updateState(false, ex);
- }
- }
- private static void _seekTo(int timeMS) {
- if (state != STATE_ALIVE || playerState == PLAYER_STATE_PREPARING)
- return;
- storedSongTime = timeMS;
- if (playerState == PLAYER_STATE_LOADED) {
- playAfterSeeking = playing;
- playerState = PLAYER_STATE_PREPARING;
- try {
- if (playing) {
- playing = false;
- player.pause();
- }
- player.seekTo(timeMS);
- _updateState(false, null);
- } catch (Throwable ex) {
- _fullCleanup();
- _updateState(false, ex);
- }
- }
- }
- private static void _processFadeInVolumeTimer(int increment) {
- volumeDBFading += increment;
- //magnitude = 10 ^ (dB/20)
- //x^p = a ^ (p * log a (x))
- //10^p = e ^ (p * log e (10))
- float multiplier = (float)Math.exp((double)volumeDBFading * 2.3025850929940456840179914546844 / 2000.0);
- boolean send = true;
- if (multiplier >= volumeMultiplier) {
- multiplier = volumeMultiplier;
- send = false;
- }
- //when dimmed, decreased the volume by 20dB
- if (volumeDimmed)
- multiplier *= 0.1f;
- if (handler != null && hasFocus && player != null && playerState == PLAYER_STATE_LOADED) {
- try {
- player.setVolume(multiplier, multiplier);
- } catch (Throwable ex) {
- ex.printStackTrace();
- }
- if (send)
- handler.sendMessageAtTime(Message.obtain(handler, MSG_FADE_IN_VOLUME_TIMER, increment, 0), SystemClock.uptimeMillis() + 50);
- }
- }
- private static void _syncVolume() {
- if (state != STATE_ALIVE)
- return;
- if (volumeControlType == VOLUME_CONTROL_STREAM) {
- final int volumeStream = localVolumeStream;
- try {
- if (audioManager != null)
- audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, ((volumeStream <= 0) ? 0 : ((volumeStream >= volumeStreamMax) ? volumeStreamMax : volumeStream)), 0);
- } catch (Throwable ex) {
- ex.printStackTrace();
- }
- return;
- }
- if (volumeDB == localVolumeDB)
- return;
- volumeDB = localVolumeDB;
- volumeMultiplier = ((volumeDB <= VOLUME_MIN_DB) ? 0.0f : ((volumeDB >= 0) ? 1.0f : (float)Math.exp((double)volumeDB * 2.3025850929940456840179914546844 / 2000.0)));
- //when dimmed, decreased the volume by 20dB
- final float multiplier = (volumeDimmed ? (volumeMultiplier * 0.1f) : volumeMultiplier);
- if (player != null) {
- try {
- player.setVolume(multiplier, multiplier);
- } catch (Throwable ex) {
- ex.printStackTrace();
- }
- }
- if (nextPlayer != null) {
- try {
- nextPlayer.setVolume(multiplier, multiplier);
- } catch (Throwable ex) {
- ex.printStackTrace();
- }
- }
- }
- private static void _overrideVolumeMultiplier(boolean toMax) {
- if (state != STATE_ALIVE)
- return;
- volumeMultiplier = ((toMax || (volumeDB >= 0)) ? 1.0f : ((volumeDB <= VOLUME_MIN_DB) ? 0.0f : (float)Math.exp((double)volumeDB * 2.3025850929940456840179914546844 / 2000.0)));
- //when dimmed, decreased the volume by 20dB
- final float multiplier = (volumeDimmed ? (volumeMultiplier * 0.1f) : volumeMultiplier);
- if (player != null) {
- try {
- player.setVolume(multiplier, multiplier);
- } catch (Throwable ex) {
- ex.printStackTrace();
- }
- }
- if (nextPlayer != null) {
- try {
- nextPlayer.setVolume(multiplier, multiplier);
- } catch (Throwable ex) {
- ex.printStackTrace();
- }
- }
- }
- private static void _processFocusGainTimer() {
- if (state != STATE_ALIVE || !hasFocus)
- return;
- //someone else may have changed our values if the engine is shared
- localHandler.sendEmptyMessageAtTime(MSG_REGISTER_MEDIA_BUTTON_EVENT_RECEIVER, SystemClock.uptimeMillis());
- if (resumePlaybackAfterFocusGain) {
- //do not restart playback in scenarios like this (it really scares people!):
- //the person has answered a call, removed the headset, ended the call without
- //the headset plugged in, and then the focus came back to us
- if (audioSinkBeforeFocusLoss != AUDIO_SINK_DEVICE && audioSink == AUDIO_SINK_DEVICE) {
- resumePlaybackAfterFocusGain = false;
- _requestFocus();
- } else if (!playing) {
- localHandler.sendMessageAtTime(Message.obtain(localHandler, MSG_PRE_PLAY, SongList.HOW_CURRENT, 0), SystemClock.uptimeMillis());
- } else {
- resumePlaybackAfterFocusGain = false;
- }
- }
- }
- private static void _enableEffects(int arg1, Runnable callback) {
- //don't even ask.......
- //(a few devices won't disable one effect while the other effect is enabled)
- Equalizer.release();
- BassBoost.release();
- Virtualizer.release();
- if ((arg1 & 1) != 0) {
- if (player != null)
- Equalizer.initialize(player.getAudioSessionId());
- Equalizer.setEnabled(true);
- } else {
- Equalizer.setEnabled(false);
- }
- if ((arg1 & 2) != 0) {
- if (player != null)
- BassBoost.initialize(player.getAudioSessionId());
- BassBoost.setEnabled(true);
- } else {
- BassBoost.setEnabled(false);
- }
- if ((arg1 & 4) != 0) {
- if (player != null)
- Virtualizer.initialize(player.getAudioSessionId());
- Virtualizer.setEnabled(true);
- } else {
- Virtualizer.setEnabled(false);
- }
- if (callback != null)
- MainHandler.postToMainThread(callback);
- }
- @Override
- public void onAudioFocusChange(int focusChange) {
- if (state != STATE_ALIVE)
- return;
- if (handler != null) {
- handler.removeMessages(MSG_FOCUS_GAIN_TIMER);
- handler.removeMessages(MSG_FADE_IN_VOLUME_TIMER);
- }
- if (localHandler != null)
- localHandler.removeMessages(MSG_HEADSET_HOOK_TIMER);
- volumeDimmed = false;
- switch (focusChange) {
- case AudioManager.AUDIOFOCUS_GAIN:
- if (!hasFocus) {
- hasFocus = true;
- if (handler != null)
- handler.sendMessageAtTime(Message.obtain(handler, MSG_FOCUS_GAIN_TIMER), SystemClock.uptimeMillis() + 1500);
- } else {
- //came here from AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
- if (player != null && playerState == PLAYER_STATE_LOADED) {
- try {
- player.setVolume(volumeMultiplier, volumeMultiplier);
- } catch (Throwable ex) {
- ex.printStackTrace();
- }
- }
- }
- break;
- case AudioManager.AUDIOFOCUS_LOSS:
- case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
- //we just cannot replace resumePlaybackAfterFocusGain's value with
- //playing, because if a second focus loss occurred BEFORE _focusGain
- //had a chance to be called, then playing would be false, when in fact,
- //we want resumePlaybackAfterFocusGain to remain true (and the audio sink
- //must be untouched)
- //also because of that, we must take resumePlaybackAfterFocusGain into
- //consideration here, because _fullCleanup always sets it to false
- final boolean oldResumePlaybackAfterFocusGain = resumePlaybackAfterFocusGain;
- final boolean resume = (localPlaying || localPlayerState == PLAYER_STATE_PREPARING);
- hasFocus = false;
- _storeSongTime();
- _fullCleanup();
- silenceMode = SILENCE_FOCUS;
- if (resume || oldResumePlaybackAfterFocusGain)
- resumePlaybackAfterFocusGain = true;
- //change audioSinkBeforeFocusLoss only at the first time we lose focus!
- //(if we lose focus several times in row, before we actually have had
- //chance to play at least once, resume will be false)
- if (resume)
- audioSinkBeforeFocusLoss = audioSink;
- _updateState(false, null);
- break;
- case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
- hasFocus = true;
- if (!doNotAttenuateVolume) {
- //when dimmed, decreased the volume by 20dB
- final float multiplier = volumeMultiplier * 0.1f;
- volumeDimmed = true;
- if (player != null && playerState == PLAYER_STATE_LOADED) {
- try {
- player.setVolume(multiplier, multiplier);
- } catch (Throwable ex) {
- ex.printStackTrace();
- }
- }
- }
- break;
- }
- }
- @Override
- public void onCompletion(MediaPlayer mediaPlayer) {
- if (state != STATE_ALIVE)
- return;
- if (playing && player == mediaPlayer)
- localHandler.sendMessageAtTime(Message.obtain(localHandler, MSG_PRE_PLAY, SongList.HOW_NEXT_AUTO, 0), SystemClock.uptimeMillis());
- }
- @Override
- public boolean onError(MediaPlayer mediaPlayer, int what, int extra) {
- if (state != STATE_ALIVE)
- return true;
- if (mediaPlayer == nextPlayer || mediaPlayer == player) {
- if (what == MediaPlayer.MEDIA_ERROR_SERVER_DIED) {
- _storeSongTime();
- _fullCleanup();
- if (reviveAlreadyTried) {
- reviveAlreadyTried = false;
- _updateState(false, new MediaServerDiedException());
- } else {
- reviveAlreadyTried = true;
- localHandler.sendMessageAtTime(Message.obtain(localHandler, MSG_PRE_PLAY, SongList.HOW_CURRENT, 0), SystemClock.uptimeMillis());
- }
- } else if (mediaPlayer == nextPlayer) {
- nextSong = null;
- nextPlayerState = PLAYER_STATE_NEW;
- } else {
- final boolean prep = (playerState == PLAYER_STATE_PREPARING);
- _fullCleanup();
- if (prep && howThePlayerStarted == SongList.HOW_NEXT_AUTO)
- //the error happened during currentSong's preparation
- _handleFailure((extra == MediaPlayer.MEDIA_ERROR_TIMED_OUT) ? new TimeoutException() : new IOException(), false);
- else
- _updateState(false, (extra == MediaPlayer.MEDIA_ERROR_TIMED_OUT) ? new TimeoutException() : new IOException());
- }
- } else {
- _fullCleanup();
- _updateState(false, new Exception("Invalid MediaPlayer"));
- }
- return true;
- }
- @Override
- public boolean onInfo(MediaPlayer mediaPlayer, int what, int extra) {
- if (mediaPlayer == player) {
- switch (what) {
- case MediaPlayer.MEDIA_INFO_BUFFERING_START:
- if (!playerBuffering) {
- playerBuffering = true;
- _updateState(true, null);
- }
- break;
- case MediaPlayer.MEDIA_INFO_BUFFERING_END:
- if (playerBuffering) {
- playerBuffering = false;
- _updateState(true, null);
- }
- break;
- }
- }
- return false;
- }
- @Override
- public void onPrepared(MediaPlayer mediaPlayer) {
- if (state != STATE_ALIVE)
- return;
- if (mediaPlayer == nextPlayer) {
- if (nextSongScheduledForPreparation == nextSong && nextSong != null) {
- nextSongScheduledForPreparation = null;
- nextPlayerState = PLAYER_STATE_LOADED;…
Large files files are truncated, but you can click here to view the full file