Pārlūkot izejas kodu

[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.
j8
Saúl Ibarra Corretgé 7 gadus atpakaļ
vecāks
revīzija
f973a695d8

+ 228
- 25
android/sdk/src/main/java/org/jitsi/meet/sdk/AudioModeModule.java Parādīt failu

@@ -21,6 +21,7 @@ import android.content.BroadcastReceiver;
21 21
 import android.content.Context;
22 22
 import android.content.Intent;
23 23
 import android.content.IntentFilter;
24
+import android.content.pm.PackageManager;
24 25
 import android.media.AudioDeviceInfo;
25 26
 import android.media.AudioManager;
26 27
 import android.os.Build;
@@ -28,13 +29,19 @@ import android.os.Handler;
28 29
 import android.os.Looper;
29 30
 import android.util.Log;
30 31
 
32
+import com.facebook.react.bridge.Arguments;
31 33
 import com.facebook.react.bridge.Promise;
32 34
 import com.facebook.react.bridge.ReactApplicationContext;
33 35
 import com.facebook.react.bridge.ReactContextBaseJavaModule;
34 36
 import com.facebook.react.bridge.ReactMethod;
37
+import com.facebook.react.bridge.WritableArray;
38
+import com.facebook.react.bridge.WritableMap;
35 39
 
40
+import java.util.Collections;
36 41
 import java.util.HashMap;
42
+import java.util.HashSet;
37 43
 import java.util.Map;
44
+import java.util.Set;
38 45
 
39 46
 /**
40 47
  * Module implementing a simple API to select the appropriate audio device for a
@@ -102,10 +109,53 @@ class AudioModeModule extends ReactContextBaseJavaModule {
102 109
     private final Handler mainThreadHandler
103 110
         = new Handler(Looper.getMainLooper());
104 111
 
112
+    /**
113
+     * {@link Runnable} for running audio device detection the main thread.
114
+     * This is only used on Android >= M.
115
+     */
116
+    private final Runnable onAudioDeviceChangeRunner = new Runnable() {
117
+        @TargetApi(Build.VERSION_CODES.M)
118
+        @Override
119
+        public void run() {
120
+            Set<String> devices = new HashSet<>();
121
+            AudioDeviceInfo[] deviceInfos
122
+                = audioManager.getDevices(AudioManager.GET_DEVICES_ALL);
123
+
124
+            for (AudioDeviceInfo info: deviceInfos) {
125
+                switch (info.getType()) {
126
+                case AudioDeviceInfo.TYPE_BLUETOOTH_SCO:
127
+                    devices.add(DEVICE_BLUETOOTH);
128
+                    break;
129
+                case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE:
130
+                    devices.add(DEVICE_EARPIECE);
131
+                    break;
132
+                case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER:
133
+                    devices.add(DEVICE_SPEAKER);
134
+                    break;
135
+                case AudioDeviceInfo.TYPE_WIRED_HEADPHONES:
136
+                case AudioDeviceInfo.TYPE_WIRED_HEADSET:
137
+                    devices.add(DEVICE_HEADPHONES);
138
+                    break;
139
+                }
140
+            }
141
+
142
+            availableDevices = devices;
143
+            Log.d(TAG, "Available audio devices: " +
144
+                availableDevices.toString());
145
+
146
+            // Reset user selection
147
+            userSelectedDevice = null;
148
+
149
+            if (mode != -1) {
150
+                updateAudioRoute(mode);
151
+            }
152
+        }
153
+    };
154
+
105 155
     /**
106 156
      * {@link Runnable} for running update operation on the main thread.
107 157
      */
108
-    private final Runnable mainThreadRunner
158
+    private final Runnable updateAudioRouteRunner
109 159
         = new Runnable() {
110 160
             @Override
111 161
             public void run() {
@@ -120,6 +170,30 @@ class AudioModeModule extends ReactContextBaseJavaModule {
120 170
      */
121 171
     private int mode = -1;
122 172
 
173
+    /**
174
+     * Audio device types.
175
+     */
176
+    private static final String DEVICE_BLUETOOTH  = "BLUETOOTH";
177
+    private static final String DEVICE_EARPIECE   = "EARPIECE";
178
+    private static final String DEVICE_HEADPHONES = "HEADPHONES";
179
+    private static final String DEVICE_SPEAKER    = "SPEAKER";
180
+
181
+    /**
182
+     * List of currently available audio devices.
183
+     */
184
+    private Set<String> availableDevices = Collections.emptySet();
185
+
186
+    /**
187
+     * Currently selected device.
188
+     */
189
+    private String selectedDevice;
190
+
191
+    /**
192
+     * User selected device. When null the default is used depending on the
193
+     * mode.
194
+     */
195
+    private String userSelectedDevice;
196
+
123 197
     /**
124 198
      * Initializes a new module instance. There shall be a single instance of
125 199
      * this module throughout the lifetime of the application.
@@ -136,6 +210,20 @@ class AudioModeModule extends ReactContextBaseJavaModule {
136 210
 
137 211
         // Setup runtime device change detection.
138 212
         setupAudioRouteChangeDetection();
213
+
214
+        // Do an initial detection on Android >= M.
215
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
216
+            mainThreadHandler.post(onAudioDeviceChangeRunner);
217
+        } else {
218
+            // On Android < M, detect if we have an earpiece.
219
+            PackageManager pm = reactContext.getPackageManager();
220
+            if (pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
221
+                availableDevices.add(DEVICE_EARPIECE);
222
+            }
223
+
224
+            // Always assume there is a speaker.
225
+            availableDevices.add(DEVICE_SPEAKER);
226
+        }
139 227
     }
140 228
 
141 229
     /**
@@ -155,6 +243,36 @@ class AudioModeModule extends ReactContextBaseJavaModule {
155 243
         return constants;
156 244
     }
157 245
 
246
+    /**
247
+     * Gets the list of available audio device categories, i.e. 'bluetooth',
248
+     * 'earpiece ', 'speaker', 'headphones'.
249
+     *
250
+     * @param promise a {@link Promise} which will be resolved with an object
251
+     *                containing a 'devices' key with a list of devices, plus a
252
+     *                'selected' key with the selected one.
253
+     */
254
+    @ReactMethod
255
+    public void getAudioDevices(final Promise promise) {
256
+        mainThreadHandler.post(new Runnable() {
257
+            @Override
258
+            public void run() {
259
+                WritableMap map = Arguments.createMap();
260
+                map.putString("selected", selectedDevice);
261
+                WritableArray devices = Arguments.createArray();
262
+                for (String device : availableDevices) {
263
+                    if (mode == VIDEO_CALL && device.equals(DEVICE_EARPIECE)) {
264
+                        // Skip earpiece when in video call mode.
265
+                        continue;
266
+                    }
267
+                    devices.pushString(device);
268
+                }
269
+                map.putArray("devices", devices);
270
+
271
+                promise.resolve(map);
272
+            }
273
+        });
274
+    }
275
+
158 276
     /**
159 277
      * Gets the name for this module to be used in the React Native bridge.
160 278
      *
@@ -168,9 +286,81 @@ class AudioModeModule extends ReactContextBaseJavaModule {
168 286
     /**
169 287
      * Helper method to trigger an audio route update when devices change. It
170 288
      * makes sure the operation is performed on the main thread.
289
+     *
290
+     * Only used on Android >= M.
171 291
      */
172 292
     void onAudioDeviceChange() {
173
-        mainThreadHandler.post(mainThreadRunner);
293
+        mainThreadHandler.post(onAudioDeviceChangeRunner);
294
+    }
295
+
296
+    /**
297
+     * Helper method to trigger an audio route update when Bluetooth devices are
298
+     * connected / disconnected.
299
+     *
300
+     * Only used on Android < M. Runs on the main thread.
301
+     */
302
+    void onBluetoothDeviceChange() {
303
+        if (bluetoothHeadsetMonitor.isHeadsetAvailable()) {
304
+            availableDevices.add(DEVICE_BLUETOOTH);
305
+        } else {
306
+            availableDevices.remove(DEVICE_BLUETOOTH);
307
+        }
308
+
309
+        if (mode != -1) {
310
+            updateAudioRoute(mode);
311
+        }
312
+    }
313
+
314
+    /**
315
+     * Helper method to trigger an audio route update when a headset is plugged
316
+     * or unplugged.
317
+     *
318
+     * Only used on Android < M.
319
+     */
320
+    void onHeadsetDeviceChange() {
321
+        mainThreadHandler.post(new Runnable() {
322
+            @Override
323
+            public void run() {
324
+                // XXX: isWiredHeadsetOn is not deprecated when used just for
325
+                // knowing if there is a wired headset connected, regardless of
326
+                // audio being routed to it.
327
+                //noinspection deprecation
328
+                if (audioManager.isWiredHeadsetOn()) {
329
+                    availableDevices.add(DEVICE_HEADPHONES);
330
+                } else {
331
+                    availableDevices.remove(DEVICE_HEADPHONES);
332
+                }
333
+
334
+                if (mode != -1) {
335
+                    updateAudioRoute(mode);
336
+                }
337
+            }
338
+        });
339
+    }
340
+
341
+    /**
342
+     * Sets the user selected audio device as the active audio device.
343
+     *
344
+     * @param device the desired device which will become active.
345
+     */
346
+    @ReactMethod
347
+    public void setAudioDevice(final String device) {
348
+        mainThreadHandler.post(new Runnable() {
349
+            @Override
350
+            public void run() {
351
+                if (!availableDevices.contains(device)) {
352
+                    Log.d(TAG, "Audio device not available: " + device);
353
+                    userSelectedDevice = null;
354
+                    return;
355
+                }
356
+
357
+                if (mode != -1) {
358
+                    Log.d(TAG, "User selected device set to: " + device);
359
+                    userSelectedDevice = device;
360
+                    updateAudioRoute(mode);
361
+                }
362
+            }
363
+        });
174 364
     }
175 365
 
176 366
     /**
@@ -278,7 +468,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
278 468
             @Override
279 469
             public void onReceive(Context context, Intent intent) {
280 470
                 Log.d(TAG, "Wired headset added / removed");
281
-                onAudioDeviceChange();
471
+                onHeadsetDeviceChange();
282 472
             }
283 473
         };
284 474
         context.registerReceiver(wiredHeadsetReceiver, wiredHeadSetFilter);
@@ -302,6 +492,8 @@ class AudioModeModule extends ReactContextBaseJavaModule {
302 492
             audioManager.abandonAudioFocus(null);
303 493
             audioManager.setSpeakerphoneOn(false);
304 494
             setBluetoothAudioRoute(false);
495
+            selectedDevice = null;
496
+            userSelectedDevice = null;
305 497
 
306 498
             return true;
307 499
         }
@@ -318,31 +510,42 @@ class AudioModeModule extends ReactContextBaseJavaModule {
318 510
             return false;
319 511
         }
320 512
 
321
-        boolean useSpeaker = (mode == VIDEO_CALL);
322
-
323
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
324
-            // On Android >= M we use the AudioDeviceCallback API, so turn on
325
-            // Bluetooth SCO from the start.
326
-            if (audioManager.isBluetoothScoAvailableOffCall()) {
327
-                audioManager.startBluetoothSco();
328
-            }
513
+        boolean bluetoothAvailable = availableDevices.contains(DEVICE_BLUETOOTH);
514
+        boolean earpieceAvailable = availableDevices.contains(DEVICE_EARPIECE);
515
+        boolean headsetAvailable = availableDevices.contains(DEVICE_HEADPHONES);
516
+
517
+        // Pick the desired device based on what's available and the mode.
518
+        String audioDevice;
519
+        if (bluetoothAvailable) {
520
+            audioDevice = DEVICE_BLUETOOTH;
521
+        } else if (headsetAvailable) {
522
+            audioDevice = DEVICE_HEADPHONES;
523
+        } else if (mode == AUDIO_CALL && earpieceAvailable) {
524
+            audioDevice = DEVICE_EARPIECE;
329 525
         } else {
330
-            // On older Android versions we must set the Bluetooth route
331
-            // manually. Also disable the speaker in that case.
332
-            setBluetoothAudioRoute(
333
-                    bluetoothHeadsetMonitor.isHeadsetAvailable());
334
-            if (bluetoothHeadsetMonitor.isHeadsetAvailable()) {
335
-                useSpeaker = false;
336
-            }
526
+            audioDevice = DEVICE_SPEAKER;
527
+        }
528
+
529
+        // Consider the user's selection
530
+        if (userSelectedDevice != null
531
+                && availableDevices.contains(userSelectedDevice)) {
532
+            audioDevice = userSelectedDevice;
337 533
         }
338 534
 
339
-        // XXX: isWiredHeadsetOn is not deprecated when used just for knowing if
340
-        // there is a wired headset connected, regardless of audio being routed
341
-        // to it.
342
-        audioManager.setSpeakerphoneOn(
343
-                useSpeaker
344
-                    && !(audioManager.isWiredHeadsetOn()
345
-                        || audioManager.isBluetoothScoOn()));
535
+        // If the previously selected device and the current default one
536
+        // match, do nothing.
537
+        if (selectedDevice != null && selectedDevice.equals(audioDevice)) {
538
+            return true;
539
+        }
540
+
541
+        selectedDevice = audioDevice;
542
+        Log.d(TAG, "Selected audio device: " + audioDevice);
543
+
544
+        // Turn bluetooth on / off
545
+        setBluetoothAudioRoute(audioDevice.equals(DEVICE_BLUETOOTH));
546
+
547
+        // Turn speaker on / off
548
+        audioManager.setSpeakerphoneOn(audioDevice.equals(DEVICE_SPEAKER));
346 549
 
347 550
         return true;
348 551
     }

+ 1
- 1
android/sdk/src/main/java/org/jitsi/meet/sdk/BluetoothHeadsetMonitor.java Parādīt failu

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

Binārs
fonts/jitsi.eot Parādīt failu


+ 3
- 0
fonts/jitsi.svg Parādīt failu

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

Binārs
fonts/jitsi.ttf Parādīt failu


Binārs
fonts/jitsi.woff Parādīt failu


+ 237
- 156
fonts/selection.json Parādīt failu

@@ -1,6 +1,197 @@
1 1
 {
2 2
   "IcoMoonType": "selection",
3 3
   "icons": [
4
+    {
5
+      "icon": {
6
+        "paths": [
7
+          "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"
8
+        ],
9
+        "attrs": [],
10
+        "isMulticolor": false,
11
+        "isMulticolor2": false,
12
+        "tags": [
13
+          "bluetooth_searching"
14
+        ],
15
+        "defaultCode": 57770,
16
+        "grid": 24
17
+      },
18
+      "attrs": [],
19
+      "properties": {
20
+        "ligatures": "bluetooth_audio, bluetooth_searching",
21
+        "id": 79,
22
+        "order": 911,
23
+        "prevSize": 24,
24
+        "code": 57770,
25
+        "name": "bluetooth"
26
+      },
27
+      "setIdx": 0,
28
+      "setId": 2,
29
+      "iconIdx": 79
30
+    },
31
+    {
32
+      "icon": {
33
+        "paths": [
34
+          "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"
35
+        ],
36
+        "attrs": [],
37
+        "isMulticolor": false,
38
+        "isMulticolor2": false,
39
+        "tags": [
40
+          "headset"
41
+        ],
42
+        "defaultCode": 58128,
43
+        "grid": 24
44
+      },
45
+      "attrs": [],
46
+      "properties": {
47
+        "ligatures": "headset",
48
+        "id": 376,
49
+        "order": 910,
50
+        "prevSize": 24,
51
+        "code": 58128,
52
+        "name": "headset"
53
+      },
54
+      "setIdx": 0,
55
+      "setId": 2,
56
+      "iconIdx": 376
57
+    },
58
+    {
59
+      "icon": {
60
+        "paths": [
61
+          "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"
62
+        ],
63
+        "attrs": [],
64
+        "isMulticolor": false,
65
+        "isMulticolor2": false,
66
+        "tags": [
67
+          "phone_in_talk"
68
+        ],
69
+        "defaultCode": 58909,
70
+        "grid": 24
71
+      },
72
+      "attrs": [],
73
+      "properties": {
74
+        "ligatures": "phone_in_talk",
75
+        "id": 566,
76
+        "order": 912,
77
+        "prevSize": 24,
78
+        "code": 58909,
79
+        "name": "phone-talk"
80
+      },
81
+      "setIdx": 0,
82
+      "setId": 2,
83
+      "iconIdx": 566
84
+    },
85
+    {
86
+      "icon": {
87
+        "paths": [
88
+          "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"
89
+        ],
90
+        "attrs": [],
91
+        "isMulticolor": false,
92
+        "isMulticolor2": false,
93
+        "tags": [
94
+          "more_vert"
95
+        ],
96
+        "defaultCode": 58836,
97
+        "grid": 24
98
+      },
99
+      "attrs": [],
100
+      "properties": {
101
+        "ligatures": "more_vert",
102
+        "id": 0,
103
+        "order": 897,
104
+        "prevSize": 24,
105
+        "code": 58836,
106
+        "name": "thumb-menu"
107
+      },
108
+      "setIdx": 1,
109
+      "setId": 1,
110
+      "iconIdx": 40
111
+    },
112
+    {
113
+      "icon": {
114
+        "paths": [
115
+          "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"
116
+        ],
117
+        "attrs": [
118
+          {}
119
+        ],
120
+        "isMulticolor": false,
121
+        "isMulticolor2": false,
122
+        "tags": [
123
+          "ninja"
124
+        ],
125
+        "grid": 24
126
+      },
127
+      "attrs": [
128
+        {}
129
+      ],
130
+      "properties": {
131
+        "order": 850,
132
+        "id": 1,
133
+        "name": "ninja",
134
+        "prevSize": 24,
135
+        "code": 59657
136
+      },
137
+      "setIdx": 1,
138
+      "setId": 1,
139
+      "iconIdx": 41
140
+    },
141
+    {
142
+      "icon": {
143
+        "paths": [
144
+          "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"
145
+        ],
146
+        "attrs": [],
147
+        "isMulticolor": false,
148
+        "isMulticolor2": false,
149
+        "tags": [
150
+          "phone"
151
+        ],
152
+        "defaultCode": 57549,
153
+        "grid": 24
154
+      },
155
+      "attrs": [],
156
+      "properties": {
157
+        "ligatures": "call, local_phone, phone",
158
+        "id": 2,
159
+        "order": 851,
160
+        "prevSize": 24,
161
+        "code": 57549,
162
+        "name": "phone"
163
+      },
164
+      "setIdx": 1,
165
+      "setId": 1,
166
+      "iconIdx": 42
167
+    },
168
+    {
169
+      "icon": {
170
+        "paths": [
171
+          "M810 554h-256v256h-84v-256h-256v-84h256v-256h84v256h256v84z"
172
+        ],
173
+        "attrs": [],
174
+        "isMulticolor": false,
175
+        "isMulticolor2": false,
176
+        "tags": [
177
+          "add"
178
+        ],
179
+        "defaultCode": 57669,
180
+        "grid": 24
181
+      },
182
+      "attrs": [],
183
+      "properties": {
184
+        "ligatures": "add",
185
+        "id": 3,
186
+        "order": 896,
187
+        "prevSize": 24,
188
+        "code": 57669,
189
+        "name": "add"
190
+      },
191
+      "setIdx": 1,
192
+      "setId": 1,
193
+      "iconIdx": 43
194
+    },
4 195
     {
5 196
       "icon": {
6 197
         "paths": [
@@ -26,7 +217,7 @@
26 217
         "prevSize": 32,
27 218
         "code": 59686
28 219
       },
29
-      "setIdx": 0,
220
+      "setIdx": 1,
30 221
       "setId": 1,
31 222
       "iconIdx": 0
32 223
     },
@@ -55,7 +246,7 @@
55 246
         "prevSize": 32,
56 247
         "code": 59682
57 248
       },
58
-      "setIdx": 0,
249
+      "setIdx": 1,
59 250
       "setId": 1,
60 251
       "iconIdx": 1
61 252
     },
@@ -84,7 +275,7 @@
84 275
         "prevSize": 32,
85 276
         "code": 59651
86 277
       },
87
-      "setIdx": 0,
278
+      "setIdx": 1,
88 279
       "setId": 1,
89 280
       "iconIdx": 2
90 281
     },
@@ -113,7 +304,7 @@
113 304
         "prevSize": 32,
114 305
         "code": 59677
115 306
       },
116
-      "setIdx": 0,
307
+      "setIdx": 1,
117 308
       "setId": 1,
118 309
       "iconIdx": 3
119 310
     },
@@ -142,7 +333,7 @@
142 333
         "prevSize": 32,
143 334
         "code": 59676
144 335
       },
145
-      "setIdx": 0,
336
+      "setIdx": 1,
146 337
       "setId": 1,
147 338
       "iconIdx": 4
148 339
     },
@@ -168,7 +359,7 @@
168 359
         "code": 59649,
169 360
         "name": "avatar"
170 361
       },
171
-      "setIdx": 0,
362
+      "setIdx": 1,
172 363
       "setId": 1,
173 364
       "iconIdx": 5
174 365
     },
@@ -194,7 +385,7 @@
194 385
         "code": 59653,
195 386
         "name": "hangup"
196 387
       },
197
-      "setIdx": 0,
388
+      "setIdx": 1,
198 389
       "setId": 1,
199 390
       "iconIdx": 6
200 391
     },
@@ -220,7 +411,7 @@
220 411
         "code": 59654,
221 412
         "name": "chat"
222 413
       },
223
-      "setIdx": 0,
414
+      "setIdx": 1,
224 415
       "setId": 1,
225 416
       "iconIdx": 7
226 417
     },
@@ -246,7 +437,7 @@
246 437
         "code": 59650,
247 438
         "name": "download"
248 439
       },
249
-      "setIdx": 0,
440
+      "setIdx": 1,
250 441
       "setId": 1,
251 442
       "iconIdx": 8
252 443
     },
@@ -272,7 +463,7 @@
272 463
         "code": 59655,
273 464
         "name": "edit"
274 465
       },
275
-      "setIdx": 0,
466
+      "setIdx": 1,
276 467
       "setId": 1,
277 468
       "iconIdx": 9
278 469
     },
@@ -298,7 +489,7 @@
298 489
         "code": 59656,
299 490
         "name": "share-doc"
300 491
       },
301
-      "setIdx": 0,
492
+      "setIdx": 1,
302 493
       "setId": 1,
303 494
       "iconIdx": 10
304 495
     },
@@ -324,7 +515,7 @@
324 515
         "code": 59652,
325 516
         "name": "kick"
326 517
       },
327
-      "setIdx": 0,
518
+      "setIdx": 1,
328 519
       "setId": 1,
329 520
       "iconIdx": 11
330 521
     },
@@ -350,7 +541,7 @@
350 541
         "code": 59679,
351 542
         "name": "menu-up"
352 543
       },
353
-      "setIdx": 0,
544
+      "setIdx": 1,
354 545
       "setId": 1,
355 546
       "iconIdx": 12
356 547
     },
@@ -376,7 +567,7 @@
376 567
         "code": 59680,
377 568
         "name": "menu-down"
378 569
       },
379
-      "setIdx": 0,
570
+      "setIdx": 1,
380 571
       "setId": 1,
381 572
       "iconIdx": 13
382 573
     },
@@ -402,7 +593,7 @@
402 593
         "code": 59659,
403 594
         "name": "full-screen"
404 595
       },
405
-      "setIdx": 0,
596
+      "setIdx": 1,
406 597
       "setId": 1,
407 598
       "iconIdx": 14
408 599
     },
@@ -428,7 +619,7 @@
428 619
         "code": 59660,
429 620
         "name": "exit-full-screen"
430 621
       },
431
-      "setIdx": 0,
622
+      "setIdx": 1,
432 623
       "setId": 1,
433 624
       "iconIdx": 15
434 625
     },
@@ -454,7 +645,7 @@
454 645
         "code": 59658,
455 646
         "name": "star-full"
456 647
       },
457
-      "setIdx": 0,
648
+      "setIdx": 1,
458 649
       "setId": 1,
459 650
       "iconIdx": 16
460 651
     },
@@ -480,7 +671,7 @@
480 671
         "code": 59661,
481 672
         "name": "security"
482 673
       },
483
-      "setIdx": 0,
674
+      "setIdx": 1,
484 675
       "setId": 1,
485 676
       "iconIdx": 17
486 677
     },
@@ -506,7 +697,7 @@
506 697
         "code": 59662,
507 698
         "name": "security-locked"
508 699
       },
509
-      "setIdx": 0,
700
+      "setIdx": 1,
510 701
       "setId": 1,
511 702
       "iconIdx": 18
512 703
     },
@@ -532,7 +723,7 @@
532 723
         "code": 59663,
533 724
         "name": "reload"
534 725
       },
535
-      "setIdx": 0,
726
+      "setIdx": 1,
536 727
       "setId": 1,
537 728
       "iconIdx": 19
538 729
     },
@@ -558,7 +749,7 @@
558 749
         "code": 59664,
559 750
         "name": "microphone"
560 751
       },
561
-      "setIdx": 0,
752
+      "setIdx": 1,
562 753
       "setId": 1,
563 754
       "iconIdx": 20
564 755
     },
@@ -584,7 +775,7 @@
584 775
         "code": 59665,
585 776
         "name": "mic-empty"
586 777
       },
587
-      "setIdx": 0,
778
+      "setIdx": 1,
588 779
       "setId": 1,
589 780
       "iconIdx": 21
590 781
     },
@@ -610,7 +801,7 @@
610 801
         "code": 59666,
611 802
         "name": "mic-disabled"
612 803
       },
613
-      "setIdx": 0,
804
+      "setIdx": 1,
614 805
       "setId": 1,
615 806
       "iconIdx": 22
616 807
     },
@@ -636,7 +827,7 @@
636 827
         "code": 59678,
637 828
         "name": "raised-hand"
638 829
       },
639
-      "setIdx": 0,
830
+      "setIdx": 1,
640 831
       "setId": 1,
641 832
       "iconIdx": 23
642 833
     },
@@ -662,7 +853,7 @@
662 853
         "code": 59675,
663 854
         "name": "contactList"
664 855
       },
665
-      "setIdx": 0,
856
+      "setIdx": 1,
666 857
       "setId": 1,
667 858
       "iconIdx": 24
668 859
     },
@@ -688,7 +879,7 @@
688 879
         "code": 59667,
689 880
         "name": "link"
690 881
       },
691
-      "setIdx": 0,
882
+      "setIdx": 1,
692 883
       "setId": 1,
693 884
       "iconIdx": 25
694 885
     },
@@ -714,7 +905,7 @@
714 905
         "code": 59668,
715 906
         "name": "shared-video"
716 907
       },
717
-      "setIdx": 0,
908
+      "setIdx": 1,
718 909
       "setId": 1,
719 910
       "iconIdx": 26
720 911
     },
@@ -740,7 +931,7 @@
740 931
         "code": 59669,
741 932
         "name": "settings"
742 933
       },
743
-      "setIdx": 0,
934
+      "setIdx": 1,
744 935
       "setId": 1,
745 936
       "iconIdx": 27
746 937
     },
@@ -766,7 +957,7 @@
766 957
         "code": 59670,
767 958
         "name": "star"
768 959
       },
769
-      "setIdx": 0,
960
+      "setIdx": 1,
770 961
       "setId": 1,
771 962
       "iconIdx": 28
772 963
     },
@@ -792,7 +983,7 @@
792 983
         "code": 59681,
793 984
         "name": "switch-camera"
794 985
       },
795
-      "setIdx": 0,
986
+      "setIdx": 1,
796 987
       "setId": 1,
797 988
       "iconIdx": 29
798 989
     },
@@ -818,7 +1009,7 @@
818 1009
         "code": 59671,
819 1010
         "name": "share-desktop"
820 1011
       },
821
-      "setIdx": 0,
1012
+      "setIdx": 1,
822 1013
       "setId": 1,
823 1014
       "iconIdx": 30
824 1015
     },
@@ -844,7 +1035,7 @@
844 1035
         "code": 59672,
845 1036
         "name": "camera"
846 1037
       },
847
-      "setIdx": 0,
1038
+      "setIdx": 1,
848 1039
       "setId": 1,
849 1040
       "iconIdx": 31
850 1041
     },
@@ -870,7 +1061,7 @@
870 1061
         "code": 59673,
871 1062
         "name": "camera-disabled"
872 1063
       },
873
-      "setIdx": 0,
1064
+      "setIdx": 1,
874 1065
       "setId": 1,
875 1066
       "iconIdx": 32
876 1067
     },
@@ -896,7 +1087,7 @@
896 1087
         "code": 59674,
897 1088
         "name": "volume"
898 1089
       },
899
-      "setIdx": 0,
1090
+      "setIdx": 1,
900 1091
       "setId": 1,
901 1092
       "iconIdx": 33
902 1093
     },
@@ -925,9 +1116,9 @@
925 1116
         "name": "recDisable",
926 1117
         "ligatures": ""
927 1118
       },
928
-      "setIdx": 0,
1119
+      "setIdx": 1,
929 1120
       "setId": 1,
930
-      "iconIdx": 36
1121
+      "iconIdx": 34
931 1122
     },
932 1123
     {
933 1124
       "icon": {
@@ -955,9 +1146,9 @@
955 1146
         "name": "recEnable",
956 1147
         "ligatures": ""
957 1148
       },
958
-      "setIdx": 0,
1149
+      "setIdx": 1,
959 1150
       "setId": 1,
960
-      "iconIdx": 37
1151
+      "iconIdx": 35
961 1152
     },
962 1153
     {
963 1154
       "icon": {
@@ -985,9 +1176,9 @@
985 1176
         "name": "presentation",
986 1177
         "ligatures": ""
987 1178
       },
988
-      "setIdx": 0,
1179
+      "setIdx": 1,
989 1180
       "setId": 1,
990
-      "iconIdx": 38
1181
+      "iconIdx": 36
991 1182
     },
992 1183
     {
993 1184
       "icon": {
@@ -1011,9 +1202,9 @@
1011 1202
         "code": 59685,
1012 1203
         "name": "dialpad"
1013 1204
       },
1014
-      "setIdx": 0,
1205
+      "setIdx": 1,
1015 1206
       "setId": 1,
1016
-      "iconIdx": 39
1207
+      "iconIdx": 37
1017 1208
     },
1018 1209
     {
1019 1210
       "icon": {
@@ -1037,9 +1228,9 @@
1037 1228
         "code": 59683,
1038 1229
         "name": "visibility"
1039 1230
       },
1040
-      "setIdx": 0,
1231
+      "setIdx": 1,
1041 1232
       "setId": 1,
1042
-      "iconIdx": 40
1233
+      "iconIdx": 38
1043 1234
     },
1044 1235
     {
1045 1236
       "icon": {
@@ -1063,119 +1254,9 @@
1063 1254
         "code": 59684,
1064 1255
         "name": "visibility-off"
1065 1256
       },
1066
-      "setIdx": 0,
1257
+      "setIdx": 1,
1067 1258
       "setId": 1,
1068
-      "iconIdx": 41
1069
-    },
1070
-    {
1071
-      "icon": {
1072
-        "paths": [
1073
-          "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"
1074
-        ],
1075
-        "attrs": [],
1076
-        "isMulticolor": false,
1077
-        "isMulticolor2": false,
1078
-        "tags": [
1079
-          "more_vert"
1080
-        ],
1081
-        "defaultCode": 58836,
1082
-        "grid": 24
1083
-      },
1084
-      "attrs": [],
1085
-      "properties": {
1086
-        "ligatures": "more_vert",
1087
-        "id": 0,
1088
-        "order": 897,
1089
-        "prevSize": 24,
1090
-        "code": 58836,
1091
-        "name": "thumb-menu"
1092
-      },
1093
-      "setIdx": 0,
1094
-      "setId": 1,
1095
-      "iconIdx": 42
1096
-    },
1097
-    {
1098
-      "icon": {
1099
-        "paths": [
1100
-          "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"
1101
-        ],
1102
-        "attrs": [
1103
-          {}
1104
-        ],
1105
-        "isMulticolor": false,
1106
-        "isMulticolor2": false,
1107
-        "tags": [
1108
-          "ninja"
1109
-        ],
1110
-        "grid": 24
1111
-      },
1112
-      "attrs": [
1113
-        {}
1114
-      ],
1115
-      "properties": {
1116
-        "order": 850,
1117
-        "id": 1,
1118
-        "name": "ninja",
1119
-        "prevSize": 24,
1120
-        "code": 59657
1121
-      },
1122
-      "setIdx": 0,
1123
-      "setId": 1,
1124
-      "iconIdx": 43
1125
-    },
1126
-    {
1127
-      "icon": {
1128
-        "paths": [
1129
-          "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"
1130
-        ],
1131
-        "attrs": [],
1132
-        "isMulticolor": false,
1133
-        "isMulticolor2": false,
1134
-        "tags": [
1135
-          "phone"
1136
-        ],
1137
-        "defaultCode": 57549,
1138
-        "grid": 24
1139
-      },
1140
-      "attrs": [],
1141
-      "properties": {
1142
-        "ligatures": "call, local_phone, phone",
1143
-        "id": 2,
1144
-        "order": 851,
1145
-        "prevSize": 24,
1146
-        "code": 57549,
1147
-        "name": "phone"
1148
-      },
1149
-      "setIdx": 0,
1150
-      "setId": 1,
1151
-      "iconIdx": 44
1152
-    },
1153
-    {
1154
-      "icon": {
1155
-        "paths": [
1156
-          "M810 554h-256v256h-84v-256h-256v-84h256v-256h84v256h256v84z"
1157
-        ],
1158
-        "attrs": [],
1159
-        "isMulticolor": false,
1160
-        "isMulticolor2": false,
1161
-        "tags": [
1162
-          "add"
1163
-        ],
1164
-        "defaultCode": 57669,
1165
-        "grid": 24
1166
-      },
1167
-      "attrs": [],
1168
-      "properties": {
1169
-        "ligatures": "add",
1170
-        "id": 3,
1171
-        "order": 896,
1172
-        "prevSize": 24,
1173
-        "code": 57669,
1174
-        "name": "add"
1175
-      },
1176
-      "setIdx": 0,
1177
-      "setId": 1,
1178
-      "iconIdx": 45
1259
+      "iconIdx": 39
1179 1260
     }
1180 1261
   ],
1181 1262
   "height": 1024,

+ 4
- 0
ios/sdk/sdk.xcodeproj/project.pbxproj Parādīt failu

@@ -10,6 +10,7 @@
10 10
 		0B412F181EDEC65D00B1A0A6 /* JitsiMeetView.h in Headers */ = {isa = PBXBuildFile; fileRef = 0B412F161EDEC65D00B1A0A6 /* JitsiMeetView.h */; settings = {ATTRIBUTES = (Public, ); }; };
11 11
 		0B412F191EDEC65D00B1A0A6 /* JitsiMeetView.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B412F171EDEC65D00B1A0A6 /* JitsiMeetView.m */; };
12 12
 		0B412F221EDEF6EA00B1A0A6 /* JitsiMeetViewDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 0B412F1B1EDEC80100B1A0A6 /* JitsiMeetViewDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; };
13
+		0B44A0191F902126009D1D64 /* MPVolumeViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B44A0181F902126009D1D64 /* MPVolumeViewManager.m */; };
13 14
 		0B93EF7B1EC608550030D24D /* CoreText.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0B93EF7A1EC608550030D24D /* CoreText.framework */; };
14 15
 		0B93EF7E1EC9DDCD0030D24D /* RCTBridgeWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 0B93EF7C1EC9DDCD0030D24D /* RCTBridgeWrapper.h */; };
15 16
 		0B93EF7F1EC9DDCD0030D24D /* RCTBridgeWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B93EF7D1EC9DDCD0030D24D /* RCTBridgeWrapper.m */; };
@@ -32,6 +33,7 @@
32 33
 		0B412F161EDEC65D00B1A0A6 /* JitsiMeetView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JitsiMeetView.h; sourceTree = "<group>"; };
33 34
 		0B412F171EDEC65D00B1A0A6 /* JitsiMeetView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JitsiMeetView.m; sourceTree = "<group>"; };
34 35
 		0B412F1B1EDEC80100B1A0A6 /* JitsiMeetViewDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JitsiMeetViewDelegate.h; sourceTree = "<group>"; };
36
+		0B44A0181F902126009D1D64 /* MPVolumeViewManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPVolumeViewManager.m; sourceTree = "<group>"; };
35 37
 		0B93EF7A1EC608550030D24D /* CoreText.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreText.framework; path = System/Library/Frameworks/CoreText.framework; sourceTree = SDKROOT; };
36 38
 		0B93EF7C1EC9DDCD0030D24D /* RCTBridgeWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBridgeWrapper.h; sourceTree = "<group>"; };
37 39
 		0B93EF7D1EC9DDCD0030D24D /* RCTBridgeWrapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBridgeWrapper.m; sourceTree = "<group>"; };
@@ -108,6 +110,7 @@
108 110
 				0B412F161EDEC65D00B1A0A6 /* JitsiMeetView.h */,
109 111
 				0B412F171EDEC65D00B1A0A6 /* JitsiMeetView.m */,
110 112
 				0B412F1B1EDEC80100B1A0A6 /* JitsiMeetViewDelegate.h */,
113
+				0B44A0181F902126009D1D64 /* MPVolumeViewManager.m */,
111 114
 				0B93EF7C1EC9DDCD0030D24D /* RCTBridgeWrapper.h */,
112 115
 				0B93EF7D1EC9DDCD0030D24D /* RCTBridgeWrapper.m */,
113 116
 				0BCA495D1EC4B6C600B793EE /* POSIX.m */,
@@ -293,6 +296,7 @@
293 296
 				0BA13D311EE83FF8007BEF7F /* ExternalAPI.m in Sources */,
294 297
 				0BCA49601EC4B6C600B793EE /* POSIX.m in Sources */,
295 298
 				0BCA495F1EC4B6C600B793EE /* AudioMode.m in Sources */,
299
+				0B44A0191F902126009D1D64 /* MPVolumeViewManager.m in Sources */,
296 300
 				0BCA49611EC4B6C600B793EE /* Proximity.m in Sources */,
297 301
 				0B412F191EDEC65D00B1A0A6 /* JitsiMeetView.m in Sources */,
298 302
 			);

+ 62
- 0
ios/sdk/src/MPVolumeViewManager.m Parādīt failu

@@ -0,0 +1,62 @@
1
+/*
2
+ * Copyright @ 2017-present Atlassian Pty Ltd
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ *     http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+#import <React/RCTUIManager.h>
18
+#import <React/RCTViewManager.h>
19
+
20
+@import MediaPlayer;
21
+
22
+
23
+@interface MPVolumeViewManager : RCTViewManager
24
+@end
25
+
26
+@implementation MPVolumeViewManager
27
+
28
+RCT_EXPORT_MODULE()
29
+
30
+- (UIView *)view {
31
+    MPVolumeView *volumeView = [[MPVolumeView alloc] init];
32
+    volumeView.showsRouteButton = YES;
33
+    volumeView.showsVolumeSlider = NO;
34
+
35
+    return (UIView *) volumeView;
36
+}
37
+
38
+RCT_EXPORT_METHOD(show:(nonnull NSNumber *)reactTag) {
39
+    [self.bridge.uiManager addUIBlock:^(
40
+                __unused RCTUIManager *uiManager,
41
+                NSDictionary<NSNumber *, UIView *> *viewRegistry) {
42
+        id view = viewRegistry[reactTag];
43
+        if (![view isKindOfClass:[MPVolumeView class]]) {
44
+            RCTLogError(@"Invalid view returned from registry, expecting \
45
+                        MPVolumeView, got: %@", view);
46
+        } else {
47
+            // Simulate a click
48
+            UIButton *btn = nil;
49
+            for (UIView *buttonView in ((UIView *) view).subviews) {
50
+                if ([buttonView isKindOfClass:[UIButton class]]) {
51
+                    btn = (UIButton *) buttonView;
52
+                    break;
53
+                }
54
+            }
55
+            if (btn != nil) {
56
+                [btn sendActionsForControlEvents:UIControlEventTouchUpInside];
57
+            }
58
+        }
59
+    }];
60
+}
61
+
62
+@end

+ 351
- 277
react/features/base/font-icons/jitsi.json
Failā izmaiņas netiks attēlotas, jo tās ir par lielu
Parādīt failu


+ 178
- 0
react/features/mobile/audio-mode/components/AudioRoutePickerDialog.js Parādīt failu

@@ -0,0 +1,178 @@
1
+// @flow
2
+
3
+import _ from 'lodash';
4
+import React, { Component } from 'react';
5
+import { NativeModules } from 'react-native';
6
+import { connect } from 'react-redux';
7
+
8
+import { hideDialog, SimpleBottomSheet } from '../../../base/dialog';
9
+
10
+const AudioMode = NativeModules.AudioMode;
11
+
12
+/**
13
+ * Maps each device type to a display name and icon.
14
+ * TODO: internationalization.
15
+ */
16
+const deviceInfoMap = {
17
+    BLUETOOTH: {
18
+        iconName: 'bluetooth',
19
+        text: 'Bluetooth',
20
+        type: 'BLUETOOTH'
21
+    },
22
+    EARPIECE: {
23
+        iconName: 'phone-talk',
24
+        text: 'Phone',
25
+        type: 'EARPIECE'
26
+    },
27
+    HEADPHONES: {
28
+        iconName: 'headset',
29
+        text: 'Headphones',
30
+        type: 'HEADPHONES'
31
+    },
32
+    SPEAKER: {
33
+        iconName: 'volume',
34
+        text: 'Speaker',
35
+        type: 'SPEAKER'
36
+    }
37
+};
38
+
39
+/**
40
+ * Variable to hold the reference to the exported component. This dialog is only
41
+ * exported if the {@code AudioMode} module has the capability to get / set
42
+ * audio devices.
43
+ */
44
+let DialogType;
45
+
46
+/**
47
+ * {@code PasswordRequiredPrompt}'s React {@code Component} prop types.
48
+ */
49
+type Props = {
50
+
51
+    /**
52
+     * Used for hiding the dialog when the selection was completed.
53
+     */
54
+    dispatch: Function
55
+};
56
+
57
+type State = {
58
+
59
+    /**
60
+     * Array of available devices.
61
+     */
62
+    devices: Array<string>
63
+};
64
+
65
+/**
66
+ * Implements a React {@code Component} which prompts the user when a password
67
+ * is required to join a conference.
68
+ */
69
+class AudioRoutePickerDialog extends Component<Props, State> {
70
+    state = {
71
+        // Available audio devices, it will be set in componentWillMount.
72
+        devices: []
73
+    };
74
+
75
+    /**
76
+     * Initializes a new {@code PasswordRequiredPrompt} instance.
77
+     *
78
+     * @param {Props} props - The read-only React {@code Component} props with
79
+     * which the new instance is to be initialized.
80
+     */
81
+    constructor(props) {
82
+        super(props);
83
+
84
+        // Bind event handlers so they are only bound once per instance.
85
+        this._onCancel = this._onCancel.bind(this);
86
+        this._onSubmit = this._onSubmit.bind(this);
87
+    }
88
+
89
+    /**
90
+     * Initializes the device list by querying the {@code AudioMode} module.
91
+     *
92
+     * @inheritdoc
93
+     */
94
+    componentWillMount() {
95
+        AudioMode.getAudioDevices().then(({ devices, selected }) => {
96
+            const audioDevices = [];
97
+
98
+            if (devices) {
99
+                for (const device of devices) {
100
+                    const info = deviceInfoMap[device];
101
+
102
+                    if (info) {
103
+                        info.selected = device === selected;
104
+                        audioDevices.push(info);
105
+                    }
106
+                }
107
+            }
108
+
109
+            if (audioDevices) {
110
+                // Make sure devices is alphabetically sorted
111
+                this.setState({ devices: _.sortBy(audioDevices, 'text') });
112
+            }
113
+        });
114
+    }
115
+
116
+    /**
117
+     * Dispatches a redux action to hide this sheet.
118
+     *
119
+     * @returns {void}
120
+     */
121
+    _hide() {
122
+        this.props.dispatch(hideDialog(DialogType));
123
+    }
124
+
125
+    _onCancel: () => void;
126
+
127
+    /**
128
+     * Cancels the dialog by hiding it.
129
+     *
130
+     * @private
131
+     * @returns {void}
132
+     */
133
+    _onCancel() {
134
+        this._hide();
135
+    }
136
+
137
+    _onSubmit: (?Object) => void;
138
+
139
+    /**
140
+     * Handles the selection of a device on the sheet. The selected device will
141
+     * be used by {@code AudioMode}.
142
+     *
143
+     * @param {Object} device - Object representing the selected device.
144
+     * @private
145
+     * @returns {void}
146
+     */
147
+    _onSubmit(device) {
148
+        this._hide();
149
+        AudioMode.setAudioDevice(device.type);
150
+    }
151
+
152
+    /**
153
+     * Implements React's {@link Component#render()}.
154
+     *
155
+     * @inheritdoc
156
+     * @returns {ReactElement}
157
+     */
158
+    render() {
159
+        if (!this.state.devices.length) {
160
+            return null;
161
+        }
162
+
163
+        return (
164
+            <SimpleBottomSheet
165
+                onCancel = { this._onCancel }
166
+                onSubmit = { this._onSubmit }
167
+                options = { this.state.devices } />
168
+        );
169
+    }
170
+}
171
+
172
+// Only export the dialog if we have support for getting / setting audio devices
173
+// in AudioMode.
174
+if (AudioMode.getAudioDevices && AudioMode.setAudioDevice) {
175
+    DialogType = connect()(AudioRoutePickerDialog);
176
+}
177
+
178
+export default DialogType;

+ 3
- 0
react/features/mobile/audio-mode/components/index.js Parādīt failu

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

+ 2
- 0
react/features/mobile/audio-mode/index.js Parādīt failu

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

+ 160
- 0
react/features/toolbox/components/AudioRouteButton.js Parādīt failu

@@ -0,0 +1,160 @@
1
+// @flow
2
+
3
+import React, { Component } from 'react';
4
+import {
5
+    findNodeHandle,
6
+    requireNativeComponent,
7
+    NativeModules,
8
+    View
9
+} from 'react-native';
10
+import { connect } from 'react-redux';
11
+
12
+import { openDialog } from '../../base/dialog';
13
+import { AudioRoutePickerDialog } from '../../mobile/audio-mode';
14
+
15
+import ToolbarButton from './ToolbarButton';
16
+
17
+/**
18
+ * Define the {@code MPVolumeView} React component. It will only be available
19
+ * on iOS.
20
+ */
21
+let MPVolumeView;
22
+
23
+if (NativeModules.MPVolumeViewManager) {
24
+    MPVolumeView = requireNativeComponent('MPVolumeView', null);
25
+}
26
+
27
+/**
28
+ * Style required to hide the {@code MPVolumeView} view, since it's displayed
29
+ * programmatically.
30
+ */
31
+const HIDE_VIEW_STYLE = { display: 'none' };
32
+
33
+type Props = {
34
+
35
+    /**
36
+     * Used to show the {@code AudioRoutePickerDialog}.
37
+     */
38
+    dispatch: Function,
39
+
40
+    /**
41
+     * The name of the Icon of this {@code AudioRouteButton}.
42
+     */
43
+    iconName: string,
44
+
45
+    /**
46
+     * The style of the Icon of this {@code AudioRouteButton}.
47
+     */
48
+    iconStyle: Object,
49
+
50
+    /**
51
+     * {@code AudioRouteButton} styles.
52
+     */
53
+    style: Array<*> | Object,
54
+
55
+    /**
56
+     * The color underlying the button.
57
+     */
58
+    underlayColor: string
59
+};
60
+
61
+/**
62
+ * A toolbar button which triggers an audio route picker when pressed.
63
+ */
64
+class AudioRouteButton extends Component<Props> {
65
+    _volumeComponent: ?Object;
66
+
67
+    /**
68
+     * Indicates if there is support for audio device selection via this button.
69
+     *
70
+     * @returns {boolean} - True if audio device selection is supported, false
71
+     * otherwise.
72
+     */
73
+    static supported() {
74
+        return Boolean(MPVolumeView || AudioRoutePickerDialog);
75
+    }
76
+
77
+    /**
78
+     * Initializes a new {@code AudioRouteButton} instance.
79
+     *
80
+     * @param {Object} props - The React {@code Component} props to initialize
81
+     * the new {@code AudioRouteButton} instance with.
82
+     */
83
+    constructor(props) {
84
+        super(props);
85
+
86
+        /**
87
+         * The internal reference to the React {@code MPVolumeView} for
88
+         * showing the volume control view.
89
+         *
90
+         * @private
91
+         * @type {ReactComponent}
92
+         */
93
+        this._volumeComponent = null;
94
+
95
+        // Bind event handlers so they are only bound once per instance.
96
+        this._onClick = this._onClick.bind(this);
97
+        this._setVolumeComponent = this._setVolumeComponent.bind(this);
98
+    }
99
+
100
+    _onClick: () => void;
101
+
102
+    /**
103
+     * Handles clicking/pressing this {@code AudioRouteButton} by showing an
104
+     * audio route picker.
105
+     *
106
+     * @private
107
+     * @returns {void}
108
+     */
109
+    _onClick() {
110
+        if (MPVolumeView) {
111
+            const handle = findNodeHandle(this._volumeComponent);
112
+
113
+            NativeModules.MPVolumeViewManager.show(handle);
114
+        } else if (AudioRoutePickerDialog) {
115
+            this.props.dispatch(openDialog(AudioRoutePickerDialog));
116
+        }
117
+    }
118
+
119
+    _setVolumeComponent: (?Object) => void;
120
+
121
+    /**
122
+     * Sets the internal reference to the React Component wrapping the
123
+     * {@code MPVolumeView} component.
124
+     *
125
+     * @param {ReactComponent} component - React Component.
126
+     * @returns {void}
127
+     */
128
+    _setVolumeComponent(component) {
129
+        this._volumeComponent = component;
130
+    }
131
+
132
+    /**
133
+     * Implements React's {@link Component#render()}.
134
+     *
135
+     * @inheritdoc
136
+     * @returns {ReactElement}
137
+     */
138
+    render() {
139
+        const { iconName, iconStyle, style, underlayColor } = this.props;
140
+
141
+        return (
142
+            <View>
143
+                <ToolbarButton
144
+                    iconName = { iconName }
145
+                    iconStyle = { iconStyle }
146
+                    onClick = { this._onClick }
147
+                    style = { style }
148
+                    underlayColor = { underlayColor } />
149
+                {
150
+                    MPVolumeView
151
+                        && <MPVolumeView
152
+                            ref = { this._setVolumeComponent }
153
+                            style = { HIDE_VIEW_STYLE } />
154
+                }
155
+            </View>
156
+        );
157
+    }
158
+}
159
+
160
+export default connect()(AudioRouteButton);

+ 10
- 0
react/features/toolbox/components/Toolbox.native.js Parādīt failu

@@ -25,6 +25,8 @@ import {
25 25
     abstractMapDispatchToProps,
26 26
     abstractMapStateToProps
27 27
 } from '../functions';
28
+
29
+import AudioRouteButton from './AudioRouteButton';
28 30
 import styles from './styles';
29 31
 import ToolbarButton from './ToolbarButton';
30 32
 
@@ -310,6 +312,14 @@ class Toolbox extends Component {
310 312
                             style = { style }
311 313
                             underlayColor = { underlayColor } />
312 314
                 }
315
+                {
316
+                    AudioRouteButton.supported()
317
+                        && <AudioRouteButton
318
+                            iconName = { 'volume' }
319
+                            iconStyle = { iconStyle }
320
+                            style = { style }
321
+                            underlayColor = { underlayColor } />
322
+                }
313 323
             </View>
314 324
         );
315 325
 

Notiek ielāde…
Atcelt
Saglabāt