Loading components/feature/media/src/main/java/mozilla/components/feature/media/MediaFeature.kt +32 −20 Original line number Diff line number Diff line Loading @@ -5,6 +5,7 @@ package mozilla.components.feature.media import android.content.Context import mozilla.components.feature.media.ext.hasMediaWithSufficientLongDuration import mozilla.components.feature.media.service.MediaService import mozilla.components.feature.media.state.MediaState import mozilla.components.feature.media.state.MediaStateMachine Loading @@ -22,35 +23,46 @@ import mozilla.components.feature.media.state.MediaStateMachine class MediaFeature( private val context: Context ) { private var serviceRunning = false private var serviceStarted = false private var observer = object : MediaStateMachine.Observer { override fun onStateChanged(state: MediaState) { notifyService(state) } } /** * Enables the feature. */ fun enable() { MediaStateMachine.register(MediaObserver(this)) MediaStateMachine.register(observer) // MediaStateMachine will only notify us about state changes but not pass the current state // to us - which might already be "playing". So let's process the current state now. notifyService(MediaStateMachine.state) } internal fun startMediaService() { @Suppress("LongMethod") private fun notifyService(state: MediaState) { when (state) { is MediaState.Playing -> { if (state.hasMediaWithSufficientLongDuration()) { MediaService.updateState(context) serviceRunning = true serviceStarted = true } } internal fun stopMediaService() { if (serviceRunning) { is MediaState.Paused -> { if (serviceStarted) { MediaService.updateState(context) } } } internal class MediaObserver( private val feature: MediaFeature ) : MediaStateMachine.Observer { override fun onStateChanged(state: MediaState) { if (state is MediaState.Playing || state is MediaState.Paused) { feature.startMediaService() } else if (state is MediaState.None) { feature.stopMediaService() is MediaState.None -> { if (serviceStarted) { MediaService.updateState(context) serviceStarted = false } } } } } components/feature/media/src/main/java/mozilla/components/feature/media/ext/MediaState.kt +19 −0 Original line number Diff line number Diff line Loading @@ -8,6 +8,11 @@ import android.support.v4.media.session.PlaybackStateCompat import mozilla.components.concept.engine.media.Media import mozilla.components.feature.media.state.MediaState /** * The minimum duration (in seconds) for media so that we bother with showing a media notification. */ private const val MINIMUM_DURATION_SECONDS = 5 /** * Gets the list of [Media] associated with this [MediaState]. */ Loading Loading @@ -62,3 +67,17 @@ internal fun MediaState.playIfPaused() { media.play() } } internal fun MediaState.hasMediaWithSufficientLongDuration(): Boolean { getMedia().forEach { media -> if (media.metadata.duration < 0) { return true } if (media.metadata.duration > MINIMUM_DURATION_SECONDS) { return true } } return false } components/feature/media/src/test/java/mozilla/components/feature/media/MediaFeatureTest.kt +35 −1 Original line number Diff line number Diff line Loading @@ -10,13 +10,13 @@ import mozilla.components.browser.session.Session import mozilla.components.browser.session.SessionManager import mozilla.components.concept.engine.media.Media import mozilla.components.feature.media.state.MediaStateMachine import mozilla.components.feature.media.state.MockMedia import mozilla.components.support.test.any import mozilla.components.support.test.mock import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.doReturn import org.mockito.Mockito.never import org.mockito.Mockito.reset import org.mockito.Mockito.verify Loading Loading @@ -45,6 +45,7 @@ class MediaFeatureTest { // A media object gets added to the session val media = MockMedia(Media.PlaybackState.UNKNOWN) doReturn(30.0).`when`(media.metadata).duration session.media = listOf(media) media.playbackState = Media.PlaybackState.WAITING Loading @@ -58,10 +59,42 @@ class MediaFeatureTest { verify(context).startService(any()) } @Test fun `Media with short duration will not start service`() { val context: Context = mock() val sessionManager = SessionManager(engine = mock()) MediaStateMachine.start(sessionManager) val feature = MediaFeature(context) feature.enable() // A session gets added val session = Session("https://www.mozilla.org") sessionManager.add(session) // A media object gets added to the session val media = MockMedia(Media.PlaybackState.UNKNOWN) doReturn(2.0).`when`(media.metadata).duration session.media = listOf(media) media.playbackState = Media.PlaybackState.WAITING // So far nothing has happened yet verify(context, never()).startService(any()) // Media starts playing! media.playbackState = Media.PlaybackState.PLAYING // Service still not started since duration is too short verify(context, never()).startService(any()) } @Test fun `Media switching from playing to pause send Intent to service`() { val context: Context = mock() val media = MockMedia(Media.PlaybackState.PLAYING) doReturn(30.0).`when`(media.metadata).duration val sessionManager = SessionManager(engine = mock()).apply { add(Session("https://www.mozilla.org").also { it.media = listOf(media) }) Loading @@ -84,6 +117,7 @@ class MediaFeatureTest { fun `Media stopping to play will notify service`() { val context: Context = mock() val media = MockMedia(Media.PlaybackState.UNKNOWN) doReturn(30.0).`when`(media.metadata).duration val sessionManager = SessionManager(engine = mock()).apply { add(Session("https://www.mozilla.org").also { it.media = listOf(media) }) Loading components/feature/media/src/test/java/mozilla/components/feature/media/MockMedia.kt 0 → 100644 +17 −0 Original line number Diff line number Diff line package mozilla.components.feature.media import mozilla.components.concept.engine.media.Media import mozilla.components.support.test.mock internal class MockMedia( initialState: PlaybackState ) : Media() { init { playbackState = initialState } override val controller: Controller = mock() override val metadata: Metadata = mock() } No newline at end of file components/feature/media/src/test/java/mozilla/components/feature/media/focus/AudioFocusTest.kt +1 −1 Original line number Diff line number Diff line Loading @@ -9,9 +9,9 @@ import android.media.AudioManager import androidx.test.ext.junit.runners.AndroidJUnit4 import mozilla.components.browser.session.Session import mozilla.components.concept.engine.media.Media import mozilla.components.feature.media.MockMedia import mozilla.components.feature.media.state.MediaState import mozilla.components.feature.media.state.MediaStateMachine import mozilla.components.feature.media.state.MockMedia import mozilla.components.support.test.any import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext Loading Loading
components/feature/media/src/main/java/mozilla/components/feature/media/MediaFeature.kt +32 −20 Original line number Diff line number Diff line Loading @@ -5,6 +5,7 @@ package mozilla.components.feature.media import android.content.Context import mozilla.components.feature.media.ext.hasMediaWithSufficientLongDuration import mozilla.components.feature.media.service.MediaService import mozilla.components.feature.media.state.MediaState import mozilla.components.feature.media.state.MediaStateMachine Loading @@ -22,35 +23,46 @@ import mozilla.components.feature.media.state.MediaStateMachine class MediaFeature( private val context: Context ) { private var serviceRunning = false private var serviceStarted = false private var observer = object : MediaStateMachine.Observer { override fun onStateChanged(state: MediaState) { notifyService(state) } } /** * Enables the feature. */ fun enable() { MediaStateMachine.register(MediaObserver(this)) MediaStateMachine.register(observer) // MediaStateMachine will only notify us about state changes but not pass the current state // to us - which might already be "playing". So let's process the current state now. notifyService(MediaStateMachine.state) } internal fun startMediaService() { @Suppress("LongMethod") private fun notifyService(state: MediaState) { when (state) { is MediaState.Playing -> { if (state.hasMediaWithSufficientLongDuration()) { MediaService.updateState(context) serviceRunning = true serviceStarted = true } } internal fun stopMediaService() { if (serviceRunning) { is MediaState.Paused -> { if (serviceStarted) { MediaService.updateState(context) } } } internal class MediaObserver( private val feature: MediaFeature ) : MediaStateMachine.Observer { override fun onStateChanged(state: MediaState) { if (state is MediaState.Playing || state is MediaState.Paused) { feature.startMediaService() } else if (state is MediaState.None) { feature.stopMediaService() is MediaState.None -> { if (serviceStarted) { MediaService.updateState(context) serviceStarted = false } } } } }
components/feature/media/src/main/java/mozilla/components/feature/media/ext/MediaState.kt +19 −0 Original line number Diff line number Diff line Loading @@ -8,6 +8,11 @@ import android.support.v4.media.session.PlaybackStateCompat import mozilla.components.concept.engine.media.Media import mozilla.components.feature.media.state.MediaState /** * The minimum duration (in seconds) for media so that we bother with showing a media notification. */ private const val MINIMUM_DURATION_SECONDS = 5 /** * Gets the list of [Media] associated with this [MediaState]. */ Loading Loading @@ -62,3 +67,17 @@ internal fun MediaState.playIfPaused() { media.play() } } internal fun MediaState.hasMediaWithSufficientLongDuration(): Boolean { getMedia().forEach { media -> if (media.metadata.duration < 0) { return true } if (media.metadata.duration > MINIMUM_DURATION_SECONDS) { return true } } return false }
components/feature/media/src/test/java/mozilla/components/feature/media/MediaFeatureTest.kt +35 −1 Original line number Diff line number Diff line Loading @@ -10,13 +10,13 @@ import mozilla.components.browser.session.Session import mozilla.components.browser.session.SessionManager import mozilla.components.concept.engine.media.Media import mozilla.components.feature.media.state.MediaStateMachine import mozilla.components.feature.media.state.MockMedia import mozilla.components.support.test.any import mozilla.components.support.test.mock import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.doReturn import org.mockito.Mockito.never import org.mockito.Mockito.reset import org.mockito.Mockito.verify Loading Loading @@ -45,6 +45,7 @@ class MediaFeatureTest { // A media object gets added to the session val media = MockMedia(Media.PlaybackState.UNKNOWN) doReturn(30.0).`when`(media.metadata).duration session.media = listOf(media) media.playbackState = Media.PlaybackState.WAITING Loading @@ -58,10 +59,42 @@ class MediaFeatureTest { verify(context).startService(any()) } @Test fun `Media with short duration will not start service`() { val context: Context = mock() val sessionManager = SessionManager(engine = mock()) MediaStateMachine.start(sessionManager) val feature = MediaFeature(context) feature.enable() // A session gets added val session = Session("https://www.mozilla.org") sessionManager.add(session) // A media object gets added to the session val media = MockMedia(Media.PlaybackState.UNKNOWN) doReturn(2.0).`when`(media.metadata).duration session.media = listOf(media) media.playbackState = Media.PlaybackState.WAITING // So far nothing has happened yet verify(context, never()).startService(any()) // Media starts playing! media.playbackState = Media.PlaybackState.PLAYING // Service still not started since duration is too short verify(context, never()).startService(any()) } @Test fun `Media switching from playing to pause send Intent to service`() { val context: Context = mock() val media = MockMedia(Media.PlaybackState.PLAYING) doReturn(30.0).`when`(media.metadata).duration val sessionManager = SessionManager(engine = mock()).apply { add(Session("https://www.mozilla.org").also { it.media = listOf(media) }) Loading @@ -84,6 +117,7 @@ class MediaFeatureTest { fun `Media stopping to play will notify service`() { val context: Context = mock() val media = MockMedia(Media.PlaybackState.UNKNOWN) doReturn(30.0).`when`(media.metadata).duration val sessionManager = SessionManager(engine = mock()).apply { add(Session("https://www.mozilla.org").also { it.media = listOf(media) }) Loading
components/feature/media/src/test/java/mozilla/components/feature/media/MockMedia.kt 0 → 100644 +17 −0 Original line number Diff line number Diff line package mozilla.components.feature.media import mozilla.components.concept.engine.media.Media import mozilla.components.support.test.mock internal class MockMedia( initialState: PlaybackState ) : Media() { init { playbackState = initialState } override val controller: Controller = mock() override val metadata: Metadata = mock() } No newline at end of file
components/feature/media/src/test/java/mozilla/components/feature/media/focus/AudioFocusTest.kt +1 −1 Original line number Diff line number Diff line Loading @@ -9,9 +9,9 @@ import android.media.AudioManager import androidx.test.ext.junit.runners.AndroidJUnit4 import mozilla.components.browser.session.Session import mozilla.components.concept.engine.media.Media import mozilla.components.feature.media.MockMedia import mozilla.components.feature.media.state.MediaState import mozilla.components.feature.media.state.MediaStateMachine import mozilla.components.feature.media.state.MockMedia import mozilla.components.support.test.any import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext Loading