Pārlūkot izejas kodu

ref(iframe-api-devices): Use labels instead of IDs

master
Hristo Terezov 6 gadus atpakaļ
vecāks
revīzija
4967488e56

+ 13
- 13
doc/api.md Parādīt failu

@@ -51,9 +51,9 @@ var domain = "meet.jit.si";
51 51
 var options = {
52 52
     ...
53 53
     devices: {
54
-        'audioInput': '<device_id>',
55
-        'audioOutput': '<device_id>',
56
-        'videoInput': '<device_id>'
54
+        'audioInput': '<deviceLabel>',
55
+        'audioOutput': '<deviceLabel>',
56
+        'videoInput': '<deviceLabel>'
57 57
     }
58 58
 }
59 59
 var api = new JitsiMeetExternalAPI(domain, options);
@@ -109,14 +109,14 @@ api.getAvailableDevices().then(function(devices) {
109 109
     ...
110 110
 });
111 111
 ```
112
-* **getCurrentDevices** - Retrieve a list with the devices that are currently sected.
112
+* **getCurrentDevices** - Retrieve a list with the devices that are currently selected.
113 113
 
114 114
 ```javascript
115 115
 api.getCurrentDevices().then(function(devices) {
116 116
     // devices = {
117
-    //     'audioInput': 'deviceID',
118
-    //     'audioOutput': 'deviceID',
119
-    //     'videoInput': 'deviceID'
117
+    //     'audioInput': 'deviceLabel',
118
+    //     'audioOutput': 'deviceLabel',
119
+    //     'videoInput': 'deviceLabel'
120 120
     // }
121 121
     ...
122 122
 });
@@ -143,20 +143,20 @@ api.isMultipleAudioInputSupported().then(function(isMultipleAudioInputSupported)
143 143
     ...
144 144
 });
145 145
 ```
146
-* **setAudioInputDevice** - Sets the audio input device to the one with the id that is passed.
146
+* **setAudioInputDevice** - Sets the audio input device to the one with the label that is passed.
147 147
 
148 148
 ```javascript
149
-api.setAudioInputDevice(deviceId);
149
+api.setAudioInputDevice(deviceLabel);
150 150
 ```
151
-* **setAudioOutputDevice** - Sets the audio output device to the one with the id that is passed.
151
+* **setAudioOutputDevice** - Sets the audio output device to the one with the label that is passed.
152 152
 
153 153
 ```javascript
154
-api.setAudioOutputDevice(deviceId);
154
+api.setAudioOutputDevice(deviceLabel);
155 155
 ```
156
-* **setVideoInputDevice** - Sets the video input device to the one with the id that is passed.
156
+* **setVideoInputDevice** - Sets the video input device to the one with the label that is passed.
157 157
 
158 158
 ```javascript
159
-api.setVideoInputDevice(deviceId);
159
+api.setVideoInputDevice(deviceLabel);
160 160
 ```
161 161
 
162 162
 You can control the embedded Jitsi Meet conference using the `JitsiMeetExternalAPI` object by using `executeCommand`:

+ 9
- 9
modules/API/external/functions.js Parādīt failu

@@ -105,12 +105,12 @@ export function isMultipleAudioInputSupported(transport: Object) {
105 105
  *
106 106
  * @param {Transport} transport - The @code{Transport} instance responsible for
107 107
  * the external communication.
108
- * @param {string} id - The id of the new device.
108
+ * @param {string} label - The label of the new device.
109 109
  * @returns {Promise}
110 110
  */
111
-export function setAudioInputDevice(transport: Object, id: string) {
111
+export function setAudioInputDevice(transport: Object, label: string) {
112 112
     return _setDevice(transport, {
113
-        id,
113
+        label,
114 114
         kind: 'audioinput'
115 115
     });
116 116
 }
@@ -120,12 +120,12 @@ export function setAudioInputDevice(transport: Object, id: string) {
120 120
  *
121 121
  * @param {Transport} transport - The @code{Transport} instance responsible for
122 122
  * the external communication.
123
- * @param {string} id - The id of the new device.
123
+ * @param {string} label - The label of the new device.
124 124
  * @returns {Promise}
125 125
  */
126
-export function setAudioOutputDevice(transport: Object, id: string) {
126
+export function setAudioOutputDevice(transport: Object, label: string) {
127 127
     return _setDevice(transport, {
128
-        id,
128
+        label,
129 129
         kind: 'audiooutput'
130 130
     });
131 131
 }
@@ -151,12 +151,12 @@ function _setDevice(transport: Object, device) {
151 151
  *
152 152
  * @param {Transport} transport - The @code{Transport} instance responsible for
153 153
  * the external communication.
154
- * @param {string} id - The id of the new device.
154
+ * @param {string} label - The label of the new device.
155 155
  * @returns {Promise}
156 156
  */
157
-export function setVideoInputDevice(transport: Object, id: string) {
157
+export function setVideoInputDevice(transport: Object, label: string) {
158 158
     return _setDevice(transport, {
159
-        id,
159
+        label,
160 160
         kind: 'videoinput'
161 161
     });
162 162
 }

+ 21
- 0
react/features/base/devices/actionTypes.js Parādīt failu

@@ -30,3 +30,24 @@ export const SET_VIDEO_INPUT_DEVICE = 'SET_VIDEO_INPUT_DEVICE';
30 30
  * }
31 31
  */
32 32
 export const UPDATE_DEVICE_LIST = 'UPDATE_DEVICE_LIST';
33
+
34
+/**
35
+ * The type of Redux action which will add a pending device requests that will
36
+ * be executed later when it is possible (when the conference is joined).
37
+ *
38
+ * {
39
+ *     type: ADD_PENDING_DEVICE_REQUEST,
40
+ *     request: Object
41
+ * }
42
+ */
43
+export const ADD_PENDING_DEVICE_REQUEST = 'ADD_PENDING_DEVICE_REQUEST';
44
+
45
+/**
46
+ * The type of Redux action which will remove all pending device requests.
47
+ *
48
+ * {
49
+ *     type: REMOVE_PENDING_DEVICE_REQUESTS,
50
+ *     request: Object
51
+ * }
52
+ */
53
+export const REMOVE_PENDING_DEVICE_REQUESTS = 'REMOVE_PENDING_DEVICE_REQUESTS';

+ 96
- 19
react/features/base/devices/actions.js Parādīt failu

@@ -2,11 +2,92 @@ import JitsiMeetJS from '../lib-jitsi-meet';
2 2
 import { updateSettings } from '../settings';
3 3
 
4 4
 import {
5
+    ADD_PENDING_DEVICE_REQUEST,
6
+    REMOVE_PENDING_DEVICE_REQUESTS,
5 7
     SET_AUDIO_INPUT_DEVICE,
6 8
     SET_VIDEO_INPUT_DEVICE,
7 9
     UPDATE_DEVICE_LIST
8 10
 } from './actionTypes';
9
-import { getDevicesFromURL } from './functions';
11
+import {
12
+    areDeviceLabelsInitialized,
13
+    getDeviceIdByLabel,
14
+    getDevicesFromURL
15
+} from './functions';
16
+
17
+/**
18
+ * Adds a pending device request.
19
+ *
20
+ * @param {Object} request - The request to be added.
21
+ * @returns {{
22
+ *      type: ADD_PENDING_DEVICE_REQUEST,
23
+ *      request: Object
24
+ * }}
25
+ */
26
+export function addPendingDeviceRequest(request) {
27
+    return {
28
+        type: ADD_PENDING_DEVICE_REQUEST,
29
+        request
30
+    };
31
+}
32
+
33
+/**
34
+ * Configures the initial A/V devices before the conference has started.
35
+ *
36
+ * @returns {Function}
37
+ */
38
+export function configureInitialDevices() {
39
+    return (dispatch, getState) => new Promise(resolve => {
40
+        const deviceLabels = getDevicesFromURL(getState());
41
+
42
+        if (deviceLabels) {
43
+            dispatch(getAvailableDevices()).then(() => {
44
+                const state = getState();
45
+
46
+                if (!areDeviceLabelsInitialized(state)) {
47
+                    // The labels are not available if the A/V permissions are
48
+                    // not yet granted.
49
+
50
+                    Object.keys(deviceLabels).forEach(key => {
51
+                        dispatch(addPendingDeviceRequest({
52
+                            type: 'devices',
53
+                            name: 'setDevice',
54
+                            device: {
55
+                                kind: key.toLowerCase(),
56
+                                label: deviceLabels[key]
57
+                            },
58
+                            // eslint-disable-next-line no-empty-function
59
+                            responseCallback() {}
60
+                        }));
61
+                    });
62
+                    resolve();
63
+
64
+                    return;
65
+                }
66
+                const newSettings = {};
67
+                const devicesKeysToSettingsKeys = {
68
+                    audioInput: 'micDeviceId',
69
+                    audioOutput: 'audioOutputDeviceId',
70
+                    videoInput: 'cameraDeviceId'
71
+                };
72
+
73
+                Object.keys(deviceLabels).forEach(key => {
74
+                    const label = deviceLabels[key];
75
+                    const deviceId = getDeviceIdByLabel(state, label);
76
+
77
+                    if (deviceId) {
78
+                        newSettings[devicesKeysToSettingsKeys[key]] = deviceId;
79
+                    }
80
+                });
81
+
82
+                dispatch(updateSettings(newSettings));
83
+                resolve();
84
+            });
85
+
86
+        } else {
87
+            resolve();
88
+        }
89
+    });
90
+}
10 91
 
11 92
 /**
12 93
  * Queries for connected A/V input and output devices and updates the redux
@@ -31,6 +112,20 @@ export function getAvailableDevices() {
31 112
     });
32 113
 }
33 114
 
115
+
116
+/**
117
+ * Remove all pending device requests.
118
+ *
119
+ * @returns {{
120
+ *      type: REMOVE_PENDING_DEVICE_REQUESTS
121
+ * }}
122
+ */
123
+export function removePendingDeviceRequests() {
124
+    return {
125
+        type: REMOVE_PENDING_DEVICE_REQUESTS
126
+    };
127
+}
128
+
34 129
 /**
35 130
  * Signals to update the currently used audio input device.
36 131
  *
@@ -80,21 +175,3 @@ export function updateDeviceList(devices) {
80 175
     };
81 176
 }
82 177
 
83
-/**
84
- * Configures the initial A/V devices before the conference has started.
85
- *
86
- * @returns {Function}
87
- */
88
-export function configureInitialDevices() {
89
-    return (dispatch, getState) => new Promise(resolve => {
90
-        const devices = getDevicesFromURL(getState());
91
-
92
-        if (devices) {
93
-            dispatch(updateSettings({
94
-                ...devices
95
-            }));
96
-            resolve();
97
-        }
98
-    });
99
-}
100
-

+ 79
- 35
react/features/base/devices/functions.js Parādīt failu

@@ -4,6 +4,30 @@ import { parseURLParams } from '../config';
4 4
 import JitsiMeetJS from '../lib-jitsi-meet';
5 5
 import { updateSettings } from '../settings';
6 6
 
7
+declare var APP: Object;
8
+
9
+/**
10
+ * Detects the use case when the labels are not available if the A/V permissions
11
+ * are not yet granted.
12
+ *
13
+ * @param {Object} state - The redux state.
14
+ * @returns {boolean} - True if the labels are already initialized and false
15
+ * otherwise.
16
+ */
17
+export function areDeviceLabelsInitialized(state: Object) {
18
+    if (APP.conference._localTracksInitialized) {
19
+        return true;
20
+    }
21
+
22
+    for (const type of [ 'audioInput', 'audioOutput', 'videoInput' ]) {
23
+        if (state['features/base/devices'][type].find(d => Boolean(d.label))) {
24
+            return true;
25
+        }
26
+    }
27
+
28
+    return false;
29
+}
30
+
7 31
 /**
8 32
  * Get device id of the audio output device which is currently in use.
9 33
  * Empty string stands for default device.
@@ -15,21 +39,50 @@ export function getAudioOutputDeviceId() {
15 39
 }
16 40
 
17 41
 /**
18
- * Set device id of the audio output device which is currently in use.
19
- * Empty string stands for default device.
42
+ * Finds a device with a label that matches the passed label and returns its id.
20 43
  *
21
- * @param {string} newId - New audio output device id.
22
- * @param {Function} dispatch - The Redux dispatch function.
23
- * @returns {Promise}
44
+ * @param {Object} state - The redux state.
45
+ * @param {string} label - The label.
46
+ * @returns {string|undefined}
24 47
  */
25
-export function setAudioOutputDeviceId(
26
-        newId: string = 'default',
27
-        dispatch: Function): Promise<*> {
28
-    return JitsiMeetJS.mediaDevices.setAudioOutputDevice(newId)
29
-        .then(() =>
30
-            dispatch(updateSettings({
31
-                audioOutputDeviceId: newId
32
-            })));
48
+export function getDeviceIdByLabel(state: Object, label: string) {
49
+    const types = [ 'audioInput', 'audioOutput', 'videoInput' ];
50
+
51
+    for (const type of types) {
52
+        const device
53
+            = state['features/base/devices'][type].find(d => d.label === label);
54
+
55
+        if (device) {
56
+            return device.deviceId;
57
+        }
58
+    }
59
+}
60
+
61
+/**
62
+ * Returns the devices set in the URL.
63
+ *
64
+ * @param {Object} state - The redux state.
65
+ * @returns {Object|undefined}
66
+ */
67
+export function getDevicesFromURL(state: Object) {
68
+    const urlParams
69
+        = parseURLParams(state['features/base/connection'].locationURL);
70
+
71
+    const audioOutput = urlParams['devices.audioOutput'];
72
+    const videoInput = urlParams['devices.videoInput'];
73
+    const audioInput = urlParams['devices.audioInput'];
74
+
75
+    if (!audioOutput && !videoInput && !audioInput) {
76
+        return undefined;
77
+    }
78
+
79
+    const devices = {};
80
+
81
+    audioOutput && (devices.audioOutput = audioOutput);
82
+    videoInput && (devices.videoInput = videoInput);
83
+    audioInput && (devices.audioInput = audioInput);
84
+
85
+    return devices;
33 86
 }
34 87
 
35 88
 /**
@@ -50,28 +103,19 @@ export function groupDevicesByKind(devices: Object[]): Object {
50 103
 }
51 104
 
52 105
 /**
53
- * Returns the devices set in the URL.
106
+ * Set device id of the audio output device which is currently in use.
107
+ * Empty string stands for default device.
54 108
  *
55
- * @param {Object} state - The redux state.
56
- * @returns {Object|undefined}
109
+ * @param {string} newId - New audio output device id.
110
+ * @param {Function} dispatch - The Redux dispatch function.
111
+ * @returns {Promise}
57 112
  */
58
-export function getDevicesFromURL(state: Object) {
59
-    const urlParams
60
-        = parseURLParams(state['features/base/connection'].locationURL);
61
-
62
-    const audioOutputDeviceId = urlParams['devices.audioOutput'];
63
-    const cameraDeviceId = urlParams['devices.videoInput'];
64
-    const micDeviceId = urlParams['devices.audioInput'];
65
-
66
-    if (!audioOutputDeviceId && !cameraDeviceId && !micDeviceId) {
67
-        return undefined;
68
-    }
69
-
70
-    const devices = {};
71
-
72
-    audioOutputDeviceId && (devices.audioOutputDeviceId = audioOutputDeviceId);
73
-    cameraDeviceId && (devices.cameraDeviceId = cameraDeviceId);
74
-    micDeviceId && (devices.micDeviceId = micDeviceId);
75
-
76
-    return devices;
113
+export function setAudioOutputDeviceId(
114
+        newId: string = 'default',
115
+        dispatch: Function): Promise<*> {
116
+    return JitsiMeetJS.mediaDevices.setAudioOutputDevice(newId)
117
+        .then(() =>
118
+            dispatch(updateSettings({
119
+                audioOutputDeviceId: newId
120
+            })));
77 121
 }

+ 37
- 2
react/features/base/devices/middleware.js Parādīt failu

@@ -1,9 +1,11 @@
1 1
 /* global APP */
2 2
 
3
-import UIEvents from '../../../../service/UI/UIEvents';
4
-
3
+import { CONFERENCE_JOINED } from '../conference';
4
+import { processRequest } from '../../device-selection';
5 5
 import { MiddlewareRegistry } from '../redux';
6
+import UIEvents from '../../../../service/UI/UIEvents';
6 7
 
8
+import { removePendingDeviceRequests } from './actions';
7 9
 import {
8 10
     SET_AUDIO_INPUT_DEVICE,
9 11
     SET_VIDEO_INPUT_DEVICE
@@ -18,6 +20,8 @@ import {
18 20
 // eslint-disable-next-line no-unused-vars
19 21
 MiddlewareRegistry.register(store => next => action => {
20 22
     switch (action.type) {
23
+    case CONFERENCE_JOINED:
24
+        return _conferenceJoined(store, next, action);
21 25
     case SET_AUDIO_INPUT_DEVICE:
22 26
         APP.UI.emitEvent(UIEvents.AUDIO_DEVICE_CHANGED, action.deviceId);
23 27
         break;
@@ -28,3 +32,34 @@ MiddlewareRegistry.register(store => next => action => {
28 32
 
29 33
     return next(action);
30 34
 });
35
+
36
+
37
+/**
38
+ * Does extra sync up on properties that may need to be updated after the
39
+ * conference was joined.
40
+ *
41
+ * @param {Store} store - The redux store in which the specified {@code action}
42
+ * is being dispatched.
43
+ * @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
44
+ * specified {@code action} to the specified {@code store}.
45
+ * @param {Action} action - The redux action {@code CONFERENCE_JOINED} which is
46
+ * being dispatched in the specified {@code store}.
47
+ * @private
48
+ * @returns {Object} The value returned by {@code next(action)}.
49
+ */
50
+function _conferenceJoined({ dispatch, getState }, next, action) {
51
+    const result = next(action);
52
+    const state = getState();
53
+    const { pendingRequests } = state['features/base/devices'];
54
+
55
+    pendingRequests.forEach(request => {
56
+        processRequest(
57
+            dispatch,
58
+            getState,
59
+            request,
60
+            request.responseCallback);
61
+    });
62
+    dispatch(removePendingDeviceRequests());
63
+
64
+    return result;
65
+}

+ 20
- 1
react/features/base/devices/reducer.js Parādīt failu

@@ -1,4 +1,6 @@
1 1
 import {
2
+    ADD_PENDING_DEVICE_REQUEST,
3
+    REMOVE_PENDING_DEVICE_REQUESTS,
2 4
     SET_AUDIO_INPUT_DEVICE,
3 5
     SET_VIDEO_INPUT_DEVICE,
4 6
     UPDATE_DEVICE_LIST
@@ -10,7 +12,8 @@ import { ReducerRegistry } from '../redux';
10 12
 const DEFAULT_STATE = {
11 13
     audioInput: [],
12 14
     audioOutput: [],
13
-    videoInput: []
15
+    videoInput: [],
16
+    pendingRequests: []
14 17
 };
15 18
 
16 19
 /**
@@ -31,10 +34,26 @@ ReducerRegistry.register(
31 34
             const deviceList = groupDevicesByKind(action.devices);
32 35
 
33 36
             return {
37
+                pendingRequests: state.pendingRequests,
34 38
                 ...deviceList
35 39
             };
36 40
         }
37 41
 
42
+        case ADD_PENDING_DEVICE_REQUEST:
43
+            return {
44
+                ...state,
45
+                pendingRequests: [
46
+                    ...state.pendingRequests,
47
+                    action.request
48
+                ]
49
+            };
50
+
51
+        case REMOVE_PENDING_DEVICE_REQUESTS:
52
+            return {
53
+                ...state,
54
+                pendingRequests: [ ]
55
+            };
56
+
38 57
         // TODO: Changing of current audio and video device id is currently
39 58
         // handled outside of react/redux. Fall through to default logic for
40 59
         // now.

+ 88
- 20
react/features/device-selection/functions.js Parādīt failu

@@ -1,7 +1,13 @@
1 1
 // @flow
2
+
3
+import type { Dispatch } from 'redux';
4
+
2 5
 import {
6
+    addPendingDeviceRequest,
7
+    areDeviceLabelsInitialized,
3 8
     getAudioOutputDeviceId,
4 9
     getAvailableDevices,
10
+    getDeviceIdByLabel,
5 11
     groupDevicesByKind,
6 12
     setAudioInputDevice,
7 13
     setAudioOutputDeviceId,
@@ -49,13 +55,15 @@ export function getDeviceSelectionDialogProps(stateful: Object | Function) {
49 55
  * @returns {boolean}
50 56
  */
51 57
 export function processRequest( // eslint-disable-line max-params
52
-        dispatch: Dispatch<*>,
58
+        dispatch: Dispatch<any>,
53 59
         getState: Function,
54 60
         request: Object,
55 61
         responseCallback: Function) {
56 62
     if (request.type === 'devices') {
57 63
         const state = getState();
58 64
         const settings = state['features/base/settings'];
65
+        const { conference } = state['features/base/conference'];
66
+        let result = true;
59 67
 
60 68
         switch (request.name) {
61 69
         case 'isDeviceListAvailable':
@@ -70,35 +78,95 @@ export function processRequest( // eslint-disable-line max-params
70 78
             responseCallback(JitsiMeetJS.isMultipleAudioInputSupported());
71 79
             break;
72 80
         case 'getCurrentDevices':
73
-            responseCallback({
74
-                audioInput: settings.micDeviceId,
75
-                audioOutput: getAudioOutputDeviceId(),
76
-                videoInput: settings.cameraDeviceId
81
+            dispatch(getAvailableDevices()).then(devices => {
82
+                if (areDeviceLabelsInitialized(state)) {
83
+                    let audioInput, audioOutput, videoInput;
84
+                    const audioOutputDeviceId = getAudioOutputDeviceId();
85
+                    const { cameraDeviceId, micDeviceId } = settings;
86
+
87
+                    devices.forEach(({ deviceId, label }) => {
88
+                        switch (deviceId) {
89
+                        case micDeviceId:
90
+                            audioInput = label;
91
+                            break;
92
+                        case audioOutputDeviceId:
93
+                            audioOutput = label;
94
+                            break;
95
+                        case cameraDeviceId:
96
+                            videoInput = label;
97
+                            break;
98
+                        }
99
+                    });
100
+
101
+                    responseCallback({
102
+                        audioInput,
103
+                        audioOutput,
104
+                        videoInput
105
+                    });
106
+                } else {
107
+                    // The labels are not available if the A/V permissions are
108
+                    // not yet granted.
109
+                    dispatch(addPendingDeviceRequest({
110
+                        type: 'devices',
111
+                        name: 'getCurrentDevices',
112
+                        responseCallback
113
+                    }));
114
+                }
77 115
             });
116
+
78 117
             break;
79 118
         case 'getAvailableDevices':
80
-            dispatch(getAvailableDevices()).then(
81
-                devices => responseCallback(groupDevicesByKind(devices)));
119
+            dispatch(getAvailableDevices()).then(devices => {
120
+                if (areDeviceLabelsInitialized(state)) {
121
+                    responseCallback(groupDevicesByKind(devices));
122
+                } else {
123
+                    // The labels are not available if the A/V permissions are
124
+                    // not yet granted.
125
+                    dispatch(addPendingDeviceRequest({
126
+                        type: 'devices',
127
+                        name: 'getAvailableDevices',
128
+                        responseCallback
129
+                    }));
130
+                }
131
+            });
82 132
 
83 133
             break;
84 134
         case 'setDevice': {
85 135
             const { device } = request;
86 136
 
87
-            switch (device.kind) {
88
-            case 'audioinput':
89
-                dispatch(setAudioInputDevice(device.id));
90
-                break;
91
-            case 'audiooutput':
92
-                setAudioOutputDeviceId(device.id, dispatch);
93
-                break;
94
-            case 'videoinput':
95
-                dispatch(setVideoInputDevice(device.id));
96
-                break;
97
-            default:
98
-                responseCallback(false);
137
+            if (!conference) {
138
+                dispatch(addPendingDeviceRequest({
139
+                    type: 'devices',
140
+                    name: 'setDevice',
141
+                    device,
142
+                    responseCallback
143
+                }));
144
+
145
+                return true;
146
+            }
147
+
148
+            const deviceId = getDeviceIdByLabel(state, device.label);
149
+
150
+            if (deviceId) {
151
+                switch (device.kind) {
152
+                case 'audioinput': {
153
+                    dispatch(setAudioInputDevice(deviceId));
154
+                    break;
155
+                }
156
+                case 'audiooutput':
157
+                    setAudioOutputDeviceId(deviceId, dispatch);
158
+                    break;
159
+                case 'videoinput':
160
+                    dispatch(setVideoInputDevice(deviceId));
161
+                    break;
162
+                default:
163
+                    result = false;
164
+                }
165
+            } else {
166
+                result = false;
99 167
             }
100 168
 
101
-            responseCallback(true);
169
+            responseCallback(result);
102 170
             break;
103 171
         }
104 172
         default:

Notiek ielāde…
Atcelt
Saglabāt