Skip to content

Commit 4df8d5c

Browse files
committed
音声ハードミュート機能を追加する
1 parent 887f5e1 commit 4df8d5c

File tree

4 files changed

+135
-1
lines changed

4 files changed

+135
-1
lines changed

CHANGES.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@
1919
- [UPDATE] Configuration.simulcastRid を非推奨にする
2020
- 移行先は `Configuration.simulcastRequestRid`
2121
- @zztkm
22+
- [ADD] 音声のハードミュート有効化/無効化機能を追加する
23+
- AudioDeviceModuleWrapper クラスを追加する
24+
- RTCAudioDeviceModule の pauseRecording/resumeRecording を実行するためのラッパークラス
25+
- インスタンスは NativePeerChannelFactory が保持する
26+
- MediaChannel に setAudioHardMute(_:) を追加する
27+
- 内部で NativePeerChannelFactory 経由で AudioDeviceModuleWrapper.setAudioHardMute(_:) を呼び出す
28+
- @t-miya
2229
- [ADD] MediaChannel に libwebrtc の統計情報を取得する `getStats` メソッドを追加する
2330
- @t-miya
2431
- [ADD] RTCAudioTrack から音声データを受け取るためのコールバックプロトコルである RTCAudioTrackSink を追加する
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import Foundation
2+
import WebRTC
3+
4+
/// RTCAudioDeviceModule の録音ポーズ/再開をラップするクラス。
5+
public final class AudioDeviceModuleWrapper {
6+
private let audioDeviceModule: RTCAudioDeviceModule
7+
// ハードミュート処理を直列化するためのキュー
8+
private let queue = DispatchQueue(label: "jp.shiguredo.sora.audio.device.wrapper")
9+
private var isHardMuted: Bool = false
10+
11+
public init(audioDeviceModule: RTCAudioDeviceModule) {
12+
self.audioDeviceModule = audioDeviceModule
13+
}
14+
15+
/// 音声のハードミュート有効化/無効化します
16+
/// RTCAudioDeviceModule の pause/resume と RTCAudioSession.isAudioEnabled での切り替えが必要
17+
/// RTCAudioSession.isAudioEnabled 操作には RTCAudioSession.useManualAudio=true が必要なため
18+
/// useManualAudio を true に変更します
19+
///
20+
/// - Parameter mute: `true` で一時停止、`false` で再開
21+
/// - Returns: 成功した場合は `true`
22+
public func setAudioHardMute(_ mute: Bool) -> Bool {
23+
queue.sync {
24+
if !setAudioHardMuteInternal(mute) {
25+
return false
26+
}
27+
28+
let session = RTCAudioSession.sharedInstance()
29+
session.lockForConfiguration()
30+
session.useManualAudio = true
31+
session.isAudioEnabled = !mute
32+
session.unlockForConfiguration()
33+
34+
Logger.debug(
35+
type: .mediaChannel,
36+
message: "setAudioHardMute via isAudioEnabled=\(!mute)")
37+
38+
isHardMuted = mute
39+
return true
40+
}
41+
}
42+
43+
/// ハードミュート状態を保持している場合にリセットします
44+
/// RTCAudioSession.useManualAudio/isAudioEnabled もデフォルトの false にリセットします
45+
///
46+
/// NativePeerChannelFactory、RTCAudioSession はシングルトンでのオブジェクト運用により
47+
/// ハードミュート状態で切断後に再接続時する際、前回の状態が残ってしまうため
48+
public func resetHardMuteIfNeeded() {
49+
guard isHardMuted else {
50+
return
51+
}
52+
queue.sync {
53+
setAudioHardMuteInternal(false)
54+
let session = RTCAudioSession.sharedInstance()
55+
session.lockForConfiguration()
56+
session.useManualAudio = false
57+
session.isAudioEnabled = false
58+
session.unlockForConfiguration()
59+
isHardMuted = false
60+
Logger.debug(
61+
type: .mediaChannel,
62+
message: "Reset audio hard mute and useManualAudio/isAudioEnabled via RTCAudioSession")
63+
}
64+
}
65+
66+
private func setAudioHardMuteInternal(_ mute: Bool) -> Bool {
67+
let result = mute ? pauseRecordingInternal() : resumeRecordingInternal()
68+
if result == 0 {
69+
Logger.debug(
70+
type: .mediaChannel,
71+
message: "setAudioHardMute via RTCAudioDeviceModule mute=\(mute)")
72+
return true
73+
} else {
74+
Logger.debug(
75+
type: .mediaChannel,
76+
message: "RTCAudioDeviceModule mute=\(mute) failed")
77+
return false
78+
}
79+
}
80+
81+
private func pauseRecordingInternal() -> Int32 {
82+
Int32(audioDeviceModule.pauseRecording())
83+
}
84+
85+
private func resumeRecordingInternal() -> Int32 {
86+
Int32(audioDeviceModule.resumeRecording())
87+
}
88+
}

Sora/MediaChannel.swift

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,16 @@ public final class MediaChannel {
279279
connectionStartTime = nil
280280
connectionTask.peerChannel = peerChannel
281281

282+
// NativePeerChannelFactory がシングルトンのため
283+
// 前回切断時に音声ハードミュート状態の場合は状態が残ってしまうので
284+
// 音声ハードミュートのリセットを行います
285+
if configuration.audioEnabled {
286+
Logger.debug(
287+
type: .mediaChannel,
288+
message: "reset audio hard mute before connecting")
289+
_ = NativePeerChannelFactory.default.audioDeviceModuleWrapper.resetHardMuteIfNeeded()
290+
}
291+
282292
signalingChannel.internalHandlers.onDisconnect = { [weak self] error, reason in
283293
guard let weakSelf = self else {
284294
return
@@ -521,6 +531,27 @@ public final class MediaChannel {
521531
return result
522532
? nil : SoraError.messagingError(reason: "failed to send message: label => \(label)")
523533
}
534+
535+
/// MediaChannel の接続中にマイクをハードミュート有効化/無効化します
536+
/// - Parameter mute: `true` で有効化、`false` で無効化
537+
/// - Returns: 成功した場合は `true`、接続状態などの理由で処理しなかった場合は `false`
538+
public func setAudioHardMute(_ mute: Bool) -> Bool {
539+
guard state == .connected else {
540+
Logger.debug(
541+
type: .mediaChannel,
542+
message: "setAudioHardMute failed, cause MediaChannel is not connected (state: \(state))")
543+
return false
544+
}
545+
546+
guard configuration.audioEnabled else {
547+
Logger.debug(
548+
type: .mediaChannel,
549+
message: "setAudioHardMute skipped because audioEnabled is false")
550+
return false
551+
}
552+
553+
return NativePeerChannelFactory.default.audioDeviceModuleWrapper.setAudioHardMute(mute)
554+
}
524555
}
525556

526557
extension MediaChannel: CustomStringConvertible {

Sora/NativePeerChannelFactory.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,26 @@ class WrapperVideoEncoderFactory: NSObject, RTCVideoEncoderFactory {
3333
class NativePeerChannelFactory {
3434
static var `default` = NativePeerChannelFactory()
3535

36+
let audioDeviceModule: RTCAudioDeviceModule
37+
/// 録音ポーズ/再開制御用に保持する ADM ラッパー
38+
let audioDeviceModuleWrapper: AudioDeviceModuleWrapper
39+
3640
var nativeFactory: RTCPeerConnectionFactory
3741

3842
init() {
3943
Logger.debug(type: .peerChannel, message: "create native peer channel factory")
4044

45+
audioDeviceModule = RTCAudioDeviceModule()
46+
audioDeviceModuleWrapper = AudioDeviceModuleWrapper(audioDeviceModule: audioDeviceModule)
47+
4148
// 映像コーデックのエンコーダーとデコーダーを用意する
4249
let encoder = WrapperVideoEncoderFactory.shared
4350
let decoder = RTCDefaultVideoDecoderFactory()
4451
nativeFactory =
4552
RTCPeerConnectionFactory(
4653
encoderFactory: encoder,
47-
decoderFactory: decoder)
54+
decoderFactory: decoder,
55+
audioDeviceModule: audioDeviceModule)
4856

4957
for info in encoder.supportedCodecs() {
5058
Logger.debug(

0 commit comments

Comments
 (0)