@@ -40,9 +40,9 @@ import ch.srgssr.pillarbox.cast.extension.getPlaybackState
40
40
import ch.srgssr.pillarbox.cast.extension.getRepeatMode
41
41
import ch.srgssr.pillarbox.cast.extension.getTracks
42
42
import ch.srgssr.pillarbox.cast.extension.getVolume
43
- import ch.srgssr.pillarbox.cast.extension.isMuted
44
43
import ch.srgssr.pillarbox.player.PillarboxDsl
45
44
import ch.srgssr.pillarbox.player.PillarboxPlayer
45
+ import com.google.android.gms.cast.Cast
46
46
import com.google.android.gms.cast.CastStatusCodes
47
47
import com.google.android.gms.cast.MediaError
48
48
import com.google.android.gms.cast.MediaInfo
@@ -58,6 +58,8 @@ import com.google.android.gms.cast.framework.media.RemoteMediaClient.ProgressLis
58
58
import com.google.android.gms.common.api.PendingResult
59
59
import com.google.common.util.concurrent.Futures
60
60
import com.google.common.util.concurrent.ListenableFuture
61
+ import java.io.IOException
62
+ import kotlin.math.roundToInt
61
63
import kotlin.time.Duration.Companion.milliseconds
62
64
63
65
/* *
@@ -115,10 +117,21 @@ class PillarboxCastPlayer internal constructor(
115
117
applicationLooper : Looper = Util .getCurrentOrMainLooper(),
116
118
clock : Clock = Clock .DEFAULT
117
119
) : SimpleBasePlayer(applicationLooper) {
120
+ private val castListener = CastListener ()
121
+ private val positionSupplier = PosSupplier (0 )
118
122
private val sessionListener = SessionListener ()
119
123
private val analyticsCollector = DefaultAnalyticsCollector (clock).apply { addListener(EventLogger ()) }
120
124
private val mediaRouter = if (isMediaRouter2Available()) MediaRouter2Wrapper (context) else null
121
125
126
+ private var castSession: CastSession ? = null
127
+ set(value) {
128
+ field?.removeCastListener(castListener)
129
+ value?.addCastListener(castListener)
130
+ field = value
131
+
132
+ remoteMediaClient = value?.remoteMediaClient
133
+ }
134
+
122
135
private var deviceInfo = if (isMediaRouter2Available()) checkNotNull(mediaRouter).fetchDeviceInfo() else DEVICE_INFO_REMOTE_EMPTY
123
136
set(value) {
124
137
if (field != value) {
@@ -131,8 +144,6 @@ class PillarboxCastPlayer internal constructor(
131
144
private var sessionAvailabilityListener: SessionAvailabilityListener ? = null
132
145
private var playlistTracker: MediaQueueTracker ? = null
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
}
@@ -190,6 +201,10 @@ class PillarboxCastPlayer internal constructor(
190
201
} else {
191
202
TrackSelectionParameters .DEFAULT
192
203
}
204
+ val deviceVolume = castSession?.let {
205
+ (it.volume * MAX_VOLUME ).roundToInt().coerceIn(VOLUME_RANGE )
206
+ }
207
+
193
208
return State .Builder ()
194
209
.setAvailableCommands(remoteMediaClient.getAvailableCommands(seekBackIncrementMs, seekForwardIncrementMs))
195
210
.setPlaybackState(if (playlist.isNotEmpty()) remoteMediaClient.getPlaybackState() else STATE_IDLE )
@@ -206,7 +221,7 @@ class PillarboxCastPlayer internal constructor(
206
221
.setShuffleModeEnabled(false )
207
222
.setRepeatMode(remoteMediaClient.getRepeatMode())
208
223
.setVolume(remoteMediaClient.getVolume().toFloat())
209
- .setIsDeviceMuted(remoteMediaClient.isMuted() )
224
+ .setIsDeviceMuted(castSession?.isMute ? : false )
210
225
.setDeviceInfo(deviceInfo)
211
226
.setMaxSeekToPreviousPositionMs(maxSeekToPreviousPositionMs)
212
227
.setSeekBackIncrementMs(seekBackIncrementMs)
@@ -215,6 +230,9 @@ class PillarboxCastPlayer internal constructor(
215
230
.setPlaybackParameters(PlaybackParameters (remoteMediaClient.getPlaybackRate()))
216
231
.setPlaylistMetadata(playlistMetadata)
217
232
.setIsLoading(isLoading && playlist.isNotEmpty())
233
+ .apply {
234
+ deviceVolume?.let (this ::setDeviceVolume)
235
+ }
218
236
.build()
219
237
}
220
238
@@ -302,8 +320,23 @@ class PillarboxCastPlayer internal constructor(
302
320
setStreamVolume(volume.toDouble())
303
321
}
304
322
305
- override fun handleSetDeviceMuted (muted : Boolean , flags : Int ) = withRemoteClient {
306
- setStreamMute(muted)
323
+ override fun handleSetDeviceVolume (
324
+ @IntRange(from = 0 ) deviceVolume : Int ,
325
+ flags : @C.VolumeFlags Int ,
326
+ ) = withCastSession(" handleSetDeviceVolume" ) {
327
+ volume = deviceVolume.coerceIn(VOLUME_RANGE ) / MAX_VOLUME .toDouble()
328
+ }
329
+
330
+ override fun handleIncreaseDeviceVolume (flags : @C.VolumeFlags Int ): ListenableFuture <* > {
331
+ return handleSetDeviceVolume(deviceVolume + 1 , flags)
332
+ }
333
+
334
+ override fun handleDecreaseDeviceVolume (flags : @C.VolumeFlags Int ): ListenableFuture <* > {
335
+ return handleSetDeviceVolume(deviceVolume - 1 , flags)
336
+ }
337
+
338
+ override fun handleSetDeviceMuted (muted : Boolean , flags : @C.VolumeFlags Int ) = withCastSession(" handleSetDeviceMuted" ) {
339
+ isMute = muted
307
340
}
308
341
309
342
override fun handleSetTrackSelectionParameters (trackSelectionParameters : TrackSelectionParameters ) = withRemoteClient {
@@ -442,6 +475,16 @@ class PillarboxCastPlayer internal constructor(
442
475
return Futures .immediateVoidFuture()
443
476
}
444
477
478
+ private fun withCastSession (method : String , command : CastSession .() -> Unit ): ListenableFuture <* > {
479
+ try {
480
+ castSession?.command()
481
+ } catch (exception: IOException ) {
482
+ Log .w(TAG , " Ignoring $method due to exception" , exception)
483
+ }
484
+
485
+ return Futures .immediateVoidFuture()
486
+ }
487
+
445
488
private fun getCastRepeatMode (repeatMode : @Player.RepeatMode Int ): Int {
446
489
return when (repeatMode) {
447
490
REPEAT_MODE_ALL -> MediaStatus .REPEAT_MODE_REPEAT_ALL
@@ -511,7 +554,7 @@ class PillarboxCastPlayer internal constructor(
511
554
512
555
override fun onSessionEnded (session : CastSession , error : Int ) {
513
556
Log .i(TAG , " onSessionEnded ${session.sessionId} with error = $error " )
514
- remoteMediaClient = null
557
+ castSession = null
515
558
}
516
559
517
560
override fun onSessionEnding (session : CastSession ) {
@@ -525,7 +568,7 @@ class PillarboxCastPlayer internal constructor(
525
568
526
569
override fun onSessionResumed (session : CastSession , wasSuspended : Boolean ) {
527
570
Log .i(TAG , " onSessionResumed ${session.sessionId} wasSuspended = $wasSuspended " )
528
- remoteMediaClient = session.remoteMediaClient
571
+ castSession = session
529
572
}
530
573
531
574
override fun onSessionResuming (session : CastSession , sessionId : String ) {
@@ -538,7 +581,7 @@ class PillarboxCastPlayer internal constructor(
538
581
539
582
override fun onSessionStarted (session : CastSession , sessionId : String ) {
540
583
Log .i(TAG , " onSessionStarted ${session.sessionId} sessionId = $sessionId " )
541
- remoteMediaClient = session.remoteMediaClient
584
+ castSession = session
542
585
}
543
586
544
587
override fun onSessionStarting (session : CastSession ) {
@@ -547,7 +590,7 @@ class PillarboxCastPlayer internal constructor(
547
590
548
591
override fun onSessionSuspended (session : CastSession , reason : Int ) {
549
592
Log .i(TAG , " onSessionSuspended ${session.sessionId} with reason = $reason " )
550
- remoteMediaClient = null
593
+ castSession = null
551
594
}
552
595
}
553
596
@@ -579,7 +622,7 @@ class PillarboxCastPlayer internal constructor(
579
622
580
623
val remoteController = controllers[1 ]
581
624
val deviceInfo = DeviceInfo .Builder (DeviceInfo .PLAYBACK_TYPE_REMOTE )
582
- .setMaxVolume(remoteController.volumeMax )
625
+ .setMaxVolume(MAX_VOLUME )
583
626
.setRoutingControllerId(remoteController.id)
584
627
.build()
585
628
@@ -599,9 +642,26 @@ class PillarboxCastPlayer internal constructor(
599
642
}
600
643
}
601
644
645
+ private inner class CastListener : Cast .Listener () {
646
+ override fun onVolumeChanged () {
647
+ Log .d(TAG , " onVolumeChanged" )
648
+ invalidateState()
649
+ }
650
+ }
651
+
602
652
private companion object {
603
653
private const val TAG = " CastSimplePlayer"
604
- private val DEVICE_INFO_REMOTE_EMPTY = DeviceInfo .Builder (DeviceInfo .PLAYBACK_TYPE_REMOTE ).build()
654
+
655
+ /* *
656
+ * @see androidx.media3.cast.CastPlayer.MAX_VOLUME
657
+ */
658
+ private const val MAX_VOLUME = 20
659
+
660
+ private val DEVICE_INFO_REMOTE_EMPTY = DeviceInfo .Builder (DeviceInfo .PLAYBACK_TYPE_REMOTE )
661
+ .setMaxVolume(MAX_VOLUME )
662
+ .build()
663
+
664
+ private val VOLUME_RANGE = 0 .. MAX_VOLUME
605
665
606
666
private fun createTrackSelectionParametersFromSelectedTracks (tracks : Tracks ): TrackSelectionParameters {
607
667
return TrackSelectionParameters .DEFAULT .buildUpon().apply {
0 commit comments