diff --git a/android/sdk/src/main/java/org/jitsi/meet/sdk/AudioModeModule.java b/android/sdk/src/main/java/org/jitsi/meet/sdk/AudioModeModule.java index 4ffe19e8a..54d34f170 100644 --- a/android/sdk/src/main/java/org/jitsi/meet/sdk/AudioModeModule.java +++ b/android/sdk/src/main/java/org/jitsi/meet/sdk/AudioModeModule.java @@ -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 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 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; } diff --git a/android/sdk/src/main/java/org/jitsi/meet/sdk/BluetoothHeadsetMonitor.java b/android/sdk/src/main/java/org/jitsi/meet/sdk/BluetoothHeadsetMonitor.java index 519809ec8..d4b04a941 100644 --- a/android/sdk/src/main/java/org/jitsi/meet/sdk/BluetoothHeadsetMonitor.java +++ b/android/sdk/src/main/java/org/jitsi/meet/sdk/BluetoothHeadsetMonitor.java @@ -71,7 +71,7 @@ class BluetoothHeadsetMonitor { headsetAvailable = (headset != null) && !headset.getConnectedDevices().isEmpty(); - audioModeModule.onAudioDeviceChange(); + audioModeModule.onBluetoothDeviceChange(); } }; diff --git a/fonts/jitsi.eot b/fonts/jitsi.eot index 30085b44e..38ada4e60 100755 Binary files a/fonts/jitsi.eot and b/fonts/jitsi.eot differ diff --git a/fonts/jitsi.svg b/fonts/jitsi.svg index f06e4c6c2..bb75dba1f 100755 --- a/fonts/jitsi.svg +++ b/fonts/jitsi.svg @@ -9,10 +9,13 @@ + + + diff --git a/fonts/jitsi.ttf b/fonts/jitsi.ttf index 4f1294da8..62aed45c0 100755 Binary files a/fonts/jitsi.ttf and b/fonts/jitsi.ttf differ diff --git a/fonts/jitsi.woff b/fonts/jitsi.woff index ddca319dd..b27004bb5 100755 Binary files a/fonts/jitsi.woff and b/fonts/jitsi.woff differ diff --git a/fonts/selection.json b/fonts/selection.json index fbd4e2180..8515b7e68 100755 --- a/fonts/selection.json +++ b/fonts/selection.json @@ -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, diff --git a/ios/sdk/sdk.xcodeproj/project.pbxproj b/ios/sdk/sdk.xcodeproj/project.pbxproj index 312768c78..798980acc 100644 --- a/ios/sdk/sdk.xcodeproj/project.pbxproj +++ b/ios/sdk/sdk.xcodeproj/project.pbxproj @@ -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 = ""; }; 0B412F171EDEC65D00B1A0A6 /* JitsiMeetView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JitsiMeetView.m; sourceTree = ""; }; 0B412F1B1EDEC80100B1A0A6 /* JitsiMeetViewDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JitsiMeetViewDelegate.h; sourceTree = ""; }; + 0B44A0181F902126009D1D64 /* MPVolumeViewManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPVolumeViewManager.m; sourceTree = ""; }; 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 = ""; }; 0B93EF7D1EC9DDCD0030D24D /* RCTBridgeWrapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBridgeWrapper.m; sourceTree = ""; }; @@ -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 */, ); diff --git a/ios/sdk/src/MPVolumeViewManager.m b/ios/sdk/src/MPVolumeViewManager.m new file mode 100644 index 000000000..99f075919 --- /dev/null +++ b/ios/sdk/src/MPVolumeViewManager.m @@ -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 +#import + +@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 *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 diff --git a/react/features/base/font-icons/jitsi.json b/react/features/base/font-icons/jitsi.json index f3b9e25e3..8515b7e68 100644 --- a/react/features/base/font-icons/jitsi.json +++ b/react/features/base/font-icons/jitsi.json @@ -1,6 +1,114 @@ { "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": [ @@ -11,24 +119,24 @@ ], "isMulticolor": false, "isMulticolor2": false, - "grid": 24, "tags": [ "ninja" - ] + ], + "grid": 24 }, "attrs": [ {} ], "properties": { - "order": 851, - "id": 121, + "order": 850, + "id": 1, "name": "ninja", - "prevSize": 32, + "prevSize": 24, "code": 59657 }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 0 + "iconIdx": 41 }, { "icon": { @@ -47,15 +155,100 @@ "attrs": [], "properties": { "ligatures": "call, local_phone, phone", - "id": 120, - "order": 848, - "prevSize": 32, + "id": 2, + "order": 851, + "prevSize": 24, "code": 57549, "name": "phone" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 41 + "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": [ + "M896 0c70.692 0 128 57.308 128 128v768c0 70.692-57.308 128-128 128s-128-57.308-128-128v-768c0-70.692 57.308-128 128-128zM512 256c70.692 0 128 57.308 128 128v512c0 70.692-57.308 128-128 128s-128-57.308-128-128v-512c0-70.692 57.308-128 128-128zM128 640v0c70.692 0 128 57.308 128 128v128c0 70.692-57.308 128-128 128s-128-57.308-128-128v-128c0-70.692 57.308-128 128-128v0z" + ], + "attrs": [ + {} + ], + "isMulticolor": false, + "isMulticolor2": false, + "grid": 0, + "tags": [ + "gsm-bars-black" + ] + }, + "attrs": [ + {} + ], + "properties": { + "order": 901, + "id": 0, + "name": "gsm-bars-black", + "prevSize": 32, + "code": 59686 + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 0 + }, + { + "icon": { + "paths": [ + "M512 85.333c-235.52 0-426.667 191.147-426.667 426.667s191.147 426.667 426.667 426.667 426.667-191.147 426.667-426.667-191.147-426.667-426.667-426.667zM554.667 725.333h-85.333v-256h85.333v256zM554.667 384h-85.333v-85.333h85.333v85.333z" + ], + "attrs": [ + {} + ], + "isMulticolor": false, + "isMulticolor2": false, + "grid": 0, + "tags": [ + "ic_info_black_24px" + ] + }, + "attrs": [ + {} + ], + "properties": { + "order": 898, + "id": 0, + "name": "info", + "prevSize": 32, + "code": 59682 + }, + "setIdx": 1, + "setId": 1, + "iconIdx": 1 }, { "icon": { @@ -76,15 +269,15 @@ {} ], "properties": { - "order": 109, + "order": 856, "id": 0, "name": "mic-camera-combined", "prevSize": 32, "code": 59651 }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 1 + "iconIdx": 2 }, { "icon": { @@ -105,15 +298,15 @@ {} ], "properties": { - "order": 104, + "order": 857, "id": 1, "name": "feedback", "prevSize": 32, "code": 59677 }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 2 + "iconIdx": 3 }, { "icon": { @@ -134,15 +327,15 @@ {} ], "properties": { - "order": 103, + "order": 858, "id": 2, "name": "toggle-filmstrip", "prevSize": 32, "code": 59676 }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 3 + "iconIdx": 4 }, { "icon": { @@ -160,15 +353,15 @@ "attrs": [], "properties": { "id": 3, - "order": 60, + "order": 859, "ligatures": "account_circle", "prevSize": 32, "code": 59649, "name": "avatar" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 4 + "iconIdx": 5 }, { "icon": { @@ -186,15 +379,15 @@ "attrs": [], "properties": { "id": 4, - "order": 849, + "order": 860, "ligatures": "call_end", "prevSize": 32, "code": 59653, "name": "hangup" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 5 + "iconIdx": 6 }, { "icon": { @@ -212,15 +405,15 @@ "attrs": [], "properties": { "id": 5, - "order": 61, + "order": 861, "ligatures": "chat_bubble_outline", "prevSize": 32, "code": 59654, "name": "chat" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 6 + "iconIdx": 7 }, { "icon": { @@ -238,15 +431,15 @@ "attrs": [], "properties": { "id": 6, - "order": 99, + "order": 862, "ligatures": "cloud_download", "prevSize": 32, "code": 59650, "name": "download" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 7 + "iconIdx": 8 }, { "icon": { @@ -264,15 +457,15 @@ "attrs": [], "properties": { "id": 7, - "order": 89, + "order": 863, "ligatures": "create, edit, mode_edit", "prevSize": 32, "code": 59655, "name": "edit" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 8 + "iconIdx": 9 }, { "icon": { @@ -290,15 +483,15 @@ "attrs": [], "properties": { "id": 8, - "order": 85, + "order": 864, "ligatures": "description", "prevSize": 32, "code": 59656, "name": "share-doc" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 9 + "iconIdx": 10 }, { "icon": { @@ -315,16 +508,16 @@ }, "attrs": [], "properties": { - "id": 10, - "order": 98, + "id": 9, + "order": 865, "ligatures": "eject", "prevSize": 32, "code": 59652, "name": "kick" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 10 + "iconIdx": 11 }, { "icon": { @@ -341,16 +534,16 @@ }, "attrs": [], "properties": { - "id": 11, - "order": 106, + "id": 10, + "order": 900, "ligatures": "expand_less", "prevSize": 32, "code": 59679, "name": "menu-up" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 11 + "iconIdx": 12 }, { "icon": { @@ -367,16 +560,16 @@ }, "attrs": [], "properties": { - "id": 12, - "order": 107, + "id": 11, + "order": 867, "ligatures": "expand_more", "prevSize": 32, "code": 59680, "name": "menu-down" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 12 + "iconIdx": 13 }, { "icon": { @@ -393,16 +586,16 @@ }, "attrs": [], "properties": { - "id": 13, - "order": 94, + "id": 12, + "order": 868, "ligatures": "fullscreen", "prevSize": 32, "code": 59659, "name": "full-screen" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 13 + "iconIdx": 14 }, { "icon": { @@ -419,16 +612,16 @@ }, "attrs": [], "properties": { - "id": 14, - "order": 92, + "id": 13, + "order": 869, "ligatures": "fullscreen_exit", "prevSize": 32, "code": 59660, "name": "exit-full-screen" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 14 + "iconIdx": 15 }, { "icon": { @@ -445,16 +638,16 @@ }, "attrs": [], "properties": { - "id": 15, - "order": 101, + "id": 14, + "order": 870, "ligatures": "grade, star", "prevSize": 32, "code": 59658, "name": "star-full" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 15 + "iconIdx": 16 }, { "icon": { @@ -471,16 +664,16 @@ }, "attrs": [], "properties": { - "id": 16, - "order": 66, + "id": 15, + "order": 871, "ligatures": "lock_open", "prevSize": 32, "code": 59661, "name": "security" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 16 + "iconIdx": 17 }, { "icon": { @@ -497,16 +690,16 @@ }, "attrs": [], "properties": { - "id": 17, - "order": 65, + "id": 16, + "order": 872, "ligatures": "lock_outline", "prevSize": 32, "code": 59662, "name": "security-locked" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 17 + "iconIdx": 18 }, { "icon": { @@ -523,16 +716,16 @@ }, "attrs": [], "properties": { - "id": 18, - "order": 67, + "id": 17, + "order": 873, "ligatures": "loop, sync", "prevSize": 32, "code": 59663, "name": "reload" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 18 + "iconIdx": 19 }, { "icon": { @@ -549,16 +742,16 @@ }, "attrs": [], "properties": { - "id": 19, - "order": 68, + "id": 18, + "order": 874, "ligatures": "mic", "prevSize": 32, "code": 59664, "name": "microphone" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 19 + "iconIdx": 20 }, { "icon": { @@ -575,16 +768,16 @@ }, "attrs": [], "properties": { - "id": 20, - "order": 69, + "id": 19, + "order": 875, "ligatures": "mic_none", "prevSize": 32, "code": 59665, "name": "mic-empty" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 20 + "iconIdx": 21 }, { "icon": { @@ -601,16 +794,16 @@ }, "attrs": [], "properties": { - "id": 21, - "order": 70, + "id": 20, + "order": 876, "ligatures": "mic_off", "prevSize": 32, "code": 59666, "name": "mic-disabled" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 21 + "iconIdx": 22 }, { "icon": { @@ -627,16 +820,16 @@ }, "attrs": [], "properties": { - "id": 22, - "order": 105, + "id": 21, + "order": 899, "ligatures": "pan_tool", "prevSize": 32, "code": 59678, "name": "raised-hand" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 22 + "iconIdx": 23 }, { "icon": { @@ -653,16 +846,16 @@ }, "attrs": [], "properties": { - "id": 23, - "order": 100, + "id": 22, + "order": 878, "ligatures": "people_outline", "prevSize": 32, "code": 59675, "name": "contactList" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 23 + "iconIdx": 24 }, { "icon": { @@ -679,16 +872,16 @@ }, "attrs": [], "properties": { - "id": 24, - "order": 87, + "id": 23, + "order": 879, "ligatures": "person_add", "prevSize": 32, "code": 59667, "name": "link" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 24 + "iconIdx": 25 }, { "icon": { @@ -705,16 +898,16 @@ }, "attrs": [], "properties": { - "id": 25, - "order": 82, + "id": 24, + "order": 880, "ligatures": "play_circle_outline", "prevSize": 32, "code": 59668, "name": "shared-video" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 25 + "iconIdx": 26 }, { "icon": { @@ -731,16 +924,16 @@ }, "attrs": [], "properties": { - "id": 26, - "order": 81, + "id": 25, + "order": 881, "ligatures": "settings", "prevSize": 32, "code": 59669, "name": "settings" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 26 + "iconIdx": 27 }, { "icon": { @@ -757,16 +950,16 @@ }, "attrs": [], "properties": { - "id": 27, - "order": 76, + "id": 26, + "order": 882, "ligatures": "star_border", "prevSize": 32, "code": 59670, "name": "star" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 27 + "iconIdx": 28 }, { "icon": { @@ -783,16 +976,16 @@ }, "attrs": [], "properties": { - "id": 28, - "order": 108, + "id": 27, + "order": 883, "ligatures": "switch_camera", "prevSize": 32, "code": 59681, "name": "switch-camera" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 28 + "iconIdx": 29 }, { "icon": { @@ -809,16 +1002,16 @@ }, "attrs": [], "properties": { - "id": 29, - "order": 93, + "id": 28, + "order": 884, "ligatures": "tv", "prevSize": 32, "code": 59671, "name": "share-desktop" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 29 + "iconIdx": 30 }, { "icon": { @@ -835,16 +1028,16 @@ }, "attrs": [], "properties": { - "id": 30, - "order": 77, + "id": 29, + "order": 885, "ligatures": "videocam", "prevSize": 32, "code": 59672, "name": "camera" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 30 + "iconIdx": 31 }, { "icon": { @@ -861,16 +1054,16 @@ }, "attrs": [], "properties": { - "id": 31, - "order": 78, + "id": 30, + "order": 886, "ligatures": "videocam_off", "prevSize": 32, "code": 59673, "name": "camera-disabled" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 31 + "iconIdx": 32 }, { "icon": { @@ -887,138 +1080,17 @@ }, "attrs": [], "properties": { - "id": 32, - "order": 79, + "id": 31, + "order": 887, "ligatures": "volume_up", "prevSize": 32, "code": 59674, "name": "volume" }, - "setIdx": 0, - "setId": 1, - "iconIdx": 32 - }, - { - "icon": { - "paths": [ - "M-0 724.847h196.337v187.951h-196.337v-187.951z", - "M271.842 543.628h196.337v369.169h-196.337v-369.169z", - "M543.656 362.438h196.337v550.36h-196.337v-550.36z", - "M815.47 181.234v731.564h119.56c-14.589-33.025-23.125-71.503-23.232-111.943 0.132-86.42 38.697-163.851 99.656-216.468l0.348-403.153h-196.332z", - "M1087.292-0v533.672c28.874-10.572 62.222-16.73 97.009-16.825 35.717 0.129 69.823 6.614 101.322 18.371l-1.999-535.218h-196.332z", - "M1192.868 584.148c-0.009-0-0.020-0-0.031-0-122.247 0-221.351 98.447-221.372 219.896-0 0.007-0 0.014-0 0.021 0 121.467 99.111 219.935 221.372 219.935 0.011 0 0.021-0 0.032-0 122.248-0.014 221.345-98.477 221.345-219.935 0-0.007-0-0.013-0-0.020-0.021-121.441-99.11-219.883-221.345-219.897zM1194.706 651.393c87.601 0.006 158.614 69.787 158.614 155.866 0 0.006-0 0.012-0 0.019-0.022 86.062-71.026 155.822-158.614 155.828-87.588-0.006-158.593-69.766-158.615-155.826-0-0.007-0-0.014-0-0.020 0-86.079 71.013-155.86 158.613-155.866z", - "M1286.795 668.318l48.348 52.528-236.375 217.567-48.348-52.528 236.375-217.567z" - ], - "width": 1414, - "attrs": [ - {}, - {}, - {}, - {}, - {}, - {}, - {} - ], - "isMulticolor": false, - "isMulticolor2": false, - "tags": [ - "connection-lost" - ], - "grid": 0 - }, - "attrs": [ - {}, - {}, - {}, - {}, - {}, - {}, - {} - ], - "properties": { - "order": 33, - "id": 33, - "name": "connection-lost", - "prevSize": 32, - "code": 59648 - }, - "setIdx": 0, + "setIdx": 1, "setId": 1, "iconIdx": 33 }, - { - "icon": { - "paths": [ - "M3.881 813.165h220.26v210.835h-220.26v-210.835z", - "M308.817 609.857h220.27v414.143h-220.27v-414.143z", - "M613.764 406.588h220.268v617.412h-220.268v-617.412z", - "M918.685 203.285h220.265v820.715h-220.265v-820.715z", - "M1223.629 0h220.263v1024h-220.263v-1024z" - ], - "width": 1444, - "attrs": [ - { - "opacity": 1, - "visibility": false - }, - { - "opacity": 1, - "visibility": false - }, - { - "opacity": 1, - "visibility": false - }, - { - "opacity": 1, - "visibility": false - }, - { - "opacity": 1, - "visibility": false - } - ], - "isMulticolor": false, - "isMulticolor2": false, - "tags": [ - "connection-2" - ], - "grid": 0 - }, - "attrs": [ - { - "opacity": 1, - "visibility": false - }, - { - "opacity": 1, - "visibility": false - }, - { - "opacity": 1, - "visibility": false - }, - { - "opacity": 1, - "visibility": false - }, - { - "opacity": 1, - "visibility": false - } - ], - "properties": { - "order": 37, - "id": 34, - "prevSize": 32, - "code": 58906, - "name": "connection", - "ligatures": "" - }, - "setIdx": 0, - "setId": 1, - "iconIdx": 34 - }, { "icon": { "paths": [ @@ -1037,16 +1109,16 @@ }, "attrs": [], "properties": { - "order": 43, - "id": 35, + "order": 890, + "id": 34, "prevSize": 32, "code": 58899, "name": "recDisable", "ligatures": "" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 35 + "iconIdx": 34 }, { "icon": { @@ -1067,16 +1139,16 @@ }, "attrs": [], "properties": { - "order": 44, - "id": 36, + "order": 891, + "id": 35, "prevSize": 32, "code": 58900, "name": "recEnable", "ligatures": "" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 36 + "iconIdx": 35 }, { "icon": { @@ -1097,16 +1169,16 @@ }, "attrs": [], "properties": { - "order": 53, - "id": 37, + "order": 892, + "id": 36, "prevSize": 32, "code": 58883, "name": "presentation", "ligatures": "" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 37 + "iconIdx": 36 }, { "icon": { @@ -1123,16 +1195,16 @@ }, "attrs": [], "properties": { - "order": 115, + "order": 893, "ligatures": "dialpad", - "id": 38, + "id": 37, "prevSize": 32, "code": 59685, "name": "dialpad" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 38 + "iconIdx": 37 }, { "icon": { @@ -1149,16 +1221,16 @@ }, "attrs": [], "properties": { - "order": 114, + "order": 894, "ligatures": "remove_red_eye, visibility", - "id": 39, + "id": 38, "prevSize": 32, "code": 59683, "name": "visibility" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 39 + "iconIdx": 38 }, { "icon": { @@ -1175,16 +1247,16 @@ }, "attrs": [], "properties": { - "order": 113, + "order": 895, "ligatures": "visibility_off", - "id": 40, + "id": 39, "prevSize": 32, "code": 59684, "name": "visibility-off" }, - "setIdx": 0, + "setIdx": 1, "setId": 1, - "iconIdx": 40 + "iconIdx": 39 } ], "height": 1024, @@ -1212,11 +1284,13 @@ "imagePref": { "prefix": "icon-", "png": true, - "useClassSelector": true + "useClassSelector": true, + "classSelector": ".icon" }, "historySize": 100, "showCodes": false, "search": "", - "showLiga": false + "showLiga": false, + "gridSize": 16 } } \ No newline at end of file diff --git a/react/features/mobile/audio-mode/components/AudioRoutePickerDialog.js b/react/features/mobile/audio-mode/components/AudioRoutePickerDialog.js new file mode 100644 index 000000000..9941af664 --- /dev/null +++ b/react/features/mobile/audio-mode/components/AudioRoutePickerDialog.js @@ -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 +}; + +/** + * Implements a React {@code Component} which prompts the user when a password + * is required to join a conference. + */ +class AudioRoutePickerDialog extends Component { + 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 ( + + ); + } +} + +// 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; diff --git a/react/features/mobile/audio-mode/components/index.js b/react/features/mobile/audio-mode/components/index.js new file mode 100644 index 000000000..17f03f9ce --- /dev/null +++ b/react/features/mobile/audio-mode/components/index.js @@ -0,0 +1,3 @@ +export { + default as AudioRoutePickerDialog +} from './AudioRoutePickerDialog'; diff --git a/react/features/mobile/audio-mode/index.js b/react/features/mobile/audio-mode/index.js index d43689289..7ce19e0e6 100644 --- a/react/features/mobile/audio-mode/index.js +++ b/react/features/mobile/audio-mode/index.js @@ -1 +1,3 @@ +export * from './components'; + import './middleware'; diff --git a/react/features/toolbox/components/AudioRouteButton.js b/react/features/toolbox/components/AudioRouteButton.js new file mode 100644 index 000000000..63553e58d --- /dev/null +++ b/react/features/toolbox/components/AudioRouteButton.js @@ -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 { + _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 ( + + + { + MPVolumeView + && + } + + ); + } +} + +export default connect()(AudioRouteButton); diff --git a/react/features/toolbox/components/Toolbox.native.js b/react/features/toolbox/components/Toolbox.native.js index 4e371d3e4..9df0f8e4b 100644 --- a/react/features/toolbox/components/Toolbox.native.js +++ b/react/features/toolbox/components/Toolbox.native.js @@ -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() + && + } );