diff --git a/app/src/main/kotlin/com/subtracks/di/AppModule.kt b/app/src/main/kotlin/com/subtracks/di/AppModule.kt index 43b98f4e..bc96c5ea 100644 --- a/app/src/main/kotlin/com/subtracks/di/AppModule.kt +++ b/app/src/main/kotlin/com/subtracks/di/AppModule.kt @@ -45,7 +45,7 @@ fun appModule( single { SyncManager(get(), get(), get()) } single { QueueRepository(get()) } single { MediaSessionConnection(context.applicationContext, get()) } - single { PlaybackController(get(), get(), get()) } + single { PlaybackController(get(), get(), get(), showMessage = toast) } viewModel { RootViewModel(get()) } viewModel { LibraryViewModel(get(), get(), get(), get(), get()) } viewModel { SettingsViewModel(get(), get()) } diff --git a/app/src/main/kotlin/com/subtracks/playback/Media3PlayerHandle.kt b/app/src/main/kotlin/com/subtracks/playback/Media3PlayerHandle.kt index 6c5d3cc6..c8e98b1f 100644 --- a/app/src/main/kotlin/com/subtracks/playback/Media3PlayerHandle.kt +++ b/app/src/main/kotlin/com/subtracks/playback/Media3PlayerHandle.kt @@ -111,7 +111,7 @@ class Media3PlayerHandle( "Playback error ${error.errorCodeName} at ${controller.currentPosition}/${controller.duration}", error, ) - listener.onError(error.errorCodeName) + listener.onError(playbackErrorMessage(error)) } override fun onPlaybackStateChanged(playbackState: Int) { @@ -171,3 +171,14 @@ class Media3PlayerHandle( const val EXTRA_DURATION_MS = "durationMs" } } + +internal fun playbackErrorMessage(error: PlaybackException): String = + when (error.errorCode) { + PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED, + PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT, + -> "Can't reach the server. Check your connection." + + PlaybackException.ERROR_CODE_IO_BAD_HTTP_STATUS -> "The server refused to stream this track." + + else -> error.errorCodeName + } diff --git a/app/src/main/kotlin/com/subtracks/playback/PlaybackController.kt b/app/src/main/kotlin/com/subtracks/playback/PlaybackController.kt index 2457727b..2fe0d1b8 100644 --- a/app/src/main/kotlin/com/subtracks/playback/PlaybackController.kt +++ b/app/src/main/kotlin/com/subtracks/playback/PlaybackController.kt @@ -98,6 +98,7 @@ class PlaybackController( private val sourceRepository: SourceRepository, private val queueRepository: QueueRepository, private val connection: PlayerConnection, + private val showMessage: (String) -> Unit = {}, dispatcher: CoroutineDispatcher = Dispatchers.Main.immediate, ) { private val scope = CoroutineScope(SupervisorJob() + dispatcher) @@ -870,6 +871,7 @@ class PlaybackController( override fun onError(message: String) { lastError = message + showMessage(message) refresh() } } diff --git a/app/src/test/kotlin/com/subtracks/playback/PlaybackControllerTest.kt b/app/src/test/kotlin/com/subtracks/playback/PlaybackControllerTest.kt index e2e0b8d3..279d6813 100644 --- a/app/src/test/kotlin/com/subtracks/playback/PlaybackControllerTest.kt +++ b/app/src/test/kotlin/com/subtracks/playback/PlaybackControllerTest.kt @@ -1,6 +1,7 @@ package com.subtracks.playback import android.content.Context +import androidx.media3.common.PlaybackException import androidx.room3.Room import androidx.sqlite.driver.bundled.BundledSQLiteDriver import androidx.test.core.app.ApplicationProvider @@ -45,6 +46,7 @@ class PlaybackControllerTest { private lateinit var queues: QueueRepository private lateinit var handle: FakePlayerHandle private lateinit var controller: PlaybackController + private val messages = CopyOnWriteArrayList() @Before fun setUp() { @@ -59,7 +61,8 @@ class PlaybackControllerTest { sources = SourceRepository(db, OkHttpClient(), prefs, networkMode = networkMode) queues = QueueRepository(db) handle = FakePlayerHandle() - controller = PlaybackController(sources, queues, FakePlayerConnection(handle), dispatcher = dispatcher) + controller = + PlaybackController(sources, queues, FakePlayerConnection(handle), showMessage = { messages += it }, dispatcher = dispatcher) } @After @@ -299,6 +302,29 @@ class PlaybackControllerTest { assertEquals(null, controller.state.value.error) } + @Test + fun playbackErrorsAreShownAsAToast() { + seedAlbum(3, sourceId = 1) + controller.playAlbum(1, "al1", 0) + await { handle.operations.contains("play") } + + handle.fail("Can't reach the server. Check your connection.") + await { messages.isNotEmpty() } + + assertEquals(listOf("Can't reach the server. Check your connection."), messages.toList()) + } + + @Test + fun networkErrorsAreReportedWithAFriendlyMessage() { + val failed = PlaybackException("net", null, PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED) + val timeout = PlaybackException("net", null, PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT) + val refused = PlaybackException("http", null, PlaybackException.ERROR_CODE_IO_BAD_HTTP_STATUS) + + assertEquals("Can't reach the server. Check your connection.", playbackErrorMessage(failed)) + assertEquals("Can't reach the server. Check your connection.", playbackErrorMessage(timeout)) + assertEquals("The server refused to stream this track.", playbackErrorMessage(refused)) + } + @Test fun startingAnEmptyQueueStopsThePlayer() { seedAlbum(3, sourceId = 1) diff --git a/app/src/test/kotlin/com/subtracks/ui/playback/QueueViewModelTest.kt b/app/src/test/kotlin/com/subtracks/ui/playback/QueueViewModelTest.kt index e6418c7e..06562d07 100644 --- a/app/src/test/kotlin/com/subtracks/ui/playback/QueueViewModelTest.kt +++ b/app/src/test/kotlin/com/subtracks/ui/playback/QueueViewModelTest.kt @@ -51,7 +51,7 @@ class QueueViewModelTest { .build() val sources = SourceRepository(db, OkHttpClient(), fakeUserPreferences()) queues = QueueRepository(db) - controller = PlaybackController(sources, queues, FakePlayerConnection(FakePlayerHandle()), dispatcher) + controller = PlaybackController(sources, queues, FakePlayerConnection(FakePlayerHandle()), dispatcher = dispatcher) viewModel = QueueViewModel(queues, controller) }