# ミュート機能

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

## 用語

### プライバシーインジケーター

Android アプリがマイクデバイスまたはカメラデバイスを利用している場合に、ユーザーに対してデバイスが使用中であることを通知するためのインジケーターです。
マイク使用時はマイクアイコン、カメラ時はカメラアイコンがそれぞれ表示され、しばらくすると緑色のドットの表示に切り替わります。

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

プライバシーインジケーターは Android 12 から、デバイス使用時にステータスバーへの表示が必須になっています。

> **ヒント**
>
> Android 12 より前の機種においてはプライバシーインジケーターを実装するかを含め任意でした。

プライバシーインジケーターの詳細については Android ドキュメント
[](https://source.android.com/docs/core/permissions/privacy-indicators?hl=ja)
をご確認ください。

### ソフトミュート

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

### ハードミュート

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

## 推奨するミュート方式について

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

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

### ハードミュートのデメリット

ハードミュートを利用する際のデメリットは特にありません。

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


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

Sora Android SDK で音声のハードミュートを有効にするには `SoraMediaChannel.setAudioHardMute(boolean enable)` を利用します。
このメソッドの引数に `true` を指定することで、マイクデバイスの録音を無効にします。
このタイミングで音声が送られ続けなくなるため、Android 端末のマイクインジケーターが消灯します。

また、マイクデバイスの音声が送られてこなくなるため、実際に音声パケットを一切送らなくなります。

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

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

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

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

```kotlin
/**
 * AudioHardMuteSampleActivity に
 * ハードミュート有効化ボタンイベント onMicHardMuteButtonClicked と
 * ハードミュート無効化ボタンイベント onMicHardUnMuteButtonClicked
 * を実装したサンプルです。
 *
 * kotlinx.coroutines の CoroutineScope, Dispatchers, launch, withContext を利用しています。
 * また、lifecycleScope を使用するためには androidx.lifecycle:lifecycle-runtime-ktx の導入が必要です。
 */

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import jp.shiguredo.sora.sdk.channel.SoraMediaChannel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

/**
 * 音声ハードミュートの有効/無効化を制御するコントローラーのサンプルクラスです。
 * `SoraMediaChannel` の `setAudioHardMute` 関数を非同期実行します。
 * `setAudioHardMute` は suspend 関数として定義されています。
 *
 * 前提:
 * - 呼び出し側で `SoraMediaChannel` の生成と破棄を管理していること
 * - `scope` には UI スレッドをブロックしない `CoroutineScope` (例: `lifecycleScope` や `viewModelScope`) を渡していること
 *
 * `setHardMuted(true)` を呼び出すと、ハードミュートが有効になりマイクインジケーターが消灯します。
 */
class AudioHardMuteController(
    private val mediaChannel: SoraMediaChannel,
    private val scope: CoroutineScope,
) {
    fun setHardMuted(muted: Boolean) {
        scope.launch {
            val success =
                withContext(Dispatchers.Default) {
                    mediaChannel.setAudioHardMute(muted)
                }
            if (!success) {
                // 必要に応じて UI へエラーを通知する実装等を追加します
            }
        }
    }
}

/**
 * 音声ハードミュート用のサンプルアクティビティです
 * mediaChannel の生成についてはダミーの実装のためこのままではコンパイルは通りません
 */
class AudioHardMuteSampleActivity : AppCompatActivity() {
    private lateinit var mediaChannel: SoraMediaChannel
    private lateinit var controller: AudioHardMuteController

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mediaChannel = createDummyMediaChannel()
        controller =
            AudioHardMuteController(mediaChannel, lifecycleScope)
    }

    // ハードミュートを有効にするボタンイベントです
    fun onMicHardMuteButtonClicked() {
        controller.setHardMuted(true)
    }

    // ハードミュートを無効にするボタンイベントです
    fun onMicHardUnMuteButtonClicked() {
        controller.setHardMuted(false)
    }

    // ダミーの MediaChannel 生成メソッドです
    private fun createDummyMediaChannel(): SoraMediaChannel {
        TODO("SoraMediaChannel を生成して返す")
    }
}
```

> **注釈**
>
> Android 端末および Android OS の割り込み等によるマイクデバイスの状態によっては録音スレッドの停止・再開が失敗することがあります。そのため `setAudioHardMute` の内部処理ではローカル音声トラックの停止も併せて行うことで、不意の音声の送信を防ぐようにしています。

> **注釈**
>
> 音声アップストリームが有効化( `enableAudioUpstream()` )されていない場合、 `setAudioHardMute` は何も行わず、 `true` を返します。

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

#### setAudioHardMute の補足

音声ハードミュートは内部処理として、Sora Android SDK が利用している libwebrtc 内で録音スレッドの停止・再開を行なっています。

録音スレッドの停止・再開はスレッド同期に時間がかかることがあります。UI スレッドでの実行はアプリ応答不能となる可能性があるため `setAudioHardMute` はワーカースレッドでの実行を前提としています。

> **注釈**
>
> 録音スレッドの同期待ちについて、libwebrtc 内では 2 秒、SDK 内では 3 秒のタイムアウトを設けています。

#### 外部 AudioDeviceModule 利用時の注意

`SoraAudioOption.audioDeviceModule` を設定していて外部から `AudioDeviceModule` を差し込んでいる場合、
`setAudioHardMute` は動作しません。この場合は、独自に `AudioDeviceModule` を停止する処理を実装する必要があります。

### Sora 接続時に音声のハードミュートを有効化する

音声のハードミュートを有効化した状態で Sora に接続するには `SoraAudioOption.initialAudioHardMute: Boolean` を利用します。

ハードミュートを有効にして接続したい場合は `true` を設定してください。デフォルト値は `false` です。

```kotlin
val option = SoraMediaOption().apply {
   audioOption = SoraAudioOption().apply {
      // 接続時の音声のハードミュートを有効にする
      initialAudioHardMute = true
   }
}
```

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

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


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

Sora Android SDK で音声のソフトミュートを有効にするには `SoraMediaChannel.setAudioSoftMute(boolean enable)` を利用します。
このメソッドの引数に `true` を指定することで、音声トラックを無効にします。
このときマイクデバイスからは音声が送られ続けている状態のままのため、Android 端末のマイクインジケーターは点灯したままになります。

また、マイクデバイスからの音声は配信されませんがソフトミュートは実際にはパケットを送っており、無音のフレーム（デジタルサイレンスパケット）を送り続けています。

> **注釈**
>
> `setAudioSoftMute` は内部で `AudioTrack.setEnabled` を呼び出しています。

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

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

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

```kotlin
/**
 * ソフトミュート有効化ボタンイベント onMicSoftMuteButtonClicked と
 * ソフトミュート無効化ボタンイベント onMicSoftUnMuteButtonClicked
 * を実装したサンプルです。
 */

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import jp.shiguredo.sora.sdk.channel.SoraMediaChannel

/**
 * 音声ソフトミュート用のサンプルアクティビティです
 * mediaChannel の生成についてはダミーの実装のためこのままではコンパイルは通りません
 */
class AudioSoftMuteSampleActivity : AppCompatActivity() {
    private lateinit var mediaChannel: SoraMediaChannel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mediaChannel = createDummyMediaChannel()
    }

    // ソフトミュートを有効にするボタンイベントです
    fun onMicSoftMuteButtonClicked() {
        mediaChannel.setAudioSoftMute(true)
    }

    // ソフトミュートを無効にするボタンイベントです
    fun onMicSoftUnMuteButtonClicked() {
        mediaChannel.setAudioSoftMute(false)
    }

    // ダミーの MediaChannel 生成メソッドです
    private fun createDummyMediaChannel(): SoraMediaChannel {
        TODO("SoraMediaChannel を生成して返す")
    }
}
```

> **ヒント**
>
> `SoraMediaChannel.Listener` の実装例については [シグナリング](signaling.html) をご確認ください。

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


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

映像のハードミュートを行うには `org.webrtc.CameraVideoCapturer` の `startCapture()` と `stopCapture()` を利用します。
また `startCapture()` と `stopCapture()` を利用しやすいようにラップした `setVideoHardMute(Boolean)` を利用する方法もあります。

`setVideoHardMute(Boolean)` は `SoraMediaOption.enableVideoUpstream` に `cameraConfig` 引数を与えることで利用可能です。
この引数は `startCapture()` 実行時の引数として利用されます。

### setVideoHardMute(Boolean) を利用するための設定

`setVideoHardMute(Boolean)` を利用する場合には SoraMediaChannel に CameraVideoCapturer をセットする必要があります。
`startCapture` と `stopCapture` を直接使う場合はこの処理は不要です。

```kotlin
// CameraVideoCapturer を作成
cameraVideoCapturer: CameraVideoCapturer = createCameraVideoCapturer()

val option = SoraMediaOption().apply {
  enableAudioDownstream()
  enableVideoDownstream(egl!!.eglBaseContext)

  enableAudioUpstream()

  // 重要: enableVideoUpstream に cameraConfig を設定
  val width = 1280
  val height = 720
  val frameRate = 30
  enableVideoUpstream(
    capturer = capturer,
    eglContext = eglBase.eglBaseContext,
    cameraConfig = SoraMediaOption.SoraCameraConfig(
       width = width,
       height = height,
       frameRate = frameRate
    ),
  )
}

mediaChannel = SoraMediaChannel(
  context           = this,
  signalingEndpoint = "wss://sora.example.com/signaling",
  channelId         = "sora",
  mediaOption       = option,
  listener          = channelListener)

// Sora に接続
mediaChannel.connect()

// Sora 接続以降で SoraMediaChannel.setVideoHardMute(Boolean) が利用可能になります
```


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

Sora Android SDK でハードミュートを有効にするには `CameraVideoCapturer.stopCapture()` または `SoraMediaChannel.setVideoHardMute(true)` を利用します。
このメソッドを実行することで、カメラデバイスからのフレーム取得(キャプチャ)が停止します。
このタイミングで映像が送られ続けなくなるため、Android 端末のカメラインジケーターが消灯します。

また、カメラデバイスからの映像が送られてこなくなるため、実際に映像パケットを一切送らなくなります。


#### 映像のソフトミュートを併用する

ハードミュートを有効にする前にソフトミュートを有効にして黒塗りフレームを送信する状態にしておくと安全です。
理由として、受信側の実装によってはパケット断が通信障害なのかハードミュートによる停止なのか判断できず、
停止時点の映像フレームを表示し続けてしまうケースがあるためです。

> **重要**
>
> `SoraMediaChannel.setVideoHardMute(true)` はソフトミュートを併用してハードミュートを行います。
> そのため、ソフトミュートの併用をアプリ側で実装する必要はありません。

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

[映像のハードミュート有効化](mute.html#e1161d) で有効化したハードミュートを無効にするには
`CameraVideoCapturer.startCapture()` または `SoraMediaChannel.setVideoHardMute(false)` を利用します。
このメソッドを実行することで、カメラデバイスからのフレーム取得が開始されます。
このタイミングで Android 端末のカメラインジケーターが点灯します。

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

ハードミュート有効化の際にソフトミュートも有効にしていた場合は、ソフトミュートの無効化も併せて行う必要があります。

> **重要**
>
> `SoraMediaChannel.setVideoHardMute(false)` はソフトミュートの無効化処理も含んでいます。
> そのため、ソフトミュートの無効化をアプリ側で実装する必要はありません。

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

```kotlin
/**
 * VideoHardMuteSampleActivity に
 * ハードミュート有効化ボタンイベント onCameraHardMuteButtonClicked と
 * ハードミュート無効化ボタンイベント onCameraHardUnMuteButtonClicked
 * を実装したサンプルです。
 *
 * kotlinx.coroutines の CoroutineScope, Dispatchers, launch, withContext を利用しています。
 * また、lifecycleScope を使用するためには androidx.lifecycle:lifecycle-runtime-ktx の導入が必要です。
 */
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import jp.shiguredo.sora.sdk.channel.SoraMediaChannel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.webrtc.CameraVideoCapturer
import org.webrtc.MediaStream
import org.webrtc.VideoTrack

/**
 * 映像ハードミュートの有効/無効化を制御するコントローラーのサンプルクラスです。
 * `CameraVideoCapturer` の startCapture/stopCapture 関数を非同期実行します。
 * startCapture/stopCapture 関数はブロッキング処理のため非同期で実行する必要があります。
 * また、ソフトミュートを併せて有効にすることでキャプチャ停止前のフレームを黒塗りフレームにします。
 * 受信するクライアント側の挙動として、映像パケットが送られてこない場合は直前のフレームを表示し続けるケースがあるためです。
 *
 * 前提:
 * - `capturer` は `SoraMediaOption#videoCapturerFactory` などから取得していること
 * - `scope` は UI スレッドをブロックしない `CoroutineScope` (例: `lifecycleScope` や `viewModelScope`) を渡すこと
 * - `startCapture` 用の `videoWidth` / `videoHeight` / `videoFPS` はキャプチャ開始時に利用した値を保持していること
 *
 * `setHardMuted(true)` を呼び出すと、ハードミュートが有効になりカメラインジケーターが消灯します。
 */
class VideoHardMuteController(
    private val capturer: CameraVideoCapturer,
    private val scope: CoroutineScope,
    private val videoWidth: Int,
    private val videoHeight: Int,
    private val videoFPS: Int,
) {
    private var localVideoTrack: VideoTrack? = null

    // MediaStream からローカル映像トラックを取得します
    // Sora のルールとして 1 ストリームにトラックは 1 本のみのため先頭要素がローカル映像トラックです
    fun onAddLocalStream(stream: MediaStream) {
        localVideoTrack = stream.videoTracks.firstOrNull()
    }

    // クリーンアップ処理です
    fun clearLocalVideoTrack() {
        localVideoTrack = null
    }

    // 映像ハードミュートの有効・無効を切り替えます
    //
    // ハードミュート有効化時
    // 1. 映像トラックを無効にして黒塗りフレームが送信される状態にします
    // 2. その後キャプチャの停止を行います
    // トラックとキャプチャの切り替えは同一コルーチン内で順番に実行します。
    // 先にトラックを無効化して黒塗りフレームを送出してからキャプチャを停止することで、
    // 黒塗りフレームが送信される前にキャプチャが止まってしまう状況を避けます。
    // VideoTrack.setEnabled() にはブロッキング処理はないためメインスレッド上での実行でも問題ありません。
    //
    // ハードミュート無効化時
    // 1. キャプチャの開始を行い映像パケットが送信される状態にします
    // 2. 映像トラックを有効にしてカメラデバイスからの映像が送信される状態にします
    // 有効化時とは逆順に処理します。
    fun setHardMuted(muted: Boolean) {
        scope.launch {
            runCatching {
                if (muted) {
                    withContext(Dispatchers.Main) {
                        localVideoTrack?.setEnabled(false)
                    }
                }
                withContext(Dispatchers.Default) {
                    if (muted) {
                        capturer.stopCapture()
                    } else {
                        capturer.startCapture(videoWidth, videoHeight, videoFPS)
                    }
                }
                if (!muted) {
                    withContext(Dispatchers.Main) {
                        localVideoTrack?.setEnabled(true)
                    }
                }
            }.onFailure {
                // 必要に応じて UI へエラーを通知する実装等を追加します
            }
        }
    }
}

/**
 * 映像ハードミュート用のサンプルアクティビティです
 * CameraVideoCapturer の生成についてはダミーの実装のためこのままではコンパイルは通りません
 */
class VideoHardMuteSampleActivity : AppCompatActivity() {
    private lateinit var capturer: CameraVideoCapturer
    private lateinit var controller: VideoHardMuteController

    // 実際のアプリでは生成した SoraMediaChannel にこの listener を登録してください
    private val channelListener =
        object : SoraMediaChannel.Listener {
            // ローカルストリームが追加されたときに呼び出されるコールバックです
            // ここで VideoHardMuteController に MediaStream を渡してローカルの映像トラックを取得させます
            override fun onAddLocalStream(
                mediaChannel: SoraMediaChannel,
                stream: MediaStream,
            ) {
                controller.onAddLocalStream(stream)
            }

            // 切断時のコールバックです。VideoHardMuteController のクリーンアップ処理を行います
            // 実際のアプリでは必要に応じて他モジュールのクリーンアップも行います
            override fun onDisconnect(mediaChannel: SoraMediaChannel) {
                controller.clearLocalVideoTrack()
            }

            // その他のコールバックについては省略します
        }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        capturer = createDummyCapturer()
        // 例として、Widht:1280。Height: 720、FPS: 30 の設定にします
        // ここではサンプルとして固定値としていますが、実際の実装ではミュート前と同じ状態で映像再開するために
        // あらかじめ変数やプロパティ等で設定値を保持しておく必要があります
        controller =
            VideoHardMuteController(capturer, lifecycleScope, 1280, 720, 30)
    }

    // ハードミュートを有効にします
    fun onCameraHardMuteButtonClicked() {
        controller.setHardMuted(true)
    }

    // ハードミュートを無効にします
    fun onCameraHardUnmuteButtonClicked() {
        controller.setHardMuted(false)
    }

    // ダミーの CameraVideoCapturer 生成メソッドです
    private fun createDummyCapturer(): CameraVideoCapturer {
        // 実際のアプリでは CameraVideoCapturerFactory などから取得してください
        TODO("CameraVideoCapturer を生成して返す")
    }
}
```

> **注釈**
>
> `VideoTrack.setEnabled(false)` を先に実行して黒塗りフレームを送出する状態にした後にハードミュートを有効にしています。復帰時はハードミュートを無効化した後に `setEnabled(true)` でカメラデバイスからのフレーム送出を再開します。
> 詳細は [映像のソフトミュートを併用する](mute.html#060fa0) をご確認ください。

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

#### カメラキャプチャラーの取得方法について

サンプルコード中ではダミー実装としているカメラキャプチャラーの取得方法については [カメラの映像を取得する](video.html#44b876) をご確認ください。

#### startCapture/stopCapture を実行するスレッドについて

`CameraVideoCapturer.startCapture()/stopCapture()` および `SoraMediaChannel.setVideoHardMute(Boolean)` は Android 端末およびカメラデバイスの状況によってはブロッキング処理となるため、
アプリ応答不能を防ぐために UI スレッドではなくワーカースレッド上で実行するようにしてください。

### Sora 接続時に映像のハードミュートを有効化する

映像のハードミュートを有効化した状態で Sora に接続するには `SoraMediaOption.SoraCameraConfig.initialVideoHardMute: Boolean` を利用します。

ハードミュートを有効にして接続したい場合は `true` を設定してください。デフォルト値は `false` です。

```kotlin
val option = SoraMediaOption().apply {
   val cameraConfig = SoraMediaOption.SoraCameraConfig(
       initialVideoHardMute = true
   )
   enableVideoUpstream(cameraConfig, eglContext)
}
```

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

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


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

Sora Android SDK で映像のソフトミュートを有効にするには `SoraMediaChannel.setVideoSoftMute(Boolean)` を利用します。
このメソッドを `true` を指定することで、映像トラックを無効にします。

このときカメラデバイスからは映像が送られ続けている状態のままのため、Android 端末のカメラインジケーターは点灯したままになります。

また、カメラデバイスからの映像は配信されませんが、ソフトミュートは実際にはパケットを送っており、黒塗りのフレームを送り続けています。

下画像は実際に Android 端末でソフトミュートを有効にした状態での配信を [Sora DevTools](https://github.com/shiguredo/sora-devtools) で受信した際のものです。

![image](https://i.gyazo.com/00bf75c3c29722e801932f05bddd9c76.png)

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

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

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

```kotlin
/**
 * ソフトミュート有効化ボタンイベント onMicSoftMuteButtonClicked と
 * ソフトミュート無効化ボタンイベント onMicSoftUnMuteButtonClicked
 * を実装したサンプルです。
 */

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import jp.shiguredo.sora.sdk.channel.SoraMediaChannel

/**
 * 映像ソフトミュート用のサンプルアクティビティです
 * mediaChannel の生成についてはダミーの実装のためこのままではコンパイルは通りません
 */
class VideoSoftMuteSampleActivity : AppCompatActivity() {
    private lateinit var mediaChannel: SoraMediaChannel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mediaChannel = createDummyMediaChannel()
    }

    // ソフトミュートを有効にするボタンイベントです
    fun onMicSoftMuteButtonClicked() {
        mediaChannel.setVideoSoftMute(true)
    }

    // ソフトミュートを無効にするボタンイベントです
    fun onMicSoftUnMuteButtonClicked() {
        mediaChannel.setVideoSoftMute(false)
    }

    // ダミーの MediaChannel 生成メソッドです
    private fun createDummyMediaChannel(): SoraMediaChannel {
        TODO("SoraMediaChannel を生成して返す")
    }
}
```

> **ヒント**
>
> `SoraMediaChannel.Listener` の実装例については [シグナリング](signaling.html) もご確認ください。

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

## プライバシーインジケーターの反映について

マイクデバイスとカメラデバイスのハードミュートを有効にした際、プライバシーインジケーターの表示が完全に消えるまでのタイミングは Android OS による制御です。配信パケット送出が停止した後も数秒間はプライバシーインジケーターの表示が残ることがあります。

詳細については Android ドキュメントのプライバシーインジケーター [](https://source.android.com/docs/core/permissions/privacy-indicators?hl=ja) をご確認ください。

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

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

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

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

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