Skip to content

Commit

Permalink
Improve controls/radio robustness
Browse files Browse the repository at this point in the history
  • Loading branch information
toasterofbread committed May 13, 2024
1 parent fc17f86 commit 5b60fa3
Show file tree
Hide file tree
Showing 20 changed files with 333 additions and 153 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ actual abstract class PlayerListener {
actual open fun onSongAdded(index: Int, song: Song) {}
actual open fun onSongRemoved(index: Int, song: Song) {}
actual open fun onSongMoved(from: Int, to: Int) {}
actual open fun onRadioCancelRequested() {}

actual open fun onEvents() {}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,13 @@ import com.toasterofbread.spmp.platform.PlayerServiceCommand
import com.toasterofbread.spmp.platform.playerservice.*
import com.toasterofbread.spmp.platform.visualiser.MusicVisualiser
import com.toasterofbread.spmp.shared.R
import com.toasterofbread.spmp.service.playercontroller.RadioHandler
import kotlinx.coroutines.*
import spms.socketapi.shared.SpMsPlayerRepeatMode
import spms.socketapi.shared.SpMsPlayerState

@androidx.annotation.OptIn(UnstableApi::class)
open class ForegroundPlayerService: MediaSessionService(), PlayerService {
open class ForegroundPlayerService(private val play_when_ready: Boolean): MediaSessionService(), PlayerService {
override val load_state: PlayerServiceLoadState = PlayerServiceLoadState(false)
override val connection_error: Throwable? = null
override val context: AppContext get() = _context
Expand Down Expand Up @@ -83,6 +84,8 @@ open class ForegroundPlayerService: MediaSessionService(), PlayerService {
listener.removeFromPlayer(player)
}

protected open fun onRadioCancelled() {}

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
return START_NOT_STICKY
Expand Down Expand Up @@ -110,14 +113,22 @@ open class ForegroundPlayerService: MediaSessionService(), PlayerService {
_context = AppContext(this, coroutine_scope)
_context.getPrefs().addListener(prefs_listener)

initialiseSessionAndPlayer()
initialiseSessionAndPlayer(play_when_ready)

_service_player = object : PlayerServicePlayer(this) {
override fun onUndoStateChanged() {
for (listener in listeners) {
listener.onUndoStateChanged()
}
}

override val radio: RadioHandler =
object : RadioHandler(this, context) {
override fun onRadioCancelled() {
super.onRadioCancelled()
this@ForegroundPlayerService.onRadioCancelled()
}
}
}

val audio_manager = getSystemService(AUDIO_SERVICE) as AudioManager?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,83 +8,162 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import spms.socketapi.shared.SpMsPlayerRepeatMode
import spms.socketapi.shared.SpMsPlayerState
import androidx.media3.common.Player
import androidx.media3.common.MediaItem as ExoMediaItem

actual class PlatformExternalPlayerService: ForegroundPlayerService(), PlayerService {
private val service: ExternalPlayerService = ExternalPlayerService()
actual class PlatformExternalPlayerService: ForegroundPlayerService(play_when_ready = true), PlayerService {
// private var cancelling_radio: Boolean = false

private val service_listener = object : PlayerListener() {
override fun onSongAdded(index: Int, song: Song) = this@PlatformExternalPlayerService.onSongAdded(index, song)
override fun onPlayingChanged(is_playing: Boolean) = this@PlatformExternalPlayerService.onPlayingChanged(is_playing)
override fun onSeeked(position_ms: Long) = this@PlatformExternalPlayerService.onSeeked(position_ms)
override fun onSongMoved(from: Int, to: Int) = this@PlatformExternalPlayerService.onSongMoved(from, to)
override fun onSongRemoved(index: Int, song: Song) = this@PlatformExternalPlayerService.onSongRemoved(index)
override fun onSongTransition(song: Song?, manual: Boolean) = this@PlatformExternalPlayerService.onSongTransition(song, manual)
override fun onRadioCancelled() {
super.onRadioCancelled()
server.onRadioCancelled()
}

private val server: ExternalPlayerService =
object : ExternalPlayerService(plays_audio = true) {
override fun createServicePlayer(): PlayerServicePlayer = this@PlatformExternalPlayerService.service_player
}

private val server_listener: PlayerListener =
object : PlayerListener() {
override fun onSongAdded(index: Int, song: Song) = this@PlatformExternalPlayerService.onSongAdded(index, song)
override fun onPlayingChanged(is_playing: Boolean) = this@PlatformExternalPlayerService.onPlayingChanged(is_playing)
override fun onSeeked(position_ms: Long) = this@PlatformExternalPlayerService.onSeeked(position_ms)
override fun onSongMoved(from: Int, to: Int) = this@PlatformExternalPlayerService.onSongMoved(from, to)
override fun onSongRemoved(index: Int, song: Song) = this@PlatformExternalPlayerService.onSongRemoved(index)
override fun onSongTransition(song: Song?, manual: Boolean) = this@PlatformExternalPlayerService.onSongTransition(current_song_index)
// override fun onRadioCancelRequested() {
// cancelling_radio = true
// [email protected]_player.radio_instance.cancelRadio()
// cancelling_radio = false
// }
}

private val player_listener: Player.Listener =
object : Player.Listener {
private var last_seek_position: Long? = null

override fun onMediaItemTransition(mediaItem: ExoMediaItem?, reason: Int) {
if (reason == Player.MEDIA_ITEM_TRANSITION_REASON_SEEK && player.currentMediaItemIndex != current_song_index) {
server.seekToSong(player.currentMediaItemIndex)
}
}

override fun onIsPlayingChanged(isPlaying: Boolean) {
if (isPlaying == target_playing || player.playbackState != Player.STATE_READY) {
return
}

if (isPlaying) {
server.play()
}
else {
server.pause()
}
}

override fun onPositionDiscontinuity(oldPosition: Player.PositionInfo, newPosition: Player.PositionInfo, reason: Int) {
if (newPosition.positionMs == target_seek) {
return
}

if (reason == Player.DISCONTINUITY_REASON_SEEK && newPosition.positionMs != last_seek_position) {
last_seek_position = newPosition.positionMs
pause()
server.seekTo(newPosition.positionMs)

if (player.playbackState == Player.STATE_READY) {
server.notifyReadyToPlay()
}
}
}

override fun onPlaybackStateChanged(playbackState: Int) {
if (playbackState == Player.STATE_READY) {
server.notifyReadyToPlay()
}
}
}

private var target_playing: Boolean = false
private var target_seek: Long? = null

override fun onCreate() {
super.onCreate()

service._context = context
service.addListener(service_listener)
server._context = context
server.addListener(server_listener)

player.addListener(player_listener)

service.onCreate()
server.onCreate()
}

override fun onDestroy() {
super.onDestroy()
service.onDestroy()
server.onDestroy()
}

private fun onSongAdded(index: Int, song: Song) { coroutine_scope.launch(Dispatchers.Main) {
super.addSong(song, index)
}}
private fun onPlayingChanged(is_playing: Boolean) { coroutine_scope.launch(Dispatchers.Main) {
target_playing = is_playing
if (is_playing) super.play()
else super.pause()
}}
private fun onSeeked(position_ms: Long) { coroutine_scope.launch(Dispatchers.Main) {
target_seek = position_ms
super.seekTo(position_ms)
}}
private fun onSongMoved(from: Int, to: Int) { coroutine_scope.launch(Dispatchers.Main) {
super.moveSong(from, to)
}}
private fun onSongRemoved(index: Int) { coroutine_scope.launch(Dispatchers.Main) {
private fun onSongRemoved(index: Int) { coroutine_scope.launch(Dispatchers.Main) {
super.removeSong(index)
}}
private fun onSongTransition(song: Song?, manual: Boolean) { coroutine_scope.launch(Dispatchers.Main) {
super.seekToSong(current_song_index)
private fun onSongTransition(index: Int) { coroutine_scope.launch(Dispatchers.Main) {
if (index < 0 || index == player.currentMediaItemIndex) {
return@launch
}
try {
super.seekToSong(index)
}
catch (e: Throwable) {
throw RuntimeException("seekToSong($index) failed", e)
}
}}

override val load_state: PlayerServiceLoadState get() = service.load_state
override val state: SpMsPlayerState get() = service.state
override val is_playing: Boolean get() = service.is_playing
override val song_count: Int get() = service.song_count
override val current_song_index: Int get() = service.current_song_index
override val current_position_ms: Long get() = service.current_position_ms
override val duration_ms: Long get() = service.duration_ms
override val has_focus: Boolean get() = service.has_focus
override val radio_instance: RadioInstance get() = service.radio_instance
override val load_state: PlayerServiceLoadState get() = server.load_state
override val state: SpMsPlayerState get() = server.state
override val is_playing: Boolean get() = server.is_playing
override val song_count: Int get() = server.song_count
override val current_song_index: Int get() = server.current_song_index
override val current_position_ms: Long get() = server.current_position_ms
override val duration_ms: Long get() = server.duration_ms
override val has_focus: Boolean get() = server.has_focus
override val radio_instance: RadioInstance get() = server.radio_instance
override var repeat_mode: SpMsPlayerRepeatMode
get() = service.repeat_mode
set(value) { service.repeat_mode = value }
get() = server.repeat_mode
set(value) { server.repeat_mode = value }
override var volume: Float
get() = service.volume
set(value) { service.volume = value }

override fun play() = service.play()
override fun pause() = service.pause()
override fun playPause() = service.playPause()
override fun seekTo(position_ms: Long) = service.seekTo(position_ms)
override fun seekToSong(index: Int) = service.seekToSong(index)
override fun seekToNext() = service.seekToNext()
override fun seekToPrevious() = service.seekToPrevious()
override fun getSong(): Song? = service.getSong()
override fun getSong(index: Int): Song? = service.getSong(index)
override fun addSong(song: Song, index: Int) = service.addSong(song, index)
override fun moveSong(from: Int, to: Int) = service.moveSong(from, to)
override fun removeSong(index: Int) = service.removeSong(index)
override fun addListener(listener: PlayerListener) = service.addListener(listener)
override fun removeListener(listener: PlayerListener) = service.removeListener(listener)
get() = server.volume
set(value) { server.volume = value }

override fun play() = server.play()
override fun pause() = server.pause()
override fun playPause() = server.playPause()
override fun seekTo(position_ms: Long) = server.seekTo(position_ms)
override fun seekToSong(index: Int) = server.seekToSong(index)
override fun seekToNext() = server.seekToNext()
override fun seekToPrevious() = server.seekToPrevious()
override fun getSong(): Song? = server.getSong()
override fun getSong(index: Int): Song? = server.getSong(index)
override fun addSong(song: Song, index: Int) = server.addSong(song, index)
override fun moveSong(from: Int, to: Int) = server.moveSong(from, to)
override fun removeSong(index: Int) = server.removeSong(index)
override fun addListener(listener: PlayerListener) = server.addListener(listener)
override fun removeListener(listener: PlayerListener) = server.removeListener(listener)

actual companion object: InternalPlayerServiceCompanion(PlatformExternalPlayerService::class), PlayerServiceCompanion {
override fun isServiceRunning(context: AppContext): Boolean = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import androidx.compose.ui.graphics.Color
import com.toasterofbread.spmp.platform.AppContext
import ProgramArguments

actual class PlatformInternalPlayerService: ForegroundPlayerService(), PlayerService {
actual class PlatformInternalPlayerService: ForegroundPlayerService(play_when_ready = true), PlayerService {
actual companion object: InternalPlayerServiceCompanion(PlatformInternalPlayerService::class), PlayerServiceCompanion {
actual fun isAvailable(context: AppContext, launch_arguments: ProgramArguments): Boolean = true
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import dev.toastbits.ytmkt.formats.VideoFormatsEndpoint
import kotlinx.coroutines.runBlocking
import java.util.concurrent.Executors

internal fun ForegroundPlayerService.initialiseSessionAndPlayer() {
internal fun ForegroundPlayerService.initialiseSessionAndPlayer(play_when_ready: Boolean) {
audio_sink = DefaultAudioSink.Builder(context.ctx)
.setAudioProcessorChain(
DefaultAudioProcessorChain(
Expand Down Expand Up @@ -108,7 +108,7 @@ internal fun ForegroundPlayerService.initialiseSessionAndPlayer() {
val player_listener: InternalPlayerServicePlayerListener = InternalPlayerServicePlayerListener(this)
player.addListener(player_listener)

player.playWhenReady = true
player.playWhenReady = play_when_ready
player.prepare()

val controller_future: ListenableFuture<MediaController> =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,18 +73,18 @@ data class SongLyrics(
get() = start!! .. end!!
}

init {
lazyAssert {
synchronized(lines) {
for (line in lines) {
for (term in line) {
if (sync_type != SyncType.NONE && (term.start == null || term.end == null)) {
return@lazyAssert false
}
}
}
}
return@lazyAssert true
}
}
// init {
// lazyAssert {
// synchronized(lines) {
// for (line in lines) {
// for (term in line) {
// if (sync_type != SyncType.NONE && (term.start == null || term.end == null)) {
// return@lazyAssert false
// }
// }
// }
// }
// return@lazyAssert true
// }
// }
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ abstract class RadioInstance(val context: AppContext) {
state = state.copy(current_filter_index = filter_index)
}

fun cancelRadio() {
open fun cancelRadio() {
cancelCurrentJob()
state = RadioState()
}
Expand Down Expand Up @@ -191,5 +191,8 @@ abstract class RadioInstance(val context: AppContext) {

return filtered
}

override fun toString(): String =
"RadioInstance(state=$state, is_loading=$is_loading, load_error=$load_error)"
}

Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ data class RadioState(
val current_filter_index: Int? = null
) {
fun isContinuationAvailable(): Boolean =
continuation != null || !initial_songs_loaded
continuation != null || (item_uid != null && !initial_songs_loaded)

internal suspend fun loadContinuation(context: AppContext): Result<RadioLoadResult?> = runCatching {
if (item_uid == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ expect abstract class PlayerListener() {
open fun onSongAdded(index: Int, song: Song)
open fun onSongRemoved(index: Int, song: Song)
open fun onSongMoved(from: Int, to: Int)
open fun onRadioCancelRequested()

open fun onEvents()
}

0 comments on commit 5b60fa3

Please sign in to comment.