ultrasonic-app-subsonic-and.../ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport...

307 lines
9.6 KiB
Java
Raw Normal View History

2013-04-06 21:47:24 +02:00
/*
This file is part of Subsonic.
Subsonic is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Subsonic is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
2015-07-26 18:15:07 +02:00
package org.moire.ultrasonic.service;
2013-04-06 21:47:24 +02:00
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.media.AudioManager;
import android.os.Build;
import android.os.Bundle;
2020-09-30 14:47:59 +02:00
import timber.log.Timber;
2013-04-06 21:47:24 +02:00
import android.view.KeyEvent;
import org.moire.ultrasonic.R;
2015-07-26 18:15:07 +02:00
import org.moire.ultrasonic.domain.PlayerState;
import org.moire.ultrasonic.util.CacheCleaner;
import org.moire.ultrasonic.util.Constants;
2015-07-26 18:15:07 +02:00
import org.moire.ultrasonic.util.Util;
2013-04-06 21:47:24 +02:00
/**
* This class is responsible for handling received events for the Media Player implementation
*
2013-04-06 21:47:24 +02:00
* @author Sindre Mehus
*/
2020-06-25 14:33:44 +02:00
public class MediaPlayerLifecycleSupport
{
2020-06-30 15:16:45 +02:00
private boolean created = false;
private DownloadQueueSerializer downloadQueueSerializer; // From DI
2020-06-25 14:33:44 +02:00
private final MediaPlayerControllerImpl mediaPlayerController; // From DI
2020-06-23 18:40:44 +02:00
private final Downloader downloader; // From DI
2020-06-25 14:33:44 +02:00
private Context context;
private BroadcastReceiver headsetEventReceiver;
2013-04-06 21:47:24 +02:00
public MediaPlayerLifecycleSupport(Context context, DownloadQueueSerializer downloadQueueSerializer,
final MediaPlayerControllerImpl mediaPlayerController, final Downloader downloader)
{
this.downloadQueueSerializer = downloadQueueSerializer;
2020-06-25 14:33:44 +02:00
this.mediaPlayerController = mediaPlayerController;
2020-06-21 09:31:38 +02:00
this.context = context;
2020-06-23 18:40:44 +02:00
this.downloader = downloader;
2020-09-30 14:47:59 +02:00
Timber.i("LifecycleSupport constructed");
2020-06-30 15:16:45 +02:00
}
public void onCreate()
2020-06-30 16:19:50 +02:00
{
2020-07-16 14:25:58 +02:00
onCreate(false, null);
2020-06-30 16:19:50 +02:00
}
2020-07-16 14:25:58 +02:00
private void onCreate(final boolean autoPlay, final Runnable afterCreated)
2020-06-30 15:16:45 +02:00
{
2020-07-16 14:25:58 +02:00
if (created)
{
if (afterCreated != null) afterCreated.run();
return;
}
registerHeadsetReceiver();
// React to media buttons.
Util.registerMediaButtonEventReceiver(context, true);
2020-06-30 15:16:45 +02:00
mediaPlayerController.onCreate();
2020-07-16 14:25:58 +02:00
if (autoPlay) mediaPlayerController.preload();
this.downloadQueueSerializer.deserializeDownloadQueue(new Consumer<State>() {
@Override
public void accept(State state) {
2020-06-30 16:19:50 +02:00
mediaPlayerController.restore(state.songs, state.currentPlayingIndex, state.currentPlayingPosition, autoPlay, false);
// Work-around: Serialize again, as the restore() method creates a serialization without current playing info.
MediaPlayerLifecycleSupport.this.downloadQueueSerializer.serializeDownloadQueue(downloader.downloadList,
2020-06-25 14:33:44 +02:00
downloader.getCurrentPlayingIndex(), mediaPlayerController.getPlayerPosition());
2020-07-16 14:25:58 +02:00
if (afterCreated != null) afterCreated.run();
}
});
new CacheCleaner(context).clean();
2020-06-30 15:16:45 +02:00
created = true;
2020-09-30 14:47:59 +02:00
Timber.i("LifecycleSupport created");
}
public void onDestroy()
{
2020-06-30 15:16:45 +02:00
if (!created) return;
downloadQueueSerializer.serializeDownloadQueueNow(downloader.downloadList,
downloader.getCurrentPlayingIndex(), mediaPlayerController.getPlayerPosition());
2020-06-25 14:33:44 +02:00
mediaPlayerController.clear(false);
context.unregisterReceiver(headsetEventReceiver);
2020-06-25 14:33:44 +02:00
mediaPlayerController.onDestroy();
2020-06-30 15:16:45 +02:00
created = false;
2020-09-30 14:47:59 +02:00
Timber.i("LifecycleSupport destroyed");
}
2020-06-25 14:33:44 +02:00
public void receiveIntent(Intent intent)
{
if (intent == null) return;
String intentAction = intent.getAction();
if (intentAction == null || intentAction.isEmpty()) return;
2020-09-30 14:47:59 +02:00
Timber.i("Received intent: %s", intentAction);
if (intentAction.equals(Constants.CMD_PROCESS_KEYCODE)) {
if (intent.getExtras() != null) {
KeyEvent event = (KeyEvent) intent.getExtras().get(Intent.EXTRA_KEY_EVENT);
if (event != null) {
handleKeyEvent(event);
}
2020-06-25 14:33:44 +02:00
}
}
else
{
handleUltrasonicIntent(intentAction);
}
2020-06-25 14:33:44 +02:00
}
/**
* The Headset Intent Receiver is responsible for resuming playback when a headset is inserted
* and pausing it when it is removed.
* Unfortunately this Intent can't be registered in the AndroidManifest, so it works only
* while Ultrasonic is running.
*/
private void registerHeadsetReceiver() {
2020-06-21 09:31:38 +02:00
final SharedPreferences sp = Util.getPreferences(context);
final String spKey = context
.getString(R.string.settings_playback_resume_play_on_headphones_plug);
headsetEventReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final Bundle extras = intent.getExtras();
if (extras == null) {
return;
}
2020-09-30 14:47:59 +02:00
Timber.i("Headset event for: %s", extras.get("name"));
final int state = extras.getInt("state");
if (state == 0) {
2020-06-25 14:33:44 +02:00
if (!mediaPlayerController.isJukeboxEnabled()) {
mediaPlayerController.pause();
}
} else if (state == 1) {
2020-06-25 14:33:44 +02:00
if (!mediaPlayerController.isJukeboxEnabled() &&
sp.getBoolean(spKey, false) &&
2020-06-25 14:33:44 +02:00
mediaPlayerController.getPlayerState() == PlayerState.PAUSED) {
mediaPlayerController.start();
}
}
}
};
2020-06-25 14:33:44 +02:00
IntentFilter headsetIntentFilter;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
{
2020-06-25 14:33:44 +02:00
headsetIntentFilter = new IntentFilter(AudioManager.ACTION_HEADSET_PLUG);
}
2020-06-25 14:33:44 +02:00
else
{
headsetIntentFilter = new IntentFilter(Intent.ACTION_HEADSET_PLUG);
}
context.registerReceiver(headsetEventReceiver, headsetIntentFilter);
}
private void handleKeyEvent(KeyEvent event)
{
if (event.getAction() != KeyEvent.ACTION_DOWN || event.getRepeatCount() > 0)
{
return;
}
2020-07-16 14:25:58 +02:00
final int keyCode = event.getKeyCode();
2020-06-30 16:19:50 +02:00
boolean autoStart = (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE ||
keyCode == KeyEvent.KEYCODE_MEDIA_PLAY ||
keyCode == KeyEvent.KEYCODE_HEADSETHOOK ||
2020-06-30 16:19:50 +02:00
keyCode == KeyEvent.KEYCODE_MEDIA_PREVIOUS ||
keyCode == KeyEvent.KEYCODE_MEDIA_NEXT);
// We can receive intents (e.g. MediaButton) when everything is stopped, so we need to start
2020-07-16 14:25:58 +02:00
onCreate(autoStart, new Runnable() {
@Override
public void run() {
switch (keyCode)
{
2020-07-16 14:25:58 +02:00
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
case KeyEvent.KEYCODE_HEADSETHOOK:
mediaPlayerController.togglePlayPause();
break;
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
mediaPlayerController.previous();
break;
case KeyEvent.KEYCODE_MEDIA_NEXT:
if (downloader.getCurrentPlayingIndex() < downloader.downloadList.size() - 1)
{
mediaPlayerController.next();
}
break;
case KeyEvent.KEYCODE_MEDIA_STOP:
mediaPlayerController.stop();
break;
case KeyEvent.KEYCODE_MEDIA_PLAY:
if (mediaPlayerController.getPlayerState() == PlayerState.IDLE)
{
mediaPlayerController.play();
}
else if (mediaPlayerController.getPlayerState() != PlayerState.STARTED)
{
mediaPlayerController.start();
}
break;
case KeyEvent.KEYCODE_MEDIA_PAUSE:
mediaPlayerController.pause();
break;
case KeyEvent.KEYCODE_1:
mediaPlayerController.setSongRating(1);
break;
case KeyEvent.KEYCODE_2:
mediaPlayerController.setSongRating(2);
break;
case KeyEvent.KEYCODE_3:
mediaPlayerController.setSongRating(3);
break;
case KeyEvent.KEYCODE_4:
mediaPlayerController.setSongRating(4);
break;
case KeyEvent.KEYCODE_5:
mediaPlayerController.setSongRating(5);
break;
default:
break;
}
2020-07-16 14:25:58 +02:00
}
});
}
/**
* This function processes the intent that could come from other applications.
*/
private void handleUltrasonicIntent(final String intentAction)
{
final boolean isRunning = created;
// If Ultrasonic is not running, do nothing to stop or pause
if (!isRunning && (intentAction.equals(Constants.CMD_PAUSE) ||
intentAction.equals(Constants.CMD_STOP))) return;
boolean autoStart = (intentAction.equals(Constants.CMD_PLAY) ||
intentAction.equals(Constants.CMD_RESUME_OR_PLAY) ||
intentAction.equals(Constants.CMD_TOGGLEPAUSE) ||
intentAction.equals(Constants.CMD_PREVIOUS) ||
intentAction.equals(Constants.CMD_NEXT));
// We can receive intents when everything is stopped, so we need to start
onCreate(autoStart, new Runnable() {
@Override
public void run() {
switch(intentAction)
{
case Constants.CMD_PLAY:
mediaPlayerController.play();
break;
case Constants.CMD_RESUME_OR_PLAY:
// If Ultrasonic wasn't running, the autoStart is enough to resume, no need to call anything
if (isRunning) mediaPlayerController.resumeOrPlay();
break;
case Constants.CMD_NEXT:
mediaPlayerController.next();
break;
case Constants.CMD_PREVIOUS:
mediaPlayerController.previous();
break;
case Constants.CMD_TOGGLEPAUSE:
mediaPlayerController.togglePlayPause();
break;
case Constants.CMD_STOP:
// TODO: There is a stop() function, shouldn't we use that?
mediaPlayerController.pause();
mediaPlayerController.seekTo(0);
break;
case Constants.CMD_PAUSE:
mediaPlayerController.pause();
break;
}
}
});
}
2012-02-26 21:25:13 +01:00
}