diff --git a/LiveKitExample-dev.xcworkspace/xcshareddata/swiftpm/Package.resolved b/LiveKitExample-dev.xcworkspace/xcshareddata/swiftpm/Package.resolved index b104952..35d9be9 100644 --- a/LiveKitExample-dev.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/LiveKitExample-dev.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "7211f5ec68d50979bdb0938d0e60396e453aaab524b20b84c083042744b32f68", + "originHash" : "5a18807be8c0dcfdfa97ef3aaaa85f1f206e48cba749de53fb56c3a304dbe5ec", "pins" : [ { "identity" : "keychainaccess", @@ -69,8 +69,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/livekit/webrtc-xcframework.git", "state" : { - "revision" : "0aa6a5ea4031d492d0493e3e4d4fbe08b5a0df78", - "version" : "137.7151.12" + "revision" : "14a945724d81cfe5a8ce331e4af15d205d2afb85", + "version" : "144.7559.1" } } ], diff --git a/Multiplatform/Controllers/AppContext.swift b/Multiplatform/Controllers/AppContext.swift index 6d10343..8136af0 100644 --- a/Multiplatform/Controllers/AppContext.swift +++ b/Multiplatform/Controllers/AppContext.swift @@ -24,8 +24,11 @@ import SwiftUI final class AppContext: NSObject, ObservableObject { private let store: ValueStore - private var audioPlayer: AVAudioPlayer? @Published var isSampleAudioPlaying: Bool = false + @Published var isSampleAudioPrepared: Bool = false + @Published var playbackMode: PlaybackOptions.Mode = .concurrent + @Published var playbackLoop: Bool = false + @Published var playbackDestination: PlaybackOptions.Destination = .localAndRemote private var mutedSpeechToastHideTask: Task? @Published var videoViewVisible: Bool = true { @@ -115,6 +118,10 @@ final class AppContext: NSObject, ObservableObject { didSet { AudioManager.shared.mixer.appVolume = appVolume } } + @Published var soundPlayerVolume: Float = 1.0 { + didSet { AudioManager.shared.mixer.soundPlayerVolume = soundPlayerVolume } + } + @Published var isRecordingAlwaysPreparedMode: Bool = false { didSet { Task { @@ -245,39 +252,39 @@ private extension AppContext { // MARK: - AudioClips extension AppContext { - func playSampleAudio() { + func prepareSampleAudio() { + guard let url = Bundle.main.url(forResource: "livekit_clip01", withExtension: "m4a") else { + print("Audio file not found") + return + } do { - if let prevPlayer = audioPlayer { - prevPlayer.stop() - } - - guard let url = Bundle.main.url(forResource: "livekit_clip01", withExtension: "m4a") else { - print("Audio file not found") - return - } + try SoundPlayer.shared.prepare(url: url, withId: "sample01") + isSampleAudioPrepared = true + } catch { + print("Failed to prepare sample audio clip: \(error)") + } + } - let player = try AVAudioPlayer(contentsOf: url) - player.delegate = self - player.play() - audioPlayer = player + func playSampleAudio() { + let options = PlaybackOptions(mode: playbackMode, + loop: playbackLoop, + destination: playbackDestination) + do { + try SoundPlayer.shared.play(id: "sample01", options: options) isSampleAudioPlaying = true } catch { - print("Failed to sample audio clip") + print("Failed to play sample audio clip: \(error)") } } func stopSampleAudio() { - if let prevPlayer = audioPlayer { - prevPlayer.stop() - } - - audioPlayer = nil + SoundPlayer.shared.stop(id: "sample01") isSampleAudioPlaying = false } -} -extension AppContext: @MainActor AVAudioPlayerDelegate { - func audioPlayerDidFinishPlaying(_: AVAudioPlayer, successfully _: Bool) { + func releaseSampleAudio() { + SoundPlayer.shared.release(id: "sample01") + isSampleAudioPrepared = false isSampleAudioPlaying = false } } diff --git a/Multiplatform/Views/AudioControlsPanel.swift b/Multiplatform/Views/AudioControlsPanel.swift index 3606891..776af2d 100644 --- a/Multiplatform/Views/AudioControlsPanel.swift +++ b/Multiplatform/Views/AudioControlsPanel.swift @@ -54,6 +54,10 @@ struct AudioControlsPanel: View { Text("App") Slider(value: $appCtx.appVolume, in: 0.0 ... 1.0) } + HStack { + Text("Sound player") + Slider(value: $appCtx.soundPlayerVolume, in: 0.0 ... 1.0) + } } Section(header: Text("Audio Devices")) { @@ -119,20 +123,42 @@ struct AudioControlsPanel: View { } } - Section(header: Text("Sample audio clip")) { - if appCtx.isSampleAudioPlaying { - Button { - appCtx.stopSampleAudio() - } label: { - Text("Stop") + Section(header: Text("Sound Player")) { + Picker("Mode", selection: $appCtx.playbackMode) { + Text("Concurrent").tag(PlaybackOptions.Mode.concurrent) + Text("Replace").tag(PlaybackOptions.Mode.replace) + } + + Picker("Destination", selection: $appCtx.playbackDestination) { + Text("Local").tag(PlaybackOptions.Destination.local) + Text("Remote").tag(PlaybackOptions.Destination.remote) + Text("Local + Remote").tag(PlaybackOptions.Destination.localAndRemote) + } + + Toggle("Loop", isOn: $appCtx.playbackLoop) + + HStack { + Button("Prepare") { + appCtx.prepareSampleAudio() } - } else { - Button { + .disabled(appCtx.isSampleAudioPrepared) + + Button("Play") { appCtx.playSampleAudio() - } label: { - Text("Play") } + .disabled(!appCtx.isSampleAudioPrepared) + + Button("Stop") { + appCtx.stopSampleAudio() + } + .disabled(!appCtx.isSampleAudioPlaying) + + Button("Release") { + appCtx.releaseSampleAudio() + } + .disabled(!appCtx.isSampleAudioPrepared) } + .buttonStyle(.bordered) } Section(header: Text("Audio Engine Availability")) {