From c02d451578686c80d470cfb7ff7adb26b5698440 Mon Sep 17 00:00:00 2001 From: David Date: Sun, 5 Jul 2026 10:07:19 -0500 Subject: [PATCH 1/2] [Playback] Read duration from properties if necessary --- .../file/DurationFallbackPlayingFile.kt | 17 +++++++ .../exoplayer/ExoPlayerPlaybackHandler.kt | 3 +- .../preparation/ExoPlayerPlaybackPreparer.kt | 5 +-- .../initialization/IPlaybackInitialization.kt | 9 ---- .../FilePropertiesDurationPlayableFile.kt | 32 ++++++++++++++ .../FilePropertiesDurationReader.kt | 24 ++++++++++ .../FilePropertiesPlaybackPreparer.kt | 44 +++++++++++++++++++ .../playback/service/PlaybackService.kt | 34 +++++++------- .../When getting the duration.kt | 36 +++++++++++++++ .../When getting the duration.kt | 36 +++++++++++++++ .../When getting the duration.kt | 36 +++++++++++++++ 11 files changed, 248 insertions(+), 28 deletions(-) create mode 100644 projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/playback/file/DurationFallbackPlayingFile.kt delete mode 100644 projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/playback/file/initialization/IPlaybackInitialization.kt create mode 100644 projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/playback/file/properties/FilePropertiesDurationPlayableFile.kt create mode 100644 projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/playback/file/properties/FilePropertiesDurationReader.kt create mode 100644 projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/playback/file/properties/FilePropertiesPlaybackPreparer.kt create mode 100644 projectBlueWater/src/test/java/com/lasthopesoftware/bluewater/client/playback/file/DurationFallback/GivenAPlayingFile/AndItsDurationIsLessThanZero/When getting the duration.kt create mode 100644 projectBlueWater/src/test/java/com/lasthopesoftware/bluewater/client/playback/file/DurationFallback/GivenAPlayingFile/AndItsDurationIsMoreThanZero/When getting the duration.kt create mode 100644 projectBlueWater/src/test/java/com/lasthopesoftware/bluewater/client/playback/file/DurationFallback/GivenAPlayingFile/AndItsDurationIsZero/When getting the duration.kt diff --git a/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/playback/file/DurationFallbackPlayingFile.kt b/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/playback/file/DurationFallbackPlayingFile.kt new file mode 100644 index 0000000000..4561b97866 --- /dev/null +++ b/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/playback/file/DurationFallbackPlayingFile.kt @@ -0,0 +1,17 @@ +package com.lasthopesoftware.bluewater.client.playback.file + +import com.lasthopesoftware.bluewater.client.playback.file.progress.ReadFileDuration +import com.lasthopesoftware.promises.extensions.cancelBackEventually +import com.lasthopesoftware.promises.extensions.toPromise +import com.namehillsoftware.handoff.promises.Promise +import org.joda.time.Duration + +class DurationFallbackPlayingFile( + private val playingFile: PlayingFile, + private val fallbackDurationReader: ReadFileDuration +) : PlayingFile by playingFile { + override val duration: Promise + get() = playingFile.duration.cancelBackEventually { duration -> + duration.takeIf { it.isLongerThan(Duration.ZERO) }?.toPromise() ?: fallbackDurationReader.duration + } +} diff --git a/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/playback/file/exoplayer/ExoPlayerPlaybackHandler.kt b/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/playback/file/exoplayer/ExoPlayerPlaybackHandler.kt index c1ec51b44a..93ddd2332d 100644 --- a/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/playback/file/exoplayer/ExoPlayerPlaybackHandler.kt +++ b/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/playback/file/exoplayer/ExoPlayerPlaybackHandler.kt @@ -121,7 +121,8 @@ class ExoPlayerPlaybackHandler(private val exoPlayer: PromisingExoPlayer) : } } - @OptIn(UnstableApi::class) override fun onPlayerError(error: PlaybackException) { + @OptIn(UnstableApi::class) + override fun onPlayerError(error: PlaybackException) { removeListener() when (val cause = error.cause) { is EOFException -> { diff --git a/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/playback/file/exoplayer/preparation/ExoPlayerPlaybackPreparer.kt b/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/playback/file/exoplayer/preparation/ExoPlayerPlaybackPreparer.kt index 9ed9937a83..f7b9e7d3f1 100644 --- a/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/playback/file/exoplayer/preparation/ExoPlayerPlaybackPreparer.kt +++ b/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/playback/file/exoplayer/preparation/ExoPlayerPlaybackPreparer.kt @@ -96,10 +96,9 @@ class ExoPlayerPlaybackPreparer( .eventually { mediaSource -> bufferingExoPlayerProvider .promiseBufferingExoPlayer(mediaSource, it) - .then { ep -> Pair(mediaSource, ep) } + .then { ep -> mediaSource to ep } } - .eventually { pair -> - val (mediaSource, newBufferingExoPlayer) = pair + .eventually { (mediaSource, newBufferingExoPlayer) -> bufferingExoPlayer = newBufferingExoPlayer val prepareAtMillis = prepareAt.millis diff --git a/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/playback/file/initialization/IPlaybackInitialization.kt b/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/playback/file/initialization/IPlaybackInitialization.kt deleted file mode 100644 index 055035b6ac..0000000000 --- a/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/playback/file/initialization/IPlaybackInitialization.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.lasthopesoftware.bluewater.client.playback.file.initialization - -import android.net.Uri -import java.io.IOException - -interface IPlaybackInitialization { - @Throws(IOException::class) - fun initializeMediaPlayer(fileUri: Uri?): TMediaPlayer -} diff --git a/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/playback/file/properties/FilePropertiesDurationPlayableFile.kt b/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/playback/file/properties/FilePropertiesDurationPlayableFile.kt new file mode 100644 index 0000000000..2657c78fbb --- /dev/null +++ b/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/playback/file/properties/FilePropertiesDurationPlayableFile.kt @@ -0,0 +1,32 @@ +package com.lasthopesoftware.bluewater.client.playback.file.properties + +import com.lasthopesoftware.bluewater.client.browsing.files.ServiceFile +import com.lasthopesoftware.bluewater.client.browsing.files.properties.ProvideLibraryFileProperties +import com.lasthopesoftware.bluewater.client.browsing.library.repository.LibraryId +import com.lasthopesoftware.bluewater.client.playback.file.DurationFallbackPlayingFile +import com.lasthopesoftware.bluewater.client.playback.file.PlayableFile +import com.lasthopesoftware.bluewater.client.playback.file.PlayingFile +import com.lasthopesoftware.promises.extensions.cancelBackThen +import com.namehillsoftware.handoff.promises.Promise + +class FilePropertiesDurationPlayableFile( + private val playableFile: PlayableFile, + private val libraryFileProperties: ProvideLibraryFileProperties, + private val libraryId: LibraryId, + private val serviceFile: ServiceFile, +) : PlayableFile by playableFile { + override fun promisePlayback(): Promise = + playableFile + .promisePlayback() + .cancelBackThen { file, _ -> + DurationFallbackPlayingFile( + file, + FilePropertiesDurationReader( + libraryFileProperties, + libraryId, + serviceFile, + ) + ) + } +} + diff --git a/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/playback/file/properties/FilePropertiesDurationReader.kt b/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/playback/file/properties/FilePropertiesDurationReader.kt new file mode 100644 index 0000000000..8398b378c0 --- /dev/null +++ b/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/playback/file/properties/FilePropertiesDurationReader.kt @@ -0,0 +1,24 @@ +package com.lasthopesoftware.bluewater.client.playback.file.properties + +import com.lasthopesoftware.bluewater.client.browsing.files.ServiceFile +import com.lasthopesoftware.bluewater.client.browsing.files.properties.FilePropertyHelpers.duration +import com.lasthopesoftware.bluewater.client.browsing.files.properties.ProvideLibraryFileProperties +import com.lasthopesoftware.bluewater.client.browsing.library.repository.LibraryId +import com.lasthopesoftware.bluewater.client.playback.file.progress.ReadFileDuration +import com.lasthopesoftware.policies.retries.RetryOnRejectionLazyPromise +import com.lasthopesoftware.promises.extensions.cancelBackThen +import org.joda.time.Duration + +class FilePropertiesDurationReader( + private val libraryFileProperties: ProvideLibraryFileProperties, + private val libraryId: LibraryId, + private val serviceFile: ServiceFile, +) : ReadFileDuration { + override val duration by RetryOnRejectionLazyPromise { + libraryFileProperties + .promiseFileProperties(libraryId, serviceFile) + .cancelBackThen { fp, _ -> + fp.duration ?: Duration.ZERO + } + } +} diff --git a/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/playback/file/properties/FilePropertiesPlaybackPreparer.kt b/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/playback/file/properties/FilePropertiesPlaybackPreparer.kt new file mode 100644 index 0000000000..112b4da554 --- /dev/null +++ b/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/playback/file/properties/FilePropertiesPlaybackPreparer.kt @@ -0,0 +1,44 @@ +package com.lasthopesoftware.bluewater.client.playback.file.properties + +import com.lasthopesoftware.bluewater.client.browsing.files.ServiceFile +import com.lasthopesoftware.bluewater.client.browsing.files.properties.ProvideLibraryFileProperties +import com.lasthopesoftware.bluewater.client.browsing.library.repository.LibraryId +import com.lasthopesoftware.bluewater.client.playback.engine.preparation.ProvidePlayableFilePreparationSources +import com.lasthopesoftware.bluewater.client.playback.file.preparation.PlayableFilePreparationSource +import com.lasthopesoftware.bluewater.client.playback.file.preparation.PreparedPlayableFile +import com.lasthopesoftware.promises.extensions.cancelBackThen +import com.namehillsoftware.handoff.promises.Promise +import org.joda.time.Duration + +class FilePropertiesPreparationProvider( + private val preparationSourceProvider: ProvidePlayableFilePreparationSources, + private val libraryFileProperties: ProvideLibraryFileProperties +) : ProvidePlayableFilePreparationSources by preparationSourceProvider { + override fun providePlayableFilePreparationSource(): PlayableFilePreparationSource = FilePropertiesPlaybackPreparer( + preparationSourceProvider.providePlayableFilePreparationSource()) + + private inner class FilePropertiesPlaybackPreparer( + private val innerPreparationSource: PlayableFilePreparationSource, + ) : PlayableFilePreparationSource { + override fun promisePreparedPlaybackFile( + libraryId: LibraryId, + serviceFile: ServiceFile, + preparedAt: Duration): Promise = + innerPreparationSource + .promisePreparedPlaybackFile(libraryId, serviceFile, preparedAt) + .cancelBackThen { file, _ -> + file?.let { f -> + PreparedPlayableFile( + FilePropertiesDurationPlayableFile( + f.playbackHandler, + libraryFileProperties, + libraryId, + serviceFile + ), + f.playableFileVolumeManager, + f.bufferingPlaybackFile, + ) + } + } + } +} diff --git a/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/playback/service/PlaybackService.kt b/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/playback/service/PlaybackService.kt index 6be85b4faa..76b94435c2 100644 --- a/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/playback/service/PlaybackService.kt +++ b/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/playback/service/PlaybackService.kt @@ -68,6 +68,7 @@ import com.lasthopesoftware.bluewater.client.playback.file.exoplayer.preparation import com.lasthopesoftware.bluewater.client.playback.file.exoplayer.preparation.mediasource.MediaSourceProvider import com.lasthopesoftware.bluewater.client.playback.file.exoplayer.preparation.mediasource.RemoteDataSourceFactoryProvider import com.lasthopesoftware.bluewater.client.playback.file.preparation.queues.QueueProviders +import com.lasthopesoftware.bluewater.client.playback.file.properties.FilePropertiesPreparationProvider import com.lasthopesoftware.bluewater.client.playback.file.volume.MaxFileVolumeProvider import com.lasthopesoftware.bluewater.client.playback.file.volume.preparation.MaxFileVolumePreparationProvider import com.lasthopesoftware.bluewater.client.playback.nowplaying.broadcasters.notification.NotificationsConfiguration @@ -491,23 +492,26 @@ import java.util.concurrent.TimeoutException .then { h -> Handler(h.looper) } .then { playbackHandler -> MaxFileVolumePreparationProvider( - FallbackPreparedPlayableFileSourceProvider( - ExoPlayerPlayableFilePreparationSourceProvider( - this, - playbackHandler, - mainLoopHandlerExecutor, - mediaSourceProvider, - bestMatchUriProvider - ), - ExoPlayerPlayableFilePreparationSourceProvider( - this, - playbackHandler, - mainLoopHandlerExecutor, - mediaSourceProvider, - remoteFileUriProvider + FilePropertiesPreparationProvider( + FallbackPreparedPlayableFileSourceProvider( + ExoPlayerPlayableFilePreparationSourceProvider( + this, + playbackHandler, + mainLoopHandlerExecutor, + mediaSourceProvider, + bestMatchUriProvider + ), + ExoPlayerPlayableFilePreparationSourceProvider( + this, + playbackHandler, + mainLoopHandlerExecutor, + mediaSourceProvider, + remoteFileUriProvider + ), ), + libraryFilePropertiesProvider, ), - maxFileVolumeProvider + maxFileVolumeProvider, ) } }) diff --git a/projectBlueWater/src/test/java/com/lasthopesoftware/bluewater/client/playback/file/DurationFallback/GivenAPlayingFile/AndItsDurationIsLessThanZero/When getting the duration.kt b/projectBlueWater/src/test/java/com/lasthopesoftware/bluewater/client/playback/file/DurationFallback/GivenAPlayingFile/AndItsDurationIsLessThanZero/When getting the duration.kt new file mode 100644 index 0000000000..eee6f1bd93 --- /dev/null +++ b/projectBlueWater/src/test/java/com/lasthopesoftware/bluewater/client/playback/file/DurationFallback/GivenAPlayingFile/AndItsDurationIsLessThanZero/When getting the duration.kt @@ -0,0 +1,36 @@ +package com.lasthopesoftware.bluewater.client.playback.file.DurationFallback.GivenAPlayingFile.AndItsDurationIsLessThanZero + +import com.lasthopesoftware.bluewater.client.playback.file.DurationFallbackPlayingFile +import com.lasthopesoftware.bluewater.shared.promises.extensions.toExpiringFuture +import com.lasthopesoftware.promises.extensions.toPromise +import io.mockk.every +import io.mockk.mockk +import org.assertj.core.api.Assertions.assertThat +import org.joda.time.Duration +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test + +class `When getting the duration` { + private val mut by lazy { + DurationFallbackPlayingFile( + mockk { + every { duration } returns Duration.standardMinutes(-2L).toPromise() + }, + mockk { + every { duration } returns Duration.standardSeconds(975).toPromise() + } + ) + } + + private var duration: Duration? = null + + @BeforeAll + fun act() { + duration = mut.duration.toExpiringFuture().get() + } + + @Test + fun `then the duration is read from the fallback source`() { + assertThat(duration).isEqualTo(Duration.standardSeconds(975)) + } +} diff --git a/projectBlueWater/src/test/java/com/lasthopesoftware/bluewater/client/playback/file/DurationFallback/GivenAPlayingFile/AndItsDurationIsMoreThanZero/When getting the duration.kt b/projectBlueWater/src/test/java/com/lasthopesoftware/bluewater/client/playback/file/DurationFallback/GivenAPlayingFile/AndItsDurationIsMoreThanZero/When getting the duration.kt new file mode 100644 index 0000000000..ce12e4ba82 --- /dev/null +++ b/projectBlueWater/src/test/java/com/lasthopesoftware/bluewater/client/playback/file/DurationFallback/GivenAPlayingFile/AndItsDurationIsMoreThanZero/When getting the duration.kt @@ -0,0 +1,36 @@ +package com.lasthopesoftware.bluewater.client.playback.file.DurationFallback.GivenAPlayingFile.AndItsDurationIsMoreThanZero + +import com.lasthopesoftware.bluewater.client.playback.file.DurationFallbackPlayingFile +import com.lasthopesoftware.bluewater.shared.promises.extensions.toExpiringFuture +import com.lasthopesoftware.promises.extensions.toPromise +import io.mockk.every +import io.mockk.mockk +import org.assertj.core.api.Assertions.assertThat +import org.joda.time.Duration +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test + +class `When getting the duration` { + private val mut by lazy { + DurationFallbackPlayingFile( + mockk { + every { duration } returns Duration.standardSeconds(372).toPromise() + }, + mockk { + every { duration } returns Duration.standardSeconds(500).toPromise() + } + ) + } + + private var duration: Duration? = null + + @BeforeAll + fun act() { + duration = mut.duration.toExpiringFuture().get() + } + + @Test + fun `then the duration is read from the playing file`() { + assertThat(duration).isEqualTo(Duration.standardSeconds(372)) + } +} diff --git a/projectBlueWater/src/test/java/com/lasthopesoftware/bluewater/client/playback/file/DurationFallback/GivenAPlayingFile/AndItsDurationIsZero/When getting the duration.kt b/projectBlueWater/src/test/java/com/lasthopesoftware/bluewater/client/playback/file/DurationFallback/GivenAPlayingFile/AndItsDurationIsZero/When getting the duration.kt new file mode 100644 index 0000000000..ff9cc96063 --- /dev/null +++ b/projectBlueWater/src/test/java/com/lasthopesoftware/bluewater/client/playback/file/DurationFallback/GivenAPlayingFile/AndItsDurationIsZero/When getting the duration.kt @@ -0,0 +1,36 @@ +package com.lasthopesoftware.bluewater.client.playback.file.DurationFallback.GivenAPlayingFile.AndItsDurationIsZero + +import com.lasthopesoftware.bluewater.client.playback.file.DurationFallbackPlayingFile +import com.lasthopesoftware.bluewater.shared.promises.extensions.toExpiringFuture +import com.lasthopesoftware.promises.extensions.toPromise +import io.mockk.every +import io.mockk.mockk +import org.assertj.core.api.Assertions.assertThat +import org.joda.time.Duration +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test + +class `When getting the duration` { + private val mut by lazy { + DurationFallbackPlayingFile( + mockk { + every { duration } returns Duration.ZERO.toPromise() + }, + mockk { + every { duration } returns Duration.standardSeconds(713).toPromise() + } + ) + } + + private var duration: Duration? = null + + @BeforeAll + fun act() { + duration = mut.duration.toExpiringFuture().get() + } + + @Test + fun `then the duration is read from the fallback source`() { + assertThat(duration).isEqualTo(Duration.standardSeconds(713)) + } +} From 7b75c52404298e57c7e15839e16d3f9949ec557e Mon Sep 17 00:00:00 2001 From: David Date: Sun, 5 Jul 2026 10:07:19 -0500 Subject: [PATCH 2/2] [Playback,ExoPlayer] Resolve duration when greater than 0 --- .../file/DurationFallbackPlayingFile.kt | 6 +- .../exoplayer/ExoPlayerPlaybackHandler.kt | 79 ++++++++++++++----- .../extensions/ValuePromiseExtension.kt | 11 +++ .../When getting the duration.kt | 36 --------- .../When getting the duration.kt | 2 +- .../When getting the duration.kt | 5 +- 6 files changed, 77 insertions(+), 62 deletions(-) delete mode 100644 projectBlueWater/src/test/java/com/lasthopesoftware/bluewater/client/playback/file/DurationFallback/GivenAPlayingFile/AndItsDurationIsZero/When getting the duration.kt rename projectBlueWater/src/test/java/com/lasthopesoftware/bluewater/client/playback/file/DurationFallback/GivenAPlayingFile/{AndItsDurationIsMoreThanZero => AndItsDurationResolvesFirst}/When getting the duration.kt (94%) rename projectBlueWater/src/test/java/com/lasthopesoftware/bluewater/client/playback/file/DurationFallback/GivenAPlayingFile/{AndItsDurationIsLessThanZero => AndTheFilePropertyDurationResolvesFirst}/When getting the duration.kt (84%) diff --git a/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/playback/file/DurationFallbackPlayingFile.kt b/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/playback/file/DurationFallbackPlayingFile.kt index 4561b97866..66de7693da 100644 --- a/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/playback/file/DurationFallbackPlayingFile.kt +++ b/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/playback/file/DurationFallbackPlayingFile.kt @@ -1,8 +1,6 @@ package com.lasthopesoftware.bluewater.client.playback.file import com.lasthopesoftware.bluewater.client.playback.file.progress.ReadFileDuration -import com.lasthopesoftware.promises.extensions.cancelBackEventually -import com.lasthopesoftware.promises.extensions.toPromise import com.namehillsoftware.handoff.promises.Promise import org.joda.time.Duration @@ -11,7 +9,5 @@ class DurationFallbackPlayingFile( private val fallbackDurationReader: ReadFileDuration ) : PlayingFile by playingFile { override val duration: Promise - get() = playingFile.duration.cancelBackEventually { duration -> - duration.takeIf { it.isLongerThan(Duration.ZERO) }?.toPromise() ?: fallbackDurationReader.duration - } + get() = Promise.whenAny(playingFile.duration, fallbackDurationReader.duration) } diff --git a/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/playback/file/exoplayer/ExoPlayerPlaybackHandler.kt b/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/playback/file/exoplayer/ExoPlayerPlaybackHandler.kt index 93ddd2332d..2a3887d7fc 100644 --- a/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/playback/file/exoplayer/ExoPlayerPlaybackHandler.kt +++ b/projectBlueWater/src/main/java/com/lasthopesoftware/bluewater/client/playback/file/exoplayer/ExoPlayerPlaybackHandler.kt @@ -11,9 +11,11 @@ import com.lasthopesoftware.bluewater.client.playback.file.PlayableFile import com.lasthopesoftware.bluewater.client.playback.file.PlayedFile import com.lasthopesoftware.bluewater.client.playback.file.PlayingFile import com.lasthopesoftware.bluewater.client.playback.file.exoplayer.error.ExoPlayerException +import com.lasthopesoftware.bluewater.client.playback.file.progress.ReadFileDuration import com.lasthopesoftware.bluewater.client.playback.file.progress.ReadFileProgress import com.lasthopesoftware.bluewater.shared.lazyLogger import com.lasthopesoftware.policies.retries.RetryOnRejectionLazyPromise +import com.lasthopesoftware.promises.ResolvedPromiseBox import com.lasthopesoftware.promises.extensions.ProgressedPromise import com.lasthopesoftware.promises.extensions.toPromise import com.namehillsoftware.handoff.cancellation.CancellationResponse @@ -32,8 +34,7 @@ class ExoPlayerPlaybackHandler(private val exoPlayer: PromisingExoPlayer) : PlayingFile, PlayedFile, Player.Listener, - CancellationResponse -{ + CancellationResponse { companion object { private val minutesAndSecondsFormatter by lazy { @@ -53,9 +54,10 @@ class ExoPlayerPlaybackHandler(private val exoPlayer: PromisingExoPlayer) : override fun respond(currentPosition: Long): Duration = Duration.millis(currentPosition) } - private val fileProgressReader = AtomicReference(PausedExoPlayerFileProgressReader(exoPlayer)) + private val fileProgressReader = + AtomicReference(PausedExoPlayerFileProgressReader(exoPlayer)) - private val promisedDuration = RetryOnRejectionLazyPromise { exoPlayer.getDuration().then(LongDurationTransformer) } + private val promisedDuration by lazy { PlayingExoPlayerFileDurationReader(exoPlayer) } init { awaitCancellation(this) @@ -81,7 +83,7 @@ class ExoPlayerPlaybackHandler(private val exoPlayer: PromisingExoPlayer) : get() = fileProgressReader.get().progress override val duration: Promise - get() = promisedDuration.value + get() = promisedDuration.duration override fun promisePlayback(): Promise { isPlaying = true @@ -106,7 +108,9 @@ class ExoPlayerPlaybackHandler(private val exoPlayer: PromisingExoPlayer) : val formatter = minutesAndSecondsFormatter logger.warn( "The player was playing, but it transitioned to idle! " + - "Playback progress: " + p.toPeriod().toString(formatter) + " / " + d.toPeriod().toString(formatter) + ". ") + "Playback progress: " + p.toPeriod().toString(formatter) + " / " + d.toPeriod() + .toString(formatter) + ". " + ) } } .eventually { exoPlayer.getPlayWhenReady() } @@ -130,11 +134,13 @@ class ExoPlayerPlaybackHandler(private val exoPlayer: PromisingExoPlayer) : resolve(this) return } + is NoSuchElementException -> { logger.warn("The player was unexpectedly unable to dequeue messages, completing playback", error) resolve(this) return } + is ProtocolException -> { when (cause.message) { "unexpected end of stream" -> { @@ -144,6 +150,7 @@ class ExoPlayerPlaybackHandler(private val exoPlayer: PromisingExoPlayer) : } } } + is ParserException -> { if (cause.message.startsWith("Searched too many bytes.")) { logger.warn("The stream was corrupted, completing playback", error) @@ -151,6 +158,7 @@ class ExoPlayerPlaybackHandler(private val exoPlayer: PromisingExoPlayer) : return } } + is HttpDataSource.InvalidResponseCodeException -> { if (cause.responseCode == 416) { logger.warn("Received an error code of " + cause.responseCode + ", completing playback", cause) @@ -184,8 +192,7 @@ class ExoPlayerPlaybackHandler(private val exoPlayer: PromisingExoPlayer) : private interface CloseableReadFileProgress : ReadFileProgress, AutoCloseable private class PausedExoPlayerFileProgressReader(private val exoPlayer: PromisingExoPlayer) : - CloseableReadFileProgress - { + CloseableReadFileProgress { private val promisedFileProgress = RetryOnRejectionLazyPromise { exoPlayer.getCurrentPosition().then(LongDurationTransformer) } @@ -199,28 +206,64 @@ class ExoPlayerPlaybackHandler(private val exoPlayer: PromisingExoPlayer) : } private class PlayingExoPlayerFileProgressReader(private val exoPlayer: PromisingExoPlayer) : - CloseableReadFileProgress - { - companion object { - private val zeroAndLong by lazy { 0L.toPromise() } - } - + CloseableReadFileProgress { @Volatile private var isClosed = false - private val currentDurationPromise = AtomicReference(zeroAndLong) + private val currentProgressPromise = AtomicReference(0L.toPromise()) override val progress: Promise get() = - if (isClosed) currentDurationPromise.get().then(LongDurationTransformer) - else currentDurationPromise.updateAndGet { prev -> + if (isClosed) currentProgressPromise.get().then(LongDurationTransformer) + else currentProgressPromise.updateAndGet { prev -> prev.cancel() exoPlayer.getCurrentPosition() }.then(LongDurationTransformer) override fun close() { isClosed = true - currentDurationPromise.get().cancel() + currentProgressPromise.get().cancel() + } + } + + private class PlayingExoPlayerFileDurationReader(private val exoPlayer: PromisingExoPlayer) : + ReadFileDuration, AutoCloseable { + @Volatile + private var isClosed = false + + private val currentDurationPromise = AtomicReference(ResolvedPromiseBox(PositiveDurationPromise(exoPlayer))) + + override val duration: Promise + get() = + if (isClosed) currentDurationPromise.get().originalPromise + else currentDurationPromise.get().resolvedPromise ?: currentDurationPromise.updateAndGet { prev -> + prev.run { + if (resolvedPromise != null) this + else { + originalPromise.cancel() + ResolvedPromiseBox(PositiveDurationPromise(exoPlayer)) + } + } + }.originalPromise + + override fun close() { + isClosed = true + currentDurationPromise.get().originalPromise.cancel() + } + } + + private class PositiveDurationPromise(exoPlayer: PromisingExoPlayer) : Proxy(), + ImmediateResponse { + init { + val promisedDuration = exoPlayer.getDuration().then(LongDurationTransformer) + doCancel(promisedDuration) + promisedDuration.then(this) + proxyRejection(promisedDuration) + } + + override fun respond(position: Duration) { + if (position.isLongerThan(Duration.ZERO)) + resolve(position) } } } diff --git a/projectBlueWater/src/main/java/com/lasthopesoftware/promises/extensions/ValuePromiseExtension.kt b/projectBlueWater/src/main/java/com/lasthopesoftware/promises/extensions/ValuePromiseExtension.kt index b88702dce4..e579d32298 100644 --- a/projectBlueWater/src/main/java/com/lasthopesoftware/promises/extensions/ValuePromiseExtension.kt +++ b/projectBlueWater/src/main/java/com/lasthopesoftware/promises/extensions/ValuePromiseExtension.kt @@ -93,6 +93,7 @@ fun T.toPromise(): Promise = when (this) { null -> Promise.empty() is Unit -> toPromise() as Promise is Int -> toPromise() as Promise + is Long -> toPromise() as Promise is Boolean -> toPromise() as Promise is String -> if (isEmpty()) EmptyStringPromise as Promise else Promise(this) else -> Promise(this) @@ -107,11 +108,21 @@ fun Int.toPromise(): Promise = when (this) { else -> Promise(this) } +fun Long.toPromise(): Promise = when (this) { + 0L -> ZeroAndLongPromise + 1L -> OneAndLongPromise + -1L -> NegativeOneAndLongPromise + else -> Promise(this) +} + fun Boolean.toPromise(): Promise = if (this) TruePromise else FalsePromise private object ZeroPromise : Promise(0) +private object ZeroAndLongPromise : Promise(0L) private object OnePromise : Promise(1) +private object OneAndLongPromise : Promise(1L) private object NegativeOnePromise : Promise(-1) +private object NegativeOneAndLongPromise : Promise(-1L) private object TruePromise : Promise(true) private object FalsePromise: Promise(false) private object EmptyStringPromise: Promise("") diff --git a/projectBlueWater/src/test/java/com/lasthopesoftware/bluewater/client/playback/file/DurationFallback/GivenAPlayingFile/AndItsDurationIsZero/When getting the duration.kt b/projectBlueWater/src/test/java/com/lasthopesoftware/bluewater/client/playback/file/DurationFallback/GivenAPlayingFile/AndItsDurationIsZero/When getting the duration.kt deleted file mode 100644 index ff9cc96063..0000000000 --- a/projectBlueWater/src/test/java/com/lasthopesoftware/bluewater/client/playback/file/DurationFallback/GivenAPlayingFile/AndItsDurationIsZero/When getting the duration.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.lasthopesoftware.bluewater.client.playback.file.DurationFallback.GivenAPlayingFile.AndItsDurationIsZero - -import com.lasthopesoftware.bluewater.client.playback.file.DurationFallbackPlayingFile -import com.lasthopesoftware.bluewater.shared.promises.extensions.toExpiringFuture -import com.lasthopesoftware.promises.extensions.toPromise -import io.mockk.every -import io.mockk.mockk -import org.assertj.core.api.Assertions.assertThat -import org.joda.time.Duration -import org.junit.jupiter.api.BeforeAll -import org.junit.jupiter.api.Test - -class `When getting the duration` { - private val mut by lazy { - DurationFallbackPlayingFile( - mockk { - every { duration } returns Duration.ZERO.toPromise() - }, - mockk { - every { duration } returns Duration.standardSeconds(713).toPromise() - } - ) - } - - private var duration: Duration? = null - - @BeforeAll - fun act() { - duration = mut.duration.toExpiringFuture().get() - } - - @Test - fun `then the duration is read from the fallback source`() { - assertThat(duration).isEqualTo(Duration.standardSeconds(713)) - } -} diff --git a/projectBlueWater/src/test/java/com/lasthopesoftware/bluewater/client/playback/file/DurationFallback/GivenAPlayingFile/AndItsDurationIsMoreThanZero/When getting the duration.kt b/projectBlueWater/src/test/java/com/lasthopesoftware/bluewater/client/playback/file/DurationFallback/GivenAPlayingFile/AndItsDurationResolvesFirst/When getting the duration.kt similarity index 94% rename from projectBlueWater/src/test/java/com/lasthopesoftware/bluewater/client/playback/file/DurationFallback/GivenAPlayingFile/AndItsDurationIsMoreThanZero/When getting the duration.kt rename to projectBlueWater/src/test/java/com/lasthopesoftware/bluewater/client/playback/file/DurationFallback/GivenAPlayingFile/AndItsDurationResolvesFirst/When getting the duration.kt index ce12e4ba82..40862908f2 100644 --- a/projectBlueWater/src/test/java/com/lasthopesoftware/bluewater/client/playback/file/DurationFallback/GivenAPlayingFile/AndItsDurationIsMoreThanZero/When getting the duration.kt +++ b/projectBlueWater/src/test/java/com/lasthopesoftware/bluewater/client/playback/file/DurationFallback/GivenAPlayingFile/AndItsDurationResolvesFirst/When getting the duration.kt @@ -1,4 +1,4 @@ -package com.lasthopesoftware.bluewater.client.playback.file.DurationFallback.GivenAPlayingFile.AndItsDurationIsMoreThanZero +package com.lasthopesoftware.bluewater.client.playback.file.DurationFallback.GivenAPlayingFile.AndItsDurationResolvesFirst import com.lasthopesoftware.bluewater.client.playback.file.DurationFallbackPlayingFile import com.lasthopesoftware.bluewater.shared.promises.extensions.toExpiringFuture diff --git a/projectBlueWater/src/test/java/com/lasthopesoftware/bluewater/client/playback/file/DurationFallback/GivenAPlayingFile/AndItsDurationIsLessThanZero/When getting the duration.kt b/projectBlueWater/src/test/java/com/lasthopesoftware/bluewater/client/playback/file/DurationFallback/GivenAPlayingFile/AndTheFilePropertyDurationResolvesFirst/When getting the duration.kt similarity index 84% rename from projectBlueWater/src/test/java/com/lasthopesoftware/bluewater/client/playback/file/DurationFallback/GivenAPlayingFile/AndItsDurationIsLessThanZero/When getting the duration.kt rename to projectBlueWater/src/test/java/com/lasthopesoftware/bluewater/client/playback/file/DurationFallback/GivenAPlayingFile/AndTheFilePropertyDurationResolvesFirst/When getting the duration.kt index eee6f1bd93..351712522b 100644 --- a/projectBlueWater/src/test/java/com/lasthopesoftware/bluewater/client/playback/file/DurationFallback/GivenAPlayingFile/AndItsDurationIsLessThanZero/When getting the duration.kt +++ b/projectBlueWater/src/test/java/com/lasthopesoftware/bluewater/client/playback/file/DurationFallback/GivenAPlayingFile/AndTheFilePropertyDurationResolvesFirst/When getting the duration.kt @@ -1,8 +1,9 @@ -package com.lasthopesoftware.bluewater.client.playback.file.DurationFallback.GivenAPlayingFile.AndItsDurationIsLessThanZero +package com.lasthopesoftware.bluewater.client.playback.file.DurationFallback.GivenAPlayingFile.AndTheFilePropertyDurationResolvesFirst import com.lasthopesoftware.bluewater.client.playback.file.DurationFallbackPlayingFile import com.lasthopesoftware.bluewater.shared.promises.extensions.toExpiringFuture import com.lasthopesoftware.promises.extensions.toPromise +import com.namehillsoftware.handoff.promises.Promise import io.mockk.every import io.mockk.mockk import org.assertj.core.api.Assertions.assertThat @@ -14,7 +15,7 @@ class `When getting the duration` { private val mut by lazy { DurationFallbackPlayingFile( mockk { - every { duration } returns Duration.standardMinutes(-2L).toPromise() + every { duration } returns Promise {} }, mockk { every { duration } returns Duration.standardSeconds(975).toPromise()