@@ -5,18 +5,19 @@ import Foundation
55actor VideoHardMuteActor {
66 // 処理実行中フラグ
77 private var isProcessing = false
8- // カメラ操作のためのキャプチャラー
9- private var capturer : CameraVideoCapturer ?
8+ // ハードミュートで stop したキャプチャを解除時に restart するための保持キャプチャラー
9+ private var storedCapturer : CameraVideoCapturer ?
1010
1111 /// ハードミュートを有効化/無効化します
1212 ///
1313 /// - Parameters:
1414 /// - mute: `true` で有効化、`false` で無効化
1515 /// - senderStream: 送信ストリーム
16+ /// - cameraSettings: カメラ設定
1617 /// - Throws:
17- /// - 既に処理実行中、またはカメラキャプチャラーが無効な場合は `SoraError.mediaChannelError`
18+ /// - 既に処理実行中の場合は `SoraError.mediaChannelError`
1819 /// - カメラ操作の失敗時は `SoraError.cameraError`
19- func setMute( mute: Bool , senderStream: MediaStream ) async throws {
20+ func setMute( mute: Bool , senderStream: MediaStream , cameraSettings : CameraSettings ) async throws {
2021 guard !isProcessing else {
2122 throw SoraError . mediaChannelError ( reason: " video hard mute operation is in progress " )
2223 }
@@ -26,25 +27,25 @@ actor VideoHardMuteActor {
2627 // ミュートを有効化します
2728 if mute {
2829 guard let currentCapturer = await currentCameraVideoCapturer ( ) else {
29- // 既にハードミュート済み(再開用キャプチャラーを保持)なら、冪等として何もしません
30- if capturer != nil { return }
31- throw SoraError . mediaChannelError ( reason: " CameraVideoCapturer is unavailable " )
30+ // キャプチャ未起動の場合は停止対象がないため、冪等として成功扱いにします
31+ return
3232 }
3333 try await stopCameraVideoCapture ( currentCapturer)
3434 // ミュート無効化する際にキャプチャラーを使用するため保持しておきます
35- capturer = currentCapturer
35+ storedCapturer = currentCapturer
3636 return
3737 }
3838
3939 // ミュートを無効化します
4040 // 現在のキャプチャラーが取得できる場合は既に再開済みとして成功扱いにします
4141 let currentCapturer = await currentCameraVideoCapturer ( )
4242 if currentCapturer != nil { return }
43- // 前回停止時のキャプチャラーが保持できていない場合エラー
44- guard let stored = capturer else {
45- throw SoraError . mediaChannelError ( reason: " CameraVideoCapturer is unavailable " )
43+ // 前回停止時のキャプチャラーが保持できていれば restart、なければ start します
44+ if let capturerForRestart = storedCapturer {
45+ try await restartCameraVideoCapture ( capturerForRestart, senderStream: senderStream)
46+ return
4647 }
47- try await restartCameraVideoCapture ( stored , senderStream: senderStream)
48+ try await startCameraVideoCapture ( cameraSettings : cameraSettings , senderStream: senderStream)
4849 }
4950
5051 // 現在のカメラキャプチャラーを取得します
@@ -95,4 +96,76 @@ actor VideoHardMuteActor {
9596 }
9697 }
9798 }
99+
100+ // カメラキャプチャを開始します
101+ private func startCameraVideoCapture(
102+ cameraSettings: CameraSettings ,
103+ senderStream: MediaStream
104+ ) async throws {
105+ // libwebrtc のカメラ用キュー(SoraDispatcher)を利用して実行します
106+ try await withCheckedThrowingContinuation { ( continuation: CheckedContinuation < Void , Error > ) in
107+ SoraDispatcher . async ( on: . camera) {
108+ // 接続時設定の position に対応した CameraVideoCapturer を取得します。
109+ // `.front` / `.back` を優先して利用し、静的プロパティ経由で参照される状態と齟齬が出ないようにします。
110+ let capturer : CameraVideoCapturer
111+ switch cameraSettings. position {
112+ case . front:
113+ guard let front = CameraVideoCapturer . front else {
114+ continuation. resume (
115+ throwing: SoraError . cameraError ( reason: " front camera is not found " ) )
116+ return
117+ }
118+ capturer = front
119+ case . back:
120+ guard let back = CameraVideoCapturer . back else {
121+ continuation. resume ( throwing: SoraError . cameraError ( reason: " back camera is not found " ) )
122+ return
123+ }
124+ capturer = back
125+ case . unspecified:
126+ continuation. resume (
127+ throwing: SoraError . cameraError (
128+ reason: " CameraSettings.position should not be .unspecified "
129+ )
130+ )
131+ return
132+ @unknown default :
133+ guard let device = CameraVideoCapturer . device ( for: cameraSettings. position) else {
134+ continuation. resume (
135+ throwing: SoraError . cameraError ( reason: " camera device is not found for position " )
136+ )
137+ return
138+ }
139+ capturer = CameraVideoCapturer ( device: device)
140+ }
141+
142+ guard
143+ // 接続時設定に基づいてカメラの解像度、フレームレートを指定します
144+ let format = CameraVideoCapturer . format (
145+ width: cameraSettings. resolution. width,
146+ height: cameraSettings. resolution. height,
147+ for: capturer. device,
148+ frameRate: cameraSettings. frameRate) ,
149+ let frameRate = CameraVideoCapturer . maxFrameRate ( cameraSettings. frameRate, for: format)
150+ else {
151+ continuation. resume (
152+ throwing: SoraError . cameraError ( reason: " failed to resolve camera settings " ) )
153+ return
154+ }
155+
156+ // カメラキャプチャを開始します
157+ // CameraVideoCapturer.start はコールバック形式です
158+ capturer. stream = senderStream
159+ // start 完了まで capturer を確実に生存させるためにクロージャ側でも保持します。
160+ // start 成功時は CameraVideoCapturer.current がセットされ、以後はそちらが保持します。
161+ capturer. start ( format: format, frameRate: frameRate) { [ capturer] error in
162+ if let error {
163+ continuation. resume ( throwing: error)
164+ } else {
165+ continuation. resume ( returning: ( ) )
166+ }
167+ }
168+ }
169+ }
170+ }
98171}
0 commit comments