Last active
March 16, 2022 15:48
-
-
Save jemshit/3fd38d3ba04556c99bd7d953f5c056f7 to your computer and use it in GitHub Desktop.
Revisions
-
jemshit revised this gist
Aug 15, 2019 . 2 changed files with 52 additions and 4 deletions.There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -38,6 +38,12 @@ sealed class MediaPlayerEvent { data class OnComplete(val looping: Boolean) : MediaPlayerEvent() } sealed class MediaPlayerAction { object SetLooping : MediaPlayerAction() object SetVolume : MediaPlayerAction() object SetAudioAttributes : MediaPlayerAction() } class MediaPlayerStateMachine(private val logger: (String, String) -> Unit = ::defaultMPStateMachineLogger, private val logTag: String = MP_STATE_MACHINE_LOG_TAG) { @@ -206,7 +212,25 @@ class MediaPlayerStateMachine(private val logger: (String, String) -> Unit = ::d return transitionSuccess } fun action(action: MediaPlayerAction, afterAction: () -> Unit ) = synchronized(this) { val actionAllowed = when (action) { MediaPlayerAction.SetLooping, MediaPlayerAction.SetAudioAttributes, MediaPlayerAction.SetVolume -> { !isFinalState() && state != MediaPlayerState.Idle && state != MediaPlayerState.Error } } logger(logTag, "action: $actionAllowed; state:${state.javaClass.simpleName}") if (actionAllowed) afterAction.invoke() } private fun isFinalState(): Boolean { return state is MediaPlayerState.End } This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -1,5 +1,6 @@ import android.annotation.SuppressLint import android.content.Context import android.media.AudioAttributes import android.media.MediaPlayer import android.media.MediaPlayer.SEEK_CLOSEST_SYNC import android.net.Uri @@ -29,12 +30,13 @@ class MediaPlayerWrapper(private val useLifecycleObserver: Boolean, private val logger: (String, String) -> Unit = ::defaultMPWrapperLogger, private val logTag: String = MP_WRAPPER_LOG_TAG ) : LifecycleObserver, KoinComponent, MediaPlayer.OnPreparedListener, MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener { private val mediaPlayer: MediaPlayer = MediaPlayer() private val stateMachine = MediaPlayerStateMachine(logger = ::emptyMPStateMachineLogger, logTag = "AUTOPILOT MPStateMachine") fun getState(): MediaPlayerState = stateMachine.state private var lifecycleStopped = false @@ -228,6 +230,26 @@ class MediaPlayerWrapper(private val useLifecycleObserver: Boolean, logger(logTag, "pause <- success") } } fun setLooping(looping: Boolean) { stateMachine.action(MediaPlayerAction.SetLooping) { mediaPlayer.isLooping = looping } } fun setVolume(left: Float, right: Float) { stateMachine.action(MediaPlayerAction.SetVolume) { mediaPlayer.setVolume(left, right) } } @RequiresApi(Build.VERSION_CODES.LOLLIPOP) fun setAudioAttributes(attributes: Any) { stateMachine.action(MediaPlayerAction.SetAudioAttributes) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) mediaPlayer.setAudioAttributes(attributes as AudioAttributes) } } //endregion fun play() { @@ -245,11 +267,13 @@ class MediaPlayerWrapper(private val useLifecycleObserver: Boolean, } /*suspend fun seekToAndPlay(msec: Long, @SuppressLint("InlinedApi") mode: Int = SEEK_CLOSEST_SYNC, volume: Float ) = withContext(dispatcherProvider.computation()) { fun _seekToAndPlay() { seekTo(msec, mode) setVolume(volume, volume) start() } -
jemshit revised this gist
Aug 8, 2019 . 1 changed file with 1 addition and 4 deletions.There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -79,9 +79,6 @@ class MediaPlayerStateMachine(private val logger: (String, String) -> Unit = ::d is MediaPlayerState.End -> { // NoOp after End } is MediaPlayerState.Initialized -> { when (event) { is MediaPlayerEvent.OnPrepare -> { @@ -211,6 +208,6 @@ class MediaPlayerStateMachine(private val logger: (String, String) -> Unit = ::d } private fun isFinalState(): Boolean { return state is MediaPlayerState.End } } -
jemshit revised this gist
Aug 8, 2019 . 1 changed file with 7 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -45,6 +45,12 @@ class MediaPlayerWrapper(private val useLifecycleObserver: Boolean, } // region Lifecycle callbacks @OnLifecycleEvent(Lifecycle.Event.ON_START) fun onStart() { logger(logTag, "onStart -> useLifecycleObserver:$useLifecycleObserver") lifecycleStopped = false } @OnLifecycleEvent(Lifecycle.Event.ON_STOP) fun onStop() { logger(logTag, "onStop -> useLifecycleObserver:$useLifecycleObserver") @@ -56,7 +62,7 @@ class MediaPlayerWrapper(private val useLifecycleObserver: Boolean, @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) fun onDestroy() { logger(logTag, "onDestroy -> useLifecycleObserver:$useLifecycleObserver") lifecycleStopped = true if (useLifecycleObserver) release() } -
jemshit revised this gist
Aug 8, 2019 . 1 changed file with 13 additions and 7 deletions.There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -52,18 +52,20 @@ class MediaPlayerStateMachine(private val logger: (String, String) -> Unit = ::d if (event is MediaPlayerEvent.OnRelease) { // if old sate is end or error, it can not transition to anything transitionSuccess = !isFinalState() if (transitionSuccess) state = MediaPlayerState.End } else if (event is MediaPlayerEvent.OnError) { // if old sate is end or error, it can not transition to anything transitionSuccess = !isFinalState() if (transitionSuccess) state = MediaPlayerState.Error } else if (event is MediaPlayerEvent.OnReset) { transitionSuccess = !isFinalState() if (transitionSuccess) state = MediaPlayerState.Idle } else { when (state) { is MediaPlayerState.Idle -> { @@ -207,4 +209,8 @@ class MediaPlayerStateMachine(private val logger: (String, String) -> Unit = ::d return transitionSuccess } private fun isFinalState(): Boolean { return state is MediaPlayerState.End || state is MediaPlayerState.Error } } -
jemshit revised this gist
Jul 25, 2019 . 1 changed file with 0 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -29,7 +29,6 @@ class MediaPlayerWrapper(private val useLifecycleObserver: Boolean, private val logger: (String, String) -> Unit = ::defaultMPWrapperLogger, private val logTag: String = MP_WRAPPER_LOG_TAG ) : LifecycleObserver, MediaPlayer.OnPreparedListener, MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener { -
jemshit revised this gist
Jul 25, 2019 . 2 changed files with 146 additions and 32 deletions.There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -1,3 +1,15 @@ import android.util.Log fun emptyMPStateMachineLogger(tag: String, message: String) { // NoOp } fun defaultMPStateMachineLogger(tag: String, message: String) { Log.d(tag, message) } const val MP_STATE_MACHINE_LOG_TAG = "MediaPlayerStateMachine" sealed class MediaPlayerState { object Idle : MediaPlayerState() object End : MediaPlayerState() @@ -26,35 +38,39 @@ sealed class MediaPlayerEvent { data class OnComplete(val looping: Boolean) : MediaPlayerEvent() } class MediaPlayerStateMachine(private val logger: (String, String) -> Unit = ::defaultMPStateMachineLogger, private val logTag: String = MP_STATE_MACHINE_LOG_TAG) { var state: MediaPlayerState = MediaPlayerState.Idle fun transition(event: MediaPlayerEvent, afterTransition: (() -> Unit)? = null ): Boolean = synchronized(this) { logger(logTag, "transitionRequest -> event:${event.javaClass.simpleName}, currentState:${state.javaClass.simpleName}") var transitionSuccess = false if (event is MediaPlayerEvent.OnRelease) { // if old sate is end or error, it can not transition to anything transitionSuccess = state !is MediaPlayerState.End && state !is MediaPlayerState.Error state = MediaPlayerState.End } else if (event is MediaPlayerEvent.OnError) { // if old sate is end or error, it can not transition to anything transitionSuccess = state !is MediaPlayerState.End && state !is MediaPlayerState.Error state = MediaPlayerState.Error } else if (event is MediaPlayerEvent.OnReset) { state = MediaPlayerState.Idle transitionSuccess = true } else { when (state) { is MediaPlayerState.Idle -> { when (event) { is MediaPlayerEvent.OnSetDataSource -> { state = MediaPlayerState.Initialized transitionSuccess = true } } } @@ -68,123 +84,127 @@ class MediaPlayerStateMachine { when (event) { is MediaPlayerEvent.OnPrepare -> { state = MediaPlayerState.Prepared transitionSuccess = true } is MediaPlayerEvent.OnPrepareAsync -> { state = MediaPlayerState.Preparing transitionSuccess = true } } } is MediaPlayerState.Preparing -> { when (event) { is MediaPlayerEvent.OnPrepared -> { state = MediaPlayerState.Prepared transitionSuccess = true } } } is MediaPlayerState.Prepared -> { when (event) { is MediaPlayerEvent.OnSeekTo -> { state = MediaPlayerState.Prepared transitionSuccess = true } is MediaPlayerEvent.OnStop -> { state = MediaPlayerState.Stopped transitionSuccess = true } is MediaPlayerEvent.OnStart -> { state = MediaPlayerState.Started transitionSuccess = true } } } is MediaPlayerState.Started -> { when (event) { is MediaPlayerEvent.OnStop -> { state = MediaPlayerState.Stopped transitionSuccess = true } is MediaPlayerEvent.OnSeekTo -> { state = MediaPlayerState.Started transitionSuccess = true } is MediaPlayerEvent.OnStart -> { state = MediaPlayerState.Started transitionSuccess = true } is MediaPlayerEvent.OnComplete -> { if (event.looping) state = MediaPlayerState.Started else state = MediaPlayerState.Completed transitionSuccess = true } is MediaPlayerEvent.OnPause -> { state = MediaPlayerState.Paused transitionSuccess = true } } } is MediaPlayerState.Stopped -> { when (event) { is MediaPlayerEvent.OnPrepare -> { state = MediaPlayerState.Prepared transitionSuccess = true } is MediaPlayerEvent.OnPrepareAsync -> { state = MediaPlayerState.Preparing transitionSuccess = true } is MediaPlayerEvent.OnStop -> { state = MediaPlayerState.Stopped transitionSuccess = true } } } is MediaPlayerState.Paused -> { when (event) { is MediaPlayerEvent.OnSeekTo -> { state = MediaPlayerState.Paused transitionSuccess = true } is MediaPlayerEvent.OnPause -> { state = MediaPlayerState.Paused transitionSuccess = true } is MediaPlayerEvent.OnStart -> { state = MediaPlayerState.Started transitionSuccess = true } is MediaPlayerEvent.OnStop -> { state = MediaPlayerState.Stopped transitionSuccess = true } } } is MediaPlayerState.Completed -> { when (event) { is MediaPlayerEvent.OnSeekTo -> { state = MediaPlayerState.Completed transitionSuccess = true } is MediaPlayerEvent.OnStart -> { state = MediaPlayerState.Started transitionSuccess = true } is MediaPlayerEvent.OnStop -> { state = MediaPlayerState.Stopped transitionSuccess = true } } } } } if (transitionSuccess) { afterTransition?.invoke() logger(logTag, "transitionSuccess <- toState:${state.javaClass.simpleName}") } else { logger(logTag, "transitionFailed <-") } return transitionSuccess } } This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -1,6 +1,35 @@ import android.annotation.SuppressLint import android.content.Context import android.media.MediaPlayer import android.media.MediaPlayer.SEEK_CLOSEST_SYNC import android.net.Uri import android.os.Build import android.util.Log import androidx.annotation.RequiresApi import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.OnLifecycleEvent import kotlinx.coroutines.delay import kotlinx.coroutines.isActive import kotlinx.coroutines.withContext import java.net.HttpCookie fun emptyMPWrapperLogger(tag: String, message: String) { // NoOp } fun defaultMPWrapperLogger(tag: String, message: String) { Log.d(tag, message) } const val MP_WRAPPER_LOG_TAG = "MediaPlayerWrapper" class MediaPlayerWrapper(private val useLifecycleObserver: Boolean, private val playAfterPrepared: Boolean, private val logger: (String, String) -> Unit = ::defaultMPWrapperLogger, private val logTag: String = MP_WRAPPER_LOG_TAG ) : LifecycleObserver, KoinComponent, MediaPlayer.OnPreparedListener, MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener { @@ -19,13 +48,15 @@ class MediaPlayerWrapper(private val useLifecycleObserver: Boolean, // region Lifecycle callbacks @OnLifecycleEvent(Lifecycle.Event.ON_STOP) fun onStop() { logger(logTag, "onStop -> useLifecycleObserver:$useLifecycleObserver") lifecycleStopped = true if (useLifecycleObserver) pause() } @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) fun onDestroy() { logger(logTag, "onDestroy -> useLifecycleObserver:$useLifecycleObserver") lifecycleStopped = false if (useLifecycleObserver) release() @@ -56,7 +87,16 @@ class MediaPlayerWrapper(private val useLifecycleObserver: Boolean, onCompleteListeners.remove(listener) override fun onPrepared(mp: MediaPlayer?) { if (stateMachine.state != MediaPlayerState.Prepared) { stateMachine.transition(MediaPlayerEvent.OnPrepared) { logger(logTag, "onPrepared callback <- transition success, playAfterPrepared:$playAfterPrepared, lifecycleStopped:$lifecycleStopped") onPreparedListeners.forEach { it.onPrepared(mp) } if (playAfterPrepared && !lifecycleStopped) start() } } else { onPreparedListeners.forEach { it.onPrepared(mp) } if (playAfterPrepared && !lifecycleStopped) @@ -65,13 +105,15 @@ class MediaPlayerWrapper(private val useLifecycleObserver: Boolean, } override fun onError(mp: MediaPlayer?, what: Int, extra: Int): Boolean { logger(logTag, "onError <- callback") stateMachine.transition(MediaPlayerEvent.OnError) onErrorListeners.forEach { it.onError(mp, what, extra) } return true } override fun onCompletion(mp: MediaPlayer?) { logger(logTag, "onCompletion <- callback, isLooping:${mediaPlayer.isLooping}") stateMachine.transition(MediaPlayerEvent.OnComplete(mediaPlayer.isLooping)) onCompleteListeners.forEach { it.onCompletion(mp) } @@ -82,24 +124,28 @@ class MediaPlayerWrapper(private val useLifecycleObserver: Boolean, fun reset() { stateMachine.transition(MediaPlayerEvent.OnReset) { mediaPlayer.reset() logger(logTag, "reset <- success") } } fun release() { stateMachine.transition(MediaPlayerEvent.OnRelease) { mediaPlayer.release() logger(logTag, "release <- success") } } fun setDataSource(path: String) { stateMachine.transition(MediaPlayerEvent.OnSetDataSource) { mediaPlayer.setDataSource(path) logger(logTag, "setDataSource <- success") } } fun setDataSource(context: Context, uri: Uri) { stateMachine.transition(MediaPlayerEvent.OnSetDataSource) { mediaPlayer.setDataSource(context, uri) logger(logTag, "setDataSource <- success, [uri:$uri]") } } @@ -109,6 +155,7 @@ class MediaPlayerWrapper(private val useLifecycleObserver: Boolean, headers: Map<String, String>) { stateMachine.transition(MediaPlayerEvent.OnSetDataSource) { mediaPlayer.setDataSource(context, uri, headers) logger(logTag, "setDataSource <- success, [uri:$uri]") } } @@ -120,24 +167,28 @@ class MediaPlayerWrapper(private val useLifecycleObserver: Boolean, cookies: List<HttpCookie>) { stateMachine.transition(MediaPlayerEvent.OnSetDataSource) { mediaPlayer.setDataSource(context, uri, headers, cookies) logger(logTag, "setDataSource <- success, [uri:$uri]") } } fun prepare() { stateMachine.transition(MediaPlayerEvent.OnPrepare) { mediaPlayer.prepare() logger(logTag, "prepare <- success") } } /*suspend fun prepareSuspended() = withContext(dispatcherProvider.computation()) { stateMachine.transition(MediaPlayerEvent.OnPrepare) { mediaPlayer.prepare() logger(logTag, "prepareSuspended <- success") } }*/ fun prepareAsync() { stateMachine.transition(MediaPlayerEvent.OnPrepareAsync) { mediaPlayer.prepareAsync() logger(logTag, "prepareAsync <- success") } } @@ -148,24 +199,28 @@ class MediaPlayerWrapper(private val useLifecycleObserver: Boolean, } else { mediaPlayer.seekTo(msec.toInt()) } logger(logTag, "seekTo <- success, [msec:$msec, mode:$mode]") } } fun start() { stateMachine.transition(MediaPlayerEvent.OnStart) { mediaPlayer.start() logger(logTag, "start <- success") } } fun stop() { stateMachine.transition(MediaPlayerEvent.OnStop) { mediaPlayer.stop() logger(logTag, "stop <- success") } } fun pause() { stateMachine.transition(MediaPlayerEvent.OnPause) { mediaPlayer.pause() logger(logTag, "pause <- success") } } //endregion @@ -174,14 +229,53 @@ class MediaPlayerWrapper(private val useLifecycleObserver: Boolean, if (stateMachine.state == MediaPlayerState.Prepared || stateMachine.state == MediaPlayerState.Paused || stateMachine.state == MediaPlayerState.Completed) { logger(logTag, "play -> state:${stateMachine.state.javaClass.simpleName}, start() called") start() } else if (stateMachine.state == MediaPlayerState.Initialized || stateMachine.state == MediaPlayerState.Stopped) { logger(logTag, "play -> state:${stateMachine.state.javaClass.simpleName}, prepareAsync() called") prepareAsync() } } /*suspend fun seekToAndPlay(msec: Long, @SuppressLint("InlinedApi") mode: Int = SEEK_CLOSEST_SYNC ) = withContext(dispatcherProvider.computation()) { fun _seekToAndPlay() { seekTo(msec, mode) start() } if (stateMachine.state == MediaPlayerState.Prepared || stateMachine.state == MediaPlayerState.Paused || stateMachine.state == MediaPlayerState.Completed) { logger(logTag, "seekToAndPlay -> state:${stateMachine.state.javaClass.simpleName}, seekTo(), start() called. [msec:$msec, mode:$mode]") _seekToAndPlay() } else if (stateMachine.state == MediaPlayerState.Initialized || stateMachine.state == MediaPlayerState.Stopped) { logger(logTag, "seekToAndPlay -> state:${stateMachine.state.javaClass.simpleName}, prepare() called") prepare() if (!lifecycleStopped && isActive) { logger(logTag, "seekToAndPlay -> state:${stateMachine.state.javaClass.simpleName}, seekTo(), start() called. [msec:$msec, mode:$mode]") _seekToAndPlay() } } else { if (stateMachine.state == MediaPlayerState.Preparing) { logger(logTag, "seekToAndPlay -> state:${stateMachine.state.javaClass.simpleName}, DELAYED") delay(50L) if (stateMachine.state == MediaPlayerState.Prepared) { logger(logTag, "seekToAndPlay -> state:${stateMachine.state.javaClass.simpleName}, after delay and prepared, seekTo(), start() called. [msec:$msec, mode:$mode]") _seekToAndPlay() } else { logger(logTag, "seekToAndPlay -> state:${stateMachine.state.javaClass.simpleName}, after delay, still NOT PREPARED!") } } else { logger(logTag, "seekToAndPlay -> state:${stateMachine.state.javaClass.simpleName}, INVALID STATE!") } } }*/ } -
jemshit revised this gist
Jul 24, 2019 . 1 changed file with 187 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,187 @@ class MediaPlayerWrapper(private val useLifecycleObserver: Boolean, private val playAfterPrepared: Boolean ) : LifecycleObserver, MediaPlayer.OnPreparedListener, MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener { val mediaPlayer: MediaPlayer = MediaPlayer() private val stateMachine = MediaPlayerStateMachine() fun getState(): MediaPlayerState = stateMachine.state private var lifecycleStopped = false init { mediaPlayer.setOnPreparedListener(this) mediaPlayer.setOnErrorListener(this) mediaPlayer.setOnCompletionListener(this) } // region Lifecycle callbacks @OnLifecycleEvent(Lifecycle.Event.ON_STOP) fun onStop() { lifecycleStopped = true if (useLifecycleObserver) pause() } @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) fun onDestroy() { lifecycleStopped = false if (useLifecycleObserver) release() } //endregion //region MediaPlayer Callbacks private val onPreparedListeners = mutableListOf<MediaPlayer.OnPreparedListener>() private val onErrorListeners = mutableListOf<MediaPlayer.OnErrorListener>() private val onCompleteListeners = mutableListOf<MediaPlayer.OnCompletionListener>() fun setOnPreparedListener(listener: MediaPlayer.OnPreparedListener) = onPreparedListeners.add(listener) fun removeOnPreparedListener(listener: MediaPlayer.OnPreparedListener) = onPreparedListeners.remove(listener) fun setOnErrorListener(listener: MediaPlayer.OnErrorListener) = onErrorListeners.add(listener) fun removeOnErrorListener(listener: MediaPlayer.OnErrorListener) = onErrorListeners.remove(listener) fun setOnCompletionListener(listener: MediaPlayer.OnCompletionListener) = onCompleteListeners.add(listener) fun removeOnCompletionListener(listener: MediaPlayer.OnCompletionListener) = onCompleteListeners.remove(listener) override fun onPrepared(mp: MediaPlayer?) { stateMachine.transition(MediaPlayerEvent.OnPrepared) { onPreparedListeners.forEach { it.onPrepared(mp) } if (playAfterPrepared && !lifecycleStopped) start() } } override fun onError(mp: MediaPlayer?, what: Int, extra: Int): Boolean { stateMachine.transition(MediaPlayerEvent.OnError) onErrorListeners.forEach { it.onError(mp, what, extra) } return true } override fun onCompletion(mp: MediaPlayer?) { stateMachine.transition(MediaPlayerEvent.OnComplete(mediaPlayer.isLooping)) onCompleteListeners.forEach { it.onCompletion(mp) } } //endregion // region Method delegations after StateMachine check fun reset() { stateMachine.transition(MediaPlayerEvent.OnReset) { mediaPlayer.reset() } } fun release() { stateMachine.transition(MediaPlayerEvent.OnRelease) { mediaPlayer.release() } } fun setDataSource(path: String) { stateMachine.transition(MediaPlayerEvent.OnSetDataSource) { mediaPlayer.setDataSource(path) } } fun setDataSource(context: Context, uri: Uri) { stateMachine.transition(MediaPlayerEvent.OnSetDataSource) { mediaPlayer.setDataSource(context, uri) } } @RequiresApi(Build.VERSION_CODES.O) fun setDataSource(context: Context, uri: Uri, headers: Map<String, String>) { stateMachine.transition(MediaPlayerEvent.OnSetDataSource) { mediaPlayer.setDataSource(context, uri, headers) } } @RequiresApi(Build.VERSION_CODES.O) fun setDataSource(context: Context, uri: Uri, headers: Map<String, String>, cookies: List<HttpCookie>) { stateMachine.transition(MediaPlayerEvent.OnSetDataSource) { mediaPlayer.setDataSource(context, uri, headers, cookies) } } fun prepare() { stateMachine.transition(MediaPlayerEvent.OnPrepare) { mediaPlayer.prepare() } } /*suspend fun prepareSuspended() = withContext(dispatcherProvider.computation()) { stateMachine.transition(MediaPlayerEvent.OnPrepare) { mediaPlayer.prepare() } }*/ fun prepareAsync() { stateMachine.transition(MediaPlayerEvent.OnPrepareAsync) { mediaPlayer.prepareAsync() } } fun seekTo(msec: Long, @SuppressLint("InlinedApi") mode: Int = SEEK_CLOSEST_SYNC) { stateMachine.transition(MediaPlayerEvent.OnSeekTo) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { mediaPlayer.seekTo(msec, mode) } else { mediaPlayer.seekTo(msec.toInt()) } } } fun start() { stateMachine.transition(MediaPlayerEvent.OnStart) { mediaPlayer.start() } } fun stop() { stateMachine.transition(MediaPlayerEvent.OnStop) { mediaPlayer.stop() } } fun pause() { stateMachine.transition(MediaPlayerEvent.OnPause) { mediaPlayer.pause() } } //endregion fun play() { if (stateMachine.state == MediaPlayerState.Prepared || stateMachine.state == MediaPlayerState.Paused || stateMachine.state == MediaPlayerState.Completed) { start() } else if (stateMachine.state == MediaPlayerState.Initialized || stateMachine.state == MediaPlayerState.Stopped) { prepareAsync() } } // Your custom methods depending on your usecases } -
jemshit created this gist
Jul 24, 2019 .There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,190 @@ sealed class MediaPlayerState { object Idle : MediaPlayerState() object End : MediaPlayerState() object Error : MediaPlayerState() object Initialized : MediaPlayerState() object Preparing : MediaPlayerState() object Prepared : MediaPlayerState() object Started : MediaPlayerState() object Stopped : MediaPlayerState() object Paused : MediaPlayerState() object Completed : MediaPlayerState() } sealed class MediaPlayerEvent { object OnRelease : MediaPlayerEvent() object OnReset : MediaPlayerEvent() object OnSetDataSource : MediaPlayerEvent() object OnError : MediaPlayerEvent() object OnPrepare : MediaPlayerEvent() object OnPrepareAsync : MediaPlayerEvent() object OnPrepared : MediaPlayerEvent() object OnSeekTo : MediaPlayerEvent() object OnStop : MediaPlayerEvent() object OnStart : MediaPlayerEvent() object OnPause : MediaPlayerEvent() data class OnComplete(val looping: Boolean) : MediaPlayerEvent() } class MediaPlayerStateMachine { var state: MediaPlayerState = MediaPlayerState.Idle fun transition(event: MediaPlayerEvent, afterTransition: (() -> Unit)? = null ): Boolean = synchronized(this) { var transitionDone = false if (event is MediaPlayerEvent.OnRelease) { state = MediaPlayerState.End transitionDone = state !is MediaPlayerState.End && state !is MediaPlayerState.Error } else if (event is MediaPlayerEvent.OnError) { state = MediaPlayerState.Error transitionDone = state !is MediaPlayerState.End && state !is MediaPlayerState.Error } else if (event is MediaPlayerEvent.OnReset) { state = MediaPlayerState.Idle transitionDone = true } else { when (state) { is MediaPlayerState.Idle -> { when (event) { is MediaPlayerEvent.OnSetDataSource -> { state = MediaPlayerState.Initialized transitionDone = true } } } is MediaPlayerState.End -> { // NoOp after End } is MediaPlayerState.Error -> { // NoOp after Error } is MediaPlayerState.Initialized -> { when (event) { is MediaPlayerEvent.OnPrepare -> { state = MediaPlayerState.Prepared transitionDone = true } is MediaPlayerEvent.OnPrepareAsync -> { state = MediaPlayerState.Preparing transitionDone = true } } } is MediaPlayerState.Preparing -> { when (event) { is MediaPlayerEvent.OnPrepared -> { state = MediaPlayerState.Prepared transitionDone = true } } } is MediaPlayerState.Prepared -> { when (event) { is MediaPlayerEvent.OnSeekTo -> { state = MediaPlayerState.Prepared transitionDone = true } is MediaPlayerEvent.OnStop -> { state = MediaPlayerState.Stopped transitionDone = true } is MediaPlayerEvent.OnStart -> { state = MediaPlayerState.Started transitionDone = true } } } is MediaPlayerState.Started -> { when (event) { is MediaPlayerEvent.OnStop -> { state = MediaPlayerState.Stopped transitionDone = true } is MediaPlayerEvent.OnSeekTo -> { state = MediaPlayerState.Started transitionDone = true } is MediaPlayerEvent.OnStart -> { state = MediaPlayerState.Started transitionDone = true } is MediaPlayerEvent.OnComplete -> { if (event.looping) state = MediaPlayerState.Started else state = MediaPlayerState.Completed transitionDone = true } is MediaPlayerEvent.OnPause -> { state = MediaPlayerState.Paused transitionDone = true } } } is MediaPlayerState.Stopped -> { when (event) { is MediaPlayerEvent.OnPrepare -> { state = MediaPlayerState.Prepared transitionDone = true } is MediaPlayerEvent.OnPrepareAsync -> { state = MediaPlayerState.Preparing transitionDone = true } is MediaPlayerEvent.OnStop -> { state = MediaPlayerState.Stopped transitionDone = true } } } is MediaPlayerState.Paused -> { when (event) { is MediaPlayerEvent.OnSeekTo -> { state = MediaPlayerState.Paused transitionDone = true } is MediaPlayerEvent.OnPause -> { state = MediaPlayerState.Paused transitionDone = true } is MediaPlayerEvent.OnStart -> { state = MediaPlayerState.Started transitionDone = true } is MediaPlayerEvent.OnStop -> { state = MediaPlayerState.Stopped transitionDone = true } } } is MediaPlayerState.Completed -> { when (event) { is MediaPlayerEvent.OnSeekTo -> { state = MediaPlayerState.Completed transitionDone = true } is MediaPlayerEvent.OnStart -> { state = MediaPlayerState.Started transitionDone = true } is MediaPlayerEvent.OnStop -> { state = MediaPlayerState.Stopped transitionDone = true } } } } } if (transitionDone) afterTransition?.invoke() return transitionDone } }