diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj index f4f5f14..067927a 100644 --- a/iosApp/iosApp.xcodeproj/project.pbxproj +++ b/iosApp/iosApp.xcodeproj/project.pbxproj @@ -11,7 +11,7 @@ /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ - 95ABF2C8EE96D87C60CB4B62 /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { + 95ABF2C8EE96D87C60CB4B62 /* Exceptions for "iosApp" folder in "iosApp" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( Info.plist, @@ -24,7 +24,7 @@ 89B7F8019E767C8CDD8E9E81 /* iosApp */ = { isa = PBXFileSystemSynchronizedRootGroup; exceptions = ( - 95ABF2C8EE96D87C60CB4B62 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, + 95ABF2C8EE96D87C60CB4B62 /* Exceptions for "iosApp" folder in "iosApp" target */, ); path = iosApp; sourceTree = ""; @@ -269,7 +269,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; - DEVELOPMENT_TEAM = "${TEAM_ID}"; + DEVELOPMENT_TEAM = 492RZGQ5SU; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = iosApp/Info.plist; diff --git a/iosApp/iosApp/Info.plist b/iosApp/iosApp/Info.plist index ed67386..435eb45 100644 --- a/iosApp/iosApp/Info.plist +++ b/iosApp/iosApp/Info.plist @@ -4,5 +4,9 @@ CADisableMinimumFrameDurationOnPhone + UIBackgroundModes + + audio + - \ No newline at end of file + diff --git a/shared/src/androidMain/kotlin/fr/ajaury/gwenedeg/di/Modules.android.kt b/shared/src/androidMain/kotlin/fr/ajaury/gwenedeg/di/Modules.android.kt index 0e61fc7..2fae03b 100644 --- a/shared/src/androidMain/kotlin/fr/ajaury/gwenedeg/di/Modules.android.kt +++ b/shared/src/androidMain/kotlin/fr/ajaury/gwenedeg/di/Modules.android.kt @@ -1,10 +1,13 @@ package fr.ajaury.gwenedeg.di import fr.ajaury.gwenedeg.player.AndroidAudioPlayer +import fr.ajaury.gwenedeg.player.AndroidAudioSessionManager import fr.ajaury.gwenedeg.player.AudioPlayer +import fr.ajaury.gwenedeg.player.AudioSessionManager import org.koin.core.module.Module import org.koin.dsl.module actual val platformModule: Module = module { single { AndroidAudioPlayer(context = get()) } + single { AndroidAudioSessionManager() } } diff --git a/shared/src/androidMain/kotlin/fr/ajaury/gwenedeg/player/AndroidAudioSessionManager.kt b/shared/src/androidMain/kotlin/fr/ajaury/gwenedeg/player/AndroidAudioSessionManager.kt new file mode 100644 index 0000000..48aeeca --- /dev/null +++ b/shared/src/androidMain/kotlin/fr/ajaury/gwenedeg/player/AndroidAudioSessionManager.kt @@ -0,0 +1,9 @@ +package fr.ajaury.gwenedeg.player + +class AndroidAudioSessionManager : AudioSessionManager { + override fun activate() { + } + + override fun deactivate() { + } +} diff --git a/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/player/AudioSessionManager.kt b/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/player/AudioSessionManager.kt new file mode 100644 index 0000000..fcd9819 --- /dev/null +++ b/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/player/AudioSessionManager.kt @@ -0,0 +1,10 @@ +package fr.ajaury.gwenedeg.player + +/** + * An Audio Session will allow to request Audio focus and declare our audio source to the system + */ +interface AudioSessionManager { + fun activate() + + fun deactivate() +} diff --git a/shared/src/iosMain/kotlin/fr/ajaury/gwenedeg/di/Modules.ios.kt b/shared/src/iosMain/kotlin/fr/ajaury/gwenedeg/di/Modules.ios.kt index 2761a97..f2007eb 100644 --- a/shared/src/iosMain/kotlin/fr/ajaury/gwenedeg/di/Modules.ios.kt +++ b/shared/src/iosMain/kotlin/fr/ajaury/gwenedeg/di/Modules.ios.kt @@ -1,6 +1,13 @@ package fr.ajaury.gwenedeg.di +import fr.ajaury.gwenedeg.player.AudioPlayer +import fr.ajaury.gwenedeg.player.AudioSessionManager +import fr.ajaury.gwenedeg.player.IosAudioPlayer +import fr.ajaury.gwenedeg.player.IosAudioSessionManager import org.koin.core.module.Module +import org.koin.dsl.module -actual val platformModule: Module - get() = TODO("Not yet implemented") +actual val platformModule: Module = module { + single { IosAudioPlayer() } + single { IosAudioSessionManager() } +} diff --git a/shared/src/iosMain/kotlin/fr/ajaury/gwenedeg/player/IosAudioPlayer.kt b/shared/src/iosMain/kotlin/fr/ajaury/gwenedeg/player/IosAudioPlayer.kt index 34d4463..a78e565 100644 --- a/shared/src/iosMain/kotlin/fr/ajaury/gwenedeg/player/IosAudioPlayer.kt +++ b/shared/src/iosMain/kotlin/fr/ajaury/gwenedeg/player/IosAudioPlayer.kt @@ -1,23 +1,39 @@ package fr.ajaury.gwenedeg.player +import gwenedeg.shared.generated.resources.Res +import kotlinx.cinterop.ExperimentalForeignApi +import platform.AVFoundation.AVPlayer +import platform.AVFoundation.pause +import platform.AVFoundation.play +import platform.AVFoundation.replaceCurrentItemWithPlayerItem +import platform.Foundation.NSURL + class IosAudioPlayer : AudioPlayer { - override fun play(file: String) { - TODO("Not yet implemented") + private var player: AVPlayer? = null + + @OptIn(ExperimentalForeignApi::class) + override fun load(filePath: String) { + val uri = Res.getUri(filePath) + val nsUrl = NSURL.URLWithString(uri) ?: error("Invalid audio URI: $uri") + + player = AVPlayer(nsUrl) + } + + override fun play() { + player?.play() } override fun pause() { - TODO("Not yet implemented") + player?.pause() } override fun stop() { - TODO("Not yet implemented") + player?.pause() + player?.replaceCurrentItemWithPlayerItem(null) } override fun release() { - TODO("Not yet implemented") - } - - override fun load(file: String) { - TODO("Not yet implemented") + player?.pause() + player = null } } diff --git a/shared/src/iosMain/kotlin/fr/ajaury/gwenedeg/player/IosAudioSessionManager.kt b/shared/src/iosMain/kotlin/fr/ajaury/gwenedeg/player/IosAudioSessionManager.kt new file mode 100644 index 0000000..e962dd0 --- /dev/null +++ b/shared/src/iosMain/kotlin/fr/ajaury/gwenedeg/player/IosAudioSessionManager.kt @@ -0,0 +1,35 @@ +package fr.ajaury.gwenedeg.player + +import kotlinx.cinterop.ExperimentalForeignApi +import platform.AVFAudio.AVAudioSession +import platform.AVFAudio.AVAudioSessionCategoryOptionDuckOthers +import platform.AVFAudio.AVAudioSessionCategoryPlayback +import platform.AVFAudio.AVAudioSessionModeSpokenAudio +import platform.AVFAudio.setActive + +class IosAudioSessionManager : AudioSessionManager { + private val session = AVAudioSession.sharedInstance() + + @OptIn(ExperimentalForeignApi::class) + override fun activate() { + session.setCategory( + category = AVAudioSessionCategoryPlayback, + mode = AVAudioSessionModeSpokenAudio, + options = AVAudioSessionCategoryOptionDuckOthers, + error = null, + ) + + session.setActive( + active = true, + error = null, + ) + } + + @OptIn(ExperimentalForeignApi::class) + override fun deactivate() { + session.setActive( + false, + error = null, + ) + } +}