diff --git a/android/app/src/main/java/com/audiobookshelf/app/player/CastPlayer.kt b/android/app/src/main/java/com/audiobookshelf/app/player/CastPlayer.kt index b1aae56b..9f7f59f9 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/player/CastPlayer.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/player/CastPlayer.kt @@ -76,7 +76,10 @@ class CastPlayer(var castContext: CastContext) : BasePlayer() { COMMAND_GET_MEDIA_ITEMS_METADATA, COMMAND_SET_MEDIA_ITEMS_METADATA, COMMAND_CHANGE_MEDIA_ITEMS, - COMMAND_GET_TRACKS) + COMMAND_GET_TRACKS, + COMMAND_GET_DEVICE_VOLUME, + COMMAND_SET_DEVICE_VOLUME, + COMMAND_ADJUST_DEVICE_VOLUME) .build() var currentPlaybackState = Player.STATE_IDLE @@ -861,27 +864,48 @@ class CastPlayer(var castContext: CastContext) : BasePlayer() { } override fun getDeviceVolume(): Int { - return 0 + val session = castContext.sessionManager.currentCastSession + return if (session != null && !session.isMute) { + (session.volume * 100).toInt() + } else { + 0 + } } override fun isDeviceMuted(): Boolean { - return false + val session = castContext.sessionManager.currentCastSession + return session?.isMute ?: false } override fun setDeviceVolume(volume: Int) { - + val session = castContext.sessionManager.currentCastSession + try { + val clamped = volume.coerceIn(0, 100) + session?.volume = clamped / 100.0 + } catch (e: Exception) { + Log.e(tag, "Failed to set cast device volume", e) + } } override fun increaseDeviceVolume() { - + val currentVolume = getDeviceVolume() + val newVolume = (currentVolume + 1).coerceAtMost(100) + setDeviceVolume(newVolume) } override fun decreaseDeviceVolume() { - + val currentVolume = getDeviceVolume() + val newVolume = (currentVolume - 1).coerceAtLeast(0) + setDeviceVolume(newVolume) } override fun setDeviceMuted(muted: Boolean) { - + val session = castContext.sessionManager.currentCastSession + try { + session?.isMute = muted + } catch (e: Exception) { + Log.e(tag, "Failed to set cast device mute state", e) + } } inner class StatusListener() : RemoteMediaClient.Callback(), SessionManagerListener, RemoteMediaClient.ProgressListener { diff --git a/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt b/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt index 5e3af81e..a967345b 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt @@ -18,6 +18,8 @@ import android.support.v4.media.MediaDescriptionCompat import android.support.v4.media.MediaMetadataCompat import android.support.v4.media.session.MediaControllerCompat import android.support.v4.media.session.MediaSessionCompat +import androidx.media.VolumeProviderCompat +import android.media.AudioManager import android.support.v4.media.session.PlaybackStateCompat import android.util.Log import androidx.annotation.RequiresApi @@ -94,6 +96,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() { private lateinit var mediaSessionConnector: MediaSessionConnector private lateinit var playerNotificationManager: PlayerNotificationManager lateinit var mediaSession: MediaSessionCompat + private var remoteVolumeProvider: VolumeProviderCompat? = null private lateinit var transportControls: MediaControllerCompat.TransportControls lateinit var mediaManager: MediaManager @@ -705,11 +708,13 @@ class PlayerNotificationService : MediaBrowserServiceCompat() { Log.d(tag, "switchToPlayer: Using Cast Player " + castPlayer?.deviceInfo) mediaSessionConnector.setPlayer(castPlayer) playerNotificationManager.setPlayer(castPlayer) + setMediaSessionToCastVolume() castPlayer as CastPlayer } else { Log.d(tag, "switchToPlayer: Using ExoPlayer") mediaSessionConnector.setPlayer(mPlayer) playerNotificationManager.setPlayer(mPlayer) + setMediaSessionToLocalVolume() mPlayer } @@ -726,6 +731,44 @@ class PlayerNotificationService : MediaBrowserServiceCompat() { } } + private fun setMediaSessionToCastVolume() { + val currentVol = try { castPlayer?.getDeviceVolume() ?: 0 } catch (e: Exception) { 0 } + val provider = object : VolumeProviderCompat(VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE, 100, currentVol) { + override fun onSetVolumeTo(volume: Int) { + val clamped = volume.coerceIn(0, 100) + try { + castPlayer?.setDeviceVolume(clamped) + val actual = castPlayer?.getDeviceVolume() ?: clamped + setCurrentVolume(actual) + } catch (_: Exception) {} + } + override fun onAdjustVolume(direction: Int) { + val current = try { castPlayer?.getDeviceVolume() ?: currentVolume } catch (_: Exception) { currentVolume } + val step = if (direction > 0) 1 else if (direction < 0) -1 else 0 + if (step == 0) return + var target = (current + step).coerceIn(0, 100) + try { + castPlayer?.setDeviceVolume(target) + var actual = castPlayer?.getDeviceVolume() ?: current + // If device ignored tiny step, try nudging up to 3 steps total. + if (actual == current) { + target = (current + step * 3).coerceIn(0, 100) + castPlayer?.setDeviceVolume(target) + actual = castPlayer?.getDeviceVolume() ?: current + } + setCurrentVolume(actual) + } catch (_: Exception) {} + } + } + remoteVolumeProvider = provider + mediaSession.setPlaybackToRemote(provider) + } + + private fun setMediaSessionToLocalVolume() { + mediaSession.setPlaybackToLocal(AudioManager.STREAM_MUSIC) + remoteVolumeProvider = null + } + fun getCurrentTrackStartOffsetMs(): Long { return if (currentPlayer.mediaItemCount > 1) { val windowIndex = currentPlayer.currentMediaItemIndex