Skip to content

Instantly share code, notes, and snippets.

@n0m0r3pa1n
Created June 11, 2021 11:33
Show Gist options
  • Save n0m0r3pa1n/d3484dce93464433e81065767d516619 to your computer and use it in GitHub Desktop.
Save n0m0r3pa1n/d3484dce93464433e81065767d516619 to your computer and use it in GitHub Desktop.
Sample player that supports both cast & simple exo player
import com.google.android.exoplayer2.*
import com.google.android.exoplayer2.audio.AudioAttributes
import com.google.android.exoplayer2.audio.AudioListener
import com.google.android.exoplayer2.ext.cast.CastPlayer
import com.google.android.exoplayer2.ext.cast.SessionAvailabilityListener
import com.google.android.exoplayer2.ext.okhttp.OkHttpDataSource
import com.google.android.exoplayer2.source.DefaultMediaSourceFactory
import com.google.android.exoplayer2.source.ProgressiveMediaSource
import com.google.android.exoplayer2.upstream.FileDataSource
import okhttp3.OkHttpClient
import javax.inject.Inject
class MyCustomPlayerPlayer @Inject constructor(
private val context: Context,
private val castPlayerManager: CastPlayerManager,
private val mediaItemMapper: MediaItemMapper,
private val mediaQueueItemMapper: MediaQueueItemMapper
) {
private val mediaItemsCastQueue = mutableSetOf<MediaItem>()
private val onMediaItemTransitionListener = object : Player.EventListener {
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
lastWindowIndexId = currentPlayer.currentWindowIndex
}
}
private val currentAudioComponent: Player.AudioComponent?
get() = currentPlayer.audioComponent
private val castPlayer: CastPlayer?
get() = castPlayerManager.getCastPlayer()
private var lastWindowIndexId: Int = 0
private val playerEventListener = PlayerEventListener()
private val fileDataSourceFactory = ProgressiveMediaSource.Factory(FileDataSource.Factory())
private lateinit var simpleExoPlayer: SimpleExoPlayer
private val chromecastSessionSelectedListener = object : SessionAvailabilityListener {
override fun onCastSessionAvailable() {
if (castPlayer != null) {
mediaItemsCastQueue.addAll(simpleExoPlayer.getMediaItems())
setCurrentPlayer(castPlayer!!)
}
}
override fun onCastSessionUnavailable() {
setCurrentPlayer(simpleExoPlayer)
}
}
private lateinit var currentPlayer: Player
val currentPosition
get() = currentPlayer.currentPosition
val mediaItemCount
get() = currentPlayer.mediaItemCount
val shouldPersistState
get() = currentPlayer.shouldPersistState
val isPlaying
get() = currentPlayer.isPlaying
val duration
get() = currentPlayer.duration
val playbackParameters
get() = currentPlayer.playbackParameters
val currentWindowIndex
get() = currentPlayer.currentWindowIndex
override val playbackState
get() = currentPlayer.playbackState
override val isCasting
get() = currentPlayer is CastPlayer || castPlayerManager.isCasting()
override val bufferedPercentage: Int
get() = currentPlayer.bufferedPercentage
fun init() {
simpleExoPlayer = createExoplayer()
currentPlayer = if (castPlayer?.isCastSessionAvailable == true) castPlayer!! else simpleExoPlayer
addListener(playerEventListener)
addListener(onMediaItemTransitionListener)
addAudioListener(playerEventListener)
castPlayerManager.setAvailabilityListener(chromecastSessionSelectedListener)
}
private fun createExoplayer(): SimpleExoPlayer {
val audioStreamInfo = AudioAttributes.Builder()
.setContentType(C.CONTENT_TYPE_SPEECH)
.setUsage(C.USAGE_MEDIA)
.build()
val okHttpClient = OkHttpClient.Builder().build()
return SimpleExoPlayer.Builder(context)
.setAudioAttributes(audioStreamInfo, true)
.setMediaSourceFactory(DefaultMediaSourceFactory(OkHttpDataSource.Factory(okHttpClient)))
.build()
}
fun getCurrentPlayer() = currentPlayer
fun clearMediaItems() {
mediaItemsCastQueue.clear()
currentPlayer.clearMediaItems()
}
fun setEpisodes(episodesList: List<Episode>, startIndex: Int, positionMs: Long) {
if (episodesList.isNullOrEmpty()) return
mediaItemsCastQueue.clear()
currentPlayer.clearMediaItems()
if (isCasting) {
mediaItemsCastQueue.addAll(
// Casting for files is not possible. Google Cast does not work
// https://stackoverflow.com/questions/32049851/it-is-posible-to-cast-or-stream-android-chromecast-a-local-file
episodesList
.filter { it.streamUrl?.startsWith("http") == true }
.map { mediaItemMapper.toMediaItem(it) }
)
playerEventListener.onMediaItemTransition(
mediaItemsCastQueue.elementAt(startIndex),
Player.MEDIA_ITEM_TRANSITION_REASON_SEEK
)
setupMediaItemsForCast(startIndex, positionMs)
} else {
setEpisodesForExoPlayer(episodesList)
currentPlayer.seekTo(startIndex, positionMs)
}
}
private fun setupMediaItemsForCast(startIndex: Int, positionMs: Long) {
castPlayer!!.loadItems(
mediaItemsCastQueue.map { mediaQueueItemMapper.toMediaQueueItem(it) }.toTypedArray(),
startIndex, positionMs, Player.REPEAT_MODE_OFF
)
}
private fun setEpisodesForExoPlayer(episodesList: List<Episode>) {
episodesList.forEach { episode ->
if (episode.streamUrl!!.startsWith("http")) {
val mediaItem = mediaItemMapper.toMediaItem(episode)
mediaItemsCastQueue.add(mediaItem)
currentPlayer.addMediaItem(mediaItem)
} else {
(currentPlayer as SimpleExoPlayer).addMediaSource(
fileDataSourceFactory.createMediaSource(mediaItemMapper.toMediaItem(episode))
)
}
}
}
fun addListener(listener: Player.EventListener) {
currentPlayer.addListener(listener)
}
private fun addAudioListener(listener: AudioListener) {
currentAudioComponent?.addAudioListener(listener)
}
fun addEventListener(listener: PlayerListenerInterface) {
playerEventListener.addListener(listener)
}
fun removeEventListener(listener: PlayerListenerInterface) {
playerEventListener.removeListener(listener)
}
fun getMediaItemAt(index: Int): MediaItem {
return currentPlayer.getMediaItemAt(index)
}
fun seekTo(positionMs: Long) {
currentPlayer.seekTo(positionMs)
}
fun prepare() {
currentPlayer.prepare()
}
fun play() {
currentPlayer.play()
}
fun pause() {
currentPlayer.pause()
}
fun previous() {
currentPlayer.previous()
}
fun next() {
currentPlayer.next()
}
fun hasNext() = currentPlayer.hasNext()
fun hasPrevious() = currentPlayer.hasPrevious()
fun setPlayWhenReady(playWhenReady: Boolean) {
currentPlayer.playWhenReady = playWhenReady
}
fun stop() {
if (::currentPlayer.isInitialized) {
currentPlayer.stop()
}
}
fun setPlaybackParameters(playbackParameters: PlaybackParameters) {
currentPlayer.setPlaybackParameters(playbackParameters)
}
fun release() {
mediaItemsCastQueue.clear()
castPlayerManager.release()
playerEventListener.removeAllListeners()
if (::currentPlayer.isInitialized) {
currentPlayer.removeListener(playerEventListener)
currentPlayer.removeListener(onMediaItemTransitionListener)
currentAudioComponent?.removeAudioListener(playerEventListener)
currentPlayer.stop()
currentPlayer.release()
}
}
private fun setCurrentPlayer(newPlayer: Player) {
if (this.currentPlayer === newPlayer) {
return
}
val previousPlayer = this.currentPlayer
val previousPlayerState = previousPlayer.getCurrentPlayerState()
previousPlayer.stop()
currentPlayer = newPlayer
currentPlayer.setPlayerState(previousPlayerState)
addListener(playerEventListener)
addAudioListener(playerEventListener)
previousPlayer.removeListener(playerEventListener)
previousPlayer.audioComponent?.removeAudioListener(playerEventListener)
}
private fun Player.setPlayerState(playerState: PlayerState) {
setMediaItems(mediaItemsCastQueue.toList(), playerState.currentWindowIndex, playerState.currentPosition)
playWhenReady = playerState.playWhenReady
prepare()
}
private fun Player.getCurrentPlayerState() = PlayerState(
currentPosition = if (playbackState != Player.STATE_ENDED) currentPosition else C.TIME_UNSET,
currentWindowIndex = if (playbackState != Player.STATE_ENDED) lastWindowIndexId else C.INDEX_UNSET,
playbackState = playbackState,
playWhenReady = if (playbackState != Player.STATE_ENDED) playWhenReady else false
)
private fun Player.getMediaItems(): List<MediaItem> {
return List(mediaItemCount) { getMediaItemAt(it) }
}
private val Player.shouldPersistState
get() = playbackState != Player.STATE_IDLE && playbackState != Player.STATE_ENDED
fun getCurrentMediaItemId(): String? {
val currentMediaIndex = currentPlayer.currentWindowIndex
lastWindowIndexId = currentMediaIndex
if (currentPlayer is CastPlayer) {
val queueSize = mediaItemsCastQueue.size
return mediaItemsCastQueue.takeIf { queueSize > currentMediaIndex }?.elementAt(currentMediaIndex)?.mediaId
} else {
return currentPlayer.currentMediaItem?.mediaId
}
}
data class PlayerState(
val currentPosition: Long,
val currentWindowIndex: Int,
val playbackState: Int? = null,
val playWhenReady: Boolean
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment