Last active
September 6, 2022 10:14
-
-
Save NielsMasdorp/d22c9286aa1bd3e1629258d156e7c6e2 to your computer and use it in GitHub Desktop.
Composite player implementation in media3
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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