くらしのマーケット開発ブログ

「くらしのマーケット」を運営する、みんなのマーケット株式会社のテックブログです

Twilio AudioSwitchを使ってAndroid Bluetooth通話を爆速で開発する方法

こんにちは、モバイルアプリエンジニアのリュウです。

最近、ワイヤレスイヤホンなどのBluetoothデバイスを使って通話する対応を行いました。 そこで、Twilio AudioSwitchというライブラリを知って利用しました。

本記事では、AudioSwitchの使い方とその開発で得た知見をご紹介します。

開発の背景

我々「くらしのマーケット」が提供しているユーザー向けアプリと店舗向けアプリには、 インターネットを介したIPネットワークを通じて音声通話をする機能(VoIP)があります。

ユーザーがアプリ内での通話をより便利に利用できるために、以下のようなさらなる進化を遂げたいと考えました。 - Bluetoothデバイスとのシームレスな連携 - Bluetoothデバイスに接続したら、Bluetoothで通話を続ける - Bluetoothデバイスに接続中に、端末のスピーカーなど他のデバイスに切り替えられる

AudioSwitchとは

AudioSwitchは、オーディオデバイスの管理を簡素化するAndroid向けのツールです。

AudioSwitchの特徴

  • オーディオデバイスの管理

    スピーカーフォン、イヤホン、またはヘッドセット間でオーディオ入出力を簡単に切り替えあれる。

  • オーディオデバイスの可用性の変更の検出

    オーディオデバイスの入出力の可用性が変更された際に、この変更を検出し、適切に対応できる。

  • エラーやタイムアウトの処理

    オーディオデバイスの選択中にエラーが発生した場合や、タイムアウトが発生した場合にも、適切に処理できる。

  • Bluetoothデバイスの選択

    Bluetoothデバイスを安全的にハンドリングできる。

AudioSwitchを採用した理由

  • 開発コストの削減

    Android標準のフレームワーク(例えばAudioManager)を使用すると、数百行もの複雑なコードが必要になるが、AudioSwitchはこれらの機能をカプセル化したため、数十行で完結できる。

  • ライブラリの一貫性

    「くらしのマーケットのアプリ」ではTwilio Voice SDKを使用して音声通話機能を開発したため、同じくTwilioが提供しているライブラリを導入することでライブラリの一貫性を保つことができる。

実際に取り組んだこと

さて、サンプルコードとともに解説していきます。

導入

まずは、GradleにAudioSwitchの設定を追加します。

dependencies {
  implementation 'com.twilio:audioswitch:1.1.8'
}

インスタンス化

AudioSwitchを使い始める前に、アプリケーション コンテキスト参照を使用してインスタンス化します。

    // 必要に応じてログの有効化・無効化を行う
    val audioSwitch = AudioSwitch(context, loggingEnabled = true)

アプリのライフサイクル全体で必要なのは、1つのAudioSwitchインスタンスだけです。
依存性の注入(DI)フレームワークを使用してコンポーネントに依存関係を注入するのも良いでしょう。

デバイスの変化をリッスンする

オーディオデバイスの可用性の変更をリアルタイムで検出できるよう、リッスンを開始します。

また、不要なタイミングに停止させます。

fun start() {
    // リッスンの開始
    audioSwitch.start { devices, selectedDevice ->
        // 有効なデバイスや選択したデバイスが変わるたびに実行される
        handleAudioDeviceChanges(devices)
    }
}

fun stop() {
    // リッスンの停止
    audioSwitch.stop()
}

デバイスの変化により選択したデバイスを自動的に切り替える

Bluetooth機器の接続状態・使用状態をもとに、接続や切断などの処理を行います。

// 有効なデバイスのキャッシュ
val cachedAvailableDevices = mutableListOf<AudioDevice>()

// 指定したデバイスを使用する
fun useAudioDevice(device: AudioDevice?) {
    if (audioSwitch.selectedAudioDevice == device) {
        return
    }
    audioSwitch.selectDevice(device)
    // デバイスを選択した後は、そのデバイスのアクティブ化が必須
    audioSwitch.activate()
}

fun findBluetooth(devices: List<AudioDevice>) =
    devices.find { it is AudioDevice.BluetoothHeadset }

fun handleAudioDeviceChanges(devices: List<AudioDevice>) {
    val oldBluetooth = findBluetooth(cachedAvailableDevices)
    val newBluetooth = findBluetooth(devices)

    // 変化したデバイスはBluetoothかどうかを確認
    if (oldBluetooth != newBluetooth) {
        when (newBluetooth) {
            // 接続 -> 切断
            null -> when {
                // 選択中のデバイスをリセットする(端末に戻る)
                audioSwitch.selectedAudioDevice is AudioDevice.BluetoothHeadset -> useAudioDevice(null)
            }

            // 切断 -> 接続
            else -> useAudioDevice(newBluetooth)
        }
    }

    // Bluetoothは変化なし
    cachedAvailableDevices.clear()
    cachedAvailableDevices.addAll(devices)
}

次回の課題

現在、Bluetooth機器のボタンで電話に応答・切電する機能に対応していません。
このため、TwilioとAndroid ConnectionServiceを統合して、この機能を実現したいと考えています。

ConnectionServiceは、Androidの通話管理システムを拡張し、通話の発信、着信、通話中の動作制御などを簡単に実装できるフレームワークです。
Twilioの通話機能と組み合わせることで、より安定した通話体験や追加機能の実装が可能となります。

まとめ

この記事では、Bluetooth通話機能の開発においてTwilio AudioSwitchがどのように活用されるかを解説しました。
今後のプロジェクトでBluetooth通話機能を実装する際は、ぜひ、検討してみてくださいね。


最後にみんなのマーケットでは、くらしのマーケットのサービス開発を一緒に盛り上げてくれるエンジニアを募集しています!
詳しくは、こちらをご覧ください。