[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:
parent
8198e52b93
commit
f973a695d8
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ class BluetoothHeadsetMonitor {
|
|||
headsetAvailable
|
||||
= (headset != null)
|
||||
&& !headset.getConnectedDevices().isEmpty();
|
||||
audioModeModule.onAudioDeviceChange();
|
||||
audioModeModule.onBluetoothDeviceChange();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
BIN
fonts/jitsi.eot
BIN
fonts/jitsi.eot
Binary file not shown.
|
@ -9,10 +9,13 @@
|
|||
<glyph unicode=" " d="" />
|
||||
<glyph unicode="" 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="" glyph-name="add" d="M810 470h-256v-256h-84v256h-256v84h256v256h84v-256h256v-84z" />
|
||||
<glyph unicode="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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 |
BIN
fonts/jitsi.ttf
BIN
fonts/jitsi.ttf
Binary file not shown.
BIN
fonts/jitsi.woff
BIN
fonts/jitsi.woff
Binary file not shown.
|
@ -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,
|
||||
|
|
|
@ -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 */,
|
||||
);
|
||||
|
|
|
@ -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
|
@ -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;
|
|
@ -0,0 +1,3 @@
|
|||
export {
|
||||
default as AudioRoutePickerDialog
|
||||
} from './AudioRoutePickerDialog';
|
|
@ -1 +1,3 @@
|
|||
export * from './components';
|
||||
|
||||
import './middleware';
|
||||
|
|
|
@ -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);
|
|
@ -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>
|
||||
);
|
||||
|
||||
|
|
Loading…
Reference in New Issue