# ミュート機能

アプリを利用している iOS 端末からの音声および映像の配信をミュートする機能です。

## 用語

### インジケーター

iOS 14 以降では、アプリがマイクやカメラを使用中、画面の最上部ステータスバーにオレンジ色の点 (または四角) や緑色の点が表示されます。

このインジケーターはマイクまたはカメラの片方のみが使用中の場合でも表示され続けます。

インジケーターについては Apple サポートページ
[](https://support.apple.com/ja-jp/108331)
をご確認ください。

### ソフトミュート

インジケーターが点灯したままの音声・映像ミュートです。マイクデバイスおよびカメラデバイスは使用中の状態となっています。
音声の場合は無音フレーム、映像の場合は黒塗りフレームを送出し続けている状態です。

### ハードミュート

インジケーターが消灯する音声・映像ミュートです。マイクデバイスおよびカメラデバイスは使用されていない状態であり、
音声および映像はフレーム送出自体がされない状態です。

## 音声のハードミュート利用について

iOS が提供する音声モジュールではマイクによる入力とスピーカーによる出力がセットで管理されています。
音声ハードミュートの切り替えにはこの音声モジュール自体の再起動を伴うため音声出力が一瞬途切れることがあります。
また、内部エラーが発生した際には音声に関する動作が不安定になる可能性があります。
音声ハードミュート機能の実運用については事前に十分な動作検証を行ってください。

## 映像のハードミュート利用について

映像については特別な理由がない限りはハードミュートを利用してください。

映像ハードミュート有効化時はカメラインジケーターが消灯します。
これはカメラデバイスからのデータ送信が完全に停止されたことを意味し、
ユーザーに対して物理的なデバイスアクセスが行われていないことを確実に伝えることができます。
プライバシー保護の観点から、最も安全な選択肢です。

## 音声のハードミュート


### 音声のハードミュート有効化

Sora iOS SDK で音声のハードミュートを有効にするには `MediaChannel.setAudioHardMute(Bool enable)` を利用します。
このメソッドの引数に `true` を指定することで、マイクデバイスの入力を無効にします。
このタイミングで iOS 端末のマイクインジケーターが消灯します。

また、音声パケットは一切送出されない状態となります。

### Sora 接続時にマイクインジケーターを点灯させないで開始する

Sora への接続設定 `Configuration.initialMicrophoneEnabled` に `false` を指定することで
接続確立時にマイク入力無効状態で開始します。
このとき、iOS 端末のマイクインジケーターは点灯せず、音声パケットは送出されません (音声ハードミュート相当)。

`Configuration.initialMicrophoneEnabled` のデフォルト値は `true` です。

音声が送出されるようにするには `MediaChannel.setAudioHardMute(false)` を実行します。

### 音声のハードミュート無効化

[音声のハードミュート有効化](mute.html#19481d) で有効化したハードミュートを無効にするには
`MediaChannel.setAudioHardMute(Bool enable)` を利用します。
このメソッドの引数に `false` を指定することで、マイクデバイスの入力を再開します。
このタイミングで iOS 端末のマイクインジケーターが点灯します。

また、音声パケットの送出も再開されます。

### 音声のハードミュート機能実装サンプル

```swift
// setAudioHardMute を使った音声ハードミュートの実行例です。
// 接続や UI 部分のうち、ハードミュート処理に不要な部分は省略しています。

import Sora
import UIKit

final class AudioHardMuteViewController: UIViewController {
  // 接続完了後に設定される MediaChannel です。
  var mediaChannel: MediaChannel?

  // マイクのハードミュート状態です。
  private var isMicHardMuted = false

  // マイクのハードミュートボタンの UI 部品です。
  @IBOutlet weak var micHardMuteButton: UIBarButtonItem?

  // マイクのハードミュートをトグルします。ボタンにアクションとして紐付けられている想定です。
  @IBAction func onMicHardMuteButton(_ _: UIBarButtonItem) {
    // 接続していない場合や音声アップストリームがない場合は何もしません。
    guard let mediaChannel = mediaChannel, mediaChannel.senderStream?.hasAudioTrack == true else {
      return
    }

    let nextMuted = !isMicHardMuted
    // ハードミュートを有効化します。
    if let error = mediaChannel.setAudioHardMute(nextMuted) {
      // エラー時のログ出力等
      _ = error
    } else {
      isMicHardMuted = nextMuted
      updateMicButton(isMuted: nextMuted)
    }
  }

  // ミュート状態によってボタンの表示を更新します。
  private func updateMicButton(isMuted: Bool) {
    micHardMuteButton?.image = UIImage(systemName: isMuted ? "mic.slash" : "mic")
  }
}
```

> **ヒント**
>
> アプリの実装例については [sora-ios-sdk-samples](https://github.com/shiguredo/sora-ios-sdk-samples) をご確認ください。




## 音声のソフトミュート

通信処理によらないクライアントレイヤーの音声ミュートです。


### 音声のソフトミュート有効化

Sora iOS SDK で音声のソフトミュートを有効にするには `MediaChannel.setAudioSoftMute(Bool enable)` を利用します。
このメソッドの引数に `true` を指定することで、音声トラックを無効にします。
このときマイクデバイス由来の音声は配信されず、代わりに無音のフレーム（デジタルサイレンスパケット）を送出する状態となります。
ただしマイクデバイスの入力は有効状態のままのため、iOS 端末のマイクインジケーターは点灯したままになります。

### 音声のソフトミュート無効化

[音声のソフトミュート有効化](mute.html#83509c) で有効化したソフトミュートを無効にするには
`MediaChannel.setAudioSoftMute(Bool enable)` を利用します。
このメソッドの引数に `false` を指定することで音声トラックが有効になり、マイクデバイスの音声が配信されるようになります。

### 音声のソフトミュート機能実装サンプル

```swift
// setAudioSoftMute を使った音声ソフトミュートを行う例です。
// 接続や UI のうちソフトミュートに不要な部分は省略しています。

import Sora
import UIKit

final class AudioSoftMuteViewController: UIViewController {
  // 接続完了後に設定される MediaChannel です。
  var mediaChannel: MediaChannel?

  // マイクのソフトミュート状態を保持します。
  private var isMicSoftMuted = false

  // マイクのソフトミュートボタンです。
  @IBOutlet weak var micSoftMuteButton: UIBarButtonItem?

  // マイクのソフトミュートをトグルします。ボタンにアクションとして紐付けてください。
  @IBAction func onMicSoftMuteButton(_ _: UIBarButtonItem) {
    // 接続していない場合や音声アップストリームがない場合は何もしません。
    guard let mediaChannel = mediaChannel, mediaChannel.senderStream?.hasAudioTrack == true else {
      return
    }

    let nextMuted = !isMicSoftMuted
    // ソフトミュートします。
    if let error = mediaChannel.setAudioSoftMute(nextMuted) {
      // エラー時のログ出力等
      _ = error
    } else {
      isMicSoftMuted = nextMuted
      updateMicButton(isMuted: nextMuted)
    }
  }

  private func updateMicButton(isMuted: Bool) {
    micSoftMuteButton?.image = UIImage(systemName: isMuted ? "mic.slash" : "mic")
  }
}
```

> **ヒント**
>
> アプリの実装例については [sora-ios-sdk-samples](https://github.com/shiguredo/sora-ios-sdk-samples) をご確認ください。


## 映像のハードミュート

映像のハードミュートには 2 つの方法があります。
基本は `MediaChannel.setVideoHardMute(Bool enable)` の利用を推奨します。
独自のキャプチャ制御等が必要な場合は `CameraVideoCapturer` の `stop()` と  `restart()` による直接制御を利用してください。

> **注釈**
>
> `MediaChannel.setVideoHardMute(Bool enable)` は `CameraVideoCapturer` の `stop()` と `restart()` を
> 利用しやすくラップしたメソッドです。


### 黒塗りフレームを送出してから停止する

映像パケット断は受信側からは通信障害かハードミュートによるものかの判別ができません。
このため受信側のクライアント実装によっては、映像が停止した時点のフレームを表示し続けることがあります。
ハードミュートを有効化する前にソフトミュートによる黒塗りフレーム送出状態にしておくことで不自然な表示になることを防ぐことができます。


### 映像のハードミュート有効化

#### MediaChannel.setVideoHardMute(Bool enable) によるハードミュート

`MediaChannel.setVideoHardMute(true)` を実行することで、カメラデバイスからのフレーム取得が停止し、
iOS 端末のカメラインジケーターが消灯します。
また、映像パケットは一切送出されない状態となります。

> **重要**
>
> `MediaChannel.setVideoHardMute(Bool enable)` は非同期 API のため、利用する際は `Task` 等を利用した
> 非同期コンテキスト内で呼び出す必要があります。

> **ヒント**
>
> `MediaChannel.setVideoHardMute(true)` では内部でソフトミュートを有効化した後にハードミュートを有効化します。
> そのため、アプリ側でハードミュート前のソフトミュート実行を実装する必要はありません。
>
> ソフトミュートを併用する理由については
> [黒塗りフレームを送出してから停止する](mute.html#5e82ac) をご確認ください。

#### CameraVideoCapturer.stop() によるハードミュート

`CameraVideoCapturer.stop()` を実行することで、カメラデバイスからのフレーム取得が停止し、
iOS 端末のカメラインジケーターが消灯します。
また、映像パケットは一切送出されない状態となります。

> **ヒント**
>
> `CameraVideoCapturer.stop()` はコールバック形式の非同期 API です。

> **ヒント**
>
> `CameraVideoCapturer.stop()` によるハードミュート有効化では、停止前に
> [ソフトミュート](mute.html#fc2a56) を有効にしておくことで受信側の不自然な表示を防ぐことができます。
>
> ソフトミュートを併用する理由については
> [黒塗りフレームを送出してから停止する](mute.html#5e82ac) をご確認ください。

### Sora 接続時にカメラインジケーターを点灯させないで開始する

Sora への接続設定 `Configuration.initialCameraEnabled` に `false` を指定することで
接続確立時にカメラキャプチャを起動しないで開始します。
このとき、iOS 端末のカメラインジケーターは点灯せず、映像パケットは送出されません (映像ハードミュート相当)。

`Configuration.initialCameraEnabled` のデフォルト値は `true` です。

映像が送出されるようにするには `MediaChannel.setVideoHardMute(false)` を実行するか、
`CameraVideoCapturer.start()` を実行します。

> **注釈**
>
> `Configuration.initialCameraEnabled` を `false` にした場合、接続自体は映像有効となりますが、
> 接続確立時点でカメラキャプチャが起動しないため、受信側には映像フレームが届きません。
> 受信側からは通信障害か映像ハードミュート相当かを判別できないため、表示は受信側実装に依存します。

### 映像のハードミュート無効化

[映像のハードミュート有効化](mute.html#e1161d) で有効化したハードミュートを無効にするには
ハードミュートを有効化した方法に合わせて、無効化の方法も選択してください。

#### MediaChannel.setVideoHardMute(Bool enable) による無効化

`MediaChannel.setVideoHardMute(false)` を実行することで、カメラデバイスからのフレーム取得が再開され、
iOS 端末のカメラインジケーターが点灯します。
また、映像パケットの送出も再開されます。

> **重要**
>
> `MediaChannel.setVideoHardMute(Bool enable)` は非同期 API のため、利用する際は `Task` 等を利用した
> 非同期コンテキスト内で呼び出す必要があります。

> **ヒント**
>
> `MediaChannel.setVideoHardMute(false)` はソフトミュートの無効化処理も含んでいます。
> そのため、ソフトミュートの無効化をアプリ側で実装する必要はありません。
>
> ソフトミュートを併用する理由については
> [黒塗りフレームを送出してから停止する](mute.html#5e82ac) をご確認ください。

#### CameraVideoCapturer.restart() による無効化

`CameraVideoCapturer.restart()` を実行することで、カメラデバイスからのフレーム取得が再開され、
iOS 端末のカメラインジケーターが点灯します。
また、映像パケットの送出も再開されます。

> **ヒント**
>
> `CameraVideoCapturer.restart()` はコールバック形式の非同期 API です。

> **ヒント**
>
> `CameraVideoCapturer.stop()` によるハードミュート有効化を行い、かつソフトミュートを有効にしていた場合は、
> 再開後に `MediaChannel.setVideoSoftMute(false)` も実行してください。
>
> ソフトミュートを併用する理由については
> [黒塗りフレームを送出してから停止する](mute.html#5e82ac) をご確認ください。

### 映像のハードミュート機能実装サンプル

```swift
// setVideoHardMute を使った映像ハードミュートを行う例です。
// 接続や UI のうちハードミュートに不要な部分は省略しています。

import Sora
import UIKit

@MainActor
final class VideoHardMuteViewController: UIViewController {
  // 接続完了後に設定される MediaChannel です。
  var mediaChannel: MediaChannel?

  // カメラのハードミュート状態を保持します。
  private var isCameraHardMuted = false

  // 映像ハードミュートに利用する MediaChannel.setVideoHardMute(:_) は非同期 API のため
  // @IBAction からそのまま await することはできません。このため Task を利用して非同期実行するようにします。
  // また、ボタン連打などによる多重実行を防ぐため実行中の Task を保持するねらいもあります。
  private var videoHardMuteTask: Task<Void, Never>?

  // カメラのハードミュートボタンです。
  @IBOutlet weak var cameraHardMuteButton: UIBarButtonItem?

  // カメラのハードミュートをトグルします。ボタンにアクションとして紐付けてください。
  @IBAction func onCameraHardMuteButton(_ _: UIBarButtonItem) {
    // 接続していない場合や映像アップストリームがない場合は何もしません。
    guard let mediaChannel = mediaChannel, mediaChannel.senderStream?.hasVideoTrack == true else {
      return
    }
    guard videoHardMuteTask == nil else {
      return
    }

    let nextMuted = !isCameraHardMuted
    cameraHardMuteButton?.isEnabled = false

    videoHardMuteTask = Task { [weak self] in
      // 処理を抜ける際のタスクの無効化とボタンの有効化を確実に行います。
      defer {
        self?.videoHardMuteTask = nil
        self?.cameraHardMuteButton?.isEnabled = true
      }

      do {
        // 映像ハードミュートを非同期で実行します。
        try await mediaChannel.setVideoHardMute(nextMuted)
        // UI の更新を行います。
        guard let self else { return }
        self.isCameraHardMuted = nextMuted
        self.updateCameraButton(isMuted: nextMuted)
      } catch {
        // エラー時のハンドリングをします。
        _ = error
      }
    }
  }

  // ミュート状態に対応したアイコンになるようにボタンを更新します
  private func updateCameraButton(isMuted: Bool) {
    cameraHardMuteButton?.image = UIImage(systemName: isMuted ? "video.slash.fill" : "video")
  }
}
```

> **ヒント**
>
> アプリの実装例については [sora-ios-sdk-samples](https://github.com/shiguredo/sora-ios-sdk-samples) をご確認ください。

## 映像のソフトミュート

通信処理によらないクライアントレイヤーの映像ミュートです。


### 映像のソフトミュート有効化

Sora iOS SDK で映像のソフトミュートを有効にするには `MediaChannel.setVideoSoftMute(Bool enable)` を利用します。
このメソッドの引数に `true` を指定することで、映像トラックを無効にします。
このときカメラデバイス由来の映像は配信されず、代わりに黒塗りのフレームを送出する状態となります。
ただしカメラデバイスの入力自体は有効のままのため、iOS 端末のカメラインジケーターは点灯したままになります。

### 映像のソフトミュート無効化

[映像のソフトミュート有効化](mute.html#fc2a56) で有効化したソフトミュートを無効にするには
`MediaChannel.setVideoSoftMute(Bool enable)` を利用します。
このメソッドの引数に `false` を指定することで映像トラックが有効になり、カメラデバイスの映像が配信されるようになります。

### 映像のソフトミュート機能実装サンプル

```swift
// setVideoSoftMute を使った映像ソフトミュートを行う例です。
// 接続や UI のうちソフトミュートに不要な部分は省略しています。

import Sora
import UIKit

final class VideoSoftMuteViewController: UIViewController {
  // 接続完了後に設定される MediaChannel です。
  var mediaChannel: MediaChannel?

  // カメラのソフトミュート状態を保持します。
  private var isCameraSoftMuted = false

  // カメラのソフトミュートボタンです。
  @IBOutlet weak var cameraSoftMuteButton: UIBarButtonItem?

  // カメラのソフトミュートをトグルします。ボタンにアクションとして紐付けてください。
  @IBAction func onCameraSoftMuteButton(_ _: UIBarButtonItem) {
    // 接続していない場合や映像アップストリームがない場合は何もしません。
    guard let mediaChannel = mediaChannel, mediaChannel.senderStream?.hasVideoTrack == true else {
      return
    }

    let nextMuted = !isCameraSoftMuted
    // 映像ソフトミュートを実行します。
    if let error = mediaChannel.setVideoSoftMute(nextMuted) {
      // エラー時のログ出力等を行います。
      _ = error
      return
    }

    isCameraSoftMuted = nextMuted
    updateCameraButton(isMuted: nextMuted)
  }

  // ミュート状態に対応したアイコンになるようにボタンを更新します
  private func updateCameraButton(isMuted: Bool) {
    cameraSoftMuteButton?.image = UIImage(systemName: isMuted ? "video.slash" : "video")
  }
}
```

> **ヒント**
>
> アプリの実装例については [sora-ios-sdk-samples](https://github.com/shiguredo/sora-ios-sdk-samples) をご確認ください。

## インジケーターの反映について

音声または映像のハードミュートを有効にした際、マイクインジケーターおよびカメラインジケーターの表示が完全に消えるまでのタイミングは iOS による制御です。
配信パケット送出が停止した後もしばらくはインジケーターの表示が残ることがあります。

## Sora 録画機能とハードミュートの併用について

Sora の録画機能での録画中に映像ハードミュートを有効にした場合、カメラデバイス由来の映像パケットが送出されない状態になるため、
停止時点でのフレームが録画され続けます。

[映像のハードミュート有効化](mute.html#e1161d) でも述べたようにソフトミュート有効化による黒塗りフレームの送出状態を挟むことで動画として不自然になることを防ぐことができます。

また、音声と映像の両方がハードミュート有効の状態で録画が開始され、ハードミュートを無効にすることなく録画終了した場合、録画用のパケットが一切送信されていないため録画ファイル自体が出力されません。

Sora の録画機能については [](https://sora-doc.shiguredo.jp/RECORDING) をご確認ください。
