Skip to content

Commit d437a43

Browse files
committed
VideoHardMuteSerialQueue のコメント追加
1 parent 6424d53 commit d437a43

File tree

3 files changed

+62
-42
lines changed

3 files changed

+62
-42
lines changed

Sora/MediaChannel.swift

Lines changed: 26 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ public final class MediaChannel {
234234
private let manager: Sora
235235

236236
// 映像ハードミュートの同時呼び出しを防ぐためのキューです
237-
// 同時に呼び出された場合はエラーになります
237+
// 同時に呼び出された場合は `SoraError.mediaChannelError` となります
238238
private let videoHardMuteSerialQueue = VideoHardMuteSerialQueue()
239239

240240
// MARK: - インスタンスの生成
@@ -645,27 +645,28 @@ public final class MediaChannel {
645645
}
646646

647647
/// MediaChannel の接続中にマイクをハードミュート有効化/無効化します
648-
/// 前提条件として、
649-
/// - 接続時設定で音声が有効になっている
650-
/// - 接続時設定でロールが sendonly または sendrecv である
651648
///
652649
/// - Parameter mute: `true` で有効化、`false` で無効化
653650
/// - Returns: 成功した場合は `nil`、失敗した場合は `Error` を返します
654651
/// - Throws: エラー時は `SoraError.mediaChannelError` がスローされます
655652
public func setAudioHardMute(_ mute: Bool) -> Error? {
653+
// 接続中か
656654
guard state == .connected else {
657655
return SoraError.mediaChannelError(
658656
reason: "MediaChannel is not connected (state: \(state))")
659657
}
660658

659+
// 接続設定で音声が有効になっているか
661660
guard configuration.audioEnabled else {
662661
return SoraError.mediaChannelError(reason: "audioEnabled is false")
663662
}
664663

664+
// 接続設定で配信側ロールになっているか
665665
guard configuration.isSender else {
666666
return SoraError.mediaChannelError(reason: "role is not sender")
667667
}
668668

669+
// 音声ハードミュートを切り替えます
669670
if !NativePeerChannelFactory.default.audioDeviceModuleWrapper.setAudioHardMute(mute) {
670671
return SoraError.mediaChannelError(
671672
reason: "AudioDeviceModuleWrapper::setAudioHardMute failed")
@@ -675,127 +676,122 @@ public final class MediaChannel {
675676
}
676677

677678
/// MediaChannel の接続中にマイクをソフトミュート有効化 / 無効化します
678-
/// 前提条件として、
679-
/// - 接続時設定で音声が有効になっている
680-
/// - 接続時設定でロールが sendonly または sendrecv である
681-
/// - 配信ストリームが存在するかつローカル音声トラックが存在する
682679
///
683680
/// - Parameter mute: `true` で有効化、`false` で無効化
684681
/// - Returns: 成功した場合は `nil`、失敗した場合は `Error` を返します
685682
/// - Throws: エラー時は `SoraError.mediaChannelError` がスローされます
686683
public func setAudioSoftMute(_ mute: Bool) -> Error? {
684+
// 接続中か
687685
guard state == .connected else {
688686
return SoraError.mediaChannelError(
689687
reason: "MediaChannel is not connected (state: \(state))")
690688
}
691689

690+
// 接続設定で音声が有効になっているか
692691
guard configuration.audioEnabled else {
693692
return SoraError.mediaChannelError(reason: "audioEnabled is false")
694693
}
695694

695+
// 接続設定で配信側ロールになっているか
696696
guard configuration.isSender else {
697697
return SoraError.mediaChannelError(reason: "role is not sender")
698698
}
699699

700+
// 送信ストリームが有効か
700701
guard let senderStream else {
701702
return SoraError.mediaChannelError(reason: "senderStream is unavailable")
702703
}
703704

705+
// ローカル音声トラックが存在するか
704706
guard senderStream.hasAudioTrack else {
705707
return SoraError.mediaChannelError(reason: "senderStream has no AudioTrack")
706708
}
707709

710+
// ローカル音声トラックの有効/無効を切り替えます
708711
senderStream.audioEnabled = !mute
709712
Logger.debug(type: .mediaChannel, message: "setAudioSoftMute mute=\(mute)")
710713
return nil
711714
}
712715

713716
/// MediaChannel の接続中に映像をソフトミュート有効化 / 無効化します
714-
/// 前提条件として、
715-
/// - 接続時設定で映像が有効になっている
716-
/// - 接続時設定でロールが sendonly または sendrecv である
717-
/// - 配信ストリームが存在するかつローカル映像トラックが存在する
718717
///
719718
/// - Parameter mute: `true` で有効化、`false` で無効化
720719
/// - Returns: 成功した場合は `nil`、失敗した場合は `Error` を返します
721720
/// - Throws: エラー時は `SoraError.mediaChannelError` がスローされます
722721
public func setVideoSoftMute(_ mute: Bool) -> Error? {
722+
// 接続中か
723723
guard state == .connected else {
724724
return SoraError.mediaChannelError(
725725
reason: "MediaChannel is not connected (state: \(state))")
726726
}
727727

728+
// 接続設定で映像が有効になっているか
728729
guard configuration.videoEnabled else {
729730
return SoraError.mediaChannelError(reason: "videoEnabled is false")
730731
}
731732

733+
// 接続設定で配信側ロールになっているか
732734
guard configuration.isSender else {
733735
return SoraError.mediaChannelError(reason: "role is not sender")
734736
}
735737

738+
// 送信ストリームが有効か
736739
guard let senderStream else {
737740
return SoraError.mediaChannelError(reason: "senderStream is unavailable")
738741
}
739742

743+
// ローカル映像トラックが存在するか
740744
guard senderStream.hasVideoTrack else {
741745
return SoraError.mediaChannelError(reason: "senderStream has no VideoTrack")
742746
}
743747

748+
// ローカル音声トラックの有効/無効を切り替えます
744749
senderStream.videoEnabled = !mute
745750
Logger.debug(type: .mediaChannel, message: "setVideoSoftMute mute=\(mute)")
746751
return nil
747752
}
748753

749754
/// MediaChannel の接続中に映像をハードミュート有効化 / 無効化します
750755
///
751-
/// ハードミュートは、カメラ入力を停止 / 再開します。
752-
/// `Configuration.cameraSettings.isEnabled == true` の場合のみ有効です。
753756
/// 内部でシリアルキューにより、操作を排他実行します。
754-
/// 同時に呼び出された場合はエラーになります。
757+
/// 同時に呼び出された場合はキュー側で `SoraError.mediaChannelError` がスローされます
755758
///
756-
/// 前提条件として、
757-
/// - 接続時設定で映像が有効になっている
758-
/// - 接続時設定でロールが sendonly または sendrecv である
759-
/// - 配信ストリームが存在するかつローカル映像トラックが存在する
760-
///
761-
/// 映像ハードミュートは、黒塗りフレーム状態で停止させるため映像ソフトミュート用処理を併用します。
762-
/// そのため、以下の処理を内部で実行します。
763-
///
764-
/// - `mute == true`: 映像ソフトミュートを有効化してから、カメラ入力を停止します
765-
/// - `mute == false`: カメラ入力を再開してから、映像ソフトミュートを無効化します。
766-
/// ハードミュート前のソフトミュートの状態に関わらず無効化します
759+
/// 映像ハードミュートは、黒塗りフレーム状態で停止させるため映像ソフトミュート用処理を併用します
767760
///
768761
/// - Parameter mute: `true` で有効化、`false` で無効化
769762
/// - Throws: エラー時は `SoraError.mediaChannelError` がスローされます
770763
public func setVideoHardMute(_ mute: Bool) async throws {
764+
// 接続中か
771765
guard state == .connected else {
772766
throw SoraError.mediaChannelError(reason: "MediaChannel is not connected (state: \(state))")
773767
}
774768

769+
// 接続設定で映像が有効になっているか
775770
guard configuration.videoEnabled else {
776771
throw SoraError.mediaChannelError(reason: "videoEnabled is false")
777772
}
778773

779-
guard configuration.cameraSettings.isEnabled else {
780-
throw SoraError.mediaChannelError(reason: "cameraSettings.isEnabled is false")
781-
}
782-
774+
// 接続設定で配信側ロールになっているか
783775
guard configuration.isSender else {
784776
throw SoraError.mediaChannelError(reason: "role is not sender")
785777
}
786778

779+
// 送信ストリームが有効か
787780
guard let senderStream else {
788781
throw SoraError.mediaChannelError(reason: "senderStream is unavailable")
789782
}
790783

784+
// ローカル映像トラックが存在するか
791785
guard senderStream.hasVideoTrack else {
792786
throw SoraError.mediaChannelError(reason: "senderStream has no VideoTrack")
793787
}
794788

795789
if mute {
790+
// 黒塗りフレーム送出 -> ハードミュート有効化の順になるようにします
796791
senderStream.videoEnabled = false
797792
try await videoHardMuteSerialQueue.set(mute: true, senderStream: senderStream)
798793
} else {
794+
// ハードミュート無効化 -> 黒塗りフレーム送出解除の順になるようにします
799795
try await videoHardMuteSerialQueue.set(mute: false, senderStream: senderStream)
800796
senderStream.videoEnabled = true
801797
}

Sora/MediaStream.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,23 +48,23 @@ public protocol MediaStream: AnyObject {
4848
// MARK: - 映像と音声の可否
4949

5050
/// 映像の可否。
51-
/// ``false`` をセットすると、サーバーへの映像の送受信を停止します。
52-
/// ``true`` をセットすると送受信を再開します。
51+
/// `false` をセットすると、サーバーへの映像の送受信を停止します。
52+
/// `true` をセットすると送受信を再開します。
5353
var videoEnabled: Bool { get set }
5454

5555
/// 音声の可否。
56-
/// ``false`` をセットすると、サーバーへの音声の送受信を停止します。
57-
/// ``true`` をセットすると送受信を再開します。
56+
/// `false` をセットすると、サーバーへの音声の送受信を停止します。
57+
/// `true` をセットすると送受信を再開します。
5858
///
5959
/// サーバーへの送受信を停止しても、マイクはミュートされませんので注意してください。
6060
var audioEnabled: Bool { get set }
6161

62-
/// 映像トラックを保持している場合は ``true`` を返します。
62+
/// 映像トラックを保持している場合は `true` を返します。
6363
///
6464
/// SDK 内部で利用する判定用のプロパティです。
6565
var hasVideoTrack: Bool { get }
6666

67-
/// 音声トラックを保持している場合は ``true`` を返します。
67+
/// 音声トラックを保持している場合は `true` を返します。
6868
///
6969
/// SDK 内部で利用する判定用のプロパティです。
7070
var hasAudioTrack: Bool { get }

Sora/VideoMute.swift

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,38 @@
11
import Foundation
22

33
// 映像ハードミュートの同時呼び出しを防ぐためのシリアルキュークラスです
4-
// 同時に呼び出された場合はエラーになります
4+
// MediaChannel.setVideoHardMute(_:) 内での使用を想定しています
5+
//
6+
// 既に処理が実行中または CameraVideoCapturer が無効な場合は `SoraError.mediaChannelError` がスローされます
57
final class VideoHardMuteSerialQueue {
68
private let queue = DispatchQueue(label: "jp.shiguredo.sora.video.hardmute")
79

10+
// 同時実行を防ぐための処理実行中フラグ
811
private var isProcessing = false
912
private var capturer: CameraVideoCapturer?
1013

14+
// queue 上で同時実行を防ぐ排他処理を行い、
15+
// libwebrtc のカメラ用キュー(SoraDispatcher)でカメラ操作を行います
1116
func set(mute: Bool, senderStream: MediaStream) async throws {
1217
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in
1318
queue.async { [self] in
19+
// 同時に呼び出された場合はエラーにします
1420
guard !isProcessing else {
1521
continuation.resume(
1622
throwing: SoraError.mediaChannelError(
1723
reason: "video hard mute operation is in progress"))
1824
return
1925
}
2026

27+
// 同時実行を防ぐための処理中フラグです
2128
isProcessing = true
22-
let cachedCapturer = capturer
29+
// CameraVideoCapturer.current は stop 実行時に nil になるため
30+
// restart 用にキャプチャラーを退避します
31+
let storedCapturer = capturer
2332

33+
// キューの完了処理です。処理中フラグの無効化と continuation を完了します
34+
// 状態更新を同一キューで直列化するため queue.async で実行します
35+
// continuation.resume も同一キュー上で呼ぶようにしています
2436
func finish(_ error: Error?, update: ((VideoHardMuteSerialQueue) -> Void)? = nil) {
2537
queue.async { [self] in
2638
update?(self)
@@ -33,37 +45,49 @@ final class VideoHardMuteSerialQueue {
3345
}
3446
}
3547

48+
// libwebrtc のカメラ用キューでカメラ操作を非同期実行します
3649
SoraDispatcher.async(on: .camera) {
3750
if mute {
51+
// ミュート有効化
52+
// 起動中のカメラがあれば停止します
3853
guard let current = CameraVideoCapturer.current else {
39-
if cachedCapturer != nil {
54+
// 前回のハードミュートでキャプチャラーを退避している場合は冪等として成功扱いにします
55+
if storedCapturer != nil {
4056
finish(nil)
4157
} else {
58+
// カメラが起動しておらず再開用キャプチャラーも無い場合は失敗にします
4259
finish(SoraError.mediaChannelError(reason: "CameraVideoCapturer is unavailable"))
4360
}
4461
return
4562
}
4663

64+
// CameraVideoCapturer.stop() により映像キャプチャを停止します
65+
// CameraVideoCapturer.stop() は実行結果を Error? コールバックで返します
4766
current.stop { error in
4867
finish(error) { serialQueue in
68+
// キャプチャラーは再開用として退避します
4969
serialQueue.capturer = current
5070
}
5171
}
5272
return
5373
}
5474

75+
// 既にカメラが起動中であれば何もしません
5576
if CameraVideoCapturer.current != nil {
5677
finish(nil)
5778
return
5879
}
5980

60-
guard let cachedCapturer else {
81+
// 退避済みのキャプチャラーの存在チェック
82+
guard let storedCapturer else {
6183
finish(SoraError.mediaChannelError(reason: "CameraVideoCapturer is unavailable"))
6284
return
6385
}
6486

65-
cachedCapturer.stream = senderStream
66-
cachedCapturer.restart { error in
87+
// マルチストリームの場合、停止時と現在の送信ストリームが異なることがあるので再設定します
88+
storedCapturer.stream = senderStream
89+
// CameraVideoCapturer.restart() により映像キャプチャを再開
90+
storedCapturer.restart { error in
6791
finish(error)
6892
}
6993
}

0 commit comments

Comments
 (0)