Extend Headset Functionality
Add callbacks for when rewind/close buttons are pressed on a headset, and when headsets are connected/disconnected.
This commit is contained in:
parent
6fc034e376
commit
ce96dd6e94
3 changed files with 82 additions and 11 deletions
|
@ -22,7 +22,7 @@ import java.io.InputStream
|
|||
const val MOSAIC_BITMAP_SIZE = 512
|
||||
const val MOSAIC_BITMAP_INCREMENT = 256
|
||||
|
||||
// A Fetcher that takes multiple cover uris and turns them into a NxN mosaic image.
|
||||
// A Fetcher that takes multiple cover uris and turns them into a 2x2 mosaic image.
|
||||
class MosaicFetcher(private val context: Context) : Fetcher<List<Uri>> {
|
||||
override suspend fun fetch(
|
||||
pool: BitmapPool,
|
||||
|
|
|
@ -4,8 +4,12 @@ import android.app.Notification
|
|||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.Service
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.media.AudioManager
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.os.Parcelable
|
||||
|
@ -35,9 +39,11 @@ import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
|||
|
||||
private const val CHANNEL_ID = "CHANNEL_AUXIO_PLAYBACK"
|
||||
private const val NOTIF_ID = 0xA0A0
|
||||
private const val CONNECTED = 1
|
||||
private const val DISCONNECTED = 0
|
||||
|
||||
// A Service that manages the single ExoPlayer instance and [attempts] to keep
|
||||
// persistence if the app closes.
|
||||
// A Service that manages the single ExoPlayer instance and manages the system-side
|
||||
// aspects of playback.
|
||||
class PlaybackService : Service(), Player.EventListener, PlaybackStateCallback {
|
||||
// TODO: Use the ExoPlayer queue functionality [To an extent]? Could make things faster.
|
||||
private val player: SimpleExoPlayer by lazy {
|
||||
|
@ -51,14 +57,13 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateCallback {
|
|||
|
||||
private val playbackManager = PlaybackStateManager.getInstance()
|
||||
private lateinit var mediaSession: MediaSessionCompat
|
||||
private lateinit var systemReceiver: SystemEventReceiver
|
||||
|
||||
private val serviceJob = Job()
|
||||
private val serviceScope = CoroutineScope(
|
||||
serviceJob + Dispatchers.Main
|
||||
)
|
||||
|
||||
private var isForeground = false
|
||||
|
||||
private lateinit var notification: Notification
|
||||
|
||||
// --- SERVICE OVERRIDES ---
|
||||
|
@ -87,6 +92,17 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateCallback {
|
|||
|
||||
notification = createNotification()
|
||||
|
||||
// Set up callback for system events
|
||||
systemReceiver = SystemEventReceiver()
|
||||
IntentFilter().apply {
|
||||
addAction(BluetoothDevice.ACTION_ACL_CONNECTED)
|
||||
addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED)
|
||||
addAction(AudioManager.ACTION_AUDIO_BECOMING_NOISY)
|
||||
addAction(Intent.ACTION_HEADSET_PLUG)
|
||||
|
||||
registerReceiver(systemReceiver, this)
|
||||
}
|
||||
|
||||
playbackManager.addCallback(this)
|
||||
}
|
||||
|
||||
|
@ -94,6 +110,7 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateCallback {
|
|||
super.onDestroy()
|
||||
|
||||
stopForeground(true)
|
||||
unregisterReceiver(systemReceiver)
|
||||
|
||||
// Release everything that could cause a memory leak if left around
|
||||
player.release()
|
||||
|
@ -138,6 +155,7 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateCallback {
|
|||
} else {
|
||||
player.pause()
|
||||
|
||||
// Be a polite service and stop being foreground if nothing is playing.
|
||||
stopForeground(false)
|
||||
}
|
||||
}
|
||||
|
@ -176,24 +194,29 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateCallback {
|
|||
KeyEvent.KEYCODE_MEDIA_PAUSE, KeyEvent.KEYCODE_MEDIA_PLAY,
|
||||
KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, KeyEvent.KEYCODE_HEADSETHOOK -> {
|
||||
playbackManager.setPlayingStatus(!playbackManager.isPlaying)
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
KeyEvent.KEYCODE_MEDIA_NEXT -> {
|
||||
playbackManager.next()
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
KeyEvent.KEYCODE_MEDIA_PREVIOUS -> {
|
||||
playbackManager.prev()
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
// TODO: Implement the other callbacks for
|
||||
// CLOSE/STOP & REWIND
|
||||
KeyEvent.KEYCODE_MEDIA_REWIND -> {
|
||||
player.seekTo(0)
|
||||
true
|
||||
}
|
||||
|
||||
KeyEvent.KEYCODE_MEDIA_STOP, KeyEvent.KEYCODE_MEDIA_CLOSE -> {
|
||||
stopSelf()
|
||||
true
|
||||
}
|
||||
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
@ -229,4 +252,50 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateCallback {
|
|||
|
||||
return notif
|
||||
}
|
||||
|
||||
// Broadcast Receiver for receiving system events [E.G Headphones connecte/disconnected
|
||||
inner class SystemEventReceiver : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
val action = intent.action
|
||||
|
||||
action?.let {
|
||||
when (it) {
|
||||
BluetoothDevice.ACTION_ACL_CONNECTED -> resume()
|
||||
BluetoothDevice.ACTION_ACL_DISCONNECTED -> pause()
|
||||
|
||||
AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED -> {
|
||||
when (intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, -1)) {
|
||||
AudioManager.SCO_AUDIO_STATE_CONNECTED -> resume()
|
||||
AudioManager.SCO_AUDIO_STATE_DISCONNECTED -> pause()
|
||||
}
|
||||
}
|
||||
|
||||
AudioManager.ACTION_AUDIO_BECOMING_NOISY -> pause()
|
||||
|
||||
Intent.ACTION_HEADSET_PLUG -> {
|
||||
when (intent.getIntExtra("state", -1)) {
|
||||
CONNECTED -> resume()
|
||||
DISCONNECTED -> pause()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun resume() {
|
||||
if (playbackManager.song != null) {
|
||||
Log.d(this::class.simpleName, "Device connected, resuming...")
|
||||
|
||||
playbackManager.setPlayingStatus(true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun pause() {
|
||||
if (playbackManager.song != null) {
|
||||
Log.d(this::class.simpleName, "Device disconnected, pausing...")
|
||||
|
||||
playbackManager.setPlayingStatus(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,8 @@ import kotlin.random.Random
|
|||
|
||||
// The manager of the current playback state [Current Song, Queue, Shuffling]
|
||||
// Never use this for ANYTHING UI related, that's what PlaybackViewModel is for.
|
||||
// Yes, I know MediaSessionCompat and friends exist, but I like having full control over the
|
||||
// playback state instead of dealing with android's likely buggy code.
|
||||
class PlaybackStateManager {
|
||||
// Playback
|
||||
private var mSong: Song? = null
|
||||
|
@ -259,7 +261,7 @@ class PlaybackStateManager {
|
|||
// Generate a new shuffled queue.
|
||||
private fun genShuffle(keepSong: Boolean) {
|
||||
// Take a random seed and then shuffle the current queue based off of that.
|
||||
// This seed will be saved in a bundle if the app closes, so that the shuffle mode
|
||||
// This seed will be saved in a database, so that the shuffle mode
|
||||
// can be restored when its started again.
|
||||
val newSeed = Random.Default.nextLong()
|
||||
|
||||
|
|
Loading…
Reference in a new issue