@@ -39,9 +39,9 @@ import ch.srgssr.pillarbox.cast.extension.getPlaybackState
39
39
import ch.srgssr.pillarbox.cast.extension.getRepeatMode
40
40
import ch.srgssr.pillarbox.cast.extension.getTracks
41
41
import ch.srgssr.pillarbox.cast.extension.getVolume
42
- import ch.srgssr.pillarbox.cast.extension.isMuted
43
42
import ch.srgssr.pillarbox.player.PillarboxDsl
44
43
import ch.srgssr.pillarbox.player.PillarboxPlayer
44
+ import com.google.android.gms.cast.Cast
45
45
import com.google.android.gms.cast.CastStatusCodes
46
46
import com.google.android.gms.cast.MediaError
47
47
import com.google.android.gms.cast.MediaInfo
@@ -57,6 +57,8 @@ import com.google.android.gms.cast.framework.media.RemoteMediaClient.ProgressLis
57
57
import com.google.android.gms.common.api.PendingResult
58
58
import com.google.common.util.concurrent.Futures
59
59
import com.google.common.util.concurrent.ListenableFuture
60
+ import java.io.IOException
61
+ import kotlin.math.roundToInt
60
62
import kotlin.time.Duration.Companion.milliseconds
61
63
62
64
/* *
@@ -114,10 +116,21 @@ class PillarboxCastPlayer internal constructor(
114
116
applicationLooper : Looper = Util .getCurrentOrMainLooper(),
115
117
clock : Clock = Clock .DEFAULT
116
118
) : SimpleBasePlayer(applicationLooper) {
119
+ private val castListener = CastListener ()
120
+ private val positionSupplier = PosSupplier (0 )
117
121
private val sessionListener = SessionListener ()
118
122
private val analyticsCollector = DefaultAnalyticsCollector (clock).apply { addListener(EventLogger ()) }
119
123
private val mediaRouter = if (isMediaRouter2Available()) MediaRouter2Wrapper (context) else null
120
124
125
+ private var castSession: CastSession ? = null
126
+ set(value) {
127
+ field?.removeCastListener(castListener)
128
+ value?.addCastListener(castListener)
129
+ field = value
130
+
131
+ remoteMediaClient = value?.remoteMediaClient
132
+ }
133
+
121
134
private var deviceInfo = if (isMediaRouter2Available()) checkNotNull(mediaRouter).fetchDeviceInfo() else DEVICE_INFO_REMOTE_EMPTY
122
135
set(value) {
123
136
if (field != value) {
@@ -131,8 +144,6 @@ class PillarboxCastPlayer internal constructor(
131
144
private var playlistTracker: MediaQueueTracker ? = null
132
145
private var trackSelectionParameters: TrackSelectionParameters = TrackSelectionParameters .DEFAULT
133
146
134
- private val positionSupplier: PosSupplier = PosSupplier (0 )
135
-
136
147
private var remoteMediaClient: RemoteMediaClient ? = null
137
148
set(value) {
138
149
if (field != value) {
@@ -158,7 +169,7 @@ class PillarboxCastPlayer internal constructor(
158
169
159
170
init {
160
171
castContext.sessionManager.addSessionManagerListener(sessionListener, CastSession ::class .java)
161
- remoteMediaClient = castContext.sessionManager.currentCastSession?.remoteMediaClient
172
+ castSession = castContext.sessionManager.currentCastSession
162
173
addListener(analyticsCollector)
163
174
analyticsCollector.setPlayer(this , applicationLooper)
164
175
}
@@ -183,6 +194,9 @@ class PillarboxCastPlayer internal constructor(
183
194
val remoteMediaClient = remoteMediaClient ? : return State .Builder ().build()
184
195
val itemCount = remoteMediaClient.mediaQueue.itemCount
185
196
val playlist = remoteMediaClient.createPlaylist()
197
+ val deviceVolume = castSession?.let {
198
+ (it.volume * MAX_VOLUME ).roundToInt().coerceIn(VOLUME_RANGE )
199
+ }
186
200
187
201
return State .Builder ()
188
202
.setAvailableCommands(remoteMediaClient.getAvailableCommands(seekBackIncrementMs, seekForwardIncrementMs))
@@ -200,14 +214,17 @@ class PillarboxCastPlayer internal constructor(
200
214
.setShuffleModeEnabled(false )
201
215
.setRepeatMode(remoteMediaClient.getRepeatMode())
202
216
.setVolume(remoteMediaClient.getVolume().toFloat())
203
- .setIsDeviceMuted(remoteMediaClient.isMuted() )
217
+ .setIsDeviceMuted(castSession?.isMute ? : false )
204
218
.setDeviceInfo(deviceInfo)
205
219
.setMaxSeekToPreviousPositionMs(maxSeekToPreviousPositionMs)
206
220
.setSeekBackIncrementMs(seekBackIncrementMs)
207
221
.setSeekForwardIncrementMs(seekForwardIncrementMs)
208
222
.setTrackSelectionParameters(trackSelectionParameters)
209
223
.setPlaybackParameters(PlaybackParameters (remoteMediaClient.getPlaybackRate()))
210
224
.setPlaylistMetadata(playlistMetadata)
225
+ .apply {
226
+ deviceVolume?.let (this ::setDeviceVolume)
227
+ }
211
228
.build()
212
229
}
213
230
@@ -295,8 +312,23 @@ class PillarboxCastPlayer internal constructor(
295
312
setStreamVolume(volume.toDouble())
296
313
}
297
314
298
- override fun handleSetDeviceMuted (muted : Boolean , flags : Int ) = withRemoteClient {
299
- setStreamMute(muted)
315
+ override fun handleSetDeviceVolume (
316
+ @IntRange(from = 0 ) deviceVolume : Int ,
317
+ flags : @C.VolumeFlags Int ,
318
+ ) = withCastSession(" handleSetDeviceVolume" ) {
319
+ volume = deviceVolume.coerceIn(VOLUME_RANGE ) / MAX_VOLUME .toDouble()
320
+ }
321
+
322
+ override fun handleIncreaseDeviceVolume (flags : @C.VolumeFlags Int ): ListenableFuture <* > {
323
+ return handleSetDeviceVolume(deviceVolume + 1 , flags)
324
+ }
325
+
326
+ override fun handleDecreaseDeviceVolume (flags : @C.VolumeFlags Int ): ListenableFuture <* > {
327
+ return handleSetDeviceVolume(deviceVolume - 1 , flags)
328
+ }
329
+
330
+ override fun handleSetDeviceMuted (muted : Boolean , flags : @C.VolumeFlags Int ) = withCastSession(" handleSetDeviceMuted" ) {
331
+ isMute = muted
300
332
}
301
333
302
334
override fun handleSetTrackSelectionParameters (trackSelectionParameters : TrackSelectionParameters ) = withRemoteClient {
@@ -436,6 +468,16 @@ class PillarboxCastPlayer internal constructor(
436
468
return Futures .immediateVoidFuture()
437
469
}
438
470
471
+ private fun withCastSession (method : String , command : CastSession .() -> Unit ): ListenableFuture <* > {
472
+ try {
473
+ castSession?.command()
474
+ } catch (exception: IOException ) {
475
+ Log .w(TAG , " Ignoring $method due to exception" , exception)
476
+ }
477
+
478
+ return Futures .immediateVoidFuture()
479
+ }
480
+
439
481
private fun getCastRepeatMode (repeatMode : @Player.RepeatMode Int ): Int {
440
482
return when (repeatMode) {
441
483
REPEAT_MODE_ALL -> MediaStatus .REPEAT_MODE_REPEAT_ALL
@@ -505,7 +547,7 @@ class PillarboxCastPlayer internal constructor(
505
547
506
548
override fun onSessionEnded (session : CastSession , error : Int ) {
507
549
Log .i(TAG , " onSessionEnded ${session.sessionId} with error = $error " )
508
- remoteMediaClient = null
550
+ castSession = null
509
551
}
510
552
511
553
override fun onSessionEnding (session : CastSession ) {
@@ -518,7 +560,7 @@ class PillarboxCastPlayer internal constructor(
518
560
519
561
override fun onSessionResumed (session : CastSession , wasSuspended : Boolean ) {
520
562
Log .i(TAG , " onSessionResumed ${session.sessionId} wasSuspended = $wasSuspended " )
521
- remoteMediaClient = session.remoteMediaClient
563
+ castSession = session
522
564
}
523
565
524
566
override fun onSessionResuming (session : CastSession , sessionId : String ) {
@@ -531,7 +573,7 @@ class PillarboxCastPlayer internal constructor(
531
573
532
574
override fun onSessionStarted (session : CastSession , sessionId : String ) {
533
575
Log .i(TAG , " onSessionStarted ${session.sessionId} sessionId = $sessionId " )
534
- remoteMediaClient = session.remoteMediaClient
576
+ castSession = session
535
577
}
536
578
537
579
override fun onSessionStarting (session : CastSession ) {
@@ -540,7 +582,7 @@ class PillarboxCastPlayer internal constructor(
540
582
541
583
override fun onSessionSuspended (session : CastSession , reason : Int ) {
542
584
Log .i(TAG , " onSessionSuspended ${session.sessionId} with reason = $reason " )
543
- remoteMediaClient = null
585
+ castSession = null
544
586
}
545
587
}
546
588
@@ -572,7 +614,7 @@ class PillarboxCastPlayer internal constructor(
572
614
573
615
val remoteController = controllers[1 ]
574
616
val deviceInfo = DeviceInfo .Builder (DeviceInfo .PLAYBACK_TYPE_REMOTE )
575
- .setMaxVolume(remoteController.volumeMax )
617
+ .setMaxVolume(MAX_VOLUME )
576
618
.setRoutingControllerId(remoteController.id)
577
619
.build()
578
620
@@ -592,9 +634,26 @@ class PillarboxCastPlayer internal constructor(
592
634
}
593
635
}
594
636
637
+ private inner class CastListener : Cast .Listener () {
638
+ override fun onVolumeChanged () {
639
+ Log .d(TAG , " onVolumeChanged" )
640
+ invalidateState()
641
+ }
642
+ }
643
+
595
644
private companion object {
596
645
private const val TAG = " CastSimplePlayer"
597
- private val DEVICE_INFO_REMOTE_EMPTY = DeviceInfo .Builder (DeviceInfo .PLAYBACK_TYPE_REMOTE ).build()
646
+
647
+ /* *
648
+ * @see androidx.media3.cast.CastPlayer.MAX_VOLUME
649
+ */
650
+ private const val MAX_VOLUME = 20
651
+
652
+ private val DEVICE_INFO_REMOTE_EMPTY = DeviceInfo .Builder (DeviceInfo .PLAYBACK_TYPE_REMOTE )
653
+ .setMaxVolume(MAX_VOLUME )
654
+ .build()
655
+
656
+ private val VOLUME_RANGE = 0 .. MAX_VOLUME
598
657
599
658
private fun getIdleReasonString (idleReason : Int ): String {
600
659
return when (idleReason) {
0 commit comments