LoginSignup
7
12

More than 1 year has passed since last update.

Bluetooth AudioデバイスからTHETAを操る

Last updated at Posted at 2020-07-01

はじめに

リコーの @mShiiina です。

弊社ではRICOH THETAという全周囲360度撮れるカメラを出しています。
RICOH THETA VRICOH THETA Z1は、OSにAndroidを採用しています。Androidアプリを作る感覚でTHETAをカスタマイズすることもでき、そのカスタマイズ機能を「プラグイン」と呼んでいます(詳細は本記事の末尾を参照)。

2020年6月のFWアップデートにより、プラグインでBluetooth Classicデバイスを使用できるようになりました。

今回は、Bluetoothイヤホン、Bluetoothオーディオを使用してリモート撮影ができるプラグインを作ってみました。動画はこんな感じです。




<2021/6/2 Update 情報>
-「ダイソーの330円片耳イヤホン(新型)でフルコントロール出来ました」の章を追加しました。

<2020/7/30 Update情報>
- 日英の二ヶ国語に対応しました。初期値は英語です。無線ボタン長押しで言語切り替えできます。プラグイン終了時に言語設定が保存されます。
- 音声ファイルをすべてAmazon Pollyで生成しなおしました。音質が向上しています。

<2020/11/24 Update情報>
この記事のプラグインをRICOH THETA Plug-in STOREに公開しました!以下の点が改善されています。

  • プラグイン起動前のBluetooth関連設定(スマホ連携あり/なし、リモコン機能 On/Off)を保存&復旧するようにしました。
  • プラグイン起動前の音量の保存と復旧、プラグイン動作中の音量保存と復帰をするようにしました。

以下のBluetoothAudioデバイスとTHETAを接続し、THETAをリモートで操作する方法をご紹介します。
・Bluetoothイヤホン:Anker Soundcore Liberty Air
・Bluetoothイヤホン:JPRiDE TWS-520
・Bluetoothオーディオ:Anker Soundcore Icon Mini

上記以外にも、いくつか動作確認をして末尾の章に一覧を掲載しました。その章をみて頂けるとわかるのですが、相性(接続可/不可)や使い勝手はオーディオ機器によってマチマチと思われます。私たちが全ての機器を試すことは不可能でして、、、かならずしもお持ちのイヤホンで使えるとは限らない点はご容赦ください。(とはいえ、AppleのAir Pods Proですら動いたし、大抵は大丈夫はなず!)

BluetoothAudioデバイスの操作に対してTHETAの以下の振る舞いを割り当て可能としました。
1. 静止画撮影
2. 露出補正の設定
3. 通常通知音の音量設定

露出補正の設定時は、こちらの記事にならって、音声を再生し設定値を読み上げています。

使用するには、以下バージョン以降のファームウェアが必要です。最新ファームウェアへのアップデートをお忘れなく!
・THETA V : 3.40.1
・THETA Z1 : 1.50.1

Bluetoothプロファイル

本プラグインは以下のプロファイルに対応しているBluetoothデバイスを使用できます。
・Headset Serviceプロファイル (HSP) ※マイクは使用できません。
・Audio/Video Remote Controlプロファイル (AVRCP)

Bluetoothイヤホンの接続、操作

Anker Soundcore Liberty AirとJPRiDE TWS-520を使用しました。

Anker Soundcore Liberty Air

Amazonで7000円程度で購入できます。

対応プロファイル:HSP、AVRCP

1.jpeg

JPRiDE TWS-520

amazonで5500円程度で購入できます。

対応プロファイル:HSP、AVRCP

2.jpeg

接続手順:
1.Soundcore Liberty Air または TWS-520 をペアリングモードにする
2.プラグインを立ち上げる(最長12秒間Bluetooth Classicデバイスを検索する)
3.接続されるまで待機する

イヤホン操作によるTHETA動作割り当て:
Bluetoothイヤホンには、タッチ式のマルチファンクションボタンがついており、接続デバイスを操作できます。今回は以下のTHETA動作を割り当てました。

イヤホン操作 THETA動作
再生 静止画撮影
一時停止 静止画撮影
曲送り 露出補正を1ステップアップ
曲戻し 露出補正を1ステップダウン

Bluetoothオーディオの接続、操作

Anker Soundcore Icon Mini を使用しました。

Amazonで3000円以下で購入できます。
対応プロファイル:Audio/Video Remote Controlプロファイル (AVRCP)

3.jpeg

オーディオ操作によるTHETA動作割り当て:

Bluetoothオーディオには、ボタンがついており、接続デバイスを操作できます。今回は以下のTHETA動作を割り当てました。

オーディオ操作 THETA動作
再生 静止画撮影
一時停止 静止画撮影
曲送り 露出補正を1ステップアップ
曲戻し 露出補正を1ステップダウン
音量アップ 通常通知音の音量1ステップアップ
音量ダウン 通常通知音の音量1ステップダウン

接続手順:
1.Soundcore Icon Miniをペアリングモードにする
2.プラグインを立ち上げる(最長12秒間Bluetooth Classicデバイスを検索する)
3.接続されるまで待機する

THETA本体の操作

THETA操作によるTHETA動作割り当て:

THETA操作 THETA動作
シャッターボタン短押し 静止画撮影
無線ボタン短押し 通常通知音の音量1ステップアップ
モードボタン短押し 通常通知音の音量1ステップダウン
無線ボタン長押し 日英言語切り替え

LED表示

THETA VではBluetoothデバイスの検索、接続、切断状態をLED3に表示します。

LED3状態 内容
黄色点灯 Bluetoothデバイス検索中
青色点灯 Bluetoothデバイス検索終了 or Bluetoothデバイス切断
白色点灯 Bluetoothデバイス接続済み

OLED表示

THETA Z1ではBluetoothデバイスの検索、接続、切断状態をOLEDの3段目に表示します。

OLED3段目状態 内容
DISCOVERY STARTED Bluetoothデバイス検索中
DISCOVERY FINISHED Bluetoothデバイス検索終了
CONNECTED Bluetoothデバイス接続済み
DISCONNECTED Bluetoothデバイス切断

webUIのKeyCode表示

webUIでプラグインが受け取ったKeyCodeを表示します。

4.png

制約

新しく追加されたWebAPIのOptions:BluetoothRoleがCentral、Central_Peripheralの場合(=通常の動作でリモコン機能をONとしている場合)は、使用できません。THETA側でBluetooth Classicの検索処理の実行/停止/接続処理を繰り返しているためです。

プラグイン起動時の退避と終了時の復旧も可能ですが、今回は実装を省略しています。後半の「THETAアプリ側のリモコン検索機能の抑制」のところで退避と復旧の方法について触れていますのでお好みで実装を追加してください。

<2020/11/24 Update情報>
RICOH THETAプラグインストアへの公開に伴い、プラグイン起動時の退避と終了時の復旧に対応しました!

プログラムの説明

ソースコードはこちらです。

全体説明

THETAプラグインSDKをベースに
~\app\src\main\java\com\theta360\pluginapplication 配下の以下ファイルとMainActivityに手を加えました。

ファイル説明

ファイル 説明
MainActivity.java 各種タスクの起動とキー割り当て操作を行います
bluetooth/BluetoothClientService.java Bluetooth Classicデバイスの検索、接続、切断を行います
bluetooth/BluetoothDeviceReceiver.java Bluetooth関連Intentのブロードキャストレシーバーです
bluetooth/MediaReceiver.java AVRCPプロファイルの音量変更Intentのブロードキャストレシーバーです
task/ChangeCaptureModeTask.java
task/ChangeEvTask.java
task/ChangeEvTask.java
task/ShutterButtonTask.java
task/SoundManagerTask.java
こちらにならって作成したファイルです
EnableBluetoothClassicTask.java Bluetooth Classicの有効/無効を切り替えます
task/ChangeVolumeTask.java 音量を変更します

マニフェスト ファイル

本プラグインでは、AndroidManifest.xmlにパーミッションとサービスを追加しています。

パーミッション

Bluetooth Classicデバイスを使用するためには、以下のパーミッションを定義しています。

AndroidMnifestfile.xml
    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

開発者としてプログラムを実行するときには、VysorからSettings->App->HEADSET Remoteの「Location」のパーミッションを手動で有効にしてください。
本プラグインは公式プラグインストア での公開を予定しています。>>> 公開しました!(2020/11/24 追記)
公式プラグインストアからインストールした場合は、インストール時にパーミッションが有効になるため、手動での設定は必要ありません。

サービス

Bluetooth Classicデバイスを扱うサービスを追加しています。

AndroidMnifestfile.xml
        <service android:name="com.theta360.pluginapplication.bluetooth.BluetoothClientService" />

メイン処理(MainActivity.java)

既存の雛形部分(キーコード解釈部)
基本的には、受け取ったkeyCodeに対応するアクションを以下コードで実行するだけです。

MainActivity.java
execKeyProcess(keyCode2KeyProcess(keyCode));

Bluetooth Classic有効/無効切り替え

Bluetooth Classicを有効にするために、プラグイン起動時にまずEnableBluetoothClassicTaskを生成します。

MainActivity.java
        new EnableBluetoothClassicTask(getApplicationContext(),EnableBluetoothClassicTask).execute();

タスク内でWebAPI Options _bluetoothClassicEnableをtrueに設定し、Bluetooth Classicを有効にします。

EnableBluetoothClassicTask.java
        HttpConnector camera = new HttpConnector("127.0.0.1:8080");
        String errorMessage = camera
                .setOption("_bluetoothClassicEnable", Boolean.toString(Boolean.TRUE));

Bluetoothモジュールの電源WebAPI Options _bluetooth_power がOFFの場合は、ONに変更して完了です。

EnableBluetoothClassicTask.java
       String bluetoothPower = camera.getOption("_bluetoothPower");
        if (bluetoothPower.equals("OFF")) {
            errorMessage = camera.setOption("_bluetoothPower", "ON");
            if (errorMessage != null) { // パラメータの設定に失敗した場合はエラーメッセージを表示
                return "NG";
            }
        }

Bluetooth Classicの有効/無効の変更を反映させるには、Bluetoothモジュールの電源OFF/ONを行う必要があります。
_bluetoothClassicPowerがONの状態で_bluetoothClassicEnableを変更するとTHETAアプリ側はBluetoothモジュールの電源OFF/ONを自動で行います。

Bluetooth Classicデバイスの検索、接続

Bluetooth Classicを有効にした後は、Bluetooth Classicデバイスの検索、接続を行います。こちらは、AndroidのBluetoothのAPIを使用します。

ざっくりとした流れは以下になります。
1. サービスを生成する
2. 検索を開始する
3. 発見したデバイスをペアリングする
4. ペアリングしたデバイスを接続する

まずサービスを生成します。

MainActivity.java
    private EnableBluetoothClassicTask.Callback mEnableBluetoothClassicTask = new EnableBluetoothClassicTask.Callback() {
        @Override
        public void onEnableBluetoothClassic(String result) {
            getApplicationContext().startService(new Intent(getApplicationContext(), BluetoothClientService.class));
        }
    };

次にBluetooth Classicデバイスの検索を開始します。
こちらは、Androidの仕様で開始から12秒後に停止します。検索中の場合は、一度停止してから開始します。

BluetoothClientService.java
    private void startClassicScan() {
        if (mBluetoothAdapter.isDiscovering()) {
            mBluetoothAdapter.cancelDiscovery();
        }
        boolean isStartDiscovery = mBluetoothAdapter.startDiscovery();
        Log.d(TAG, "startClassicScan :" + isStartDiscovery);
    }

接続対象のデバイスを発見した場合、ペアリングを行います。
接続対象のデバイスは、CoDで判断します。
CoDは、BluetoothSIGで定められたデバイスの用途を識別する値です。

Device Type Major Device Class Minor Device Class field
Bluetoothオーディオ Audio/Video Wearable Headset Device 1028
Bluetoothイヤホン Audio/Video Headphones 1048
BluetoothClientService.java
        @Override
        public void onFound(BluetoothDevice bluetoothDevice,
                BluetoothClass bluetoothClass,
                int rssi) {
            String name = bluetoothDevice.getName();
            Log.d(TAG, "name" + name);
            if (name != null) {
                int type = bluetoothDevice.getType();
                Log.d(TAG, "type" + String.valueOf(type));
                if (type == bluetoothDevice.DEVICE_TYPE_CLASSIC || type == bluetoothDevice.DEVICE_TYPE_DUAL) {
                    int classNo = bluetoothClass.getDeviceClass();
                    Log.d(TAG, "class" + classNo);
                    if ((classNo == COD_AUDIO_VIDEO_HEADPHONES) || (classNo
                            == COD_AUDIO_VIDEO_WEARABLE_HEADSET_DEVICE)) {
                        stopClassicScan();
                        bluetoothDevice.createBond();
                    }
                }
            }
        }

※ 「DEVICE_TYPE_DUAL」もペアリング対象にすることでApple Air Pods Proに対応できました。

ペアリング完了後に接続します。
bondStateがBOND_BONDEDになるとペアリング完了です。

BluetoothClientService.java
        public void onBondStateChanged(BluetoothDevice bluetoothDevice, int bondState) {
            Log.d(TAG, "onBondStateChanged");
            if (bondState == BluetoothDevice.BOND_BONDED) {
                connect(bluetoothDevice);
            }
        }

getProfileProxyを使用してプロファイル指定し、サービスリスナーと関連付けます。
プロファイルは、 BluetoothProfile.HEADSETを使用します。

BluetoothClientService.java
    private void connect(BluetoothDevice device) {
        int state = mBluetoothAdapter.getProfileConnectionState(BluetoothProfile.HEADSET);
        if (state == BluetoothProfile.STATE_DISCONNECTED) {
            int type = device.getType();
            if (type == BluetoothDevice.DEVICE_TYPE_CLASSIC || type == BluetoothDevice.DEVICE_TYPE_DUAL) {
                int classNo = device.getBluetoothClass().getDeviceClass();
                Log.d(TAG, "class" + classNo);
                boolean isGetProfile = false;
                if ((classNo == COD_AUDIO_VIDEO_HEADPHONES) || (classNo
                        == COD_AUDIO_VIDEO_WEARABLE_HEADSET_DEVICE)) {
                    {
                        isGetProfile = mBluetoothAdapter
                                .getProfileProxy(mContext, mServiceListener,
                                        BluetoothProfile.HEADSET);
                    }
                }

                Log.d(TAG, "isGetProfile :" + isGetProfile);
                if (isGetProfile) {
                    mBluetoothDevice = device;
                }
            }
        }
    }

※ 「DEVICE_TYPE_DUAL」もペアリング対象にすることでApple Air Pods Proに対応できました。

onServiceConnected()内でBluetoothHeadsetクラスのconnect()を行います。

BluetoothClientService.java
        public void onServiceConnected(int i, BluetoothProfile bluetoothProfile) {
            try {
                if (i == BluetoothProfile.HEADSET) {
                    Class bluetoothHeadset = Class
                            .forName("android.bluetooth.BluetoothHeadset");
                    Object object = bluetoothHeadset.cast(bluetoothProfile);
                    Method getConnectionState = bluetoothHeadset
                            .getDeclaredMethod("getConnectionState", BluetoothDevice.class);
                    int connectionState = (int) getConnectionState
                            .invoke(object, mBluetoothDevice);
                    if (connectionState == BluetoothProfile.STATE_DISCONNECTED) {
                        Method connect = bluetoothHeadset
                                .getDeclaredMethod("connect", BluetoothDevice.class);
                        boolean isConnected = (boolean) connect
                                .invoke(object, mBluetoothDevice);
                        Log.d(TAG, "isConnected : " + isConnected);
                        if (isConnected) {
                            mProfile = bluetoothProfile;
                        }
                    }
                }
            } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException |
                    InvocationTargetException e) {
                e.printStackTrace();
            }
        }

BluetoothAdapter.EXTRA_CONNECTION_STATEで取得した状態がBluetoothAdapter.STATE_CONNECTEDになれば接続完了です。

BluetoothClientService.java
        public void onConnectionStateChanged(int connectionState) {
            Log.d(TAG, "onConnectionStateChanged");
            if (Build.MODEL.equals("RICOH THETA Z1")) {
                Intent intent = new Intent(Constants.ACTION_OLED_TEXT_SHOW);
                if (connectionState == BluetoothAdapter.STATE_DISCONNECTED) {
                    intent.putExtra(Constants.TEXT_BOTTOM, "disconnected");
                } else if (connectionState == BluetoothAdapter.STATE_CONNECTED) {
                    intent.putExtra(Constants.TEXT_BOTTOM, "connected");
                }
                sendBroadcast(intent);
            } else {
                //LED3点灯
                Intent intentLedShow = new Intent("com.theta360.plugin.ACTION_LED_SHOW");
                if (connectionState == BluetoothAdapter.STATE_DISCONNECTED) {
                    intentLedShow.putExtra("color", LedColor.BLUE.toString());
                } else if (connectionState == BluetoothAdapter.STATE_CONNECTED) {
                    intentLedShow.putExtra("color", LedColor.WHITE.toString());
                }
                intentLedShow.putExtra("target", LedTarget.LED3.toString());
                mContext.sendBroadcast(intentLedShow);
            }
        }

音量調整

本プラグインで扱う音量データは2種類です。
1.ストリームボリューム(AndroidのAudioManagerのパラメータ、StreamTypeはSTREAM_MUSICを使用)
Androidが管理しています。実際に出力する音量です。
2.シャッターボリューム(WebAPIのOptions)
THETAアプリが管理しています。実際に出力する音量ではありません。実際に出力する音量を変更するには、ストリームボリュームの変更が必要です。
プラグインからIntentを使用してTHETAアプリで音声出力する際は、シャッターボリュームからストリームボリュームの値を算出し、ストリームボリュームを設定してから音声を出力しています。
つまり、THETAアプリで音声再生する場合はストリームボリュームはIntentを投げた時点のシャッターボリュームの音量に調整されます。
シャッターボリュームは、 WebAPI Options _shutter_volume で変更することができます。

本プラグインでは、2種類の操作で音量変更ができます。
・Bluetoothオーディオ操作(AVRCP)
・THETA本体ボタン操作
音量変更は、ChangeVolumeTask.javaで行っています。

Bluetoothオーディオ操作で音量変更した場合、Android側でストリームボリュームが変更され、プラグインにはストリームボリュームが変更されたことを表すIntent:"android.media.VOLUME_CHANGED_ACTION"が通知されます。
この時点では、シャッターボリュームは変更されていないため、プラグインで変更しています。ChangeVolumeTaskにストリームボリュームとACTION_TYPE_SET_VOLを渡してタスクを生成しています。

MainActivity.java
    private MediaReceiver.Callback mMediaReceiverCallback = new MediaReceiver.Callback() {
        @Override
        public void onChangeVolume(int stream_type, int prev_vol, int vol) {
            if (stream_type == AudioManager.STREAM_MUSIC) {
                if (vol != prev_vol) {
                    new ChangeVolumeTask(getApplicationContext(), vol,
                            ChangeVolumeTask.ACTION_TYPE_SET_VOL).execute();
                }
            }
        }
    };

THETA本体ボタン操作で音量変更した場合、その時点のシャッターボリュームからストリームボリュームを算出し、1ステップ増減させた音量をシャッターボリュームとストリームボリュームにそれぞれ設定します。

MainActivity.java
           case SET_VOL_PLUS:
                new ChangeVolumeTask(getApplicationContext(), 0,
                        ChangeVolumeTask.ACTION_TYPE_UP_VOL).execute();
                break;
            case SET_VOL_MINUS:
                new ChangeVolumeTask(getApplicationContext(), 0,
                        ChangeVolumeTask.ACTION_TYPE_DOWN_VOL).execute();
                break;

音量変更時の通知音声はこちらにならって、SoundManagerTaskで行っています。プラグインでAudioManagerを使用し、音声を再生します。
接続したBluetoothデバイスから音声出力するために再生時のStreamTypeはSTREAM_MUSICにしています。

THETAアプリ側のリモコン検索機能の抑制

本プラグインでは実装していませんが、プラグインでBluetooth Client機能を使用する場合は、THETAアプリ側のBluetoothリモコン機能をOFFにする必要があります。(2020/11/20にRICOH THETA Plug-in STOREに公開したVer1.2.0から対応しました)
Bluetoothリモコン機能はWebAPI Options:_bluetoothRoleで切り替えることができます。

_bluetoothRole Bluetoothリモコン機能
Peripheral OFF
Central ON
Central_Peripheral ON

例えば、プラグイン起動時にonCreate()でOptions:_bluetoothRoleを取得し、onPause()で取得済みのRoleに戻せば、プラグイン起動中のみTHETA本体のBluetoothリモコン機能をOFFすることが可能です。

サービスの終了(2020/11/24追記)

今回、Bluetooth Classicで接続したオーディオデバイス側のボタン操作を拾うためにサービスを利用しました。一般スマートフォンでは、音楽プレーヤーアプリを作るときに利用する手法です。サービスは、正しく終了させないとアプリケーション終了後も処理が残ってしまう可能性があります。
確実に終了させましょう。

Googleのドキュメントには以下の記述があります

サービスの全体の生存期間は、onCreate() が呼び出されてから、onDestroy() から戻るまでの間です。
アクティビティと同様に、サービスは onCreate() で初期セットアップを行い、onDestroy() で残りのすべてのリソースを解放します。
たとえば、音楽再生サービスでは、音楽を再生するスレッドを onCreate() で作成でき、onDestroy() でスレッドを停止できます。

本プラグインでも上記に従って onDestroyメソッドのOverrideを追記し、サービスの終了とメディアレシーバーの登録解除をしています。

MainActivity.java
    @Override
    protected void onDestroy() {
        getApplicationContext()
                .stopService(new Intent(getApplicationContext(), BluetoothClientService.class));
        if (mMediaReceiver != null) {
            getApplicationContext().unregisterReceiver(mMediaReceiver);
        }

        super.onDestroy();
    }

動作確認したBluetoothオーディオ機器

身近な方々に協力して頂き、もう少し多くのBluetoothオーディオ機器で動作確認してみました。
一覧にして結果をまとめておきます。

オーディオ機器 再生/一時停止
(撮影)
曲送り
(露出+)
曲戻し
(露出-)
Vol+ Vol-
Anker
Icon Mini
(スピーカー)
Anker
Liberty Air
(イヤホン)

操作しにくい

操作しにくい
N/A N/A
Anker
Zolo Liberty
(イヤホン)
×
不安定
×
不安定
N/A N/A
JPRiDE
TWS-520
(イヤホン)
N/A N/A
VANKYO
X100
(イヤホン)

操作しにくい

操作しにくい
Apple
Air Pods Pro
(イヤホン)
N/A N/A
  • N/Aはイヤホン側に機能がありません
  • JPRiDE TWS-520のマニュアルには1回タップで音量調節できる主旨の説明がありますが、実際にはスマートフォン(iOS,Androidどちらも)でも認識できなかったのでN/A扱いにしました。
  • Anker Liberty Airは、タッチ認識位置とタッチ感度の都合、イヤホン操作自体が難かったです。
  • VANKYO X100の音量操作はロングタップです。タップ中に連続してキーコードが送出されるため狙いの音量とするのが難しいです。とはいえ、他の操作は行いやすく、Amazonのタイムセールで3000円程度で購入できることもあります。THETA専用に購入してしまうのもアリかと。おすすめです。
  • Apple Air Pods Proは、Android OSの機器とペアリングする際、ケースのボタンを押してペアリング状態にする必要があります。ご注意ください。

ダイソーの330円片耳イヤホン(新型)でフルコントロール出来ました(2021/6/2 追記)

2020年の年末くらいからでしょうか、ダイソー片耳イヤホンに、以下2種類が追加されたようです。
MicrosoftTeams-image (3).png
*左: コンパクトでボタンが少ない機種(型番 E-BT-ME1)
*右: 通常サイズでボタンがしっかりある機種(型番 E-BT-ME2)

どちらもTHETAとペアリングして、本プラグインを利用できますが、フルコントロールができるのは右側です。

THETA動作 型番:E-BT-ME1
コンパクトサイズ
型番:E-BT-ME2
通常サイズ
静止画撮影 再生/一時停止 再生/一時停止
露出補正を1ステップアップ 曲送り 曲送り
露出補正を1ステップダウン 曲戻し
通常通知音の音量1ステップアップ 音量アップ
通常通知音の音量1ステップダウン 音量ダウン

この2機種より以前から販売されている、パッケージが大きいタイプ(右の通常サイズと見栄えは同じ)では、本プラグインを利用できないのでご注意ください。
新旧どちらも店頭に並んでいますので、製品型番「E-BT-ME2」で探すのが良いと思います。

2021年5月末時点、ダイソーでは、1100円で完全独立bluetoothイヤホンも発売されたようなのですが、イヤホンをTHETAのリモコンとして使うなら、片耳のものが使いやすいです。

まとめ

今回の事例ではBluetoothデバイスによるリモート撮影をご紹介しました。Bluetoothイヤホンを使ったお手軽なリモート操作をぜひ試してください。
イヤホンの音声アシスタント起動も試しましたが、通知を拾えたり、拾えなかったりと挙動がいまいち掴めませんでした。
今回のハマリポイントは、音量変更時の音声再生でした。プラグインでの音量変更時の通知音をTHETA側にIntentで依頼するとストリームボリューム音量の変更イベント通知が止まらない現象が発生しました。
原因は、AVRCPの音量変更イベントを拾うために、ストリームボリューム音量変化時にシャッターボリューム音量をストリームボリューム音量へ調整する処理を行っていたためです。
THETA側での音声再生時は、ストリームボリューム音量をシャッターボリューム音量へ調整してから、音声再生を行います。
そのため、プラグイン側でシャッターボリューム音量の変更、THETA側でストリームボリューム音量の変更を時間差で延々と調整し続けていました。
対策として、プラグイン側でAudioManegerを用意し、音声再生を行いました。これにより、ストリームボリュームの調整とシャッターボリュームの音量調整の同期がとれ問題を解消できました。

RICOH THETAプラグインパートナープログラムについて

THETAプラグインをご存じない方はこちらをご覧ください。
パートナープログラムへの登録方法はこちらにもまとめてあります。
QiitaのRICOH THETAプラグイン開発者コミュニティ TOPページ「About」に便利な記事リンク集もあります。
興味を持たれた方はTwitterのフォローとTHETAプラグイン開発者コミュニティ(Slack)への参加もよろしくおねがいします。

7
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
12