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() 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() } 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) { 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 = !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 -> { when (event) { is MediaPlayerEvent.OnSetDataSource -> { state = MediaPlayerState.Initialized transitionSuccess = true } } } is MediaPlayerState.End -> { // NoOp after End } is MediaPlayerState.Initialized -> { 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 } 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 } }