1
0
Fork 0
mirror of https://gitlab.com/futo-org/fcast.git synced 2025-06-24 21:25:23 +00:00

Fixed playback updates stopping.

This commit is contained in:
Koen 2024-01-06 10:53:59 +01:00
parent 4bfbf8770c
commit f0fa5c067f
2 changed files with 105 additions and 77 deletions

View file

@ -76,29 +76,6 @@ class NetworkService : Service() {
Log.e(TAG, "Failed to send version ${session.id}") Log.e(TAG, "Failed to send version ${session.id}")
} }
} }
var encounteredError = false
while (!_stopped && !encounteredError) {
try {
val updateMessage = generateUpdateMessage()
withContext(Dispatchers.IO) {
try {
session.send(Opcode.PlaybackUpdate, updateMessage)
Log.i(TAG, "Update sent ${session.id}")
} catch (eSend: Throwable) {
Log.e(TAG, "Unhandled error sending update ${session.id}", eSend)
encounteredError = true
return@withContext
}
}
} catch (eTimer: Throwable) {
Log.e(TAG, "Unhandled error on timer thread ${session.id}", eTimer)
} finally {
delay(1000)
}
}
Log.i(TAG, "Send loop closed ${session.id}")
} }
} }
@ -158,33 +135,12 @@ class NetworkService : Service() {
instance = null instance = null
} }
fun generateUpdateMessage(): PlaybackUpdateMessage {
val player = PlayerActivity.instance
return if (player != null) {
PlaybackUpdateMessage(
System.currentTimeMillis(),
player.currentPosition / 1000.0,
player.duration / 1000.0,
if (player.isPlaying) 1 else 2,
player.speed.toDouble()
)
} else {
PlaybackUpdateMessage(
System.currentTimeMillis(),
0.0,
0.0,
0,
0.0
)
}
}
private inline fun <reified T> send(opcode: Opcode, message: T) { private inline fun <reified T> send(opcode: Opcode, message: T) {
val sender: (FCastSession) -> Unit = { session: FCastSession -> val sender: (FCastSession) -> Unit = { session: FCastSession ->
_scope?.launch(Dispatchers.IO) { _scope?.launch(Dispatchers.IO) {
try { try {
session.send(opcode, message) session.send(opcode, message)
Log.i(TAG, "Playback error sent ${session.id}") Log.i(TAG, "Opcode sent (opcode = $opcode) ${session.id}")
} catch (e: Throwable) { } catch (e: Throwable) {
Log.w(TAG, "Failed to send playback error", e) Log.w(TAG, "Failed to send playback error", e)
} }
@ -196,15 +152,18 @@ class NetworkService : Service() {
} }
fun sendPlaybackError(error: String) { fun sendPlaybackError(error: String) {
Log.i(TAG, "sendPlaybackError")
val message = PlaybackErrorMessage(error) val message = PlaybackErrorMessage(error)
send(Opcode.PlaybackError, message) send(Opcode.PlaybackError, message)
} }
fun sendPlaybackUpdate(message: PlaybackUpdateMessage) { fun sendPlaybackUpdate(message: PlaybackUpdateMessage) {
Log.i(TAG, "sendPlaybackUpdate")
send(Opcode.PlaybackUpdate, message) send(Opcode.PlaybackUpdate, message)
} }
fun sendCastVolumeUpdate(value: VolumeUpdateMessage) { fun sendCastVolumeUpdate(value: VolumeUpdateMessage) {
Log.i(TAG, "sendCastVolumeUpdate")
send(Opcode.VolumeUpdate, value) send(Opcode.VolumeUpdate, value)
} }

View file

@ -19,6 +19,7 @@ import android.widget.TextView
import androidx.annotation.OptIn import androidx.annotation.OptIn
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.lifecycle.lifecycleScope
import androidx.media3.common.MediaItem import androidx.media3.common.MediaItem
import androidx.media3.common.PlaybackException import androidx.media3.common.PlaybackException
import androidx.media3.common.PlaybackParameters import androidx.media3.common.PlaybackParameters
@ -33,9 +34,10 @@ import androidx.media3.exoplayer.hls.HlsMediaSource
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
import androidx.media3.ui.PlayerView import androidx.media3.ui.PlayerView
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import java.io.File import java.io.File
@ -52,21 +54,15 @@ class PlayerActivity : AppCompatActivity() {
private lateinit var _exoPlayer: ExoPlayer private lateinit var _exoPlayer: ExoPlayer
private var _shouldPlaybackRestartOnConnectivity: Boolean = false private var _shouldPlaybackRestartOnConnectivity: Boolean = false
private lateinit var _connectivityManager: ConnectivityManager private lateinit var _connectivityManager: ConnectivityManager
private lateinit var _scope: CoroutineScope
private var _wasPlaying = false private var _wasPlaying = false
val currentPosition get() = _exoPlayer.currentPosition
val speed get() = _exoPlayer.playbackParameters.speed
val duration get() = _exoPlayer.duration
val isPlaying get() = _exoPlayer.isPlaying
private val _connectivityEvents = object : ConnectivityManager.NetworkCallback() { private val _connectivityEvents = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) { override fun onAvailable(network: Network) {
super.onAvailable(network) super.onAvailable(network)
Log.i(TAG, "_connectivityEvents onAvailable") Log.i(TAG, "_connectivityEvents onAvailable")
try { try {
_scope.launch(Dispatchers.Main) { lifecycleScope.launch(Dispatchers.Main) {
Log.i(TAG, "onConnectionAvailable") Log.i(TAG, "onConnectionAvailable")
val pos = _exoPlayer.currentPosition val pos = _exoPlayer.currentPosition
@ -100,15 +96,17 @@ class PlayerActivity : AppCompatActivity() {
setStatus(true, null) setStatus(true, null)
} }
NetworkService.instance?.generateUpdateMessage()?.let { sendPlaybackUpdate()
_scope.launch(Dispatchers.IO) {
try {
NetworkService.instance?.sendPlaybackUpdate(it)
} catch (e: Throwable) {
Log.e(TAG, "Unhandled error sending playback update", e)
}
} }
override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) {
super.onPlayWhenReadyChanged(playWhenReady, reason)
sendPlaybackUpdate()
} }
override fun onPositionDiscontinuity(oldPosition: Player.PositionInfo, newPosition: Player.PositionInfo, reason: Int) {
super.onPositionDiscontinuity(oldPosition, newPosition, reason)
sendPlaybackUpdate()
} }
override fun onPlayerError(error: PlaybackException) { override fun onPlayerError(error: PlaybackException) {
@ -134,8 +132,15 @@ class PlayerActivity : AppCompatActivity() {
val fullMessage = getFullExceptionMessage(error) val fullMessage = getFullExceptionMessage(error)
setStatus(false, fullMessage) setStatus(false, fullMessage)
_scope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
try { try {
NetworkService.instance?.sendPlaybackUpdate(PlaybackUpdateMessage(
System.currentTimeMillis(),
0.0,
0.0,
0,
0.0
))
NetworkService.instance?.sendPlaybackError(fullMessage) NetworkService.instance?.sendPlaybackError(fullMessage)
} catch (e: Throwable) { } catch (e: Throwable) {
Log.e(TAG, "Unhandled error sending playback error", e) Log.e(TAG, "Unhandled error sending playback error", e)
@ -145,7 +150,7 @@ class PlayerActivity : AppCompatActivity() {
override fun onVolumeChanged(volume: Float) { override fun onVolumeChanged(volume: Float) {
super.onVolumeChanged(volume) super.onVolumeChanged(volume)
_scope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
try { try {
NetworkService.instance?.sendCastVolumeUpdate(VolumeUpdateMessage(System.currentTimeMillis(), volume.toDouble())) NetworkService.instance?.sendCastVolumeUpdate(VolumeUpdateMessage(System.currentTimeMillis(), volume.toDouble()))
} catch (e: Throwable) { } catch (e: Throwable) {
@ -156,17 +161,58 @@ class PlayerActivity : AppCompatActivity() {
override fun onPlaybackParametersChanged(playbackParameters: PlaybackParameters) { override fun onPlaybackParametersChanged(playbackParameters: PlaybackParameters) {
super.onPlaybackParametersChanged(playbackParameters) super.onPlaybackParametersChanged(playbackParameters)
NetworkService.instance?.generateUpdateMessage()?.let { sendPlaybackUpdate()
_scope.launch(Dispatchers.IO) { }
}
private fun sendPlaybackUpdate() {
val state: Int
if (_exoPlayer.playbackState == ExoPlayer.STATE_READY) {
if (_exoPlayer.playWhenReady) {
state = 1
} else {
state = 2
}
} else if (_exoPlayer.playbackState == ExoPlayer.STATE_BUFFERING) {
if (_exoPlayer.playWhenReady) {
state = 1
} else {
state = 2
}
} else {
state = 0
}
val time: Double
val duration: Double
val speed: Double
if (state != 0) {
duration = (_exoPlayer.duration / 1000.0).coerceAtLeast(1.0)
time = (_exoPlayer.currentPosition / 1000.0).coerceAtLeast(0.0).coerceAtMost(duration)
speed = _exoPlayer.playbackParameters.speed.toDouble().coerceAtLeast(0.01)
} else {
time = 0.0
duration = 0.0
speed = 1.0
}
val playbackUpdate = PlaybackUpdateMessage(
System.currentTimeMillis(),
time,
duration,
state,
speed
)
lifecycleScope.launch(Dispatchers.IO) {
try { try {
NetworkService.instance?.sendPlaybackUpdate(it) NetworkService.instance?.sendPlaybackUpdate(playbackUpdate)
} catch (e: Throwable) { } catch (e: Throwable) {
Log.e(TAG, "Unhandled error sending playback update", e) Log.e(TAG, "Unhandled error sending playback update", e)
} }
} }
} }
}
}
@OptIn(UnstableApi::class) @OptIn(UnstableApi::class)
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -180,7 +226,6 @@ class PlayerActivity : AppCompatActivity() {
_imageSpinner = findViewById(R.id.image_spinner) _imageSpinner = findViewById(R.id.image_spinner)
_textMessage = findViewById(R.id.text_message) _textMessage = findViewById(R.id.text_message)
_layoutOverlay = findViewById(R.id.layout_overlay) _layoutOverlay = findViewById(R.id.layout_overlay)
_scope = CoroutineScope(Dispatchers.Main)
setStatus(true, null) setStatus(true, null)
@ -219,6 +264,17 @@ class PlayerActivity : AppCompatActivity() {
instance = this instance = this
NetworkService.activityCount++ NetworkService.activityCount++
lifecycleScope.launch(Dispatchers.Main) {
while (lifecycleScope.isActive) {
try {
sendPlaybackUpdate()
delay(1000)
} catch (e: Throwable) {
Log.e(TAG, "Failed to send playback update.", e)
}
}
}
} }
override fun onWindowFocusChanged(hasFocus: Boolean) { override fun onWindowFocusChanged(hasFocus: Boolean) {
@ -288,12 +344,25 @@ class PlayerActivity : AppCompatActivity() {
Log.i(TAG, "onDestroy") Log.i(TAG, "onDestroy")
instance = null instance = null
_scope.cancel()
_connectivityManager.unregisterNetworkCallback(_connectivityEvents) _connectivityManager.unregisterNetworkCallback(_connectivityEvents)
_exoPlayer.removeListener(_playerEventListener) _exoPlayer.removeListener(_playerEventListener)
_exoPlayer.stop() _exoPlayer.stop()
_playerControlView.player = null _playerControlView.player = null
NetworkService.activityCount-- NetworkService.activityCount--
GlobalScope.launch(Dispatchers.IO) {
try {
NetworkService.instance?.sendPlaybackUpdate(PlaybackUpdateMessage(
System.currentTimeMillis(),
0.0,
0.0,
0,
0.0
))
} catch (e: Throwable) {
Log.e(TAG, "Failed to send playback update.", e)
}
}
} }
@OptIn(UnstableApi::class) @OptIn(UnstableApi::class)