Skip to content

Commit

Permalink
Improves media integration with Android
Browse files Browse the repository at this point in the history
  • Loading branch information
kelsos committed Nov 6, 2020
1 parent f02a0b5 commit 54c6652
Show file tree
Hide file tree
Showing 15 changed files with 110 additions and 125 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ constructor(
model.coverPath = path
savePath(path)
bus.post(CoverChangedEvent(path))
bus.post(RemoteClientMetaData(model.trackInfo, model.coverPath))
bus.post(RemoteClientMetaData(model.trackInfo, model.coverPath, model.duration))
UpdateWidgets.updateCover(context, path)
} catch (e: Exception) {
removeCover(e)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ constructor(
val path = node.path("path").textValue()
model.trackInfo = TrackInfo(artist, title, album, year, path)
save(model.trackInfo)
bus.post(RemoteClientMetaData(model.trackInfo, model.coverPath))
bus.post(RemoteClientMetaData(model.trackInfo, model.coverPath, model.duration))
bus.post(TrackInfoChangeEvent(model.trackInfo))
UpdateWidgets.updateTrackInfo(context, model.trackInfo)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ constructor(

model.playState = e.dataString
if (model.playState != PlayerState.STOPPED) {
bus.post(PlayStateChange(model.playState))
bus.post(PlayStateChange(model.playState, model.position))
} else {
stop()
}
Expand All @@ -45,7 +45,7 @@ constructor(
action?.cancel()
action = scope.async {
delay(800)
bus.post(PlayStateChange(model.playState))
bus.post(PlayStateChange(model.playState, model.position))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,34 @@ package com.kelsos.mbrc.commands.visual

import com.fasterxml.jackson.databind.node.ObjectNode
import com.kelsos.mbrc.events.bus.RxBus
import com.kelsos.mbrc.events.ui.UpdatePosition
import com.kelsos.mbrc.events.ui.PlayStateChange
import com.kelsos.mbrc.events.ui.RemoteClientMetaData
import com.kelsos.mbrc.events.ui.UpdateDuration
import com.kelsos.mbrc.interfaces.ICommand
import com.kelsos.mbrc.interfaces.IEvent
import com.kelsos.mbrc.model.MainDataModel
import javax.inject.Inject

class UpdatePlaybackPositionCommand
@Inject constructor(private val bus: RxBus) : ICommand {
@Inject
constructor(
private val bus: RxBus,
private val model: MainDataModel,
) : ICommand {

override fun execute(e: IEvent) {
val oNode = e.data as ObjectNode
bus.post(UpdatePosition(oNode.path("current").asInt(), oNode.path("total").asInt()))
val data = e.data as ObjectNode
val duration = data.path("total").asLong()
val position = data.path("current").asLong()
bus.post(UpdateDuration(position.toInt(), duration.toInt()))

bus.post(RemoteClientMetaData(model.trackInfo, model.coverPath, duration))

if (position != model.position) {
bus.post(PlayStateChange(model.playState, position))
}

model.duration = duration
model.position = position
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ package com.kelsos.mbrc.events.ui

import com.kelsos.mbrc.annotations.PlayerState.State

data class PlayStateChange(@State val state: String)
data class PlayStateChange(@State val state: String, val position: Long)
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ package com.kelsos.mbrc.events.ui

import com.kelsos.mbrc.domain.TrackInfo

data class RemoteClientMetaData(val trackInfo: TrackInfo, val coverPath: String = "")
data class RemoteClientMetaData(val trackInfo: TrackInfo, val coverPath: String = "", val duration: Long)
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.kelsos.mbrc.events.ui

class UpdateDuration(val position: Int, val duration: Int)

This file was deleted.

2 changes: 2 additions & 0 deletions app/src/main/kotlin/com/kelsos/mbrc/model/MainDataModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import javax.inject.Singleton
class MainDataModel
@Inject
constructor() {
var position: Long = 0
var duration: Long = 0
var trackInfo: TrackInfo = TrackInfo()
var coverPath: String = ""
var rating: Float = 0f
Expand Down
106 changes: 28 additions & 78 deletions app/src/main/kotlin/com/kelsos/mbrc/services/RemoteSessionManager.kt
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
@file:Suppress("DEPRECATION")

package com.kelsos.mbrc.services

import android.annotation.TargetApi
import android.app.Application
import android.app.PendingIntent
import android.content.ComponentName
import android.content.Intent
import android.media.AudioManager
import android.media.RemoteControlClient
import android.os.Build
import android.support.v4.media.MediaMetadataCompat
import android.support.v4.media.session.MediaSessionCompat
import android.support.v4.media.session.MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS
Expand All @@ -34,7 +30,6 @@ import javax.inject.Singleton

@Singleton
class RemoteSessionManager
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
@Inject
constructor(
context: Application,
Expand All @@ -48,10 +43,8 @@ constructor(
lateinit var handler: MediaIntentHandler

init {

bus.register(this, RemoteClientMetaData::class.java) { this.metadataUpdate(it) }
bus.register(this, PlayStateChange::class.java) { this.updateState(it) }
bus.register(this, PlayStateChange::class.java) { this.onPlayStateChange(it) }
bus.register(
this,
ConnectionStatusChangeEvent::class.java
Expand Down Expand Up @@ -93,6 +86,10 @@ constructor(
override fun onStop() {
postAction(UserAction(Protocol.PlayerStop, true))
}

override fun onSeekTo(pos: Long) {
postAction(UserAction.create(Protocol.NowPlayingPosition, pos))
}
})
}

Expand All @@ -104,7 +101,6 @@ constructor(
val playbackState = builder.build()
mediaSession.isActive = false
mediaSession.setPlaybackState(playbackState)
ensureTransportControls(playbackState)
}
abandonFocus()
}
Expand All @@ -126,94 +122,42 @@ constructor(
val bitmap = RemoteUtils.coverBitmapSync(data.coverPath)

val builder = MediaMetadataCompat.Builder()
builder.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, trackInfo.album)
builder.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, trackInfo.artist)
builder.putString(MediaMetadataCompat.METADATA_KEY_TITLE, trackInfo.title)
builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, bitmap)
.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, trackInfo.album)
.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, trackInfo.artist)
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, trackInfo.title)
.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, bitmap)
.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, data.duration)
mediaSession.setMetadata(builder.build())

}

private fun updateState(stateChange: PlayStateChange) {
private fun updateState(change: PlayStateChange) {
when (change.state) {
PlayerState.PLAYING -> requestFocus()
else -> abandonFocus()
}

if (mediaSession == null) {
return
}

val builder = PlaybackStateCompat.Builder()
builder.setActions(PLAYBACK_ACTIONS)
when (stateChange.state) {
when (change.state) {
PlayerState.PLAYING -> {
builder.setState(PlaybackStateCompat.STATE_PLAYING, -1, 1f)
builder.setState(PlaybackStateCompat.STATE_PLAYING, change.position, 1f)
mediaSession.isActive = true

}
PlayerState.PAUSED -> {
builder.setState(PlaybackStateCompat.STATE_PAUSED, -1, 0f)
builder.setState(PlaybackStateCompat.STATE_PAUSED, change.position, 0f)
mediaSession.isActive = true

}
else -> {
builder.setState(PlaybackStateCompat.STATE_STOPPED, -1, 0f)
builder.setState(PlaybackStateCompat.STATE_STOPPED, change.position, 0f)
mediaSession.isActive = false
}
}
val playbackState = builder.build()
mediaSession.setPlaybackState(playbackState)
ensureTransportControls(playbackState)
}

private fun onPlayStateChange(change: PlayStateChange) {
when (change.state) {
PlayerState.PLAYING -> {
requestFocus()
}
else -> {
abandonFocus()

}
}
}

private fun ensureTransportControls(playbackState: PlaybackStateCompat) {
val actions = playbackState.actions
val remoteObj = mediaSession!!.remoteControlClient
if (actions != 0L && remoteObj != null) {

var transportControls = 0

if (actions and PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS != 0L) {
transportControls = transportControls or RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS
}

if (actions and PlaybackStateCompat.ACTION_REWIND != 0L) {
transportControls = transportControls or RemoteControlClient.FLAG_KEY_MEDIA_REWIND
}

if (actions and PlaybackStateCompat.ACTION_PLAY != 0L) {
transportControls = transportControls or RemoteControlClient.FLAG_KEY_MEDIA_PLAY
}

if (actions and PlaybackStateCompat.ACTION_PLAY_PAUSE != 0L) {
transportControls = transportControls or RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE
}

if (actions and PlaybackStateCompat.ACTION_PAUSE != 0L) {
transportControls = transportControls or RemoteControlClient.FLAG_KEY_MEDIA_PAUSE
}

if (actions and PlaybackStateCompat.ACTION_STOP != 0L) {
transportControls = transportControls or RemoteControlClient.FLAG_KEY_MEDIA_STOP
}

if (actions and PlaybackStateCompat.ACTION_FAST_FORWARD != 0L) {
transportControls = transportControls or RemoteControlClient.FLAG_KEY_MEDIA_FAST_FORWARD
}

if (actions and PlaybackStateCompat.ACTION_SKIP_TO_NEXT != 0L) {
transportControls = transportControls or RemoteControlClient.FLAG_KEY_MEDIA_NEXT
}

(remoteObj as RemoteControlClient).setTransportControlFlags(transportControls)
}
mediaSession.setPlaybackState(builder.build())
}

private fun requestFocus(): Boolean {
Expand All @@ -237,6 +181,12 @@ constructor(

companion object {
private const val PLAYBACK_ACTIONS =
PlaybackStateCompat.ACTION_PAUSE or PlaybackStateCompat.ACTION_PLAY_PAUSE or PlaybackStateCompat.ACTION_PLAY or PlaybackStateCompat.ACTION_SKIP_TO_NEXT or PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS or PlaybackStateCompat.ACTION_STOP
PlaybackStateCompat.ACTION_PAUSE or
PlaybackStateCompat.ACTION_PLAY_PAUSE or
PlaybackStateCompat.ACTION_PLAY or
PlaybackStateCompat.ACTION_SKIP_TO_NEXT or
PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS or
PlaybackStateCompat.ACTION_STOP or
PlaybackStateCompat.ACTION_SEEK_TO
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import com.kelsos.mbrc.enums.LfmStatus
import com.kelsos.mbrc.events.ui.OnMainFragmentOptionsInflated
import com.kelsos.mbrc.events.ui.ShuffleChange
import com.kelsos.mbrc.events.ui.ShuffleChange.ShuffleState
import com.kelsos.mbrc.events.ui.UpdatePosition
import com.kelsos.mbrc.events.ui.UpdateDuration
import com.kelsos.mbrc.extensions.getDimens
import com.kelsos.mbrc.helper.ProgressSeekerHelper
import com.kelsos.mbrc.helper.ProgressSeekerHelper.ProgressUpdate
Expand Down Expand Up @@ -269,33 +269,31 @@ class MainActivity : BaseActivity(), MainView, ProgressUpdate {

override fun updatePlayState(@State state: String) {
val accentColor = ContextCompat.getColor(this, R.color.accent)
@DrawableRes val resId: Int
val tag: String
val tag = tag(state)

when (state) {
if (playPauseButton.tag == tag) {
return
}
@DrawableRes val resId: Int = when (state) {
PlayerState.PLAYING -> {
resId = R.drawable.ic_pause_circle_filled_black_24dp
tag = "Playing"
/* Start the animation if the track is playing*/
presenter.requestNowPlayingPosition()
trackProgressAnimation(progressBar.progress, progressBar.max)
R.drawable.ic_pause_circle_filled_black_24dp
}
PlayerState.PAUSED -> {
resId = R.drawable.ic_play_circle_filled_black_24dp
tag = PAUSED
/* Stop the animation if the track is paused*/
progressHelper.stop()
R.drawable.ic_play_circle_filled_black_24dp
}
PlayerState.STOPPED -> {
resId = R.drawable.ic_play_circle_filled_black_24dp
tag = STOPPED
/* Stop the animation if the track is paused*/
progressHelper.stop()
activateStoppedState()
R.drawable.ic_play_circle_filled_black_24dp
}
else -> {
resId = R.drawable.ic_play_circle_filled_black_24dp
tag = STOPPED
R.drawable.ic_play_circle_filled_black_24dp
}
}

Expand Down Expand Up @@ -348,10 +346,14 @@ class MainActivity : BaseActivity(), MainView, ProgressUpdate {
* current progress of playback
*/

override fun updateProgress(position: UpdatePosition) {
val total = position.total
val current = position.current
override fun updateProgress(duration: UpdateDuration) {
updateProgress(duration.position, duration.duration)
}

private fun updateProgress(
current: Int,
total: Int,
) {
var currentSeconds = current / 1000
var totalSeconds = total / 1000

Expand All @@ -373,7 +375,11 @@ class MainActivity : BaseActivity(), MainView, ProgressUpdate {
progressBar.max = total
progressBar.progress = current

trackProgressAnimation(position.current, position.total)
trackProgressAnimation(current, total)
}

override fun updateDuration(position: Int, duration: Int) {
updateProgress(position, duration)
}

override fun updateScrobbleStatus(active: Boolean) {
Expand Down Expand Up @@ -434,6 +440,13 @@ class MainActivity : BaseActivity(), MainView, ProgressUpdate {
companion object {
private const val PAUSED = "Paused"
private const val STOPPED = "Stopped"
private const val PLAYING = "Playing"

fun tag(@PlayerState.State state: String): String = when(state) {
PlayerState.PLAYING -> PLAYING
PlayerState.PAUSED -> PAUSED
else -> STOPPED
}
}

@javax.inject.Scope
Expand Down
Loading

0 comments on commit 54c6652

Please sign in to comment.