Skip to content

Instantly share code, notes, and snippets.

@NielsMasdorp
Last active September 6, 2022 10:14
Show Gist options
  • Save NielsMasdorp/d22c9286aa1bd3e1629258d156e7c6e2 to your computer and use it in GitHub Desktop.
Save NielsMasdorp/d22c9286aa1bd3e1629258d156e7c6e2 to your computer and use it in GitHub Desktop.
Composite player implementation in media3
@UnstableApi
class StreamService : MediaSessionService(),
Listener, MediaSession.SessionCallback, SessionAvailabilityListener {
private lateinit var player: CompositePlayer
private lateinit var mediaSession: MediaSession
override fun onCreate() {
super.onCreate()
initialize()
}
override fun onDestroy() {
player.removeListener(this)
player.release()
mediaSession.release()
super.onDestroy()
}
override fun onGetSession(controllerInfo: MediaSession.ControllerInfo) = mediaSession
override fun onCastSessionAvailable() = player.switchToCast()
override fun onCastSessionUnavailable() = player.switchToLocal()
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
// etc
}
override fun onMediaMetadataChanged(mediaMetadata: MediaMetadata) {
// etc
}
override fun onPlayerError(error: PlaybackException) {
// etc
}
/**
* Called when [Player] events happen
*/
override fun onEvents(player: Player, events: Events) {
// etc
}
private fun initialize() {
val localPlayer = ExoPlayer.Builder(this)
.setAudioAttributes(
AudioAttributes.Builder()
.setUsage(C.USAGE_MEDIA)
.setContentType(C.CONTENT_TYPE_MUSIC)
.build(), true // Automatic requesting and dropping audio focus
)
.setHandleAudioBecomingNoisy(true) // Handle headphones disconnect
.setWakeMode(C.WAKE_MODE_NETWORK) // Wake+WiFi lock while playing
.build()
val intent = Intent(this, NederadioActivity::class.java)
val immutableFlag = if (Build.VERSION.SDK_INT >= 23) FLAG_IMMUTABLE else 0
val pendingIntent = PendingIntent.getActivity(
this,
0,
intent,
immutableFlag or FLAG_UPDATE_CURRENT
)
val castContext = CastContext.getSharedInstance(this)
val castPlayer = CastPlayer(castContext, StreamMediaItemConverter())
player = CompositePlayer(
localPlayer = localPlayer,
castPlayer = castPlayer
).apply {
setSessionAvailabilityListener(this@StreamService)
addListener(this@StreamService)
}
mediaSession = MediaSession.Builder(this, player)
.setSessionActivity(pendingIntent)
.setSessionCallback(this)
.build()
}
}
------------------------------------------
@UnstableApi
class CompositePlayer(
private val localPlayer: ExoPlayer,
private val castPlayer: CastPlayer
) : Player, Player.Listener {
private var currentListeners: CopyOnWriteArraySet<Player.Listener> = CopyOnWriteArraySet()
private var currentMediaItem: MediaItem? = null
private var currentPlayer: Player = if (castPlayer.isCastSessionAvailable) {
castPlayer
} else {
localPlayer
}
init {
localPlayer.addListener(this)
castPlayer.addListener(this)
}
fun setSessionAvailabilityListener(listener: SessionAvailabilityListener) {
castPlayer.setSessionAvailabilityListener(listener)
}
fun switchToLocal() = switchPlayer(localPlayer)
fun switchToCast() = switchPlayer(castPlayer)
private fun switchPlayer(newPlayer: Player) {
if (currentPlayer == newPlayer) return
currentMediaItem?.let { current ->
currentPlayer.stop()
currentPlayer.clearMediaItems()
currentPlayer = newPlayer
currentPlayer.setMediaItem(current)
currentPlayer.prepare()
currentPlayer.play()
} ?: run {
currentPlayer = newPlayer
}
}
override fun getApplicationLooper(): Looper = currentPlayer.applicationLooper
override fun addListener(listener: Player.Listener) {
currentListeners.add(listener)
}
override fun removeListener(listener: Player.Listener) {
currentListeners.remove(listener)
}
override fun setMediaItems(mediaItems: MutableList<MediaItem>) =
currentPlayer.setMediaItems(mediaItems)
override fun setMediaItems(mediaItems: MutableList<MediaItem>, resetPosition: Boolean) =
currentPlayer.setMediaItems(mediaItems, resetPosition)
override fun setMediaItems(
mediaItems: MutableList<MediaItem>,
startWindowIndex: Int,
startPositionMs: Long
) = currentPlayer.setMediaItems(mediaItems, startWindowIndex, startPositionMs)
override fun setMediaItem(mediaItem: MediaItem) {
currentMediaItem = mediaItem
currentPlayer.setMediaItem(mediaItem)
}
override fun setMediaItem(mediaItem: MediaItem, startPositionMs: Long) =
currentPlayer.setMediaItem(mediaItem, startPositionMs)
override fun setMediaItem(mediaItem: MediaItem, resetPosition: Boolean) =
currentPlayer.setMediaItem(mediaItem, resetPosition)
override fun addMediaItem(mediaItem: MediaItem) = currentPlayer.addMediaItem(mediaItem)
override fun addMediaItem(index: Int, mediaItem: MediaItem) =
currentPlayer.addMediaItem(index, mediaItem)
override fun addMediaItems(mediaItems: MutableList<MediaItem>) =
currentPlayer.addMediaItems(mediaItems)
override fun addMediaItems(index: Int, mediaItems: MutableList<MediaItem>) =
currentPlayer.addMediaItems(index, mediaItems)
override fun moveMediaItem(currentIndex: Int, newIndex: Int) =
currentPlayer.moveMediaItem(currentIndex, newIndex)
override fun moveMediaItems(fromIndex: Int, toIndex: Int, newIndex: Int) =
currentPlayer.moveMediaItems(fromIndex, toIndex, newIndex)
override fun removeMediaItem(index: Int) = currentPlayer.removeMediaItem(index)
override fun removeMediaItems(fromIndex: Int, toIndex: Int) =
currentPlayer.removeMediaItems(fromIndex, toIndex)
override fun clearMediaItems() = currentPlayer.clearMediaItems()
override fun isCommandAvailable(command: Int): Boolean =
currentPlayer.isCommandAvailable(command)
override fun canAdvertiseSession(): Boolean = currentPlayer.canAdvertiseSession()
override fun getAvailableCommands(): Player.Commands = currentPlayer.availableCommands
override fun prepare() = currentPlayer.prepare()
override fun getPlaybackState(): Int = currentPlayer.playbackState
override fun getPlaybackSuppressionReason(): Int = currentPlayer.playbackSuppressionReason
override fun isPlaying(): Boolean = currentPlayer.isPlaying
override fun getPlayerError(): PlaybackException? = currentPlayer.playerError
override fun play() = currentPlayer.play()
override fun pause() = currentPlayer.pause()
override fun setPlayWhenReady(playWhenReady: Boolean) {
currentPlayer.playWhenReady = playWhenReady
}
override fun getPlayWhenReady(): Boolean = currentPlayer.playWhenReady
override fun setRepeatMode(repeatMode: Int) {
currentPlayer.repeatMode = repeatMode
}
override fun getRepeatMode(): Int = currentPlayer.repeatMode
override fun setShuffleModeEnabled(shuffleModeEnabled: Boolean) {
currentPlayer.shuffleModeEnabled = shuffleModeEnabled
}
override fun getShuffleModeEnabled(): Boolean = currentPlayer.shuffleModeEnabled
override fun isLoading(): Boolean = currentPlayer.isLoading
override fun seekToDefaultPosition() = currentPlayer.seekToDefaultPosition()
override fun seekToDefaultPosition(windowIndex: Int) =
currentPlayer.seekToDefaultPosition(windowIndex)
override fun seekTo(positionMs: Long) = currentPlayer.seekTo(positionMs)
override fun seekTo(windowIndex: Int, positionMs: Long) =
currentPlayer.seekTo(windowIndex, positionMs)
override fun getSeekBackIncrement(): Long = currentPlayer.seekBackIncrement
override fun seekBack() = currentPlayer.seekBack()
override fun getSeekForwardIncrement(): Long = currentPlayer.seekForwardIncrement
override fun seekForward() = currentPlayer.seekForward()
override fun hasPrevious(): Boolean = currentPlayer.hasPrevious()
override fun hasPreviousWindow(): Boolean = currentPlayer.hasPreviousWindow()
override fun hasPreviousMediaItem(): Boolean = currentPlayer.hasPreviousMediaItem()
override fun previous() = currentPlayer.previous()
override fun seekToPreviousWindow() = currentPlayer.seekToPreviousWindow()
override fun seekToPreviousMediaItem() = currentPlayer.seekToPreviousMediaItem()
override fun getMaxSeekToPreviousPosition(): Long = currentPlayer.maxSeekToPreviousPosition
override fun seekToPrevious() = currentPlayer.seekToPrevious()
override fun hasNext(): Boolean = currentPlayer.hasNext()
override fun hasNextWindow(): Boolean = currentPlayer.hasNextWindow()
override fun hasNextMediaItem(): Boolean = currentPlayer.hasNextMediaItem()
override fun next() = currentPlayer.next()
override fun seekToNextWindow() = currentPlayer.seekToNextWindow()
override fun seekToNextMediaItem() = currentPlayer.seekToNextMediaItem()
override fun seekToNext() = currentPlayer.seekToNext()
override fun setPlaybackParameters(playbackParameters: PlaybackParameters) {
currentPlayer.playbackParameters = playbackParameters
}
override fun setPlaybackSpeed(speed: Float) = currentPlayer.setPlaybackSpeed(speed)
override fun getPlaybackParameters(): PlaybackParameters = currentPlayer.playbackParameters
override fun stop() = currentPlayer.stop()
override fun stop(reset: Boolean) = currentPlayer.stop(reset)
override fun release() {
currentListeners.clear()
localPlayer.removeListener(this)
castPlayer.removeListener(this)
localPlayer.release()
castPlayer.release()
castPlayer.setSessionAvailabilityListener(null)
}
override fun getCurrentTrackGroups(): TrackGroupArray = currentPlayer.currentTrackGroups
override fun getCurrentTrackSelections(): TrackSelectionArray =
currentPlayer.currentTrackSelections
override fun getCurrentTracksInfo(): TracksInfo = currentPlayer.currentTracksInfo
override fun getTrackSelectionParameters(): TrackSelectionParameters =
currentPlayer.trackSelectionParameters
override fun setTrackSelectionParameters(parameters: TrackSelectionParameters) {
currentPlayer.trackSelectionParameters = parameters
}
override fun getMediaMetadata(): MediaMetadata =
currentMediaItem?.mediaMetadata ?: MediaMetadata.EMPTY
override fun getPlaylistMetadata(): MediaMetadata = currentPlayer.playlistMetadata
override fun setPlaylistMetadata(mediaMetadata: MediaMetadata) {
currentPlayer.playlistMetadata = mediaMetadata
}
override fun getCurrentManifest(): Any? = currentPlayer.currentManifest
override fun getCurrentTimeline(): Timeline = currentPlayer.currentTimeline
override fun getCurrentPeriodIndex(): Int = currentPlayer.currentPeriodIndex
override fun getCurrentWindowIndex(): Int = currentPlayer.currentWindowIndex
override fun getCurrentMediaItemIndex(): Int = currentPlayer.currentMediaItemIndex
override fun getNextWindowIndex(): Int = currentPlayer.nextWindowIndex
override fun getNextMediaItemIndex(): Int = currentPlayer.nextMediaItemIndex
override fun getPreviousWindowIndex(): Int = currentPlayer.previousWindowIndex
override fun getPreviousMediaItemIndex(): Int = currentPlayer.previousMediaItemIndex
override fun getCurrentMediaItem(): MediaItem? = currentMediaItem
override fun getMediaItemCount(): Int = currentPlayer.mediaItemCount
override fun getMediaItemAt(index: Int): MediaItem = currentPlayer.getMediaItemAt(index)
override fun getDuration(): Long = currentPlayer.duration
override fun getCurrentPosition(): Long = currentPlayer.currentPosition
override fun getBufferedPosition(): Long = currentPlayer.bufferedPosition
override fun getBufferedPercentage(): Int = currentPlayer.bufferedPercentage
override fun getTotalBufferedDuration(): Long = currentPlayer.totalBufferedDuration
override fun isCurrentWindowDynamic(): Boolean = currentPlayer.isCurrentWindowDynamic
override fun isCurrentMediaItemDynamic(): Boolean = currentPlayer.isCurrentMediaItemDynamic
override fun isCurrentWindowLive(): Boolean = currentPlayer.isCurrentWindowLive
override fun isCurrentMediaItemLive(): Boolean = currentPlayer.isCurrentMediaItemLive
override fun getCurrentLiveOffset(): Long = currentPlayer.currentLiveOffset
override fun isCurrentWindowSeekable(): Boolean = currentPlayer.isCurrentWindowSeekable
override fun isCurrentMediaItemSeekable(): Boolean = currentPlayer.isCurrentMediaItemSeekable
override fun isPlayingAd(): Boolean = currentPlayer.isPlayingAd
override fun getCurrentAdGroupIndex(): Int = currentPlayer.currentAdGroupIndex
override fun getCurrentAdIndexInAdGroup(): Int = currentPlayer.currentAdIndexInAdGroup
override fun getContentDuration(): Long = currentPlayer.contentDuration
override fun getContentPosition(): Long = currentPlayer.contentPosition
override fun getContentBufferedPosition(): Long = currentPlayer.contentBufferedPosition
override fun getAudioAttributes(): AudioAttributes = currentPlayer.audioAttributes
override fun setVolume(volume: Float) {
currentPlayer.volume = volume
}
override fun getVolume(): Float = currentPlayer.volume
override fun clearVideoSurface() = currentPlayer.clearVideoSurface()
override fun clearVideoSurface(surface: Surface?) = currentPlayer.clearVideoSurface(surface)
override fun setVideoSurface(surface: Surface?) = currentPlayer.setVideoSurface(surface)
override fun setVideoSurfaceHolder(surfaceHolder: SurfaceHolder?) =
currentPlayer.setVideoSurfaceHolder(surfaceHolder)
override fun clearVideoSurfaceHolder(surfaceHolder: SurfaceHolder?) =
currentPlayer.clearVideoSurfaceHolder(surfaceHolder)
override fun setVideoSurfaceView(surfaceView: SurfaceView?) =
currentPlayer.setVideoSurfaceView(surfaceView)
override fun clearVideoSurfaceView(surfaceView: SurfaceView?) =
currentPlayer.clearVideoSurfaceView(surfaceView)
override fun setVideoTextureView(textureView: TextureView?) =
currentPlayer.setVideoTextureView(textureView)
override fun clearVideoTextureView(textureView: TextureView?) =
currentPlayer.clearVideoTextureView(textureView)
override fun getVideoSize(): VideoSize = currentPlayer.videoSize
override fun getCurrentCues(): MutableList<Cue> = currentPlayer.currentCues
override fun getDeviceInfo(): DeviceInfo = currentPlayer.deviceInfo
override fun getDeviceVolume(): Int = currentPlayer.deviceVolume
override fun isDeviceMuted(): Boolean = currentPlayer.isDeviceMuted
override fun setDeviceVolume(volume: Int) {
currentPlayer.deviceVolume = volume
}
override fun increaseDeviceVolume() = currentPlayer.increaseDeviceVolume()
override fun decreaseDeviceVolume() = currentPlayer.decreaseDeviceVolume()
override fun setDeviceMuted(muted: Boolean) {
currentPlayer.isDeviceMuted = muted
}
// Player.Listener callbacks ---------------------------------------------------
override fun onTimelineChanged(timeline: Timeline, reason: Int) {
for (listener in currentListeners) {
listener.onTimelineChanged(timeline, reason)
}
}
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
for (listener in currentListeners) {
listener.onMediaItemTransition(currentMediaItem, reason)
}
}
override fun onTracksChanged(
trackGroups: TrackGroupArray,
trackSelections: TrackSelectionArray
) {
for (listener in currentListeners) {
listener.onTracksChanged(trackGroups, trackSelections)
}
}
override fun onTracksInfoChanged(tracksInfo: TracksInfo) {
for (listener in currentListeners) {
listener.onTracksInfoChanged(tracksInfo)
}
}
override fun onMediaMetadataChanged(mediaMetadata: MediaMetadata) {
for (listener in currentListeners) {
listener.onMediaMetadataChanged(mediaMetadata)
}
}
override fun onPlaylistMetadataChanged(mediaMetadata: MediaMetadata) {
for (listener in currentListeners) {
listener.onPlaylistMetadataChanged(mediaMetadata)
}
}
override fun onIsLoadingChanged(isLoading: Boolean) {
for (listener in currentListeners) {
listener.onIsLoadingChanged(isLoading)
}
}
override fun onLoadingChanged(isLoading: Boolean) {
for (listener in currentListeners) {
listener.onLoadingChanged(isLoading)
}
}
override fun onAvailableCommandsChanged(availableCommands: Player.Commands) {
for (listener in currentListeners) {
listener.onAvailableCommandsChanged(availableCommands)
}
}
override fun onTrackSelectionParametersChanged(parameters: TrackSelectionParameters) {
for (listener in currentListeners) {
listener.onTrackSelectionParametersChanged(parameters)
}
}
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
for (listener in currentListeners) {
listener.onPlayerStateChanged(playWhenReady, playbackState)
}
}
override fun onPlaybackStateChanged(playbackState: Int) {
for (listener in currentListeners) {
listener.onPlaybackStateChanged(playbackState)
}
}
override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) {
for (listener in currentListeners) {
listener.onPlayWhenReadyChanged(playWhenReady, reason)
}
}
override fun onPlaybackSuppressionReasonChanged(playbackSuppressionReason: Int) {
for (listener in currentListeners) {
listener.onPlaybackSuppressionReasonChanged(playbackSuppressionReason)
}
}
override fun onIsPlayingChanged(isPlaying: Boolean) {
for (listener in currentListeners) {
listener.onIsPlayingChanged(isPlaying)
}
}
override fun onRepeatModeChanged(repeatMode: Int) {
for (listener in currentListeners) {
listener.onRepeatModeChanged(repeatMode)
}
}
override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) {
for (listener in currentListeners) {
listener.onShuffleModeEnabledChanged(shuffleModeEnabled)
}
}
override fun onPlayerError(error: PlaybackException) {
for (listener in currentListeners) {
listener.onPlayerError(error)
}
}
override fun onPlayerErrorChanged(error: PlaybackException?) {
for (listener in currentListeners) {
listener.onPlayerErrorChanged(error)
}
}
override fun onPositionDiscontinuity(
oldPosition: Player.PositionInfo,
newPosition: Player.PositionInfo,
reason: Int
) {
for (listener in currentListeners) {
listener.onPositionDiscontinuity(oldPosition, newPosition, reason)
}
}
override fun onPositionDiscontinuity(reason: Int) {
for (listener in currentListeners) {
listener.onPositionDiscontinuity(reason)
}
}
override fun onPlaybackParametersChanged(playbackParameters: PlaybackParameters) {
for (listener in currentListeners) {
listener.onPlaybackParametersChanged(playbackParameters)
}
}
override fun onSeekBackIncrementChanged(seekBackIncrementMs: Long) {
for (listener in currentListeners) {
listener.onSeekBackIncrementChanged(seekBackIncrementMs)
}
}
override fun onSeekForwardIncrementChanged(seekForwardIncrementMs: Long) {
for (listener in currentListeners) {
listener.onSeekForwardIncrementChanged(seekForwardIncrementMs)
}
}
override fun onMaxSeekToPreviousPositionChanged(maxSeekToPreviousPositionMs: Long) {
for (listener in currentListeners) {
listener.onMaxSeekToPreviousPositionChanged(maxSeekToPreviousPositionMs)
}
}
override fun onSeekProcessed() {
for (listener in currentListeners) {
listener.onSeekProcessed()
}
}
override fun onEvents(player: Player, events: Player.Events) {
for (listener in currentListeners) {
listener.onEvents(player, events)
}
}
override fun onAudioSessionIdChanged(audioSessionId: Int) {
for (listener in currentListeners) {
listener.onAudioSessionIdChanged(audioSessionId)
}
}
override fun onAudioAttributesChanged(audioAttributes: AudioAttributes) {
for (listener in currentListeners) {
listener.onAudioAttributesChanged(audioAttributes)
}
}
override fun onVolumeChanged(volume: Float) {
for (listener in currentListeners) {
listener.onVolumeChanged(volume)
}
}
override fun onSkipSilenceEnabledChanged(skipSilenceEnabled: Boolean) {
for (listener in currentListeners) {
listener.onSkipSilenceEnabledChanged(skipSilenceEnabled)
}
}
override fun onDeviceInfoChanged(deviceInfo: DeviceInfo) {
for (listener in currentListeners) {
listener.onDeviceInfoChanged(deviceInfo)
}
}
override fun onDeviceVolumeChanged(volume: Int, muted: Boolean) {
for (listener in currentListeners) {
listener.onDeviceVolumeChanged(volume, muted)
}
}
override fun onVideoSizeChanged(videoSize: VideoSize) {
for (listener in currentListeners) {
listener.onVideoSizeChanged(videoSize)
}
}
override fun onSurfaceSizeChanged(width: Int, height: Int) {
for (listener in currentListeners) {
listener.onSurfaceSizeChanged(width, height)
}
}
override fun onRenderedFirstFrame() {
for (listener in currentListeners) {
listener.onRenderedFirstFrame()
}
}
override fun onCues(cues: MutableList<Cue>) {
for (listener in currentListeners) {
listener.onCues(cues)
}
}
override fun onMetadata(metadata: Metadata) {
for (listener in currentListeners) {
listener.onMetadata(metadata)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment