From 44f67b1b0ee0d1bf235e944ab3dbd19001441c37 Mon Sep 17 00:00:00 2001 From: Dennis Skokov Date: Tue, 7 Jan 2025 14:32:38 +0100 Subject: [PATCH] iOS/Android shared sources --- .../shared/viewmodel/media/SoundsSources.kt | 29 ---------------- .../viewmodel/media/MediaSoundLoopPlayer.kt | 2 +- .../shared/viewmodel/media/SoundsSources.kt | 6 ++-- .../shared/viewmodel/media/SoundsSources.kt | 33 ------------------- .../androidMain/kotlin/DefaultMediaManager.kt | 2 +- .../androidMain/kotlin/DefaultSoundPlayer.kt | 25 +++++++------- media/src/androidMain/kotlin/MediaSource.kt | 8 +++-- media/src/commonMain/kotlin/MediaSource.kt | 3 +- media/src/commonMain/kotlin/SoundPlayer.kt | 2 +- .../src/iosMain/kotlin/DefaultMediaManager.kt | 1 + .../src/iosMain/kotlin/DefaultSoundPlayer.kt | 15 ++++++--- media/src/iosMain/kotlin/MediaSource.kt | 4 +++ 12 files changed, 40 insertions(+), 90 deletions(-) delete mode 100644 example/shared/src/androidMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/media/SoundsSources.kt delete mode 100644 example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/media/SoundsSources.kt diff --git a/example/shared/src/androidMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/media/SoundsSources.kt b/example/shared/src/androidMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/media/SoundsSources.kt deleted file mode 100644 index 959ba2790..000000000 --- a/example/shared/src/androidMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/media/SoundsSources.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - Copyright 2025 Splendo Consulting B.V. The Netherlands - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - */ - -package com.splendo.kaluga.example.shared.viewmodel.media - -import android.content.res.AssetFileDescriptor -import android.net.Uri -import com.splendo.kaluga.base.ApplicationHolder -import com.splendo.kaluga.example.shared.R -import com.splendo.kaluga.media.MediaSource -import com.splendo.kaluga.media.mediaSourceFromUrl - -actual object SoundsSources { - actual val beep: MediaSource = MediaSource.Id(id = "sound") -} \ No newline at end of file diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/media/MediaSoundLoopPlayer.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/media/MediaSoundLoopPlayer.kt index dbd1c27ba..d12f7b6cc 100644 --- a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/media/MediaSoundLoopPlayer.kt +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/media/MediaSoundLoopPlayer.kt @@ -29,7 +29,7 @@ import kotlin.time.Duration.Companion.minutes class MediaSoundLoopPlayer( private val coroutineScope: CoroutineScope, - private val mediaSource: MediaSource, + private val mediaSource: MediaSource.Local, ) { private var player: DefaultSoundPlayer? = null diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/media/SoundsSources.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/media/SoundsSources.kt index f7168fd2d..37b01200b 100644 --- a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/media/SoundsSources.kt +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/media/SoundsSources.kt @@ -18,7 +18,9 @@ package com.splendo.kaluga.example.shared.viewmodel.media import com.splendo.kaluga.media.MediaSource +import com.splendo.kaluga.media.mediaSourceFromLocalFile -expect object SoundsSources { - val beep: MediaSource +object SoundsSources { + //TODO: Throw correct error + val beep: MediaSource.Local = mediaSourceFromLocalFile("sound", "mp3") ?: error("") } \ No newline at end of file diff --git a/example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/media/SoundsSources.kt b/example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/media/SoundsSources.kt deleted file mode 100644 index c055953dd..000000000 --- a/example/shared/src/iosMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/media/SoundsSources.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - Copyright 2025 Splendo Consulting B.V. The Netherlands - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - */ - -package com.splendo.kaluga.example.shared.viewmodel.media - -import com.splendo.kaluga.media.MediaSource -import platform.Foundation.NSBundle -import platform.Foundation.NSURL - -actual object SoundsSources { - actual val beep: MediaSource by lazy { - val path = NSBundle.mainBundle.pathForResource("sound", "mp3") - require(path != null) { "Invalid file for sound" } - MediaSource.URL( - NSURL.fileURLWithPath(path), - listOf(MediaSource.URL.Option.PreferPreciseDurationAndTiming(isPreferred = true)), - ) - } -} \ No newline at end of file diff --git a/media/src/androidMain/kotlin/DefaultMediaManager.kt b/media/src/androidMain/kotlin/DefaultMediaManager.kt index b64d4ad80..ff480a100 100644 --- a/media/src/androidMain/kotlin/DefaultMediaManager.kt +++ b/media/src/androidMain/kotlin/DefaultMediaManager.kt @@ -136,7 +136,7 @@ actual class DefaultMediaManager(mediaSurfaceProvider: MediaSurfaceProvider?, co } else { mediaPlayer.setDataSource(source.context, source.uri, source.headers) } - is MediaSource.Id -> TODO() + is MediaSource.Bundle -> TODO() } DefaultPlayableMedia(source, mediaPlayer) } catch (e: Throwable) { diff --git a/media/src/androidMain/kotlin/DefaultSoundPlayer.kt b/media/src/androidMain/kotlin/DefaultSoundPlayer.kt index 2e8bf051e..a2fd5e5a5 100644 --- a/media/src/androidMain/kotlin/DefaultSoundPlayer.kt +++ b/media/src/androidMain/kotlin/DefaultSoundPlayer.kt @@ -21,7 +21,7 @@ import android.media.AudioAttributes import android.media.SoundPool import com.splendo.kaluga.base.ApplicationHolder -actual class DefaultSoundPlayer actual constructor(source: MediaSource) : SoundPlayer { +actual class DefaultSoundPlayer actual constructor(source: MediaSource.Local) : SoundPlayer { private val soundPool = SoundPool.Builder().apply { val attributes = AudioAttributes.Builder().apply { @@ -40,19 +40,16 @@ actual class DefaultSoundPlayer actual constructor(source: MediaSource) : SoundP soundPool.release() } - // private fun SoundPool.load(source: MediaSource): Int = if (source is MediaSource.Url) load(source.url) else throw MediaSoundError.UnexpectedMediaSourceShouldBeURL - private fun SoundPool.load(source: MediaSource): Int = when (source) { - is MediaSource.Url -> TODO() - is MediaSource.Asset -> TODO() - is MediaSource.File -> TODO() - is MediaSource.Content -> TODO() - is MediaSource.Id -> load( - ApplicationHolder.applicationContext, - ApplicationHolder.applicationContext.resources.getIdentifier(source.id, "raw", ApplicationHolder.applicationContext.packageName), - 1, - ) + private fun SoundPool.load(source: MediaSource.Local): Int = when (source) { + is MediaSource.Asset -> load(source.descriptor, 1) + is MediaSource.File -> load(source.descriptor, source.offset, source.length, 1) + is MediaSource.Bundle -> ApplicationHolder.applicationContext.let { context -> + load( + context, + context.resources.getIdentifier(source.fileName, source.defType, context.packageName), + 1, + ) + } else -> throw MediaSoundError.UnexpectedMediaSourceShouldBeId } - - // private fun SoundPool.load(url: URL): Int = if (url.path != null) load(url.path, 1) else throw MediaSoundError.CannotAccessMediaSource } diff --git a/media/src/androidMain/kotlin/MediaSource.kt b/media/src/androidMain/kotlin/MediaSource.kt index bd641bc30..bcd3a2071 100644 --- a/media/src/androidMain/kotlin/MediaSource.kt +++ b/media/src/androidMain/kotlin/MediaSource.kt @@ -36,13 +36,13 @@ actual sealed class MediaSource { * A [MediaSource] that has an associated [AssetFileDescriptor] * @property descriptor the [AssetFileDescriptor] associated with the media source */ - data class Asset(val descriptor: AssetFileDescriptor) : MediaSource() + data class Asset(val descriptor: AssetFileDescriptor) : Local() /** * A [MediaSource] that has an associated [FileDescriptor] * @property descriptor the [FileDescriptor] associated with the media source */ - data class File(val descriptor: FileDescriptor) : MediaSource() + data class File(val descriptor: FileDescriptor, val offset: Long, val length: Long) : Local() /** * A [MediaSource] that is located at a [URL] @@ -66,7 +66,7 @@ actual sealed class MediaSource { val cookies: List? = null, ) : MediaSource() - data class Id(val id: String) : MediaSource() + data class Bundle(val fileName: String, val defType: String = "raw") : Local() } /** @@ -79,3 +79,5 @@ actual fun mediaSourceFromUrl(url: String): MediaSource? = try { } catch (e: MalformedURLException) { null } + +actual fun mediaSourceFromLocalFile(fileName: String, fileType: String): MediaSource.Local? = MediaSource.Bundle(fileName) diff --git a/media/src/commonMain/kotlin/MediaSource.kt b/media/src/commonMain/kotlin/MediaSource.kt index 8c12c27cf..2abc5bf6f 100644 --- a/media/src/commonMain/kotlin/MediaSource.kt +++ b/media/src/commonMain/kotlin/MediaSource.kt @@ -31,5 +31,4 @@ expect sealed class MediaSource { */ expect fun mediaSourceFromUrl(url: String): MediaSource? -// TODO: Implement it -// expect fun mediaSourceFromLocalFile(fileName: String, fileType: String): MediaSource.Local? +expect fun mediaSourceFromLocalFile(fileName: String, fileType: String): MediaSource.Local? diff --git a/media/src/commonMain/kotlin/SoundPlayer.kt b/media/src/commonMain/kotlin/SoundPlayer.kt index 54cafdf6b..ab8461a76 100644 --- a/media/src/commonMain/kotlin/SoundPlayer.kt +++ b/media/src/commonMain/kotlin/SoundPlayer.kt @@ -29,7 +29,7 @@ interface SoundPlayer : AutoCloseable { /** * A default implementation of [SoundPlayer] */ -expect class DefaultSoundPlayer(source: MediaSource) : SoundPlayer { +expect class DefaultSoundPlayer(source: MediaSource.Local) : SoundPlayer { override fun close() override fun play() } diff --git a/media/src/iosMain/kotlin/DefaultMediaManager.kt b/media/src/iosMain/kotlin/DefaultMediaManager.kt index 971069116..8081f88eb 100644 --- a/media/src/iosMain/kotlin/DefaultMediaManager.kt +++ b/media/src/iosMain/kotlin/DefaultMediaManager.kt @@ -288,6 +288,7 @@ actual class DefaultMediaManager(mediaSurfaceProvider: MediaSurfaceProvider?, pr private val MediaSource.avPlayerItem: AVPlayerItem get() = when (this) { is MediaSource.Asset -> AVPlayerItem(asset) is MediaSource.URL -> AVPlayerItem(AVURLAsset.URLAssetWithURL(url, options.associate { it.entry })) + is MediaSource.Bundle -> TODO() } actual override suspend fun renderVideoOnSurface(surface: MediaSurface?) { diff --git a/media/src/iosMain/kotlin/DefaultSoundPlayer.kt b/media/src/iosMain/kotlin/DefaultSoundPlayer.kt index 080116556..55d74f9e7 100644 --- a/media/src/iosMain/kotlin/DefaultSoundPlayer.kt +++ b/media/src/iosMain/kotlin/DefaultSoundPlayer.kt @@ -31,6 +31,7 @@ import platform.AVFAudio.AVAudioFile import platform.AVFAudio.AVAudioSession import platform.AVFAudio.AVAudioSessionCategoryPlayback import platform.AVFAudio.AVAudioSessionRouteChangeNotification +import platform.Foundation.NSBundle import platform.Foundation.NSError import platform.Foundation.NSNotification import platform.Foundation.NSNotificationCenter @@ -41,9 +42,8 @@ import platform.Foundation.NSURL import platform.UIKit.UIApplicationDidEnterBackgroundNotification import platform.UIKit.UIApplicationWillEnterForegroundNotification -actual class DefaultSoundPlayer actual constructor(source: MediaSource) : SoundPlayer { - private val url = if (source is MediaSource.URL) source.url else throw MediaSoundError.UnexpectedMediaSourceShouldBeURL - private val file = accessFile(url) +actual class DefaultSoundPlayer actual constructor(source: MediaSource.Local) : SoundPlayer { + private val file = accessFile(source) init { observeNotifications() @@ -167,8 +167,15 @@ actual class DefaultSoundPlayer actual constructor(source: MediaSource) : SoundP onNotification, ) - private fun accessFile(url: NSURL): AVAudioFile = execute( + private fun accessFile(source: MediaSource.Local): AVAudioFile = execute( block = { errorPtr -> + val path = when (source) { + is MediaSource.Bundle -> NSBundle.mainBundle.pathForResource(source.fileName, source.fileType) + else -> error("Should be bundle") + } + + require(path != null) { "Invalid file for sound" } + val url = NSURL.fileURLWithPath(path) AVAudioFile(forReading = url, error = errorPtr) }, handleError = { error -> diff --git a/media/src/iosMain/kotlin/MediaSource.kt b/media/src/iosMain/kotlin/MediaSource.kt index 7fd783e73..db59fcbc4 100644 --- a/media/src/iosMain/kotlin/MediaSource.kt +++ b/media/src/iosMain/kotlin/MediaSource.kt @@ -135,6 +135,8 @@ actual sealed class MediaSource { } } } + + data class Bundle(val fileName: String, val fileType: String) : Local() } /** @@ -144,3 +146,5 @@ actual sealed class MediaSource { */ actual fun mediaSourceFromUrl(url: String): MediaSource? = NSURL.URLWithString(url)?.let { MediaSource.URL(it, options = listOf(MediaSource.URL.Option.PreferPreciseDurationAndTiming(true))) } + +actual fun mediaSourceFromLocalFile(fileName: String, fileType: String): MediaSource.Local? = MediaSource.Bundle(fileName, fileType)