[RN] Add audio route picker

Due to the difference in nature, the iOS and Android implementations are
completely different:

iOS: MPVolumeView is used, which allows us to place a button which will launch a
native route picker provided by iOS itself. This view is different depending on
the iOS version, with the iOS 11 version being more complete.

Android: A completely custom component is used, which displays a bottom sheet
with the device categories, not devices individually. This is akin to the sheet
in the builtin dialer.
This commit is contained in:
Saúl Ibarra Corretgé 2017-10-24 10:40:39 +02:00 committed by Lyubo Marinov
parent 8198e52b93
commit f973a695d8
15 changed files with 1238 additions and 458 deletions

View File

@ -21,6 +21,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.os.Build;
@ -28,13 +29,19 @@ import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* Module implementing a simple API to select the appropriate audio device for a
@ -102,10 +109,53 @@ class AudioModeModule extends ReactContextBaseJavaModule {
private final Handler mainThreadHandler
= new Handler(Looper.getMainLooper());
/**
* {@link Runnable} for running audio device detection the main thread.
* This is only used on Android >= M.
*/
private final Runnable onAudioDeviceChangeRunner = new Runnable() {
@TargetApi(Build.VERSION_CODES.M)
@Override
public void run() {
Set<String> devices = new HashSet<>();
AudioDeviceInfo[] deviceInfos
= audioManager.getDevices(AudioManager.GET_DEVICES_ALL);
for (AudioDeviceInfo info: deviceInfos) {
switch (info.getType()) {
case AudioDeviceInfo.TYPE_BLUETOOTH_SCO:
devices.add(DEVICE_BLUETOOTH);
break;
case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE:
devices.add(DEVICE_EARPIECE);
break;
case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER:
devices.add(DEVICE_SPEAKER);
break;
case AudioDeviceInfo.TYPE_WIRED_HEADPHONES:
case AudioDeviceInfo.TYPE_WIRED_HEADSET:
devices.add(DEVICE_HEADPHONES);
break;
}
}
availableDevices = devices;
Log.d(TAG, "Available audio devices: " +
availableDevices.toString());
// Reset user selection
userSelectedDevice = null;
if (mode != -1) {
updateAudioRoute(mode);
}
}
};
/**
* {@link Runnable} for running update operation on the main thread.
*/
private final Runnable mainThreadRunner
private final Runnable updateAudioRouteRunner
= new Runnable() {
@Override
public void run() {
@ -120,6 +170,30 @@ class AudioModeModule extends ReactContextBaseJavaModule {
*/
private int mode = -1;
/**
* Audio device types.
*/
private static final String DEVICE_BLUETOOTH = "BLUETOOTH";
private static final String DEVICE_EARPIECE = "EARPIECE";
private static final String DEVICE_HEADPHONES = "HEADPHONES";
private static final String DEVICE_SPEAKER = "SPEAKER";
/**
* List of currently available audio devices.
*/
private Set<String> availableDevices = Collections.emptySet();
/**
* Currently selected device.
*/
private String selectedDevice;
/**
* User selected device. When null the default is used depending on the
* mode.
*/
private String userSelectedDevice;
/**
* Initializes a new module instance. There shall be a single instance of
* this module throughout the lifetime of the application.
@ -136,6 +210,20 @@ class AudioModeModule extends ReactContextBaseJavaModule {
// Setup runtime device change detection.
setupAudioRouteChangeDetection();
// Do an initial detection on Android >= M.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mainThreadHandler.post(onAudioDeviceChangeRunner);
} else {
// On Android < M, detect if we have an earpiece.
PackageManager pm = reactContext.getPackageManager();
if (pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
availableDevices.add(DEVICE_EARPIECE);
}
// Always assume there is a speaker.
availableDevices.add(DEVICE_SPEAKER);
}
}
/**
@ -155,6 +243,36 @@ class AudioModeModule extends ReactContextBaseJavaModule {
return constants;
}
/**
* Gets the list of available audio device categories, i.e. 'bluetooth',
* 'earpiece ', 'speaker', 'headphones'.
*
* @param promise a {@link Promise} which will be resolved with an object
* containing a 'devices' key with a list of devices, plus a
* 'selected' key with the selected one.
*/
@ReactMethod
public void getAudioDevices(final Promise promise) {
mainThreadHandler.post(new Runnable() {
@Override
public void run() {
WritableMap map = Arguments.createMap();
map.putString("selected", selectedDevice);
WritableArray devices = Arguments.createArray();
for (String device : availableDevices) {
if (mode == VIDEO_CALL && device.equals(DEVICE_EARPIECE)) {
// Skip earpiece when in video call mode.
continue;
}
devices.pushString(device);
}
map.putArray("devices", devices);
promise.resolve(map);
}
});
}
/**
* Gets the name for this module to be used in the React Native bridge.
*
@ -168,9 +286,81 @@ class AudioModeModule extends ReactContextBaseJavaModule {
/**
* Helper method to trigger an audio route update when devices change. It
* makes sure the operation is performed on the main thread.
*
* Only used on Android >= M.
*/
void onAudioDeviceChange() {
mainThreadHandler.post(mainThreadRunner);
mainThreadHandler.post(onAudioDeviceChangeRunner);
}
/**
* Helper method to trigger an audio route update when Bluetooth devices are
* connected / disconnected.
*
* Only used on Android < M. Runs on the main thread.
*/
void onBluetoothDeviceChange() {
if (bluetoothHeadsetMonitor.isHeadsetAvailable()) {
availableDevices.add(DEVICE_BLUETOOTH);
} else {
availableDevices.remove(DEVICE_BLUETOOTH);
}
if (mode != -1) {
updateAudioRoute(mode);
}
}
/**
* Helper method to trigger an audio route update when a headset is plugged
* or unplugged.
*
* Only used on Android < M.
*/
void onHeadsetDeviceChange() {
mainThreadHandler.post(new Runnable() {
@Override
public void run() {
// XXX: isWiredHeadsetOn is not deprecated when used just for
// knowing if there is a wired headset connected, regardless of
// audio being routed to it.
//noinspection deprecation
if (audioManager.isWiredHeadsetOn()) {
availableDevices.add(DEVICE_HEADPHONES);
} else {
availableDevices.remove(DEVICE_HEADPHONES);
}
if (mode != -1) {
updateAudioRoute(mode);
}
}
});
}
/**
* Sets the user selected audio device as the active audio device.
*
* @param device the desired device which will become active.
*/
@ReactMethod
public void setAudioDevice(final String device) {
mainThreadHandler.post(new Runnable() {
@Override
public void run() {
if (!availableDevices.contains(device)) {
Log.d(TAG, "Audio device not available: " + device);
userSelectedDevice = null;
return;
}
if (mode != -1) {
Log.d(TAG, "User selected device set to: " + device);
userSelectedDevice = device;
updateAudioRoute(mode);
}
}
});
}
/**
@ -278,7 +468,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
@Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "Wired headset added / removed");
onAudioDeviceChange();
onHeadsetDeviceChange();
}
};
context.registerReceiver(wiredHeadsetReceiver, wiredHeadSetFilter);
@ -302,6 +492,8 @@ class AudioModeModule extends ReactContextBaseJavaModule {
audioManager.abandonAudioFocus(null);
audioManager.setSpeakerphoneOn(false);
setBluetoothAudioRoute(false);
selectedDevice = null;
userSelectedDevice = null;
return true;
}
@ -318,31 +510,42 @@ class AudioModeModule extends ReactContextBaseJavaModule {
return false;
}
boolean useSpeaker = (mode == VIDEO_CALL);
boolean bluetoothAvailable = availableDevices.contains(DEVICE_BLUETOOTH);
boolean earpieceAvailable = availableDevices.contains(DEVICE_EARPIECE);
boolean headsetAvailable = availableDevices.contains(DEVICE_HEADPHONES);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// On Android >= M we use the AudioDeviceCallback API, so turn on
// Bluetooth SCO from the start.
if (audioManager.isBluetoothScoAvailableOffCall()) {
audioManager.startBluetoothSco();
}
// Pick the desired device based on what's available and the mode.
String audioDevice;
if (bluetoothAvailable) {
audioDevice = DEVICE_BLUETOOTH;
} else if (headsetAvailable) {
audioDevice = DEVICE_HEADPHONES;
} else if (mode == AUDIO_CALL && earpieceAvailable) {
audioDevice = DEVICE_EARPIECE;
} else {
// On older Android versions we must set the Bluetooth route
// manually. Also disable the speaker in that case.
setBluetoothAudioRoute(
bluetoothHeadsetMonitor.isHeadsetAvailable());
if (bluetoothHeadsetMonitor.isHeadsetAvailable()) {
useSpeaker = false;
}
audioDevice = DEVICE_SPEAKER;
}
// XXX: isWiredHeadsetOn is not deprecated when used just for knowing if
// there is a wired headset connected, regardless of audio being routed
// to it.
audioManager.setSpeakerphoneOn(
useSpeaker
&& !(audioManager.isWiredHeadsetOn()
|| audioManager.isBluetoothScoOn()));
// Consider the user's selection
if (userSelectedDevice != null
&& availableDevices.contains(userSelectedDevice)) {
audioDevice = userSelectedDevice;
}
// If the previously selected device and the current default one
// match, do nothing.
if (selectedDevice != null && selectedDevice.equals(audioDevice)) {
return true;
}
selectedDevice = audioDevice;
Log.d(TAG, "Selected audio device: " + audioDevice);
// Turn bluetooth on / off
setBluetoothAudioRoute(audioDevice.equals(DEVICE_BLUETOOTH));
// Turn speaker on / off
audioManager.setSpeakerphoneOn(audioDevice.equals(DEVICE_SPEAKER));
return true;
}

View File

@ -71,7 +71,7 @@ class BluetoothHeadsetMonitor {
headsetAvailable
= (headset != null)
&& !headset.getConnectedDevices().isEmpty();
audioModeModule.onAudioDeviceChange();
audioModeModule.onBluetoothDeviceChange();
}
};

Binary file not shown.

View File

@ -9,10 +9,13 @@
<glyph unicode="&#x20;" d="" />
<glyph unicode="&#xe0cd;" glyph-name="phone" d="M282 564c62-120 162-220 282-282l94 94c12 12 30 16 44 10 48-16 100-24 152-24 24 0 42-18 42-42v-150c0-24-18-42-42-42-400 0-726 326-726 726 0 24 18 42 42 42h150c24 0 42-18 42-42 0-54 8-104 24-152 4-14 2-32-10-44z" />
<glyph unicode="&#xe145;" glyph-name="add" d="M810 470h-256v-256h-84v256h-256v84h256v256h84v-256h256v-84z" />
<glyph unicode="&#xe1aa;" glyph-name="bluetooth" d="M550 328l-80 82v-162zM470 776v-162l80 82zM670 696l-184-184 184-184-244-242h-42v324l-196-196-60 60 238 238-238 238 60 60 196-196v324h42zM834 738c40-64 62-142 62-222 0-84-24-160-66-226l-50 50c26 52 42 110 42 172s-16 120-42 172zM608 512l98 98c12-30 20-64 20-98s-8-70-20-100z" />
<glyph unicode="&#xe310;" glyph-name="headset" d="M512 982c212 0 384-172 384-384v-300c0-70-58-128-128-128h-128v342h170v86c0 166-132 298-298 298s-298-132-298-298v-86h170v-342h-128c-70 0-128 58-128 128v300c0 212 172 384 384 384z" />
<glyph unicode="&#xe5d4;" glyph-name="thumb-menu" d="M512 342c46 0 86-40 86-86s-40-86-86-86-86 40-86 86 40 86 86 86zM512 598c46 0 86-40 86-86s-40-86-86-86-86 40-86 86 40 86 86 86zM512 682c-46 0-86 40-86 86s40 86 86 86 86-40 86-86-40-86-86-86z" />
<glyph unicode="&#xe603;" glyph-name="presentation" horiz-adv-x="1088" d="M952.495 1019.065h-818.689c-72.81 0-132.183-60.63-132.183-135.162v-750.719c0-74.473 59.372-135.101 132.183-135.101h818.686c72.936 0 132.314 60.625 132.314 135.101v750.722c0.003 74.532-59.378 135.159-132.311 135.159zM946.346 139.651h-806.14v737.822h806.015l0.126-737.822zM685.753 738.544h216.911v-566.758h-216.911v566.758zM428.672 610.002h216.911v-438.216h-216.911v438.216zM172.339 481.46h216.161v-309.677h-216.161v309.677z" />
<glyph unicode="&#xe613;" glyph-name="recDisable" horiz-adv-x="1140" d="M1123.444 1003.015c-23.593 26.481-64.131 28.989-90.74 5.395l-1008.269-893.436c-26.609-23.468-28.991-64.131-5.46-90.676 12.674-14.306 30.308-21.649 48.126-21.649 15.123 0 30.372 5.401 42.544 16.195l130.045 115.22c90.743-81.844 210.569-132.165 342.473-132.101 282.816 0.061 510.913 227.969 511.287 510.972 0.126 109.934-34.682 211.367-93.499 294.72l118.088 104.625c26.483 23.526 28.997 64.129 5.404 90.735zM944.422 513.818c0.128-200.922-161.896-363.201-362.509-362.952-87.56 0.123-167.573 31.151-230.061 82.569l331.277 293.509v-73.176c1.071-60.993 32.696-92.18 94.944-93.692 61.997 1.512 93.686 32.763 95.131 93.756v41.096h-72.227v-47.499c0.251-4.642-0.564-10.607-2.511-17.949-1.25-3.261-3.448-6.020-6.525-8.093-3.197-2.572-7.845-3.828-13.868-3.828-10.543 0.31-17.132 4.268-19.827 11.921-1.068 3.512-1.947 6.905-2.508 10.163-0.254 2.887-0.377 5.532-0.377 7.786v143.511l42.477 37.634c0.215-0.432 0.452-0.851 0.63-1.303 1.947-6.467 2.762-12.799 2.511-19.076v-36.772h72.227v30.121c-0.246 31.245-9.086 54.699-26.363 70.447l40.711 36.069c35.787-56.055 56.803-122.585 56.867-194.244zM239.795 395.47c-12.613 37.023-19.827 76.557-19.827 117.913-0.19 200.236 161.584 362.009 361.945 362.135 56.853 0 110.313-13.302 158.133-36.398l117.846 104.421c-79.444 50.952-173.758 80.817-275.292 80.948-283.377 0.181-511.354-227.729-511.789-511.675-0.126-79.567 18.636-154.679 51.137-221.882l117.848 104.538zM388.576 690.020h-97.514v-249.057l72.23 64.070v0.689h0.815l117.72 104.418c0 0.564 0.123 0.94 0.123 1.509 0.753 53.898-30.369 80.069-93.374 78.37zM405.959 625.517c1.942-2.767 3.074-6.469 3.323-11.112 0.312-4.452 0.438-9.6 0.438-15.246 0.251-10.916-0.689-19.83-2.949-26.985-2.952-7.594-10.983-11.357-24.159-11.357h-19.325v74.043h15.31c7.842 0 13.865-0.683 18.072-2.19 4.397-1.573 7.468-3.953 9.29-7.153z" />
<glyph unicode="&#xe614;" glyph-name="recEnable" horiz-adv-x="1142" d="M581.278 1025.708c284.857-0.19 514.807-230.517 514.427-514.997-0.378-285.047-230.073-514.553-514.869-514.615-284.541-0.062-515.311 230.517-514.933 514.422 0.439 285.936 230.009 515.439 515.375 515.19zM580.579 875.756c-201.764-0.123-364.666-163.032-364.478-364.663 0-202.018 162.524-364.735 364.478-364.984 202.018-0.316 365.174 163.030 365.048 365.423-0.252 201.767-163.156 364.35-365.048 364.224zM287.698 688.907h98.196c63.442 1.767 94.785-24.518 94.027-78.863 0.254-19.081-2.211-34.882-7.456-47.521-6.005-12.508-18.706-21.988-38.167-28.181v-0.819c28.373-6.259 43.031-23.573 43.981-51.946v-57.689c0-11.247 0.254-22.813 0.758-34.756 0.819-12.005 3.033-20.979 6.696-27.043h-71.846c-3.727 6.064-6.128 15.038-7.14 27.043-1.012 11.943-1.454 23.509-1.138 34.756v52.321c0 9.603-2.214 16.553-6.573 20.979-4.675 4.107-12.701 6.19-24.012 6.19h-14.599v-141.291h-72.73v326.82zM360.428 558.861h19.463c13.271 0 21.359 3.794 24.331 11.375 2.276 7.204 3.221 16.304 2.969 27.171 0 5.815-0.126 10.867-0.442 15.418-0.252 4.675-1.392 8.404-3.352 11.247-1.831 3.157-4.926 5.561-9.352 7.14-4.233 1.454-10.299 2.211-18.2 2.211h-15.418v-74.564zM498.372 688.907h162.082v-62.687h-89.35v-65.587h78.103v-62.685h-78.103v-73.11h92.822v-62.749h-165.557v326.818zM682.507 599.999c0.316 31.782 9.416 55.542 27.425 71.407 17.44 15.29 40.185 22.936 68.181 22.936 28.247 0 51.119-7.646 68.623-23 17.82-15.798 26.92-39.623 27.171-71.407v-30.333h-72.73v37.031c0.254 6.192-0.57 12.639-2.527 19.209-1.264 3.157-3.475 5.938-6.573 8.214-3.221 1.515-7.898 2.404-13.964 2.404-10.615-0.316-17.249-3.855-19.967-10.618-2.211-6.573-3.223-13.017-2.907-19.209v-161.956c0-2.273 0.126-4.865 0.38-7.772 0.568-3.411 1.454-6.824 2.527-10.233 2.717-7.775 9.352-11.756 19.967-12.007 6.067 0 10.744 1.261 13.964 3.791 3.098 2.15 5.309 4.867 6.573 8.216 1.96 7.33 2.782 13.33 2.527 18.007v47.837h72.73v-41.328c-1.451-61.547-33.364-93.015-95.794-94.469-62.685 1.454-94.53 32.922-95.607 94.343v148.937z" />
<glyph unicode="&#xe61d;" glyph-name="phone-talk" d="M640 512c0 70-58 128-128 128v86c118 0 214-96 214-214h-86zM810 512c0 166-132 298-298 298v86c212 0 384-172 384-384h-86zM854 362c24 0 42-18 42-42v-150c0-24-18-42-42-42-400 0-726 326-726 726 0 24 18 42 42 42h150c24 0 42-18 42-42 0-54 8-104 24-152 4-14 2-32-10-44l-94-94c62-122 162-220 282-282l94 94c12 12 30 14 44 10 48-16 98-24 152-24z" />
<glyph unicode="&#xe901;" glyph-name="avatar" d="M512 204c106 0 200 56 256 138-2 84-172 132-256 132-86 0-254-48-256-132 56-82 150-138 256-138zM512 810c-70 0-128-58-128-128s58-128 128-128 128 58 128 128-58 128-128 128zM512 938c236 0 426-190 426-426s-190-426-426-426-426 190-426 426 190 426 426 426z" />
<glyph unicode="&#xe902;" glyph-name="download" d="M726 470h-128v170h-172v-170h-128l214-214zM826 596c110-8 198-100 198-212 0-118-96-214-214-214h-554c-142 0-256 114-256 256 0 132 100 240 228 254 54 102 160 174 284 174 156 0 284-110 314-258z" />
<glyph unicode="&#xe903;" glyph-name="mic-camera-combined" d="M756.704 628.138l267.296 202.213v-635.075l-267.296 202.213v-191.923c0-12.085-11.296-21.863-25.216-21.863h-706.272c-13.92 0-25.216 9.777-25.216 21.863v612.25c0 12.085 11.296 21.863 25.216 21.863h706.272c13.92 0 25.216-9.777 25.216-21.863v-189.679zM371.338 376.228c47.817 0 86.529 40.232 86.529 89.811v184.835c0 49.651-38.713 89.883-86.529 89.883-47.788 0-86.515-40.232-86.515-89.883v-184.835c0-49.579 38.756-89.811 86.515-89.811v0zM356.754 314.070v-32.78h33.718v33.412c73.858 9.606 131.235 73.73 131.235 151.351v88.232h-30.636v-88.232c0-67.57-53.696-122.534-119.734-122.534-66.024 0-119.691 54.964-119.691 122.534v88.232h-30.636v-88.232c0-79.215 59.674-144.502 135.744-151.969v-0.014z" />

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Binary file not shown.

View File

@ -1,6 +1,197 @@
{
"IcoMoonType": "selection",
"icons": [
{
"icon": {
"paths": [
"M550 696l-80-82v162zM470 248v162l80-82zM670 328l-184 184 184 184-244 242h-42v-324l-196 196-60-60 238-238-238-238 60-60 196 196v-324h42zM834 286c40 64 62 142 62 222 0 84-24 160-66 226l-50-50c26-52 42-110 42-172s-16-120-42-172zM608 512l98-98c12 30 20 64 20 98s-8 70-20 100z"
],
"attrs": [],
"isMulticolor": false,
"isMulticolor2": false,
"tags": [
"bluetooth_searching"
],
"defaultCode": 57770,
"grid": 24
},
"attrs": [],
"properties": {
"ligatures": "bluetooth_audio, bluetooth_searching",
"id": 79,
"order": 911,
"prevSize": 24,
"code": 57770,
"name": "bluetooth"
},
"setIdx": 0,
"setId": 2,
"iconIdx": 79
},
{
"icon": {
"paths": [
"M512 42c212 0 384 172 384 384v300c0 70-58 128-128 128h-128v-342h170v-86c0-166-132-298-298-298s-298 132-298 298v86h170v342h-128c-70 0-128-58-128-128v-300c0-212 172-384 384-384z"
],
"attrs": [],
"isMulticolor": false,
"isMulticolor2": false,
"tags": [
"headset"
],
"defaultCode": 58128,
"grid": 24
},
"attrs": [],
"properties": {
"ligatures": "headset",
"id": 376,
"order": 910,
"prevSize": 24,
"code": 58128,
"name": "headset"
},
"setIdx": 0,
"setId": 2,
"iconIdx": 376
},
{
"icon": {
"paths": [
"M640 512c0-70-58-128-128-128v-86c118 0 214 96 214 214h-86zM810 512c0-166-132-298-298-298v-86c212 0 384 172 384 384h-86zM854 662c24 0 42 18 42 42v150c0 24-18 42-42 42-400 0-726-326-726-726 0-24 18-42 42-42h150c24 0 42 18 42 42 0 54 8 104 24 152 4 14 2 32-10 44l-94 94c62 122 162 220 282 282l94-94c12-12 30-14 44-10 48 16 98 24 152 24z"
],
"attrs": [],
"isMulticolor": false,
"isMulticolor2": false,
"tags": [
"phone_in_talk"
],
"defaultCode": 58909,
"grid": 24
},
"attrs": [],
"properties": {
"ligatures": "phone_in_talk",
"id": 566,
"order": 912,
"prevSize": 24,
"code": 58909,
"name": "phone-talk"
},
"setIdx": 0,
"setId": 2,
"iconIdx": 566
},
{
"icon": {
"paths": [
"M512 682c46 0 86 40 86 86s-40 86-86 86-86-40-86-86 40-86 86-86zM512 426c46 0 86 40 86 86s-40 86-86 86-86-40-86-86 40-86 86-86zM512 342c-46 0-86-40-86-86s40-86 86-86 86 40 86 86-40 86-86 86z"
],
"attrs": [],
"isMulticolor": false,
"isMulticolor2": false,
"tags": [
"more_vert"
],
"defaultCode": 58836,
"grid": 24
},
"attrs": [],
"properties": {
"ligatures": "more_vert",
"id": 0,
"order": 897,
"prevSize": 24,
"code": 58836,
"name": "thumb-menu"
},
"setIdx": 1,
"setId": 1,
"iconIdx": 40
},
{
"icon": {
"paths": [
"M330.667 554.667c-0.427-14.933 6.4-29.44 17.92-39.253 32 6.827 61.867 20.053 88.747 39.253 0 29.013-23.893 52.907-53.333 52.907s-52.907-23.467-53.333-52.907zM586.667 554.667c26.88-18.773 56.747-32 88.747-38.827 11.52 9.813 18.347 24.32 17.92 38.827 0 29.867-23.893 53.76-53.333 53.76s-53.333-23.893-53.333-53.76v0zM512 384c-118.187-1.707-234.667 27.733-338.347 85.333l-2.987 42.667c0 52.48 12.373 104.107 35.84 151.040 101.12-15.36 203.093-23.040 305.493-23.040s204.373 7.68 305.493 23.040c23.467-46.933 35.84-98.56 35.84-151.040l-2.987-42.667c-103.68-57.6-220.16-87.040-338.347-85.333zM512 85.333c235.641 0 426.667 191.025 426.667 426.667s-191.025 426.667-426.667 426.667c-235.641 0-426.667-191.025-426.667-426.667s191.025-426.667 426.667-426.667z"
],
"attrs": [
{}
],
"isMulticolor": false,
"isMulticolor2": false,
"tags": [
"ninja"
],
"grid": 24
},
"attrs": [
{}
],
"properties": {
"order": 850,
"id": 1,
"name": "ninja",
"prevSize": 24,
"code": 59657
},
"setIdx": 1,
"setId": 1,
"iconIdx": 41
},
{
"icon": {
"paths": [
"M282 460c62 120 162 220 282 282l94-94c12-12 30-16 44-10 48 16 100 24 152 24 24 0 42 18 42 42v150c0 24-18 42-42 42-400 0-726-326-726-726 0-24 18-42 42-42h150c24 0 42 18 42 42 0 54 8 104 24 152 4 14 2 32-10 44z"
],
"attrs": [],
"isMulticolor": false,
"isMulticolor2": false,
"tags": [
"phone"
],
"defaultCode": 57549,
"grid": 24
},
"attrs": [],
"properties": {
"ligatures": "call, local_phone, phone",
"id": 2,
"order": 851,
"prevSize": 24,
"code": 57549,
"name": "phone"
},
"setIdx": 1,
"setId": 1,
"iconIdx": 42
},
{
"icon": {
"paths": [
"M810 554h-256v256h-84v-256h-256v-84h256v-256h84v256h256v84z"
],
"attrs": [],
"isMulticolor": false,
"isMulticolor2": false,
"tags": [
"add"
],
"defaultCode": 57669,
"grid": 24
},
"attrs": [],
"properties": {
"ligatures": "add",
"id": 3,
"order": 896,
"prevSize": 24,
"code": 57669,
"name": "add"
},
"setIdx": 1,
"setId": 1,
"iconIdx": 43
},
{
"icon": {
"paths": [
@ -26,7 +217,7 @@
"prevSize": 32,
"code": 59686
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 0
},
@ -55,7 +246,7 @@
"prevSize": 32,
"code": 59682
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 1
},
@ -84,7 +275,7 @@
"prevSize": 32,
"code": 59651
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 2
},
@ -113,7 +304,7 @@
"prevSize": 32,
"code": 59677
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 3
},
@ -142,7 +333,7 @@
"prevSize": 32,
"code": 59676
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 4
},
@ -168,7 +359,7 @@
"code": 59649,
"name": "avatar"
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 5
},
@ -194,7 +385,7 @@
"code": 59653,
"name": "hangup"
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 6
},
@ -220,7 +411,7 @@
"code": 59654,
"name": "chat"
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 7
},
@ -246,7 +437,7 @@
"code": 59650,
"name": "download"
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 8
},
@ -272,7 +463,7 @@
"code": 59655,
"name": "edit"
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 9
},
@ -298,7 +489,7 @@
"code": 59656,
"name": "share-doc"
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 10
},
@ -324,7 +515,7 @@
"code": 59652,
"name": "kick"
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 11
},
@ -350,7 +541,7 @@
"code": 59679,
"name": "menu-up"
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 12
},
@ -376,7 +567,7 @@
"code": 59680,
"name": "menu-down"
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 13
},
@ -402,7 +593,7 @@
"code": 59659,
"name": "full-screen"
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 14
},
@ -428,7 +619,7 @@
"code": 59660,
"name": "exit-full-screen"
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 15
},
@ -454,7 +645,7 @@
"code": 59658,
"name": "star-full"
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 16
},
@ -480,7 +671,7 @@
"code": 59661,
"name": "security"
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 17
},
@ -506,7 +697,7 @@
"code": 59662,
"name": "security-locked"
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 18
},
@ -532,7 +723,7 @@
"code": 59663,
"name": "reload"
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 19
},
@ -558,7 +749,7 @@
"code": 59664,
"name": "microphone"
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 20
},
@ -584,7 +775,7 @@
"code": 59665,
"name": "mic-empty"
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 21
},
@ -610,7 +801,7 @@
"code": 59666,
"name": "mic-disabled"
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 22
},
@ -636,7 +827,7 @@
"code": 59678,
"name": "raised-hand"
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 23
},
@ -662,7 +853,7 @@
"code": 59675,
"name": "contactList"
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 24
},
@ -688,7 +879,7 @@
"code": 59667,
"name": "link"
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 25
},
@ -714,7 +905,7 @@
"code": 59668,
"name": "shared-video"
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 26
},
@ -740,7 +931,7 @@
"code": 59669,
"name": "settings"
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 27
},
@ -766,7 +957,7 @@
"code": 59670,
"name": "star"
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 28
},
@ -792,7 +983,7 @@
"code": 59681,
"name": "switch-camera"
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 29
},
@ -818,7 +1009,7 @@
"code": 59671,
"name": "share-desktop"
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 30
},
@ -844,7 +1035,7 @@
"code": 59672,
"name": "camera"
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 31
},
@ -870,7 +1061,7 @@
"code": 59673,
"name": "camera-disabled"
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 32
},
@ -896,7 +1087,7 @@
"code": 59674,
"name": "volume"
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 33
},
@ -925,9 +1116,9 @@
"name": "recDisable",
"ligatures": ""
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 36
"iconIdx": 34
},
{
"icon": {
@ -955,9 +1146,9 @@
"name": "recEnable",
"ligatures": ""
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 37
"iconIdx": 35
},
{
"icon": {
@ -985,9 +1176,9 @@
"name": "presentation",
"ligatures": ""
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 38
"iconIdx": 36
},
{
"icon": {
@ -1011,9 +1202,9 @@
"code": 59685,
"name": "dialpad"
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 39
"iconIdx": 37
},
{
"icon": {
@ -1037,9 +1228,9 @@
"code": 59683,
"name": "visibility"
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 40
"iconIdx": 38
},
{
"icon": {
@ -1063,119 +1254,9 @@
"code": 59684,
"name": "visibility-off"
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 41
},
{
"icon": {
"paths": [
"M512 682c46 0 86 40 86 86s-40 86-86 86-86-40-86-86 40-86 86-86zM512 426c46 0 86 40 86 86s-40 86-86 86-86-40-86-86 40-86 86-86zM512 342c-46 0-86-40-86-86s40-86 86-86 86 40 86 86-40 86-86 86z"
],
"attrs": [],
"isMulticolor": false,
"isMulticolor2": false,
"tags": [
"more_vert"
],
"defaultCode": 58836,
"grid": 24
},
"attrs": [],
"properties": {
"ligatures": "more_vert",
"id": 0,
"order": 897,
"prevSize": 24,
"code": 58836,
"name": "thumb-menu"
},
"setIdx": 0,
"setId": 1,
"iconIdx": 42
},
{
"icon": {
"paths": [
"M330.667 554.667c-0.427-14.933 6.4-29.44 17.92-39.253 32 6.827 61.867 20.053 88.747 39.253 0 29.013-23.893 52.907-53.333 52.907s-52.907-23.467-53.333-52.907zM586.667 554.667c26.88-18.773 56.747-32 88.747-38.827 11.52 9.813 18.347 24.32 17.92 38.827 0 29.867-23.893 53.76-53.333 53.76s-53.333-23.893-53.333-53.76v0zM512 384c-118.187-1.707-234.667 27.733-338.347 85.333l-2.987 42.667c0 52.48 12.373 104.107 35.84 151.040 101.12-15.36 203.093-23.040 305.493-23.040s204.373 7.68 305.493 23.040c23.467-46.933 35.84-98.56 35.84-151.040l-2.987-42.667c-103.68-57.6-220.16-87.040-338.347-85.333zM512 85.333c235.641 0 426.667 191.025 426.667 426.667s-191.025 426.667-426.667 426.667c-235.641 0-426.667-191.025-426.667-426.667s191.025-426.667 426.667-426.667z"
],
"attrs": [
{}
],
"isMulticolor": false,
"isMulticolor2": false,
"tags": [
"ninja"
],
"grid": 24
},
"attrs": [
{}
],
"properties": {
"order": 850,
"id": 1,
"name": "ninja",
"prevSize": 24,
"code": 59657
},
"setIdx": 0,
"setId": 1,
"iconIdx": 43
},
{
"icon": {
"paths": [
"M282 460c62 120 162 220 282 282l94-94c12-12 30-16 44-10 48 16 100 24 152 24 24 0 42 18 42 42v150c0 24-18 42-42 42-400 0-726-326-726-726 0-24 18-42 42-42h150c24 0 42 18 42 42 0 54 8 104 24 152 4 14 2 32-10 44z"
],
"attrs": [],
"isMulticolor": false,
"isMulticolor2": false,
"tags": [
"phone"
],
"defaultCode": 57549,
"grid": 24
},
"attrs": [],
"properties": {
"ligatures": "call, local_phone, phone",
"id": 2,
"order": 851,
"prevSize": 24,
"code": 57549,
"name": "phone"
},
"setIdx": 0,
"setId": 1,
"iconIdx": 44
},
{
"icon": {
"paths": [
"M810 554h-256v256h-84v-256h-256v-84h256v-256h84v256h256v84z"
],
"attrs": [],
"isMulticolor": false,
"isMulticolor2": false,
"tags": [
"add"
],
"defaultCode": 57669,
"grid": 24
},
"attrs": [],
"properties": {
"ligatures": "add",
"id": 3,
"order": 896,
"prevSize": 24,
"code": 57669,
"name": "add"
},
"setIdx": 0,
"setId": 1,
"iconIdx": 45
"iconIdx": 39
}
],
"height": 1024,

View File

@ -10,6 +10,7 @@
0B412F181EDEC65D00B1A0A6 /* JitsiMeetView.h in Headers */ = {isa = PBXBuildFile; fileRef = 0B412F161EDEC65D00B1A0A6 /* JitsiMeetView.h */; settings = {ATTRIBUTES = (Public, ); }; };
0B412F191EDEC65D00B1A0A6 /* JitsiMeetView.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B412F171EDEC65D00B1A0A6 /* JitsiMeetView.m */; };
0B412F221EDEF6EA00B1A0A6 /* JitsiMeetViewDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 0B412F1B1EDEC80100B1A0A6 /* JitsiMeetViewDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; };
0B44A0191F902126009D1D64 /* MPVolumeViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B44A0181F902126009D1D64 /* MPVolumeViewManager.m */; };
0B93EF7B1EC608550030D24D /* CoreText.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0B93EF7A1EC608550030D24D /* CoreText.framework */; };
0B93EF7E1EC9DDCD0030D24D /* RCTBridgeWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 0B93EF7C1EC9DDCD0030D24D /* RCTBridgeWrapper.h */; };
0B93EF7F1EC9DDCD0030D24D /* RCTBridgeWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B93EF7D1EC9DDCD0030D24D /* RCTBridgeWrapper.m */; };
@ -32,6 +33,7 @@
0B412F161EDEC65D00B1A0A6 /* JitsiMeetView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JitsiMeetView.h; sourceTree = "<group>"; };
0B412F171EDEC65D00B1A0A6 /* JitsiMeetView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JitsiMeetView.m; sourceTree = "<group>"; };
0B412F1B1EDEC80100B1A0A6 /* JitsiMeetViewDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JitsiMeetViewDelegate.h; sourceTree = "<group>"; };
0B44A0181F902126009D1D64 /* MPVolumeViewManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPVolumeViewManager.m; sourceTree = "<group>"; };
0B93EF7A1EC608550030D24D /* CoreText.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreText.framework; path = System/Library/Frameworks/CoreText.framework; sourceTree = SDKROOT; };
0B93EF7C1EC9DDCD0030D24D /* RCTBridgeWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBridgeWrapper.h; sourceTree = "<group>"; };
0B93EF7D1EC9DDCD0030D24D /* RCTBridgeWrapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBridgeWrapper.m; sourceTree = "<group>"; };
@ -108,6 +110,7 @@
0B412F161EDEC65D00B1A0A6 /* JitsiMeetView.h */,
0B412F171EDEC65D00B1A0A6 /* JitsiMeetView.m */,
0B412F1B1EDEC80100B1A0A6 /* JitsiMeetViewDelegate.h */,
0B44A0181F902126009D1D64 /* MPVolumeViewManager.m */,
0B93EF7C1EC9DDCD0030D24D /* RCTBridgeWrapper.h */,
0B93EF7D1EC9DDCD0030D24D /* RCTBridgeWrapper.m */,
0BCA495D1EC4B6C600B793EE /* POSIX.m */,
@ -293,6 +296,7 @@
0BA13D311EE83FF8007BEF7F /* ExternalAPI.m in Sources */,
0BCA49601EC4B6C600B793EE /* POSIX.m in Sources */,
0BCA495F1EC4B6C600B793EE /* AudioMode.m in Sources */,
0B44A0191F902126009D1D64 /* MPVolumeViewManager.m in Sources */,
0BCA49611EC4B6C600B793EE /* Proximity.m in Sources */,
0B412F191EDEC65D00B1A0A6 /* JitsiMeetView.m in Sources */,
);

View File

@ -0,0 +1,62 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <React/RCTUIManager.h>
#import <React/RCTViewManager.h>
@import MediaPlayer;
@interface MPVolumeViewManager : RCTViewManager
@end
@implementation MPVolumeViewManager
RCT_EXPORT_MODULE()
- (UIView *)view {
MPVolumeView *volumeView = [[MPVolumeView alloc] init];
volumeView.showsRouteButton = YES;
volumeView.showsVolumeSlider = NO;
return (UIView *) volumeView;
}
RCT_EXPORT_METHOD(show:(nonnull NSNumber *)reactTag) {
[self.bridge.uiManager addUIBlock:^(
__unused RCTUIManager *uiManager,
NSDictionary<NSNumber *, UIView *> *viewRegistry) {
id view = viewRegistry[reactTag];
if (![view isKindOfClass:[MPVolumeView class]]) {
RCTLogError(@"Invalid view returned from registry, expecting \
MPVolumeView, got: %@", view);
} else {
// Simulate a click
UIButton *btn = nil;
for (UIView *buttonView in ((UIView *) view).subviews) {
if ([buttonView isKindOfClass:[UIButton class]]) {
btn = (UIButton *) buttonView;
break;
}
}
if (btn != nil) {
[btn sendActionsForControlEvents:UIControlEventTouchUpInside];
}
}
}];
}
@end

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,178 @@
// @flow
import _ from 'lodash';
import React, { Component } from 'react';
import { NativeModules } from 'react-native';
import { connect } from 'react-redux';
import { hideDialog, SimpleBottomSheet } from '../../../base/dialog';
const AudioMode = NativeModules.AudioMode;
/**
* Maps each device type to a display name and icon.
* TODO: internationalization.
*/
const deviceInfoMap = {
BLUETOOTH: {
iconName: 'bluetooth',
text: 'Bluetooth',
type: 'BLUETOOTH'
},
EARPIECE: {
iconName: 'phone-talk',
text: 'Phone',
type: 'EARPIECE'
},
HEADPHONES: {
iconName: 'headset',
text: 'Headphones',
type: 'HEADPHONES'
},
SPEAKER: {
iconName: 'volume',
text: 'Speaker',
type: 'SPEAKER'
}
};
/**
* Variable to hold the reference to the exported component. This dialog is only
* exported if the {@code AudioMode} module has the capability to get / set
* audio devices.
*/
let DialogType;
/**
* {@code PasswordRequiredPrompt}'s React {@code Component} prop types.
*/
type Props = {
/**
* Used for hiding the dialog when the selection was completed.
*/
dispatch: Function
};
type State = {
/**
* Array of available devices.
*/
devices: Array<string>
};
/**
* Implements a React {@code Component} which prompts the user when a password
* is required to join a conference.
*/
class AudioRoutePickerDialog extends Component<Props, State> {
state = {
// Available audio devices, it will be set in componentWillMount.
devices: []
};
/**
* Initializes a new {@code PasswordRequiredPrompt} instance.
*
* @param {Props} props - The read-only React {@code Component} props with
* which the new instance is to be initialized.
*/
constructor(props) {
super(props);
// Bind event handlers so they are only bound once per instance.
this._onCancel = this._onCancel.bind(this);
this._onSubmit = this._onSubmit.bind(this);
}
/**
* Initializes the device list by querying the {@code AudioMode} module.
*
* @inheritdoc
*/
componentWillMount() {
AudioMode.getAudioDevices().then(({ devices, selected }) => {
const audioDevices = [];
if (devices) {
for (const device of devices) {
const info = deviceInfoMap[device];
if (info) {
info.selected = device === selected;
audioDevices.push(info);
}
}
}
if (audioDevices) {
// Make sure devices is alphabetically sorted
this.setState({ devices: _.sortBy(audioDevices, 'text') });
}
});
}
/**
* Dispatches a redux action to hide this sheet.
*
* @returns {void}
*/
_hide() {
this.props.dispatch(hideDialog(DialogType));
}
_onCancel: () => void;
/**
* Cancels the dialog by hiding it.
*
* @private
* @returns {void}
*/
_onCancel() {
this._hide();
}
_onSubmit: (?Object) => void;
/**
* Handles the selection of a device on the sheet. The selected device will
* be used by {@code AudioMode}.
*
* @param {Object} device - Object representing the selected device.
* @private
* @returns {void}
*/
_onSubmit(device) {
this._hide();
AudioMode.setAudioDevice(device.type);
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
if (!this.state.devices.length) {
return null;
}
return (
<SimpleBottomSheet
onCancel = { this._onCancel }
onSubmit = { this._onSubmit }
options = { this.state.devices } />
);
}
}
// Only export the dialog if we have support for getting / setting audio devices
// in AudioMode.
if (AudioMode.getAudioDevices && AudioMode.setAudioDevice) {
DialogType = connect()(AudioRoutePickerDialog);
}
export default DialogType;

View File

@ -0,0 +1,3 @@
export {
default as AudioRoutePickerDialog
} from './AudioRoutePickerDialog';

View File

@ -1 +1,3 @@
export * from './components';
import './middleware';

View File

@ -0,0 +1,160 @@
// @flow
import React, { Component } from 'react';
import {
findNodeHandle,
requireNativeComponent,
NativeModules,
View
} from 'react-native';
import { connect } from 'react-redux';
import { openDialog } from '../../base/dialog';
import { AudioRoutePickerDialog } from '../../mobile/audio-mode';
import ToolbarButton from './ToolbarButton';
/**
* Define the {@code MPVolumeView} React component. It will only be available
* on iOS.
*/
let MPVolumeView;
if (NativeModules.MPVolumeViewManager) {
MPVolumeView = requireNativeComponent('MPVolumeView', null);
}
/**
* Style required to hide the {@code MPVolumeView} view, since it's displayed
* programmatically.
*/
const HIDE_VIEW_STYLE = { display: 'none' };
type Props = {
/**
* Used to show the {@code AudioRoutePickerDialog}.
*/
dispatch: Function,
/**
* The name of the Icon of this {@code AudioRouteButton}.
*/
iconName: string,
/**
* The style of the Icon of this {@code AudioRouteButton}.
*/
iconStyle: Object,
/**
* {@code AudioRouteButton} styles.
*/
style: Array<*> | Object,
/**
* The color underlying the button.
*/
underlayColor: string
};
/**
* A toolbar button which triggers an audio route picker when pressed.
*/
class AudioRouteButton extends Component<Props> {
_volumeComponent: ?Object;
/**
* Indicates if there is support for audio device selection via this button.
*
* @returns {boolean} - True if audio device selection is supported, false
* otherwise.
*/
static supported() {
return Boolean(MPVolumeView || AudioRoutePickerDialog);
}
/**
* Initializes a new {@code AudioRouteButton} instance.
*
* @param {Object} props - The React {@code Component} props to initialize
* the new {@code AudioRouteButton} instance with.
*/
constructor(props) {
super(props);
/**
* The internal reference to the React {@code MPVolumeView} for
* showing the volume control view.
*
* @private
* @type {ReactComponent}
*/
this._volumeComponent = null;
// Bind event handlers so they are only bound once per instance.
this._onClick = this._onClick.bind(this);
this._setVolumeComponent = this._setVolumeComponent.bind(this);
}
_onClick: () => void;
/**
* Handles clicking/pressing this {@code AudioRouteButton} by showing an
* audio route picker.
*
* @private
* @returns {void}
*/
_onClick() {
if (MPVolumeView) {
const handle = findNodeHandle(this._volumeComponent);
NativeModules.MPVolumeViewManager.show(handle);
} else if (AudioRoutePickerDialog) {
this.props.dispatch(openDialog(AudioRoutePickerDialog));
}
}
_setVolumeComponent: (?Object) => void;
/**
* Sets the internal reference to the React Component wrapping the
* {@code MPVolumeView} component.
*
* @param {ReactComponent} component - React Component.
* @returns {void}
*/
_setVolumeComponent(component) {
this._volumeComponent = component;
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
const { iconName, iconStyle, style, underlayColor } = this.props;
return (
<View>
<ToolbarButton
iconName = { iconName }
iconStyle = { iconStyle }
onClick = { this._onClick }
style = { style }
underlayColor = { underlayColor } />
{
MPVolumeView
&& <MPVolumeView
ref = { this._setVolumeComponent }
style = { HIDE_VIEW_STYLE } />
}
</View>
);
}
}
export default connect()(AudioRouteButton);

View File

@ -25,6 +25,8 @@ import {
abstractMapDispatchToProps,
abstractMapStateToProps
} from '../functions';
import AudioRouteButton from './AudioRouteButton';
import styles from './styles';
import ToolbarButton from './ToolbarButton';
@ -310,6 +312,14 @@ class Toolbox extends Component {
style = { style }
underlayColor = { underlayColor } />
}
{
AudioRouteButton.supported()
&& <AudioRouteButton
iconName = { 'volume' }
iconStyle = { iconStyle }
style = { style }
underlayColor = { underlayColor } />
}
</View>
);