mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-03-06 11:40:21 +00:00
Compare commits
32 Commits
saghul-pat
...
android-sd
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
384f7d7890 | ||
|
|
4d9dcf5d43 | ||
|
|
e217e10af5 | ||
|
|
7feec7c11d | ||
|
|
36eb27e233 | ||
|
|
b791fc32fd | ||
|
|
b1a70240fc | ||
|
|
50d7c1521f | ||
|
|
5d9762b429 | ||
|
|
4e78502c9e | ||
|
|
6ff733dae0 | ||
|
|
2dc59b9ea0 | ||
|
|
e65918564b | ||
|
|
ce9744b9c3 | ||
|
|
b413457a4f | ||
|
|
75daedf9ab | ||
|
|
45eeea447a | ||
|
|
16fcc55ad1 | ||
|
|
a70009e486 | ||
|
|
06502e5aac | ||
|
|
6316447d4b | ||
|
|
df5fa71b92 | ||
|
|
10e951c17c | ||
|
|
f12317dc59 | ||
|
|
829e5597d5 | ||
|
|
f2e0704b93 | ||
|
|
a7aaf31c79 | ||
|
|
4967488e56 | ||
|
|
ed1d3d3df5 | ||
|
|
427f49367b | ||
|
|
659e420005 | ||
|
|
9a46606f0d |
@@ -13,7 +13,7 @@ Found a bug and know how to fix it? Great! Please read on.
|
||||
## Contributor License Agreement
|
||||
While the Jitsi projects are released under the
|
||||
[Apache License 2.0](https://github.com/jitsi/jitsi-meet/blob/master/LICENSE), the copyright
|
||||
holder and principal creator is [Atlassian](https://www.atlassian.com/). To
|
||||
holder and principal creator is [8x8](https://www.8x8.com/). To
|
||||
ensure that we can continue making these projects available under an Open Source license,
|
||||
we need you to sign our Apache-based contributor
|
||||
license agreement as either a [corporation](https://jitsi.org/ccla) or an
|
||||
|
||||
@@ -73,25 +73,25 @@ allprojects {
|
||||
// consequently, we should qualify their version.
|
||||
def versionQualifier = '-jitsi-1'
|
||||
if ('react-native-background-timer' == project.name)
|
||||
versionQualifier = '-jitsi-3' // 2.0.0 + react-native 0.57
|
||||
versionQualifier = '-jitsi-4' // 2.1.1 + react-native 0.59
|
||||
else if ('react-native-calendar-events' == project.name)
|
||||
versionQualifier = '-jitsi-2' // 1.6.4 + react-native 0.57
|
||||
versionQualifier = '-jitsi-3' // 1.6.4 + react-native 0.59
|
||||
else if ('react-native-fast-image' == project.name)
|
||||
versionQualifier = '-jitsi-2' // 5.1.1 + react-native 0.57
|
||||
versionQualifier = '-jitsi-3' // 5.1.1 + react-native 0.59
|
||||
else if ('react-native-google-signin' == project.name)
|
||||
versionQualifier = '-jitsi-2' // 1.0.2 + react-native 0.57
|
||||
versionQualifier = '-jitsi-3' // 1.0.2 + react-native 0.59
|
||||
else if ('react-native-immersive' == project.name)
|
||||
versionQualifier = '-jitsi-5' // 2.0.0 + react-native 0.57
|
||||
versionQualifier = '-jitsi-6' // 2.0.0 + react-native 0.59
|
||||
else if ('react-native-keep-awake' == project.name)
|
||||
versionQualifier = '-jitsi-4' // 4.0.0 + react-native 0.57
|
||||
versionQualifier = '-jitsi-5' // 4.0.0 + react-native 0.59
|
||||
else if ('react-native-linear-gradient' == project.name)
|
||||
versionQualifier = '-jitsi-1' // 2.4.0 + react-native 0.57
|
||||
versionQualifier = '-jitsi-2' // 2.5.3 + react-native 0.59
|
||||
else if ('react-native-sound' == project.name)
|
||||
versionQualifier = '-jitsi-2' // 0.10.9 + react-native 0.57
|
||||
versionQualifier = '-jitsi-3' // 0.10.12 + react-native 0.59
|
||||
else if ('react-native-vector-icons' == project.name)
|
||||
versionQualifier = '-jitsi-3' // 6.0.2 + react-native 0.57
|
||||
versionQualifier = '-jitsi-4' // 6.0.2 + react-native 0.59
|
||||
else if ('react-native-webrtc' == project.name)
|
||||
versionQualifier = '-jitsi-9' // 6322a9b5a38ce590cfaea4041072ea87c8dbf558 + react-native 0.57
|
||||
versionQualifier = '-jitsi-10' // 032ee5c90e2c5ff27ab2f952217104772fcbd155 + react-native 0.59
|
||||
|
||||
project.version = "${json.version}${versionQualifier}"
|
||||
|
||||
|
||||
@@ -64,6 +64,9 @@ class AppInfoModule
|
||||
|
||||
Map<String, Object> constants = new HashMap<>();
|
||||
|
||||
constants.put(
|
||||
"buildNumber",
|
||||
packageInfo == null ? "" : String.valueOf(packageInfo.versionCode));
|
||||
constants.put(
|
||||
"name",
|
||||
applicationInfo == null
|
||||
|
||||
@@ -1919,21 +1919,6 @@ export default {
|
||||
JitsiConferenceEvents.PARTICIPANT_PROPERTY_CHANGED,
|
||||
(participant, name, oldValue, newValue) => {
|
||||
switch (name) {
|
||||
case 'features_screen-sharing': {
|
||||
APP.store.dispatch(participantUpdated({
|
||||
conference: room,
|
||||
id: participant.getId(),
|
||||
features: { 'screen-sharing': true }
|
||||
}));
|
||||
break;
|
||||
}
|
||||
case 'raisedHand':
|
||||
APP.store.dispatch(participantUpdated({
|
||||
conference: room,
|
||||
id: participant.getId(),
|
||||
raisedHand: newValue === 'true'
|
||||
}));
|
||||
break;
|
||||
case 'remoteControlSessionStatus':
|
||||
APP.UI.setRemoteControlActiveStatus(
|
||||
participant.getId(),
|
||||
@@ -2417,7 +2402,7 @@ export default {
|
||||
*/
|
||||
updateAudioIconEnabled() {
|
||||
const audioMediaDevices
|
||||
= APP.store.getState()['features/base/devices'].audioInput;
|
||||
= APP.store.getState()['features/base/devices'].availableDevices.audioInput;
|
||||
const audioDeviceCount
|
||||
= audioMediaDevices ? audioMediaDevices.length : 0;
|
||||
|
||||
@@ -2440,7 +2425,7 @@ export default {
|
||||
*/
|
||||
updateVideoIconEnabled() {
|
||||
const videoMediaDevices
|
||||
= APP.store.getState()['features/base/devices'].videoInput;
|
||||
= APP.store.getState()['features/base/devices'].availableDevices.videoInput;
|
||||
const videoDeviceCount
|
||||
= videoMediaDevices ? videoMediaDevices.length : 0;
|
||||
|
||||
|
||||
270
doc/api.md
270
doc/api.md
@@ -7,7 +7,7 @@ You can use the Jitsi Meet API to embed Jitsi Meet in to your application. You a
|
||||
To embed Jitsi Meet in your application you need to add the Jitsi Meet API library:
|
||||
|
||||
```javascript
|
||||
<script src="https://meet.jit.si/external_api.js"></script>
|
||||
<script src='https://meet.jit.si/external_api.js'></script>
|
||||
```
|
||||
## API
|
||||
|
||||
@@ -16,7 +16,7 @@ To embed Jitsi Meet in your application you need to add the Jitsi Meet API libra
|
||||
The next step for embedding Jitsi Meet is to create the Jitsi Meet API object.
|
||||
Its constructor gets a number of options:
|
||||
|
||||
* **domain**: domain used to build the conference URL, "meet.jit.si" for
|
||||
* **domain**: domain used to build the conference URL, 'meet.jit.si' for
|
||||
example.
|
||||
* **options**: object with properties - the optional arguments:
|
||||
* **roomName**: (optional) name of the room to join.
|
||||
@@ -29,46 +29,160 @@ Its constructor gets a number of options:
|
||||
* **jwt**: (optional) [JWT](https://jwt.io/) token.
|
||||
* **onload**: (optional) handler for the iframe onload event.
|
||||
* **invitees**: (optional) Array of objects containing information about new participants that will be invited in the call.
|
||||
* **devices**: (optional) A map containing information about the initial devices that will be used in the call.
|
||||
|
||||
Example:
|
||||
|
||||
```javascript
|
||||
var domain = "meet.jit.si";
|
||||
var options = {
|
||||
roomName: "JitsiMeetAPIExample",
|
||||
const domain = 'meet.jit.si';
|
||||
const options = {
|
||||
roomName: 'JitsiMeetAPIExample',
|
||||
width: 700,
|
||||
height: 700,
|
||||
parentNode: document.querySelector('#meet')
|
||||
}
|
||||
var api = new JitsiMeetExternalAPI(domain, options);
|
||||
};
|
||||
const api = new JitsiMeetExternalAPI(domain, options);
|
||||
```
|
||||
|
||||
You can set the initial media devices for the call:
|
||||
|
||||
```javascript
|
||||
const domain = 'meet.jit.si';
|
||||
const options = {
|
||||
...
|
||||
devices: {
|
||||
audioInput: '<deviceLabel>',
|
||||
audioOutput: '<deviceLabel>',
|
||||
videoInput: '<deviceLabel>'
|
||||
},
|
||||
...
|
||||
};
|
||||
const api = new JitsiMeetExternalAPI(domain, options);
|
||||
```
|
||||
|
||||
You can overwrite options set in [config.js] and [interface_config.js].
|
||||
For example, to enable the filmstrip-only interface mode, you can use:
|
||||
|
||||
```javascript
|
||||
var options = {
|
||||
interfaceConfigOverwrite: {filmStripOnly: true}
|
||||
const options = {
|
||||
...
|
||||
interfaceConfigOverwrite: { filmStripOnly: true },
|
||||
...
|
||||
};
|
||||
var api = new JitsiMeetExternalAPI(domain, options);
|
||||
const api = new JitsiMeetExternalAPI(domain, options);
|
||||
```
|
||||
|
||||
You can also pass a jwt token to Jitsi Meet:
|
||||
|
||||
```javascript
|
||||
var options = {
|
||||
jwt: "<jwt_token>",
|
||||
noSsl: false
|
||||
const options = {
|
||||
...
|
||||
jwt: '<jwt_token>',
|
||||
noSsl: false,
|
||||
...
|
||||
};
|
||||
var api = new JitsiMeetExternalAPI(domain, options);
|
||||
const api = new JitsiMeetExternalAPI(domain, options);
|
||||
```
|
||||
|
||||
### Controlling the embedded Jitsi Meet Conference
|
||||
|
||||
Device management `JitsiMeetExternalAPI` methods:
|
||||
* **getAvailableDevices** - Retrieve a list of available devices.
|
||||
|
||||
```javascript
|
||||
api.getAvailableDevices().then(devices => {
|
||||
// devices = {
|
||||
// audioInput: [{
|
||||
// deviceId: 'ID'
|
||||
// groupId: 'grpID'
|
||||
// kind: 'audioinput'
|
||||
// label: 'label'
|
||||
// },....],
|
||||
// audioOutput: [{
|
||||
// deviceId: 'ID'
|
||||
// groupId: 'grpID'
|
||||
// kind: 'audioOutput'
|
||||
// label: 'label'
|
||||
// },....],
|
||||
// videoInput: [{
|
||||
// deviceId: 'ID'
|
||||
// groupId: 'grpID'
|
||||
// kind: 'videoInput'
|
||||
// label: 'label'
|
||||
// },....]
|
||||
// }
|
||||
...
|
||||
});
|
||||
```
|
||||
* **getCurrentDevices** - Retrieve a list with the devices that are currently selected.
|
||||
|
||||
```javascript
|
||||
api.getCurrentDevices().then(devices => {
|
||||
// devices = {
|
||||
// audioInput: {
|
||||
// deviceId: 'ID'
|
||||
// groupId: 'grpID'
|
||||
// kind: 'videoInput'
|
||||
// label: 'label'
|
||||
// },
|
||||
// audioOutput: {
|
||||
// deviceId: 'ID'
|
||||
// groupId: 'grpID'
|
||||
// kind: 'videoInput'
|
||||
// label: 'label'
|
||||
// },
|
||||
// videoInput: {
|
||||
// deviceId: 'ID'
|
||||
// groupId: 'grpID'
|
||||
// kind: 'videoInput'
|
||||
// label: 'label'
|
||||
// }
|
||||
// }
|
||||
...
|
||||
});
|
||||
```
|
||||
* **isDeviceChangeAvailable** - Resolves with true if the device change is available and with false if not.
|
||||
|
||||
```javascript
|
||||
// The accepted deviceType values are - 'output', 'input' or undefined.
|
||||
api.isDeviceChangeAvailable(deviceType).then(isDeviceChangeAvailable => {
|
||||
...
|
||||
});
|
||||
```
|
||||
* **isDeviceListAvailable** - Resolves with true if the device list is available and with false if not.
|
||||
|
||||
```javascript
|
||||
api.isDeviceListAvailable().then(isDeviceListAvailable => {
|
||||
...
|
||||
});
|
||||
```
|
||||
* **isMultipleAudioInputSupported** - Resolves with true if multiple audio input is supported and with false if not.
|
||||
|
||||
```javascript
|
||||
api.isMultipleAudioInputSupported().then(isMultipleAudioInputSupported => {
|
||||
...
|
||||
});
|
||||
```
|
||||
* **setAudioInputDevice** - Sets the audio input device to the one with the label or id that is passed.
|
||||
|
||||
```javascript
|
||||
api.setAudioInputDevice(deviceLabel, deviceId);
|
||||
```
|
||||
* **setAudioOutputDevice** - Sets the audio output device to the one with the label or id that is passed.
|
||||
|
||||
```javascript
|
||||
api.setAudioOutputDevice(deviceLabel, deviceId);
|
||||
```
|
||||
* **setVideoInputDevice** - Sets the video input device to the one with the label or id that is passed.
|
||||
|
||||
```javascript
|
||||
api.setVideoInputDevice(deviceLabel, deviceId);
|
||||
```
|
||||
|
||||
You can control the embedded Jitsi Meet conference using the `JitsiMeetExternalAPI` object by using `executeCommand`:
|
||||
|
||||
```javascript
|
||||
api.executeCommand(command, ...arguments)
|
||||
api.executeCommand(command, ...arguments);
|
||||
```
|
||||
|
||||
The `command` parameter is String object with the name of the command. The following commands are currently supported:
|
||||
@@ -85,57 +199,60 @@ api.executeCommand('subject', 'New Conference Subject');
|
||||
|
||||
* **toggleAudio** - Mutes / unmutes the audio for the local participant. No arguments are required.
|
||||
```javascript
|
||||
api.executeCommand('toggleAudio')
|
||||
api.executeCommand('toggleAudio');
|
||||
```
|
||||
|
||||
* **toggleVideo** - Mutes / unmutes the video for the local participant. No arguments are required.
|
||||
```javascript
|
||||
api.executeCommand('toggleVideo')
|
||||
api.executeCommand('toggleVideo');
|
||||
```
|
||||
|
||||
* **toggleFilmStrip** - Hides / shows the filmstrip. No arguments are required.
|
||||
```javascript
|
||||
api.executeCommand('toggleFilmStrip')
|
||||
api.executeCommand('toggleFilmStrip');
|
||||
```
|
||||
|
||||
* **toggleChat** - Hides / shows the chat. No arguments are required.
|
||||
```javascript
|
||||
api.executeCommand('toggleChat')
|
||||
api.executeCommand('toggleChat');
|
||||
```
|
||||
|
||||
* **toggleShareScreen** - Starts / stops screen sharing. No arguments are required.
|
||||
```javascript
|
||||
api.executeCommand('toggleShareScreen')
|
||||
api.executeCommand('toggleShareScreen');
|
||||
```
|
||||
|
||||
* **hangup** - Hangups the call. No arguments are required.
|
||||
```javascript
|
||||
api.executeCommand('hangup')
|
||||
api.executeCommand('hangup');
|
||||
```
|
||||
|
||||
* **email** - Changes the local email address. This command requires one argument - the new email address to be set.
|
||||
```javascript
|
||||
api.executeCommand('email', 'example@example.com')
|
||||
api.executeCommand('email', 'example@example.com');
|
||||
```
|
||||
|
||||
* **avatarUrl** - Changes the local avatar URL. This command requires one argument - the new avatar URL to be set.
|
||||
```javascript
|
||||
api.executeCommand('avatarUrl', 'https://avatars0.githubusercontent.com/u/3671647')
|
||||
api.executeCommand('avatarUrl', 'https://avatars0.githubusercontent.com/u/3671647');
|
||||
```
|
||||
|
||||
You can also execute multiple commands using the `executeCommands` method:
|
||||
```javascript
|
||||
api.executeCommands(commands)
|
||||
api.executeCommands(commands);
|
||||
```
|
||||
The `commands` parameter is an object with the names of the commands as keys and the arguments for the commands as values:
|
||||
```javascript
|
||||
api.executeCommands({displayName: ['nickname'], toggleAudio: []});
|
||||
api.executeCommands({
|
||||
displayName: [ 'nickname' ],
|
||||
toggleAudio: []
|
||||
});
|
||||
```
|
||||
|
||||
You can add event listeners to the embedded Jitsi Meet using the `addEventListener` method.
|
||||
**NOTE: This method still exists but it is deprecated. JitsiMeetExternalAPI class extends [EventEmitter]. Use [EventEmitter] methods (`addListener` or `on`).**
|
||||
```javascript
|
||||
api.addEventListener(event, listener)
|
||||
api.addEventListener(event, listener);
|
||||
```
|
||||
|
||||
The `event` parameter is a String object with the name of the event.
|
||||
@@ -146,36 +263,36 @@ The following events are currently supported:
|
||||
changes. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
"id": id, // the id of the participant that changed his avatar.
|
||||
"avatarURL": avatarURL // the new avatar URL.
|
||||
id: string, // the id of the participant that changed his avatar.
|
||||
avatarURL: string // the new avatar URL.
|
||||
}
|
||||
```
|
||||
|
||||
* **audioAvailabilityChanged** - event notifications about audio availability status changes. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
"available": available // new available status - boolean
|
||||
available: boolean // new available status - boolean
|
||||
}
|
||||
```
|
||||
|
||||
* **audioMuteStatusChanged** - event notifications about audio mute status changes. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
"muted": muted // new muted status - boolean
|
||||
muted: boolean // new muted status - boolean
|
||||
}
|
||||
```
|
||||
|
||||
* **screenSharingStatusChanged** - receives event notifications about turning on/off the local user screen sharing. The listener will receive object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
"on": on, //whether screen sharing is on
|
||||
"details": {
|
||||
on: boolean, //whether screen sharing is on
|
||||
details: {
|
||||
|
||||
// From where the screen sharing is capturing, if known. Values which are
|
||||
// passed include "window", "screen", "proxy", "device". The value undefined
|
||||
// will be passed if the source type is unknown or screen share is off.
|
||||
sourceType: sourceType
|
||||
}
|
||||
// From where the screen sharing is capturing, if known. Values which are
|
||||
// passed include 'window', 'screen', 'proxy', 'device'. The value undefined
|
||||
// will be passed if the source type is unknown or screen share is off.
|
||||
sourceType: string|undefined
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -183,9 +300,9 @@ changes. The listener will receive an object with the following structure:
|
||||
messages. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
"from": from, // The id of the user that sent the message
|
||||
"nick": nick, // the nickname of the user that sent the message
|
||||
"message": txt // the text of the message
|
||||
from: string, // The id of the user that sent the message
|
||||
nick: string, // the nickname of the user that sent the message
|
||||
message: string // the text of the message
|
||||
}
|
||||
```
|
||||
|
||||
@@ -193,7 +310,7 @@ messages. The listener will receive an object with the following structure:
|
||||
messages. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
"message": txt // the text of the message
|
||||
message: string // the text of the message
|
||||
}
|
||||
```
|
||||
|
||||
@@ -201,69 +318,77 @@ messages. The listener will receive an object with the following structure:
|
||||
changes. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
"id": id, // the id of the participant that changed his display name
|
||||
"displayname": displayName // the new display name
|
||||
id: string, // the id of the participant that changed his display name
|
||||
displayname: string // the new display name
|
||||
}
|
||||
```
|
||||
|
||||
* **deviceListChanged** - event notifications about device list changes. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
devices: Object // the new list of available devices.
|
||||
}
|
||||
```
|
||||
NOTE: The devices object has the same format as the getAvailableDevices result format.
|
||||
|
||||
* **emailChange** - event notifications about email
|
||||
changes. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
"id": id, // the id of the participant that changed his email
|
||||
"email": email // the new email
|
||||
id: string, // the id of the participant that changed his email
|
||||
email: string // the new email
|
||||
}
|
||||
```
|
||||
* **filmstripDisplayChanged** - event notifications about the visibility of the filmstrip being updated.
|
||||
```javascript
|
||||
{
|
||||
"visible": visible, // Whether or not the filmstrip is displayed or hidden.
|
||||
visible: boolean // Whether or not the filmstrip is displayed or hidden.
|
||||
}
|
||||
```
|
||||
|
||||
* **participantJoined** - event notifications about new participants who join the room. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
"id": id, // the id of the participant
|
||||
"displayName": displayName // the display name of the participant
|
||||
id: string, // the id of the participant
|
||||
displayName: string // the display name of the participant
|
||||
}
|
||||
```
|
||||
|
||||
* **participantLeft** - event notifications about participants that leave the room. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
"id": id // the id of the participant
|
||||
id: string // the id of the participant
|
||||
}
|
||||
```
|
||||
|
||||
* **videoConferenceJoined** - event notifications fired when the local user has joined the video conference. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
"roomName": room, // the room name of the conference
|
||||
"id": id, // the id of the local participant
|
||||
"displayName": displayName, // the display name of the local participant
|
||||
"avatarURL": avatarURL // the avatar URL of the local participant
|
||||
roomName: string, // the room name of the conference
|
||||
id: string, // the id of the local participant
|
||||
displayName: string, // the display name of the local participant
|
||||
avatarURL: string // the avatar URL of the local participant
|
||||
}
|
||||
```
|
||||
|
||||
* **videoConferenceLeft** - event notifications fired when the local user has left the video conference. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
"roomName": room // the room name of the conference
|
||||
roomName: string // the room name of the conference
|
||||
}
|
||||
```
|
||||
|
||||
* **videoAvailabilityChanged** - event notifications about video availability status changes. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
"available": available // new available status - boolean
|
||||
available: boolean // new available status - boolean
|
||||
}
|
||||
```
|
||||
|
||||
* **videoMuteStatusChanged** - event notifications about video mute status changes. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
"muted": muted // new muted status - boolean
|
||||
muted: boolean // new muted status - boolean
|
||||
}
|
||||
```
|
||||
|
||||
@@ -273,7 +398,7 @@ changes. The listener will receive an object with the following structure:
|
||||
The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
"subject": subject // the new subject
|
||||
subject: string // the new subject
|
||||
}
|
||||
```
|
||||
|
||||
@@ -295,79 +420,80 @@ function outgoingMessageListener(object)
|
||||
|
||||
api.addEventListeners({
|
||||
incomingMessage: incomingMessageListener,
|
||||
outgoingMessage: outgoingMessageListener})
|
||||
outgoingMessage: outgoingMessageListener
|
||||
});
|
||||
```
|
||||
|
||||
If you want to remove a listener you can use `removeEventListener` method with argument the name of the event.
|
||||
**NOTE: This method still exists but it is deprecated. JitsiMeetExternalAPI class extends [EventEmitter]. Use [EventEmitter] methods( `removeListener`).**
|
||||
```javascript
|
||||
api.removeEventListener("incomingMessage");
|
||||
api.removeEventListener('incomingMessage');
|
||||
```
|
||||
|
||||
If you want to remove more than one event you can use `removeEventListeners` method with an Array with the names of the events as an argument.
|
||||
**NOTE: This method still exists but it is deprecated. JitsiMeetExternalAPI class extends [EventEmitter]. Use [EventEmitter] methods.**
|
||||
```javascript
|
||||
api.removeEventListeners(["incomingMessage", "outgoingMessageListener"]);
|
||||
api.removeEventListeners([ 'incomingMessage', 'outgoingMessageListener' ]);
|
||||
```
|
||||
|
||||
You can get the number of participants in the conference with the following API function:
|
||||
```javascript
|
||||
var numberOfParticipants = api.getNumberOfParticipants();
|
||||
const numberOfParticipants = api.getNumberOfParticipants();
|
||||
```
|
||||
|
||||
You can get the avatar URL of a participant in the conference with the following API function:
|
||||
```javascript
|
||||
var avatarURL = api.getAvatarURL(participantId);
|
||||
const avatarURL = api.getAvatarURL(participantId);
|
||||
```
|
||||
|
||||
You can get the display name of a participant in the conference with the following API function:
|
||||
```javascript
|
||||
var displayName = api.getDisplayName(participantId);
|
||||
const displayName = api.getDisplayName(participantId);
|
||||
```
|
||||
|
||||
You can get the email of a participant in the conference with the following API function:
|
||||
```javascript
|
||||
var email = api.getEmail(participantId);
|
||||
const email = api.getEmail(participantId);
|
||||
```
|
||||
|
||||
You can get the iframe HTML element where Jitsi Meet is loaded with the following API function:
|
||||
```javascript
|
||||
var iframe = api.getIFrame();
|
||||
const iframe = api.getIFrame();
|
||||
```
|
||||
|
||||
You can check whether the audio is muted with the following API function:
|
||||
```javascript
|
||||
api.isAudioMuted().then(function(muted) {
|
||||
api.isAudioMuted().then(muted => {
|
||||
...
|
||||
});
|
||||
```
|
||||
|
||||
You can check whether the video is muted with the following API function:
|
||||
```javascript
|
||||
api.isVideoMuted().then(function(muted) {
|
||||
api.isVideoMuted().then(muted => {
|
||||
...
|
||||
});
|
||||
```
|
||||
|
||||
You can check whether the audio is available with the following API function:
|
||||
```javascript
|
||||
api.isAudioAvailable().then(function(available) {
|
||||
api.isAudioAvailable().then(available => {
|
||||
...
|
||||
});
|
||||
```
|
||||
|
||||
You can check whether the video is available with the following API function:
|
||||
```javascript
|
||||
api.isVideoAvailable().then(function(available) {
|
||||
api.isVideoAvailable().then(available => {
|
||||
...
|
||||
});
|
||||
```
|
||||
|
||||
You can invite new participants to the call with the following API function:
|
||||
```javascript
|
||||
api.invite([{...}, {...}, {...}]).then(function() {
|
||||
api.invite([ {...}, {...}, {...} ]).then(() => {
|
||||
// success
|
||||
}).catch(function() {
|
||||
}).catch(() => {
|
||||
// failure
|
||||
});
|
||||
```
|
||||
@@ -375,7 +501,7 @@ api.invite([{...}, {...}, {...}]).then(function() {
|
||||
|
||||
You can remove the embedded Jitsi Meet Conference with the following API function:
|
||||
```javascript
|
||||
api.dispose()
|
||||
api.dispose();
|
||||
```
|
||||
|
||||
NOTE: It's a good practice to remove the conference before the page is unloaded.
|
||||
|
||||
@@ -149,10 +149,10 @@ PODS:
|
||||
- RNGoogleSignin (1.0.2):
|
||||
- GoogleSignIn
|
||||
- React
|
||||
- RNSound (0.10.9):
|
||||
- RNSound (0.10.12):
|
||||
- React/Core
|
||||
- RNSound/Core (= 0.10.9)
|
||||
- RNSound/Core (0.10.9):
|
||||
- RNSound/Core (= 0.10.12)
|
||||
- RNSound/Core (0.10.12):
|
||||
- React/Core
|
||||
- RNVectorIcons (6.0.2):
|
||||
- React
|
||||
@@ -275,7 +275,7 @@ SPEC CHECKSUMS:
|
||||
react-native-keep-awake: eba3137546b10003361b37c761f6c429b59814ae
|
||||
react-native-webrtc: a14197fefe96ab462dc098b79c428fc5a7f68216
|
||||
RNGoogleSignin: 361174d9a3090d295b06257162b560d8efc8a6ed
|
||||
RNSound: 53d2fc9c6589bd68daba530262b7560393def3ac
|
||||
RNSound: e157320f503bdd4f4ee6d8542e948d54f90c3c3a
|
||||
RNVectorIcons: d819334932bcda3332deb3d2c8ea4d069e0b98f9
|
||||
SDWebImage: 3f3f0c02f09798048c47a5ed0a13f17b063572d8
|
||||
yoga: 3768a3026ade0fb46a68f3a31a917cf86bc34fc4
|
||||
|
||||
@@ -23,7 +23,11 @@ platform :ios do
|
||||
)
|
||||
|
||||
# Actually build the app
|
||||
build_app(scheme: "jitsi-meet", include_bitcode: false)
|
||||
build_app(
|
||||
scheme: "jitsi-meet",
|
||||
include_bitcode: false,
|
||||
export_xcargs: "-allowProvisioningUpdates"
|
||||
)
|
||||
|
||||
# Upload the build to TestFlight (but don't distribute it)
|
||||
upload_to_testflight(skip_submission: true, skip_waiting_for_build_processing: true)
|
||||
|
||||
@@ -61,8 +61,15 @@ RCT_EXPORT_MODULE();
|
||||
}
|
||||
}
|
||||
|
||||
// build number
|
||||
NSString *buildNumber = infoDictionary[@"CFBundleVersion"];
|
||||
if (buildNumber == nil) {
|
||||
buildNumber = @"";
|
||||
}
|
||||
|
||||
return @{
|
||||
@"calendarEnabled": [NSNumber numberWithBool:calendarEnabled],
|
||||
@"buildNumber": buildNumber,
|
||||
@"name": name,
|
||||
@"sdkBundlePath": sdkBundlePath,
|
||||
@"version": version
|
||||
|
||||
1
lang/languages-ca.json
Normal file
1
lang/languages-ca.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -1,11 +1,18 @@
|
||||
{
|
||||
"en": "Inglese",
|
||||
"az": "Azero",
|
||||
"bg": "Bulgaro",
|
||||
"cs": "Ceco",
|
||||
"de": "Tedesco",
|
||||
"el": "Greco",
|
||||
"eo": "Esperanto",
|
||||
"es": "Spagnolo",
|
||||
"fr": "Francese",
|
||||
"hy": "Armeno",
|
||||
"it": "Italiano",
|
||||
"ja": "Giapponese",
|
||||
"ko": "Coreano",
|
||||
"nb": "Norvegese bokmal",
|
||||
"oc": "Occitano",
|
||||
"pl": "Polacco",
|
||||
"ptBR": "Portoghese (Brasile)",
|
||||
@@ -14,7 +21,6 @@
|
||||
"sl": "Sloveno",
|
||||
"sv": "Svedese",
|
||||
"tr": "Turco",
|
||||
"zhCN": "Cinese (Cina)",
|
||||
"nb": "",
|
||||
"eo": ""
|
||||
"vi": "Vietnamita",
|
||||
"zhCN": "Cinese (Cina)"
|
||||
}
|
||||
@@ -1,20 +1,26 @@
|
||||
{
|
||||
"en": "",
|
||||
"bg": "",
|
||||
"de": "",
|
||||
"es": "",
|
||||
"fr": "",
|
||||
"hy": "",
|
||||
"it": "",
|
||||
"oc": "",
|
||||
"pl": "",
|
||||
"ptBR": "",
|
||||
"ru": "",
|
||||
"sk": "",
|
||||
"sl": "",
|
||||
"sv": "",
|
||||
"tr": "",
|
||||
"zhCN": "",
|
||||
"nb": "",
|
||||
"eo": ""
|
||||
"en": "英語",
|
||||
"az": "アゼルバイジャン語",
|
||||
"bg": "ブルガリア語",
|
||||
"cs": "チェコ語",
|
||||
"de": "ドイツ語",
|
||||
"el": "ギリシア語",
|
||||
"eo": "エスペラント語",
|
||||
"es": "スペイン語",
|
||||
"fr": "フランス語",
|
||||
"hy": "アルメニア語",
|
||||
"it": "イタリア語",
|
||||
"ja": "日本語",
|
||||
"ko": "韓国語",
|
||||
"nb": "ノルウェー語 (ブークモール)",
|
||||
"oc": "オック語",
|
||||
"pl": "ポーランド語",
|
||||
"ptBR": "ポルトガル語 (ブラジル)",
|
||||
"ru": "ロシア語",
|
||||
"sk": "スロバキア語",
|
||||
"sl": "スロベニア語",
|
||||
"sv": "スウェーデン語",
|
||||
"tr": "トルコ語",
|
||||
"vi": "ベトナム語",
|
||||
"zhCN": "中国語 (中国)"
|
||||
}
|
||||
@@ -1,11 +1,18 @@
|
||||
{
|
||||
"en": "英语",
|
||||
"az": "",
|
||||
"bg": "保加利亚语",
|
||||
"cs": "",
|
||||
"de": "德语",
|
||||
"el": "",
|
||||
"eo": "世界语",
|
||||
"es": "西班牙语",
|
||||
"fr": "法语",
|
||||
"hy": "亚美尼亚语",
|
||||
"it": "意大利语",
|
||||
"ja": "",
|
||||
"ko": "",
|
||||
"nb": "挪威布克摩尔语",
|
||||
"oc": "欧西坦语",
|
||||
"pl": "波兰语",
|
||||
"ptBR": "葡萄牙语(巴西)",
|
||||
@@ -14,7 +21,6 @@
|
||||
"sl": "斯洛文尼亚语",
|
||||
"sv": "瑞典语",
|
||||
"tr": "土耳其语",
|
||||
"zhCN": "中文(中国)",
|
||||
"nb": "挪威布克摩尔语",
|
||||
"eo": "世界语"
|
||||
"vi": "",
|
||||
"zhCN": "中文(中国)"
|
||||
}
|
||||
@@ -1,11 +1,18 @@
|
||||
{
|
||||
"en": "English",
|
||||
"az": "Azerbaijani",
|
||||
"bg": "Bulgarian",
|
||||
"cs": "Czech",
|
||||
"de": "German",
|
||||
"el": "Greek",
|
||||
"eo": "Esperanto",
|
||||
"es": "Spanish",
|
||||
"fr": "French",
|
||||
"hy": "Armenian",
|
||||
"it": "Italian",
|
||||
"ja": "日本語",
|
||||
"ko": "韓文",
|
||||
"nb": "Norwegian Bokmal",
|
||||
"oc": "Occitan",
|
||||
"pl": "Polish",
|
||||
"ptBR": "Portuguese (Brazil)",
|
||||
@@ -14,7 +21,6 @@
|
||||
"sl": "Slovenian",
|
||||
"sv": "Swedish",
|
||||
"tr": "Turkish",
|
||||
"zhCN": "中文 简体 (中国)",
|
||||
"nb": "Norwegian Bokmal",
|
||||
"eo": "Esperanto"
|
||||
"vi": "Vietnamese",
|
||||
"zhCN": "中文 简体 (中国)"
|
||||
}
|
||||
@@ -65,7 +65,7 @@
|
||||
"privacy": "Privaatheid",
|
||||
"recentList": "Onlangs",
|
||||
"recentListDelete": "Skrap",
|
||||
"recentListEmpty": "Die lys van onlangse gesprekke is leeg. Gesels met u span al u onlangse gesprekke sal hier wees.",
|
||||
"recentListEmpty": "Die lys van onlangse gesprekke is leeg. Gesels met u span en al u onlangse gesprekke sal hier wys.",
|
||||
"roomname": "Gee kamernaam",
|
||||
"roomnameHint": "Gee die naam of URL van die kamer waar u wil aansluit. Dink gerus enige naam uit. Laat weet net die mense wat u ontmoet wat dit is sodat hulle die selfde naam gee.",
|
||||
"sendFeedback": "Stuur terugvoer",
|
||||
@@ -241,7 +241,7 @@
|
||||
},
|
||||
"notify": {
|
||||
"disconnected": "ontkoppel",
|
||||
"moderator": "",
|
||||
"moderator": "U is ou moderator!",
|
||||
"connectedOneMember": "__name__ het gekoppel",
|
||||
"connectedTwoMembers": "__first__ en __second__ het gekoppel",
|
||||
"connectedThreePlusMembers": "__name__ en __count__ ander het gekoppel",
|
||||
@@ -357,7 +357,7 @@
|
||||
"permissionDenied": "Toestemming gewyer",
|
||||
"screenSharingFailedToInstall": "Oeps! Die uitbreiding vir skermdeling kon nie installeer nie.",
|
||||
"screenSharingFailedToInstallTitle": "Uitbreiding vir skermdeling kon nie installeer nie",
|
||||
"screenSharingFirefoxPermissionDeniedError": "",
|
||||
"screenSharingFirefoxPermissionDeniedError": "Iets het skeefgeloop toe ons die skerm probeer deel het. Maak seker dat ons dei toestemming gegee word om dit te doen. ",
|
||||
"screenSharingFirefoxPermissionDeniedTitle": "Oeps! Ons kon nie skermdeling begin nie!",
|
||||
"screenSharingPermissionDeniedError": "",
|
||||
"cameraUnsupportedResolutionError": "Die kamera ondersteun nie die nodige videoresolusie nie.",
|
||||
@@ -483,13 +483,13 @@
|
||||
"unavailableTitle": ""
|
||||
},
|
||||
"transcribing": {
|
||||
"pending": "",
|
||||
"pending": "Berei tans voor om die vergadering te transkribeer...",
|
||||
"off": "",
|
||||
"error": "",
|
||||
"expandedLabel": "",
|
||||
"failedToStart": "",
|
||||
"tr": "",
|
||||
"labelToolTip": "",
|
||||
"labelToolTip": "Die vergadering word getranskribeer",
|
||||
"ccButtonTooltip": "",
|
||||
"start": "",
|
||||
"stop": ""
|
||||
|
||||
746
lang/main-ca.json
Normal file
746
lang/main-ca.json
Normal file
@@ -0,0 +1,746 @@
|
||||
{
|
||||
"addPeople": {
|
||||
"add": "",
|
||||
"countryNotSupported": "Encara no és possible usar aquesta destinació.",
|
||||
"countryReminder": "Truqueu des de fora els EUA? Assegureu-vos que comenceu b el codi de país!",
|
||||
"disabled": "",
|
||||
"failedToAdd": "No s'hi ha pogut afegir cap membre",
|
||||
"footerText": "",
|
||||
"invite": "",
|
||||
"loading": "",
|
||||
"loadingNumber": "S'està validant el número de telèfon",
|
||||
"loadingPeople": "S'estan cercant les persones a convidar",
|
||||
"noResults": "No s'ha trobat cap resultat coincident",
|
||||
"notAvailable": "No podeu convidar-hi persones.",
|
||||
"noValidNumbers": "Introduïu un número de telèfon",
|
||||
"searchNumbers": "Afegeix-hi números de telèfon",
|
||||
"searchPeople": "Cerca-hi persones",
|
||||
"searchPeopleAndNumbers": "Cerca persones o n'afegeix els números de telèfon",
|
||||
"telephone": "Telèfon: __number__",
|
||||
"title": "Convida persones a aquesta reunió"
|
||||
},
|
||||
"audioDevices": {
|
||||
"bluetooth": "Bluetooth",
|
||||
"headphones": "Auriculars",
|
||||
"phone": "Telèfon",
|
||||
"speaker": ""
|
||||
},
|
||||
"audioOnly": {
|
||||
"audioOnly": "Només l'àudio",
|
||||
"featureToggleDisabled": ""
|
||||
},
|
||||
"calendarSync": {
|
||||
"addMeetingURL": "Afegeix un enllaç de reunió",
|
||||
"confirmAddLink": "",
|
||||
"confirmAddLinkTitle": "",
|
||||
"error": {
|
||||
"appConfiguration": "La integració de l'agenda no està configurada correctament.",
|
||||
"generic": "S'ha produït un error. Comproveu la configuració de l'agenda o intenteu d'actualitzar-la.",
|
||||
"notSignedIn": ""
|
||||
},
|
||||
"join": "Afegeix-m'hi",
|
||||
"joinTooltip": "Uniu-vos a la reunió",
|
||||
"nextMeeting": "reunió següent",
|
||||
"noEvents": "No hi ha cap esdeveniment previst a l'agenda.",
|
||||
"ongoingMeeting": "reunió en curs",
|
||||
"permissionButton": "Obre la configuració",
|
||||
"permissionMessage": "",
|
||||
"refresh": "Actualitza l'agenda",
|
||||
"today": ""
|
||||
},
|
||||
"chat": {
|
||||
"error": "",
|
||||
"messagebox": "Introduïu text...",
|
||||
"nickname": {
|
||||
"popover": "Trieu un sobrenom",
|
||||
"title": "Introduïu un sobrenom per a usar el xat"
|
||||
},
|
||||
"title": "Xat"
|
||||
},
|
||||
"connection": {
|
||||
"ATTACHED": "Adjunt",
|
||||
"AUTHENTICATING": "S'està autenticant",
|
||||
"AUTHFAIL": "",
|
||||
"CONNECTED": "",
|
||||
"CONNECTING": "",
|
||||
"CONNFAIL": "La connexió ha fallat",
|
||||
"DISCONNECTED": "",
|
||||
"DISCONNECTING": "S'està desconnectant",
|
||||
"ERROR": "",
|
||||
"RECONNECTING": "S'ha produït un problema de xarxa. S'esta tornant a connectar..."
|
||||
},
|
||||
"connectionindicator": {
|
||||
"address": "Adreça:",
|
||||
"bandwidth": "Ample de banda estimat:",
|
||||
"bitrate": "",
|
||||
"bridgeCount": "Nombre de servidors: ",
|
||||
"connectedTo": "Connectat a:",
|
||||
"framerate": "",
|
||||
"header": "Dades de connexió",
|
||||
"less": "Menys informació",
|
||||
"localaddress": "Adreça local:",
|
||||
"localaddress_plural": "Adreces locals:",
|
||||
"localport": "Port local:",
|
||||
"localport_plural": "Ports locals:",
|
||||
"more": "Més informació",
|
||||
"na": "",
|
||||
"packetloss": "Pèrdua de paquets:",
|
||||
"quality": {
|
||||
"good": "",
|
||||
"inactive": "Inactiva",
|
||||
"lost": "Perduda",
|
||||
"nonoptimal": "No òptima",
|
||||
"poor": "Pobra"
|
||||
},
|
||||
"remoteaddress": "Adreça remota:",
|
||||
"remoteaddress_plural": "Adreces remotes:",
|
||||
"remoteport": "Port remot:",
|
||||
"remoteport_plural": "Ports remots:",
|
||||
"resolution": "Resolució:",
|
||||
"status": "Connexió:",
|
||||
"transport": "Transport:",
|
||||
"transport_plural": "Transports:",
|
||||
"turn": " (torn)"
|
||||
},
|
||||
"contactlist_plural": "__count__ membres",
|
||||
"dateUtils": {
|
||||
"earlier": "",
|
||||
"today": "Avui",
|
||||
"yesterday": "Ahir"
|
||||
},
|
||||
"deepLinking": {
|
||||
"appNotInstalled": "",
|
||||
"description": "No ha passat res? Hem intentat iniciar la reunió en l'aplicació d'escriptori _app_. Torneu a intentar-ho en l'aplicació web _app_.",
|
||||
"downloadApp": "Baixa l'aplicació",
|
||||
"launchWebButton": "Inicia al web",
|
||||
"openApp": "Continua en l'aplicació",
|
||||
"title": "S'està iniciant la reunió en _app_....",
|
||||
"tryAgainButton": "Torna-ho a intentar en l'escriptori"
|
||||
},
|
||||
"defaultLink": "p. ex. __url__",
|
||||
"defaultNickname": "p. ex. Pere Cullera",
|
||||
"deviceError": {
|
||||
"cameraError": "No s'ha pogut accedir a la càmera",
|
||||
"cameraPermission": "",
|
||||
"microphoneError": "",
|
||||
"microphonePermission": ""
|
||||
},
|
||||
"deviceSelection": {
|
||||
"deviceSettings": "Configuració de l'aparell",
|
||||
"noPermission": "",
|
||||
"previewUnavailable": "",
|
||||
"selectADevice": "Seleccioneu un aparell",
|
||||
"testAudio": "Reprodueix un so de prova"
|
||||
},
|
||||
"dialog": {
|
||||
"accessibilityLabel": {
|
||||
"liveStreaming": "Transmissió en directe"
|
||||
},
|
||||
"allow": "Permet",
|
||||
"alreadySharedVideoMsg": "Un altre membre ja està compartint un vídeo. Aquesta conferència només permet un vídeo compartit a la vegada.",
|
||||
"alreadySharedVideoTitle": "Només es permet un vídeo compartit a la vegada",
|
||||
"applicationWindow": "Finestra de l'aplicació",
|
||||
"Back": "Enrere",
|
||||
"cameraConstraintFailedError": "La càmera no satisfà algun dels requeriments.",
|
||||
"cameraNotFoundError": "",
|
||||
"cameraNotSendingData": "No podem accedir a la càmera. Comproveu si alguna altra aplicació l'està usant, seleccioneu un altre aparell al menú de configuració o intenteu de recarregar l'aplicació.",
|
||||
"cameraNotSendingDataTitle": "",
|
||||
"cameraPermissionDeniedError": "",
|
||||
"cameraUnknownError": "",
|
||||
"cameraUnsupportedResolutionError": "",
|
||||
"Cancel": "Cancel·la",
|
||||
"close": "Tanca",
|
||||
"conferenceDisconnectMsg": "",
|
||||
"conferenceDisconnectTitle": "",
|
||||
"conferenceReloadMsg": "Estem intentat de corregir-ho. Tornem a connectar en __seconds__ segons...",
|
||||
"conferenceReloadTitle": "Malauradament, alguna cosa no ha anat bé.",
|
||||
"confirm": "Confirmo",
|
||||
"confirmNo": "",
|
||||
"confirmYes": "",
|
||||
"connectError": "Vaja! Alguna cosa no ha anat bé i no podem connectar a la conferència.",
|
||||
"connectErrorWithMsg": "Vaja! Alguna cosa no ha anat bé i no podem connectar a la conferència: _msg_",
|
||||
"connecting": "S'està connectant",
|
||||
"contactSupport": "Contacte amb l'assistència",
|
||||
"copy": "Copia",
|
||||
"currentPassword": "La contrasenya actual és",
|
||||
"defaultError": "S'ha produït algun tipus d'error",
|
||||
"detectext": "S'ha produït un error en intentar detectar l'extensió de compartició d'escriptori.",
|
||||
"dismiss": "",
|
||||
"displayNameRequired": "",
|
||||
"done": "",
|
||||
"doNotShowMessageAgain": "",
|
||||
"enterDisplayName": "",
|
||||
"error": "Error",
|
||||
"externalInstallationMsg": "",
|
||||
"externalInstallationTitle": "",
|
||||
"failedpermissions": "",
|
||||
"feedbackHelp": "Els vostres comentaris ens ajuden a millorar la nostra experiència de vídeo.",
|
||||
"feedbackQuestion": "",
|
||||
"goToStore": "",
|
||||
"gracefulShutdown": "",
|
||||
"hungUp": "Heu penjat",
|
||||
"IamHost": "Sóc l'amfitrió",
|
||||
"incorrectPassword": "El nom o la contrasenya no són correctes",
|
||||
"inlineInstallationMsg": "",
|
||||
"inlineInstallExtension": "Instal·la-ho ara",
|
||||
"internalError": "",
|
||||
"internalErrorTitle": "",
|
||||
"joinAgain": "",
|
||||
"kickMessage": "",
|
||||
"kickParticipantButton": "",
|
||||
"kickParticipantDialog": "",
|
||||
"kickParticipantTitle": "",
|
||||
"kickTitle": "",
|
||||
"liveStreaming": "",
|
||||
"liveStreamingDisabledForGuestTooltip": "",
|
||||
"liveStreamingDisabledTooltip": "",
|
||||
"lockMessage": "No s'ha pogut blocar la conferència.",
|
||||
"lockRoom": "Bloca la sala",
|
||||
"lockTitle": "El bloqueig ha fallat",
|
||||
"logoutQuestion": "Esteu segur de voler tancar la sessió i aturar la conferència?",
|
||||
"logoutTitle": "",
|
||||
"maxUsersLimitReached": "",
|
||||
"maxUsersLimitReachedTitle": "",
|
||||
"micConstraintFailedError": "",
|
||||
"micNotFoundError": "No s'ha trobat cap micròfon.",
|
||||
"micNotSendingData": "No podem accedir al micròfon. Seleccioneu un altre aparell al menú de configuració o intenteu de recarregar l'aplicació.",
|
||||
"micNotSendingDataTitle": "",
|
||||
"micPermissionDeniedError": "",
|
||||
"micUnknownError": "",
|
||||
"muteParticipantBody": "",
|
||||
"muteParticipantButton": "",
|
||||
"muteParticipantDialog": "",
|
||||
"muteParticipantTitle": "Voleu silenciar aquest membre?",
|
||||
"Ok": "",
|
||||
"oops": "Vaja!",
|
||||
"password": "Introduïu una contrasenya",
|
||||
"passwordError": "",
|
||||
"passwordError2": "",
|
||||
"passwordErrorTitle": "",
|
||||
"passwordLabel": "Contrasenya",
|
||||
"passwordNotSupported": "",
|
||||
"passwordNotSupportedTitle": "",
|
||||
"passwordRequired": "",
|
||||
"permissionDenied": "S'ha denegat el permís",
|
||||
"popupError": "El vostre navegador bloca les finestres emergents d'aquest lloc. Habiliteu les finestres emergents a la configuració de seguretat del navegador i torneu-ho a intentar.",
|
||||
"popupErrorTitle": "Finestres emergents blocades",
|
||||
"recording": "",
|
||||
"recordingDisabledForGuestTooltip": "Els convidats no poden iniciar enregistraments.",
|
||||
"recordingDisabledTooltip": "",
|
||||
"recordingToken": "",
|
||||
"rejoinNow": "",
|
||||
"remoteControlAllowedMessage": "",
|
||||
"remoteControlDeniedMessage": "",
|
||||
"remoteControlErrorMessage": "",
|
||||
"remoteControlRequestMessage": "",
|
||||
"remoteControlShareScreenWarning": "Tingueu present que si pitgeu \"Permet\" compartireu la vostra pantalla!",
|
||||
"remoteControlStopMessage": "",
|
||||
"remoteControlTitle": "Control d'escriptori remot",
|
||||
"Remove": "",
|
||||
"removePassword": "",
|
||||
"removeSharedVideoMsg": "",
|
||||
"removeSharedVideoTitle": "",
|
||||
"reservationError": "",
|
||||
"reservationErrorMsg": "",
|
||||
"retry": "",
|
||||
"Save": "",
|
||||
"screenSharingFailedToInstall": "",
|
||||
"screenSharingFailedToInstallTitle": "",
|
||||
"screenSharingFirefoxPermissionDeniedError": "",
|
||||
"screenSharingFirefoxPermissionDeniedTitle": "",
|
||||
"screenSharingPermissionDeniedError": "",
|
||||
"serviceUnavailable": "El servei no és disponible",
|
||||
"sessTerminated": "La trucada ha finalitzat",
|
||||
"Share": "",
|
||||
"shareVideoLinkError": "Proporcioneu un enllaç de YouTube correcte.",
|
||||
"shareVideoTitle": "Comparteix un vídeo",
|
||||
"shareYourScreen": "Comparteix la pantalla",
|
||||
"shareYourScreenDisabled": "",
|
||||
"shareYourScreenDisabledForGuest": "Els convidats no poden compartir la pantalla.",
|
||||
"SLDFailure": "",
|
||||
"sorryFeedback": "",
|
||||
"SRDFailure": "",
|
||||
"startLiveStreaming": "Inicia la transmissió en directe",
|
||||
"startRecording": "Inicia l'enregistrament",
|
||||
"startRemoteControlErrorMessage": "",
|
||||
"stopLiveStreaming": "",
|
||||
"stopRecording": "",
|
||||
"stopRecordingWarning": "",
|
||||
"stopStreamingWarning": "",
|
||||
"streamKey": "",
|
||||
"Submit": "",
|
||||
"thankYou": "",
|
||||
"token": "",
|
||||
"tokenAuthFailed": "",
|
||||
"tokenAuthFailedTitle": "L'autenticació ha fallat",
|
||||
"transcribing": "",
|
||||
"unableToSwitch": "",
|
||||
"unlockRoom": "",
|
||||
"userPassword": "",
|
||||
"WaitForHostMsg": "",
|
||||
"WaitForHostMsgWOk": "",
|
||||
"WaitingForHost": "",
|
||||
"warning": "",
|
||||
"Yes": "",
|
||||
"yourEntireScreen": ""
|
||||
},
|
||||
"dialOut": {
|
||||
"statusMessage": ""
|
||||
},
|
||||
"email": {
|
||||
"and": "",
|
||||
"body": "",
|
||||
"sharedKey": "",
|
||||
"subject": ""
|
||||
},
|
||||
"feedback": {
|
||||
"average": "",
|
||||
"bad": "",
|
||||
"detailsLabel": "",
|
||||
"good": "",
|
||||
"rateExperience": "",
|
||||
"veryBad": "",
|
||||
"veryGood": ""
|
||||
},
|
||||
"incomingCall": {
|
||||
"answer": "",
|
||||
"audioCallTitle": "",
|
||||
"decline": "",
|
||||
"productLabel": "",
|
||||
"videoCallTitle": ""
|
||||
},
|
||||
"info": {
|
||||
"accessibilityLabel": "",
|
||||
"addPassword": "",
|
||||
"cancelPassword": "",
|
||||
"conferenceURL": "",
|
||||
"country": "",
|
||||
"dialANumber": "",
|
||||
"dialInConferenceID": "",
|
||||
"dialInNotSupported": "",
|
||||
"dialInNumber": "",
|
||||
"dialInTollFree": "",
|
||||
"genericError": "",
|
||||
"inviteLiveStream": "",
|
||||
"invitePhone": "",
|
||||
"invitePhoneAlternatives": "",
|
||||
"inviteURL": "",
|
||||
"liveStreamURL": "",
|
||||
"moreNumbers": "",
|
||||
"noNumbers": "",
|
||||
"noPassword": "",
|
||||
"noRoom": "",
|
||||
"numbers": "",
|
||||
"password": "",
|
||||
"title": "Comparteix",
|
||||
"tooltip": "",
|
||||
"label": ""
|
||||
},
|
||||
"inviteDialog": {
|
||||
"alertOk": "D'acord",
|
||||
"alertText": "",
|
||||
"alertTitle": "",
|
||||
"header": "Convida",
|
||||
"searchCallOnlyPlaceholder": "",
|
||||
"searchPeopleOnlyPlaceholder": "",
|
||||
"searchPlaceholder": "",
|
||||
"send": ""
|
||||
},
|
||||
"inlineDialogFailure": {
|
||||
"msg": "",
|
||||
"retry": "",
|
||||
"support": "",
|
||||
"supportMsg": ""
|
||||
},
|
||||
"inviteUrlDefaultMsg": "",
|
||||
"keyboardShortcuts": {
|
||||
"focusLocal": "",
|
||||
"focusRemote": "",
|
||||
"fullScreen": "",
|
||||
"keyboardShortcuts": "",
|
||||
"localRecording": "",
|
||||
"mute": "",
|
||||
"pushToTalk": "",
|
||||
"raiseHand": "",
|
||||
"showSpeakerStats": "",
|
||||
"toggleChat": "",
|
||||
"toggleFilmstrip": "",
|
||||
"toggleScreensharing": "",
|
||||
"toggleShortcuts": "",
|
||||
"videoMute": ""
|
||||
},
|
||||
"liveStreaming": {
|
||||
"busy": "",
|
||||
"busyTitle": "",
|
||||
"buttonTooltip": "",
|
||||
"changeSignIn": "",
|
||||
"choose": "",
|
||||
"chooseCTA": "",
|
||||
"enterStreamKey": "",
|
||||
"error": "",
|
||||
"errorAPI": "",
|
||||
"errorLiveStreamNotEnabled": "",
|
||||
"expandedOff": "",
|
||||
"expandedOn": "",
|
||||
"expandedPending": "",
|
||||
"failedToStart": "",
|
||||
"getStreamKeyManually": "",
|
||||
"invalidStreamKey": "",
|
||||
"off": "",
|
||||
"on": "",
|
||||
"pending": "",
|
||||
"serviceName": "",
|
||||
"signedInAs": "",
|
||||
"signIn": "",
|
||||
"signInCTA": "",
|
||||
"signOut": "",
|
||||
"start": "",
|
||||
"streamIdHelp": "Què és això?",
|
||||
"unavailableTitle": ""
|
||||
},
|
||||
"localRecording": {
|
||||
"clientState": {
|
||||
"off": "Inactiu",
|
||||
"on": "Actiu",
|
||||
"unknown": "Desconegut"
|
||||
},
|
||||
"dialogTitle": "Controls d'enregistrament local",
|
||||
"duration": "Durada",
|
||||
"durationNA": "N/D",
|
||||
"encoding": "Codificació",
|
||||
"label": "",
|
||||
"labelToolTip": "",
|
||||
"localRecording": "",
|
||||
"me": "",
|
||||
"messages": {
|
||||
"engaged": "",
|
||||
"finished": "",
|
||||
"finishedModerator": "",
|
||||
"notModerator": "No sou el moderador. No podeu iniciar ni aturar un enregistrament local."
|
||||
},
|
||||
"moderator": "",
|
||||
"no": "No",
|
||||
"participant": "",
|
||||
"participantStats": "",
|
||||
"sessionToken": "",
|
||||
"start": "",
|
||||
"stop": "",
|
||||
"yes": "Sí"
|
||||
},
|
||||
"me": "",
|
||||
"notify": {
|
||||
"connectedOneMember": "",
|
||||
"connectedThreePlusMembers": "",
|
||||
"connectedTwoMembers": "",
|
||||
"disconnected": "desconnectat",
|
||||
"focus": "",
|
||||
"focusFail": "",
|
||||
"grantedTo": "",
|
||||
"me": "Jo",
|
||||
"moderator": "",
|
||||
"muted": "Heu iniciat una conversa silenciada.",
|
||||
"mutedTitle": "Esteu silenciat!",
|
||||
"raisedHand": "Vull parlar.",
|
||||
"somebody": "Algú",
|
||||
"suboptimalExperienceDescription": "",
|
||||
"suboptimalExperienceTitle": ""
|
||||
},
|
||||
"passwordSetRemotely": "",
|
||||
"poweredby": "",
|
||||
"presenceStatus": {
|
||||
"busy": "Ocupat",
|
||||
"calling": "",
|
||||
"connected": "Connectat",
|
||||
"connecting": "Està connectant...",
|
||||
"connecting2": "Està connectant*...",
|
||||
"disconnected": "Desconnectat",
|
||||
"expired": "",
|
||||
"ignored": "Ignorat",
|
||||
"initializingCall": "S'està inicialitzant la trucada...",
|
||||
"invited": "Convidat",
|
||||
"rejected": "Rebutjat",
|
||||
"ringing": ""
|
||||
},
|
||||
"profile": {
|
||||
"setDisplayNameLabel": "",
|
||||
"setEmailInput": "",
|
||||
"setEmailLabel": "",
|
||||
"title": ""
|
||||
},
|
||||
"raisedHand": "Vull parlar",
|
||||
"recentList": {
|
||||
"joinPastMeeting": ""
|
||||
},
|
||||
"recording": {
|
||||
"authDropboxText": "Puja a Dropbox",
|
||||
"availableSpace": "",
|
||||
"beta": "BETA",
|
||||
"busy": "",
|
||||
"busyTitle": "",
|
||||
"buttonTooltip": "Inicia o atura l'enregistrament",
|
||||
"error": "L'enregistrament ha fallat. Torneu-ho a intentar més tard.",
|
||||
"expandedOff": "",
|
||||
"expandedOn": "",
|
||||
"expandedPending": "",
|
||||
"failedToStart": "",
|
||||
"live": "",
|
||||
"loggedIn": "",
|
||||
"off": "",
|
||||
"on": "Enregistrament",
|
||||
"pending": "S'està preparant per a enregistrar la reunió...",
|
||||
"rec": "ENREG",
|
||||
"serviceDescription": "",
|
||||
"serviceName": "Servei d'enregistrament",
|
||||
"signIn": "Inicia sessió",
|
||||
"signOut": "Tanca la sessió",
|
||||
"startRecordingBody": "Esteu segur de voler iniciar l'enregistrament?",
|
||||
"unavailable": "",
|
||||
"unavailableTitle": "L'enregistrament no és disponible"
|
||||
},
|
||||
"sectionList": {
|
||||
"pullToRefresh": ""
|
||||
},
|
||||
"settings": {
|
||||
"audioVideo": "",
|
||||
"calendar": {
|
||||
"about": "",
|
||||
"disconnect": "",
|
||||
"microsoftSignIn": "",
|
||||
"signedIn": "",
|
||||
"title": ""
|
||||
},
|
||||
"cameraAndMic": "Càmera i micròfon",
|
||||
"devices": "Aparells",
|
||||
"followMe": "Tothom em segueix",
|
||||
"language": "Llengua",
|
||||
"loggedIn": "",
|
||||
"moderator": "",
|
||||
"more": "Més",
|
||||
"name": "",
|
||||
"noDevice": "",
|
||||
"password": "",
|
||||
"selectAudioOutput": "Sortida d'àudio",
|
||||
"selectCamera": "",
|
||||
"selectMic": "",
|
||||
"startAudioMuted": "",
|
||||
"startVideoMuted": "",
|
||||
"title": "",
|
||||
"update": ""
|
||||
},
|
||||
"settingsView": {
|
||||
"alertOk": "D'acord",
|
||||
"alertTitle": "",
|
||||
"alertURLText": "L'URL introduït no és vàlid",
|
||||
"conferenceSection": "Conferència",
|
||||
"displayName": "Nom visible",
|
||||
"email": "Adreça electrònica",
|
||||
"header": "",
|
||||
"profileSection": "",
|
||||
"serverURL": "URL del servidor",
|
||||
"startWithAudioMuted": "Inicia amb l'àudio silenciat",
|
||||
"startWithVideoMuted": "Inicia amb el vídeo silenciat"
|
||||
},
|
||||
"share": {
|
||||
"dialInfoText": "",
|
||||
"mainText": ""
|
||||
},
|
||||
"speaker": "Altaveu",
|
||||
"speakerStats": {
|
||||
"hours": "",
|
||||
"minutes": "",
|
||||
"name": "Nom",
|
||||
"seconds": "",
|
||||
"speakerStats": "",
|
||||
"speakerTime": ""
|
||||
},
|
||||
"startupoverlay": {
|
||||
"policyText": "",
|
||||
"title": ""
|
||||
},
|
||||
"suspendedoverlay": {
|
||||
"rejoinKeyTitle": "",
|
||||
"text": "",
|
||||
"title": ""
|
||||
},
|
||||
"toolbar": {
|
||||
"accessibilityLabel": {
|
||||
"audioOnly": "Canvia només l'àudio",
|
||||
"audioRoute": "",
|
||||
"callQuality": "",
|
||||
"cc": "Canvia l'estat dels subtítols",
|
||||
"chat": "Canvia l'estat de la finestra de xats",
|
||||
"document": "Canvia el document compartit",
|
||||
"feedback": "",
|
||||
"fullScreen": "Activa o desactiva la pantalla completa",
|
||||
"hangup": "Abandona la trucada",
|
||||
"invite": "",
|
||||
"kick": "",
|
||||
"localRecording": "Activa o desactiva les controls d'enregistrament local",
|
||||
"lockRoom": "Activa o desactiva el bloqueig de la sala",
|
||||
"moreActions": "Canvia el menú d'accions addicionals",
|
||||
"moreActionsMenu": "Menú d'accions addicionals",
|
||||
"mute": "Activa o desactiva el silenci de l'àudio",
|
||||
"pip": "",
|
||||
"profile": "",
|
||||
"raiseHand": "Aixeca o abaixa la mà",
|
||||
"recording": "Activa o desactiva l'enregistrament",
|
||||
"remoteMute": "Silencia el participant",
|
||||
"Settings": "Canvia la configuració",
|
||||
"sharedvideo": "Activa o desactiva la compartició de vídeo",
|
||||
"shareRoom": "",
|
||||
"shareYourScreen": "",
|
||||
"shortcuts": "",
|
||||
"speakerStats": "",
|
||||
"tileView": "",
|
||||
"toggleCamera": "",
|
||||
"videomute": ""
|
||||
},
|
||||
"addPeople": "Afegeix persones a la trucada",
|
||||
"audioonly": "Activa o desactiva el mode de només àudio",
|
||||
"audioOnlyOff": "Desactiva el mode de només àudio",
|
||||
"audioOnlyOn": "Activa el mode de només àudio",
|
||||
"audioRoute": "Seleccioneu l'aparell de so",
|
||||
"authenticate": "Autentica",
|
||||
"callQuality": "Gestiona la qualitat de la trucada",
|
||||
"cameraDisabled": "La càmera no és disponible",
|
||||
"chat": "Obre o tanca el xat",
|
||||
"closeChat": "Tanca el xat",
|
||||
"documentClose": "Tanca el document compartit",
|
||||
"documentOpen": "Obre el document compartit",
|
||||
"enterFullScreen": "Mostra en pantalla completa",
|
||||
"enterTileView": "",
|
||||
"etherpad": "Obre o tanca el document compartit",
|
||||
"exitFullScreen": "Surt de la pantalla completa",
|
||||
"exitTileView": "",
|
||||
"feedback": "Deixa comentaris",
|
||||
"filmstrip": "Mostra o amaga vídeos",
|
||||
"fullscreen": "",
|
||||
"hangup": "Surt",
|
||||
"invite": "Convida-hi persones",
|
||||
"lock": "",
|
||||
"login": "",
|
||||
"logout": "Tanca la sessió",
|
||||
"lowerYourHand": "",
|
||||
"micDisabled": "",
|
||||
"micMutedPopup": "",
|
||||
"moreActions": "Accions addionals",
|
||||
"mute": "",
|
||||
"openChat": "Obre el xat",
|
||||
"pip": "",
|
||||
"profile": "Edita el perfil",
|
||||
"raiseHand": "Aixeca o abaixa la mà",
|
||||
"raiseYourHand": "Aixeca la mà",
|
||||
"Settings": "Configuració",
|
||||
"sharedvideo": "Comparteix un vídeo de YouTube",
|
||||
"sharedVideoMutedPopup": "",
|
||||
"shareRoom": "Convida-hi algú",
|
||||
"shortcuts": "Mostra les dreceres",
|
||||
"sip": "",
|
||||
"speakerStats": "Estadístiques de l'interlocutor",
|
||||
"startScreenSharing": "",
|
||||
"startSubtitles": "Inicia els subtítols",
|
||||
"stopScreenSharing": "Atura la compartició de la pantalla",
|
||||
"stopSubtitles": "Atura els subtítols",
|
||||
"stopSharedVideo": "Atura el vídeo de YouTube",
|
||||
"talkWhileMutedPopup": "Intenteu parlar? Esteu silenciat.",
|
||||
"tileViewToggle": "",
|
||||
"toggleCamera": "",
|
||||
"unableToUnmutePopup": "",
|
||||
"videomute": "Inicia o atura la càmera"
|
||||
},
|
||||
"transcribing": {
|
||||
"ccButtonTooltip": "Inicia o atura els subtítols",
|
||||
"error": "La transcripció ha fallat. Torneu-ho a intentar més tard.",
|
||||
"expandedLabel": "",
|
||||
"failedToStart": "",
|
||||
"labelToolTip": "",
|
||||
"off": "",
|
||||
"pending": "",
|
||||
"start": "",
|
||||
"stop": "",
|
||||
"tr": "TR"
|
||||
},
|
||||
"userMedia": {
|
||||
"androidGrantPermissions": "",
|
||||
"chromeGrantPermissions": "",
|
||||
"edgeGrantPermissions": "",
|
||||
"electronGrantPermissions": "",
|
||||
"firefoxGrantPermissions": "",
|
||||
"iexplorerGrantPermissions": "",
|
||||
"nwjsGrantPermissions": "",
|
||||
"operaGrantPermissions": "",
|
||||
"react-nativeGrantPermissions": "",
|
||||
"safariGrantPermissions": ""
|
||||
},
|
||||
"videoSIPGW": {
|
||||
"busy": "",
|
||||
"busyTitle": "",
|
||||
"errorAlreadyInvited": "",
|
||||
"errorInvite": "",
|
||||
"errorInviteFailed": "",
|
||||
"errorInviteFailedTitle": "",
|
||||
"errorInviteTitle": "",
|
||||
"pending": "",
|
||||
"serviceName": "Servei de sales",
|
||||
"unavailableTitle": ""
|
||||
},
|
||||
"videoStatus": {
|
||||
"audioOnly": "AUD",
|
||||
"audioOnlyExpanded": "",
|
||||
"callQuality": "Qualitat de la trucada",
|
||||
"hd": "HD",
|
||||
"hdTooltip": "",
|
||||
"highDefinition": "Alta definició",
|
||||
"labelTooiltipNoVideo": "No hi ha vídeo",
|
||||
"labelTooltipAudioOnly": "",
|
||||
"labelTooltipVideo": "Qualitat de vídeo actual",
|
||||
"ld": "LD",
|
||||
"ldTooltip": "",
|
||||
"lowDefinition": "Baixa definició",
|
||||
"onlyAudioAvailable": "Només hi ha disponible l'àudio",
|
||||
"onlyAudioSupported": "",
|
||||
"p2pEnabled": "",
|
||||
"p2pVideoQualityDescription": "",
|
||||
"qualityButtonTip": "Canvia la qualitat de vídeo rebut",
|
||||
"recHighDefinitionOnly": "",
|
||||
"sd": "",
|
||||
"sdTooltip": "",
|
||||
"standardDefinition": ""
|
||||
},
|
||||
"videothumbnail": {
|
||||
"domute": "",
|
||||
"flip": "",
|
||||
"kick": "",
|
||||
"moderator": "Moderador",
|
||||
"mute": "El membre està silenciat",
|
||||
"muted": "Silenciat",
|
||||
"remoteControl": "Control remot",
|
||||
"videomute": "El membre ha aturat la càmera"
|
||||
},
|
||||
"welcomepage": {
|
||||
"accessibilityLabel": {
|
||||
"join": "Toqueu per a unir-vos-hi",
|
||||
"roomname": ""
|
||||
},
|
||||
"appDescription": "",
|
||||
"audioVideoSwitch": {
|
||||
"audio": "Veu",
|
||||
"video": ""
|
||||
},
|
||||
"calendar": "Agenda",
|
||||
"connectCalendarButton": "",
|
||||
"connectCalendarText": "",
|
||||
"enterRoomTitle": "Inicia una reunió nova",
|
||||
"go": "",
|
||||
"join": "",
|
||||
"privacy": "Privadesa",
|
||||
"recentList": "Recents",
|
||||
"recentListDelete": "Suprimeix",
|
||||
"recentListEmpty": "",
|
||||
"roomname": "Introduïu el nom de la sala",
|
||||
"roomnameHint": "",
|
||||
"sendFeedback": "",
|
||||
"terms": "Condicions",
|
||||
"title": "Videoconferència segura, plena de funcionalitats i completament gratuïta i lliure"
|
||||
}
|
||||
}
|
||||
@@ -57,7 +57,7 @@
|
||||
"video": "Vidéo"
|
||||
},
|
||||
"calendar": "Calendrier",
|
||||
"connectCalendarText": "Connectez votre calendrier pour voir toutes vos réunion dans __app__. Ajoutez les réunions __app__ dans votre calendrier pour les lancer en un seul clic.",
|
||||
"connectCalendarText": "Connectez votre calendrier pour voir toutes vos réunions dans __app__. Ajoutez les réunions __app__ dans votre calendrier pour les lancer en un seul clic.",
|
||||
"connectCalendarButton": "Connecter votre calendrier",
|
||||
"enterRoomTitle": "Démarrer une nouvelle réunion",
|
||||
"go": "Créer",
|
||||
@@ -239,6 +239,7 @@
|
||||
},
|
||||
"status": "Connexion:"
|
||||
},
|
||||
"\u0005connectionindicator": {},
|
||||
"notify": {
|
||||
"disconnected": "déconnecté",
|
||||
"moderator": "Droits modérateur accordés !",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
1001
lang/main-ja.json
1001
lang/main-ja.json
File diff suppressed because it is too large
Load Diff
@@ -1,23 +1,19 @@
|
||||
{
|
||||
"contactlist": "",
|
||||
"passwordSetRemotely": "",
|
||||
"connectionsettings": "连接设置",
|
||||
"contactlist_plural": "__count__ 位成员",
|
||||
"passwordSetRemotely": "被其他用户设置",
|
||||
"poweredby": "技术支持",
|
||||
"feedback": {
|
||||
"average": "",
|
||||
"bad": "差",
|
||||
"good": "好",
|
||||
"rateExperience": "请评价您的会议体验。",
|
||||
"veryBad": "非常差",
|
||||
"veryGood": "非常好"
|
||||
},
|
||||
"inviteUrlDefaultMsg": "您的会议正在被创建。。。",
|
||||
"me": "我",
|
||||
"speaker": "发言人",
|
||||
"raisedHand": "请求发言",
|
||||
"defaultNickname": "例如 星视通",
|
||||
"defaultLink": "例如 __url__",
|
||||
"callingName": "__name__",
|
||||
"audioDevices": {
|
||||
"bluetooth": "蓝牙",
|
||||
"headphones": "耳机",
|
||||
"phone": "电话",
|
||||
"speaker": "发言人"
|
||||
},
|
||||
"audioOnly": {
|
||||
"audioOnly": "只有音频",
|
||||
"featureToggleDisabled": "在仅使用音频模式下切换功能无效"
|
||||
@@ -26,6 +22,7 @@
|
||||
"react-nativeGrantPermissions": "当浏览器要求权限许可时选择 <b><i>允许</i></b>",
|
||||
"chromeGrantPermissions": "当浏览器要求权限许可时选择 <b><i>允许</i></b>",
|
||||
"androidGrantPermissions": "当浏览器要求权限许可时选择 <b><i>允许</i></b>",
|
||||
"electronGrantPermissions": "请授权使用您的摄像头和麦克风",
|
||||
"firefoxGrantPermissions": "当浏览器要求权限许可时选择<b><i>共享设备</i></b> ",
|
||||
"operaGrantPermissions": "当浏览器要求权限许可时选择 <b><i>允许</i></b>",
|
||||
"iexplorerGrantPermissions": "当浏览器要求权限许可时选择 <b><i>可以</i></b>",
|
||||
@@ -38,117 +35,146 @@
|
||||
"raiseHand": "申请或取消发言",
|
||||
"pushToTalk": "按住说话",
|
||||
"toggleScreensharing": "在摄像头和屏幕共享之间切换",
|
||||
"toggleFilmstrip": "显示或隐藏视频",
|
||||
"toggleShortcuts": "显示或隐藏帮助菜单",
|
||||
"toggleFilmstrip": "显示/隐藏 视频缩略图",
|
||||
"toggleShortcuts": "显示/隐藏 快捷键",
|
||||
"focusLocal": "切换到本地视频上",
|
||||
"focusRemote": "切换到远端视频上",
|
||||
"focusRemote": "显示对方的视频",
|
||||
"toggleChat": "打开或关闭聊天",
|
||||
"mute": "静音或取消静音",
|
||||
"fullScreen": "全屏或退出全屏",
|
||||
"fullScreen": "开启 / 关闭 全屏",
|
||||
"videoMute": "开启或关闭视频",
|
||||
"showSpeakerStats": "查看扬声器状态"
|
||||
"showSpeakerStats": "查看扬声器状态",
|
||||
"localRecording": "显示 / 隐藏 本地录制选项"
|
||||
},
|
||||
"\u0005keyboardShortcuts": {},
|
||||
"welcomepage": {
|
||||
"disable": "不再显示该页",
|
||||
"feature1": {
|
||||
"content": "无需下载. __app__ 直接通过浏览器使用。 分享您的会议链接给其他人即可参与会议。",
|
||||
"title": "简单易用"
|
||||
"accessibilityLabel": {
|
||||
"join": "点击加入",
|
||||
"roomname": "请输入房间名"
|
||||
},
|
||||
"feature2": {
|
||||
"content": "多方视频会议所需带宽仅需128Kbps。 屏幕共享和语音会议所需的带宽更少。",
|
||||
"title": "低带宽"
|
||||
},
|
||||
"feature3": {
|
||||
"content": "__app__ 有Apache许可. 在此许可下,您可以免费下载,使用,修改和分享该代码",
|
||||
"title": "开源"
|
||||
},
|
||||
"feature4": {
|
||||
"content": "",
|
||||
"title": "不限用户数"
|
||||
},
|
||||
"feature5": {
|
||||
"content": "和他人共享屏幕非常简单。 __app__ 对于在线演示、讲座和技术支持会议再合适不过了。",
|
||||
"title": "屏幕共享"
|
||||
},
|
||||
"feature6": {
|
||||
"content": "是否担心隐私安全? __app__ 可以设定会议室密码防止他人进入会议。",
|
||||
"title": "安全"
|
||||
},
|
||||
"feature7": {
|
||||
"content": "__app__ 的一大特色是Etherpad——一个完美适用于会议、写作等场景,可实时协作的文本编辑器。",
|
||||
"title": "共享笔记"
|
||||
},
|
||||
"feature8": {
|
||||
"content": "通过简单地整合Piwik, Google Analytics或者其他使用监控和统计系统来了解您的使用者。",
|
||||
"title": "使用统计"
|
||||
"appDescription": "快来使用全队视频通话。您可以邀请任何您认识的人。__app__ 是一个完全加密,100% 开源的视频会议解决方案。无需注册帐号,无限时免费使用。",
|
||||
"audioVideoSwitch": {
|
||||
"audio": "语音",
|
||||
"video": "视频"
|
||||
},
|
||||
"calendar": "日历",
|
||||
"connectCalendarText": "",
|
||||
"connectCalendarButton": "",
|
||||
"enterRoomTitle": "",
|
||||
"go": "开始",
|
||||
"join": "加入",
|
||||
"privacy": "隐私",
|
||||
"recentList": "最近",
|
||||
"recentListDelete": "",
|
||||
"recentListEmpty": "",
|
||||
"roomname": "请输入房间名",
|
||||
"roomnamePlaceHolder": "房间名",
|
||||
"roomnameHint": "请输入您想加入房间的 URL 地址或者房间名。您也可以想个房名创建房间,只要其他人输入和您一样的名称就能加入您的房间。",
|
||||
"sendFeedback": "发送反馈",
|
||||
"terms": "条款"
|
||||
"terms": "条款",
|
||||
"title": ""
|
||||
},
|
||||
"startupoverlay": {
|
||||
"policyText": "~",
|
||||
"policyText": " ",
|
||||
"title": "__app__ 需要使用您的麦克风和摄像头。"
|
||||
},
|
||||
"suspendedoverlay": {
|
||||
"title": "由于您的电脑休眠,视频通话已经中断。",
|
||||
"text": "重新连接",
|
||||
"title": "由于您的电脑进入休眠模式,视频通话已经中断。",
|
||||
"text": "按下 <i>重新加入</i> 按钮重新连接。",
|
||||
"rejoinKeyTitle": "重新加入"
|
||||
},
|
||||
"\u0005suspendedoverlay": {},
|
||||
"toolbar": {
|
||||
"addPeople": "",
|
||||
"accessibilityLabel": {
|
||||
"audioOnly": "",
|
||||
"audioRoute": "",
|
||||
"callQuality": "",
|
||||
"chat": "显示 / 隐藏 聊天窗口",
|
||||
"cc": "",
|
||||
"document": "开启 / 关闭 文档共享",
|
||||
"feedback": "",
|
||||
"fullScreen": "进入 / 退出 全屏模式",
|
||||
"hangup": "退出聊天室",
|
||||
"invite": "",
|
||||
"localRecording": "",
|
||||
"lockRoom": "",
|
||||
"moreActions": "显示 / 隐藏 更多选择",
|
||||
"moreActionsMenu": "",
|
||||
"mute": "静音 / 取消静音",
|
||||
"pip": "",
|
||||
"profile": "编辑您的简介",
|
||||
"raiseHand": "举手 / 取消举手",
|
||||
"recording": "开启 / 停止 视频录制",
|
||||
"Settings": "显示 / 隐藏 设置",
|
||||
"sharedvideo": "",
|
||||
"shareRoom": "",
|
||||
"shareYourScreen": "",
|
||||
"shortcuts": "",
|
||||
"speakerStats": "显示 / 隐藏 演说者资料",
|
||||
"toggleCamera": "",
|
||||
"tileView": "",
|
||||
"videomute": "静音 / 取消静音"
|
||||
},
|
||||
"addPeople": "添加成员到您的通话中",
|
||||
"audioonly": "启用/禁用仅音频模式(节省带宽)",
|
||||
"audioOnlyOn": "启用/禁用仅音频模式(节省带宽)",
|
||||
"audioOnlyOff": "",
|
||||
"audioRoute": "",
|
||||
"callQuality": "",
|
||||
"enterFullScreen": "",
|
||||
"exitFullScreen": "",
|
||||
"feedback": "",
|
||||
"moreActions": "",
|
||||
"mute": "静音 / 解除静音",
|
||||
"videomute": "开启 / 关闭 摄像头",
|
||||
"authenticate": "认证",
|
||||
"lock": "锁定 / 解锁 房间",
|
||||
"invite": "分享链接",
|
||||
"chat": "开启 / 关闭 聊天",
|
||||
"etherpad": "开启 / 关闭 共享文档",
|
||||
"documentOpen": "开启 / 关闭 共享文档",
|
||||
"documentClose": "开启 / 关闭 共享文档",
|
||||
"shareRoom": "",
|
||||
"sharedvideo": "分享YouTube视频",
|
||||
"sharescreen": "开启 / 关闭 屏幕共享",
|
||||
"stopSharedVideo": "",
|
||||
"fullscreen": "开启 / 关闭 全屏",
|
||||
"sip": "呼叫SIP号码",
|
||||
"Settings": "设置",
|
||||
"hangup": "离开",
|
||||
"login": "登录",
|
||||
"logout": "登出",
|
||||
"dialpad": "开启 / 关闭 拨号盘",
|
||||
"sharedVideoMutedPopup": "",
|
||||
"micMutedPopup": "",
|
||||
"sharedVideoMutedPopup": "您共享的视频已经禁音,现在可以和其他成员交谈了。",
|
||||
"toggleCamera": "",
|
||||
"micMutedPopup": "您可以欣赏您共享的视频,因为您的麦克风已被静音。",
|
||||
"talkWhileMutedPopup": "您在尝试发言吗? 当前您已被静音。",
|
||||
"unableToUnmutePopup": "正在共享视频的时候您不能解除静音。",
|
||||
"cameraDisabled": "摄像头不可用",
|
||||
"micDisabled": "麦克风不可用",
|
||||
"filmstrip": "显示 / 隐藏 视频",
|
||||
"pip": "",
|
||||
"profile": "编辑您的简介",
|
||||
"raiseHand": "请求 / 取消 发言"
|
||||
"raiseHand": "请求 / 取消 发言",
|
||||
"shortcuts": "",
|
||||
"speakerStats": "发言者状态",
|
||||
"tileViewToggle": "画面模式",
|
||||
"invite": "邀请"
|
||||
},
|
||||
"unsupportedBrowser": {
|
||||
"appInstalled": "或者如果您已经安装了<br /><strong>那么</strong>",
|
||||
"appNotInstalled": "您需要在您的移动设备上安装 <strong>__app__</strong> 来参与会议",
|
||||
"downloadApp": "下载应用",
|
||||
"joinConversation": "加入会议",
|
||||
"startConference": "发起会议"
|
||||
},
|
||||
"bottomtoolbar": {
|
||||
"chat": "开启 / 关闭 聊天",
|
||||
"filmstrip": "显示 / 隐藏 视频",
|
||||
"contactlist": ""
|
||||
"\u0005toolbar": {
|
||||
"accessibilityLabel": {}
|
||||
},
|
||||
"chat": {
|
||||
"nickname": {
|
||||
"title": "请在下面的方框内输入昵称",
|
||||
"popover": "选择一个昵称"
|
||||
},
|
||||
"error": "",
|
||||
"messagebox": "请输入文本..."
|
||||
},
|
||||
"settings": {
|
||||
"calendar": {
|
||||
"about": "",
|
||||
"disconnect": "已断开连接",
|
||||
"microsoftSignIn": "",
|
||||
"signedIn": "",
|
||||
"title": "日历"
|
||||
},
|
||||
"title": "设置",
|
||||
"update": "更新",
|
||||
"name": "名称",
|
||||
@@ -158,11 +184,18 @@
|
||||
"selectMic": "麦克风",
|
||||
"selectAudioOutput": "音频输出",
|
||||
"followMe": "所有人跟随我",
|
||||
"language": "语言",
|
||||
"loggedIn": "",
|
||||
"noDevice": "未发现设备",
|
||||
"cameraAndMic": "摄像头和麦克风",
|
||||
"moderator": "主持人",
|
||||
"moderator": "管理员",
|
||||
"more": "更多",
|
||||
"password": "设定密码",
|
||||
"audioVideo": "音频和视频"
|
||||
"audioVideo": "音频和视频",
|
||||
"devices": "设备"
|
||||
},
|
||||
"\u0005settings": {
|
||||
"calendar": {}
|
||||
},
|
||||
"profile": {
|
||||
"title": "简介",
|
||||
@@ -171,9 +204,9 @@
|
||||
"setEmailInput": "输入您的邮箱"
|
||||
},
|
||||
"videothumbnail": {
|
||||
"moderator": "",
|
||||
"videomute": "",
|
||||
"mute": "",
|
||||
"moderator": "管理员",
|
||||
"videomute": "成员关闭了他的摄像头",
|
||||
"mute": "成员已静音",
|
||||
"kick": "踢出",
|
||||
"muted": "已静音",
|
||||
"domute": "静音",
|
||||
@@ -182,20 +215,18 @@
|
||||
},
|
||||
"connectionindicator": {
|
||||
"header": "连接数据",
|
||||
"bitrate": "比特率",
|
||||
"packetloss": "丢包",
|
||||
"resolution": "分辨率",
|
||||
"connectedTo": "连接到:",
|
||||
"bitrate": "比特率:",
|
||||
"bridgeCount": "服务器数量:",
|
||||
"packetloss": "丢包:",
|
||||
"resolution": "分辨率:",
|
||||
"framerate": "帧率:",
|
||||
"less": "显示更少",
|
||||
"more": "显示更多",
|
||||
"address": "地址",
|
||||
"remoteport_plural": "远程端口:",
|
||||
"address": "地址:",
|
||||
"remoteport": "远程端口:",
|
||||
"localport_plural": "本地端口:",
|
||||
"localport": "本地端口:",
|
||||
"localaddress_plural": "本地地址:",
|
||||
"localaddress": "本地地址:",
|
||||
"remoteaddress_plural": "远程地址:",
|
||||
"remoteaddress": "远程地址:",
|
||||
"transport": "传输:",
|
||||
"bandwidth": "估计带宽:",
|
||||
@@ -204,61 +235,67 @@
|
||||
"quality": {
|
||||
"good": "好",
|
||||
"inactive": "未激活",
|
||||
"lost": "",
|
||||
"nonoptimal": "",
|
||||
"poor": ""
|
||||
"lost": "掉线",
|
||||
"nonoptimal": "中",
|
||||
"poor": "差"
|
||||
},
|
||||
"status": "连接中"
|
||||
"status": "连接:"
|
||||
},
|
||||
"\u0005connectionindicator": {},
|
||||
"notify": {
|
||||
"disconnected": "已断开连接",
|
||||
"moderator": "已授权主持人权限!",
|
||||
"connected": "已连接",
|
||||
"connectedOneMember": "__name__ 已连接",
|
||||
"connectedTwoMembers": "__first__ 以及 __second__ 已连接",
|
||||
"connectedThreePlusMembers": "__name__ 还有 __count__ 位其他成员已接入",
|
||||
"somebody": "某人",
|
||||
"me": "自己",
|
||||
"focus": "会议聚焦",
|
||||
"focusFail": "__component__ 不可用 - 在__ms__秒后重试",
|
||||
"grantedTo": "主持权限已授予__to__!",
|
||||
"grantedToUnknown": "主持权限已授予$t(somebody)!",
|
||||
"muted": "您已经开始了通话,并处于静音状态。",
|
||||
"mutedTitle": "您已被静音!",
|
||||
"raisedHand": "请求发言"
|
||||
"raisedHand": "请求发言",
|
||||
"suboptimalExperienceTitle": "浏览器警告",
|
||||
"suboptimalExperienceDescription": "呃…恐怕您对 __appName__ 的体验会很不好。我们正在尝试优化对此浏览器的支持。眼下,请尝试使用 <a href='static/recommendedBrowsers.html' target='_blank'>已知体验很好的浏览器</a>。"
|
||||
},
|
||||
"dialog": {
|
||||
"add": "添加",
|
||||
"accessibilityLabel": {
|
||||
"liveStreaming": "流媒体直播中"
|
||||
},
|
||||
"allow": "允许",
|
||||
"confirm": "",
|
||||
"kickMessage": "您已被踢出会议!",
|
||||
"popupErrorTitle": "",
|
||||
"popupError": "",
|
||||
"kickTitle": "",
|
||||
"popupErrorTitle": "弹出窗口被拦截",
|
||||
"popupError": "您的浏览器在此网站上阻止了弹出式窗口。请在浏览器的安全设置中打开它并再试一次。",
|
||||
"passwordErrorTitle": "密码错误",
|
||||
"passwordError": "此会议现在受密码保护。只有会议的拥有者可以设定密码。",
|
||||
"passwordError2": "此会议现在受密码保护。只有会议的拥有者可以设定密码。",
|
||||
"connectError": "发生错误,无法连接至会议!",
|
||||
"connectErrorWithMsg": "发生错误,无法连接至会议: __msg__",
|
||||
"incorrectPassword": "",
|
||||
"incorrectPassword": "错误的用户名或者密码",
|
||||
"connecting": "连接中",
|
||||
"copy": "复制",
|
||||
"contactSupport": "",
|
||||
"contactSupport": "联系支持",
|
||||
"error": "错误",
|
||||
"createPassword": "创建密码",
|
||||
"detectext": "尝试检测桌面共享扩展时发生错误",
|
||||
"failedpermissions": "未能获取使用本地麦克风或摄像头的权限。",
|
||||
"conferenceReloadTitle": "不好意思,出错了。",
|
||||
"conferenceReloadMsg": "我们试着修复它。重连中__秒",
|
||||
"conferenceReloadMsg": "不好意思,出错了。",
|
||||
"conferenceDisconnectTitle": "你已经断开。",
|
||||
"conferenceDisconnectMsg": "需要检查你的网络连接。重新连接中 __秒",
|
||||
"dismiss": "",
|
||||
"conferenceDisconnectMsg": "请检查你的网络连接。将会在 __seconds__ 秒后重新连接…",
|
||||
"dismiss": "解除,离开",
|
||||
"rejoinNow": "马上重新加入",
|
||||
"maxUsersLimitReachedTitle": "",
|
||||
"maxUsersLimitReached": "",
|
||||
"maxUsersLimitReachedTitle": "成员数量达到上限",
|
||||
"maxUsersLimitReached": "由于会议已达到人数上限,不能加入。请联系会议组织者或者再次尝试!",
|
||||
"lockRoom": "",
|
||||
"lockTitle": "锁定失败",
|
||||
"lockMessage": "锁定会议失败。",
|
||||
"warning": "警告",
|
||||
"passwordNotSupportedTitle": "",
|
||||
"passwordNotSupported": "",
|
||||
"passwordNotSupportedTitle": "不支持密码",
|
||||
"passwordNotSupported": "不支持设置会议密码",
|
||||
"internalErrorTitle": "内部错误",
|
||||
"internalError": "",
|
||||
"internalError": "哎呀!出现了点问题。错误: __error__",
|
||||
"unableToSwitch": "无法转换视频流。",
|
||||
"SLDFailure": "发生错误,无法静音! (SLD故障)",
|
||||
"SRDFailure": "发生错误,无法关闭视频! (SRD故障)",
|
||||
@@ -275,8 +312,8 @@
|
||||
"shareVideoLinkError": "请提供正确的youtube链接。",
|
||||
"removeSharedVideoTitle": "移除共享的视频",
|
||||
"removeSharedVideoMsg": "您确定要移除共享的视频吗?",
|
||||
"alreadySharedVideoMsg": "",
|
||||
"alreadySharedVideoTitle": "",
|
||||
"alreadySharedVideoMsg": "另一个成员正在共享视频。此会议一次只能共享一个视频。",
|
||||
"alreadySharedVideoTitle": "只能同时分享 1 个视频",
|
||||
"WaitingForHost": "等待主持人。。。",
|
||||
"WaitForHostMsg": "会议<b>__room__ </b>还没有开始。如果您是主持人请授权开始,否则请等待主持人。",
|
||||
"IamHost": "我是主持人。",
|
||||
@@ -284,18 +321,14 @@
|
||||
"Submit": "提交",
|
||||
"retry": "重试",
|
||||
"logoutTitle": "登出",
|
||||
"logoutQuestion": "你确定要登出并停止会议吗",
|
||||
"sessTerminated": "",
|
||||
"logoutQuestion": "你确定要登出并停止会议吗?",
|
||||
"sessTerminated": "通话已终止",
|
||||
"hungUp": "挂断",
|
||||
"joinAgain": "重新加入",
|
||||
"Share": "分享",
|
||||
"Save": "保存",
|
||||
"recording": "录制中",
|
||||
"recordingToken": "输入记录标识",
|
||||
"passwordCheck": "确定要移除密码吗?",
|
||||
"passwordMsg": "设定密码来锁定房间",
|
||||
"shareLink": "分享此会议的链接",
|
||||
"yourPassword": "输入新的密码",
|
||||
"Back": "返回",
|
||||
"serviceUnavailable": "服务不可用",
|
||||
"gracefulShutdown": "服务器正在维护,请稍后再试。",
|
||||
@@ -303,66 +336,75 @@
|
||||
"reservationError": "预定系统错误",
|
||||
"reservationErrorMsg": "错误代号: __code__, 提示信息: __msg__",
|
||||
"password": "输入密码",
|
||||
"unlockRoom": "",
|
||||
"userPassword": "用户密码",
|
||||
"token": "标识",
|
||||
"tokenAuthFailedTitle": "认证失败",
|
||||
"tokenAuthFailed": "对不起,您未被允许参加此会议。",
|
||||
"displayNameRequired": "需要显示名称",
|
||||
"enterDisplayName": "请输入您的显示名称",
|
||||
"extensionRequired": "需要扩展程序",
|
||||
"firefoxExtensionPrompt": "您需要安装Firefox的扩展才能使用屏幕共享功能。请从<a href='__url__'>这里获取后</a>!重试。",
|
||||
"feedbackHelp": "您的反馈将帮助我们提高我们的视频体验。",
|
||||
"feedbackQuestion": "告诉我们您的联系方式。",
|
||||
"feedbackQuestion": "告诉我们您的通话体验!",
|
||||
"thankYou": "感谢使用__appName__!",
|
||||
"sorryFeedback": "很抱歉听到这些,能告诉我们更多详细情况吗?",
|
||||
"liveStreaming": "流媒体直播中",
|
||||
"streamKey": "流 名称/关键字",
|
||||
"startLiveStreaming": "开始流媒体直播",
|
||||
"streamKey": "流媒体直播密钥",
|
||||
"startLiveStreaming": "开始直播",
|
||||
"startRecording": "停止录制",
|
||||
"stopStreamingWarning": "确定要停止流媒体直播吗?",
|
||||
"stopRecordingWarning": "确定要停止录制吗",
|
||||
"stopLiveStreaming": "停止流媒体直播",
|
||||
"stopRecording": "停止录制",
|
||||
"doNotShowWarningAgain": "不再显示此警告",
|
||||
"doNotShowMessageAgain": "不再显示此信息",
|
||||
"permissionDenied": "许可禁止",
|
||||
"screenSharingFailedToInstall": "",
|
||||
"screenSharingFailedToInstallTitle": "",
|
||||
"screenSharingPermissionDeniedError": "",
|
||||
"micErrorPresent": "连接到麦克风时发生错误。",
|
||||
"cameraErrorPresent": "连接到摄像头时发生错误。",
|
||||
"screenSharingFailedToInstall": "哎呀!屏幕共享插件安装失败。",
|
||||
"screenSharingFailedToInstallTitle": "屏幕共享插件安装失败",
|
||||
"screenSharingFirefoxPermissionDeniedError": "尝试进行屏幕共享时遇到了问题。请确认给予了相应的权限。",
|
||||
"screenSharingFirefoxPermissionDeniedTitle": "哎呀!我们无法启动屏幕共享!",
|
||||
"screenSharingPermissionDeniedError": "哎呀!您的视频共享插件发生了一点问题。请刷新重试。",
|
||||
"cameraUnsupportedResolutionError": "您的摄像头不支持所需分辨率。",
|
||||
"cameraUnknownError": "由于不可预知的错误,无法使用摄像头。",
|
||||
"cameraUnknownError": "由于未知错误,无法使用摄像头。",
|
||||
"cameraPermissionDeniedError": "您未授权使用您的摄像头。您仍可参加会议但是其他人无法看到,使用地址栏里的摄像头按钮来启动摄像头。",
|
||||
"cameraNotFoundError": "未发现摄像头",
|
||||
"cameraConstraintFailedError": "你的摄像头不满足要求。",
|
||||
"micUnknownError": "未知错误,麦克风不可用",
|
||||
"micPermissionDeniedError": "您未授权使用麦克风,您仍可参加会议但是其他人无法听到,使用地址栏里的摄像头按钮来启动麦克风",
|
||||
"micUnknownError": "未知错误,麦克风不可用。",
|
||||
"micPermissionDeniedError": "您未授权使用麦克风,您仍可参加会议但是其他人无法听到,使用地址栏里的摄像头按钮来启动麦克风。",
|
||||
"micNotFoundError": "未发现麦克风",
|
||||
"micConstraintFailedError": "你的麦克风不满足要求。",
|
||||
"micNotSendingDataTitle": "",
|
||||
"micNotSendingData": "",
|
||||
"cameraNotSendingDataTitle": "",
|
||||
"cameraNotSendingData": "",
|
||||
"micNotSendingDataTitle": "无法访问麦克风",
|
||||
"micNotSendingData": "我们无法访问您的麦克风。请从设定菜单里选择其他设备或者重新加载。",
|
||||
"cameraNotSendingDataTitle": "无法访问摄像头",
|
||||
"cameraNotSendingData": "我们无法访问您的摄像头。请检查是否有其他程序正在使用这个设备,否则请从设定菜单里选择其他设备或者重新加载。",
|
||||
"goToStore": "跳转至应用商店",
|
||||
"externalInstallationTitle": "需要扩展程序",
|
||||
"externalInstallationMsg": "您需要安装桌面共享扩展",
|
||||
"inlineInstallationMsg": "您需要安装桌面共享扩展",
|
||||
"inlineInstallExtension": "",
|
||||
"muteParticipantTitle": "",
|
||||
"inlineInstallExtension": "立刻安装",
|
||||
"muteParticipantTitle": "静音该成员?",
|
||||
"muteParticipantBody": "您无法对他们解除静音,但是他们自己可以随时解除静音。",
|
||||
"muteParticipantButton": "静音",
|
||||
"liveStreamingDisabledTooltip": "",
|
||||
"liveStreamingDisabledForGuestTooltip": "",
|
||||
"recordingDisabledTooltip": "",
|
||||
"recordingDisabledForGuestTooltip": "",
|
||||
"remoteControlTitle": "远程桌面控制",
|
||||
"remoteControlRequestMessage": "你允许 __用户__ 远程控制你的桌面吗?",
|
||||
"remoteControlShareScreenWarning": "注意:如果按下“允许”你将共享你的屏幕!",
|
||||
"remoteControlDeniedMessage": "__user__ 拒绝了您的远程控制请求",
|
||||
"remoteControlAllowedMessage": "__user__ 接受了您的远程控制请求",
|
||||
"remoteControlErrorMessage": "在尝试向__user__请求远程控制权限时发生了一个错误",
|
||||
"startRemoteControlErrorMessage": "",
|
||||
"remoteControlErrorMessage": "在尝试向__user__请求远程控制权限时发生了一个错误!",
|
||||
"startRemoteControlErrorMessage": "尝试开始远程控制会话时发生了一个错误!",
|
||||
"remoteControlStopMessage": "远程控制结束!",
|
||||
"close": "关闭",
|
||||
"shareYourScreen": "共享你的屏幕",
|
||||
"shareYourScreenDisabled": "",
|
||||
"shareYourScreenDisabledForGuest": "",
|
||||
"yourEntireScreen": "你的整个屏幕",
|
||||
"applicationWindow": "应用窗口"
|
||||
"applicationWindow": "应用窗口",
|
||||
"transcribing": ""
|
||||
},
|
||||
"\u0005dialog": {
|
||||
"accessibilityLabel": {}
|
||||
},
|
||||
"email": {
|
||||
"sharedKey": [
|
||||
@@ -392,6 +434,10 @@
|
||||
],
|
||||
"and": "添加"
|
||||
},
|
||||
"share": {
|
||||
"mainText": "",
|
||||
"dialInfoText": ""
|
||||
},
|
||||
"connection": {
|
||||
"ERROR": "错误",
|
||||
"CONNECTING": "连接中",
|
||||
@@ -405,31 +451,83 @@
|
||||
"ATTACHED": "已接入"
|
||||
},
|
||||
"recording": {
|
||||
"busy": "",
|
||||
"busyTitle": "",
|
||||
"beta": "",
|
||||
"busy": "我们正在释放录制资源。请几分钟之后再试。",
|
||||
"busyTitle": "所有的录制设备正忙",
|
||||
"buttonTooltip": "开始 / 结束录制",
|
||||
"error": "录制失败。请重新尝试。",
|
||||
"expandedOff": "录制已停止",
|
||||
"expandedOn": "",
|
||||
"expandedPending": "",
|
||||
"failedToStart": "录制启动失败",
|
||||
"live": "",
|
||||
"off": "录制已停止",
|
||||
"on": "录制中",
|
||||
"pending": "录制中,等待一位与会者加入",
|
||||
"unavailable": "",
|
||||
"unavailableTitle": ""
|
||||
"pending": "",
|
||||
"rec": "",
|
||||
"authDropboxText": "",
|
||||
"serviceName": "录像服务",
|
||||
"signOut": "",
|
||||
"signIn": "",
|
||||
"loggedIn": "",
|
||||
"availableSpace": "",
|
||||
"startRecordingBody": "确定要停止录制吗",
|
||||
"unavailable": "噢!__serviceName__ 暂时无法使用。我们正在解决此问题。请稍后再试。",
|
||||
"unavailableTitle": "录制不可用"
|
||||
},
|
||||
"\u0005recording": {},
|
||||
"liveStreaming": {
|
||||
"busy": "",
|
||||
"busyTitle": "",
|
||||
"buttonTooltip": "",
|
||||
"error": "",
|
||||
"failedToStart": "",
|
||||
"transcribing": {
|
||||
"pending": "",
|
||||
"off": "",
|
||||
"error": "录制失败。请重新尝试。",
|
||||
"expandedLabel": "",
|
||||
"failedToStart": "",
|
||||
"tr": "",
|
||||
"labelToolTip": "",
|
||||
"ccButtonTooltip": "",
|
||||
"start": "",
|
||||
"stop": ""
|
||||
},
|
||||
"\u0005transcribing": {},
|
||||
"liveStreaming": {
|
||||
"busy": "我们正在释放串流资源。请几分钟后再试。",
|
||||
"busyTitle": "所有的串流设备正忙",
|
||||
"buttonTooltip": "开始 / 停止直播",
|
||||
"changeSignIn": "切换帐号",
|
||||
"choose": "选择一个直播流",
|
||||
"chooseCTA": "请选择直播选项。您现在以 __email__ 身份登录。",
|
||||
"enterStreamKey": "在此输入您的 YouTube 串流密钥。",
|
||||
"error": "流媒体直播失败。请重试。",
|
||||
"errorAPI": "在访问您的 YouTube 直播服务时发生问题。请重新登录。",
|
||||
"errorLiveStreamNotEnabled": "",
|
||||
"expandedOff": "",
|
||||
"expandedOn": "",
|
||||
"expandedPending": "",
|
||||
"failedToStart": "直播服务启动失败",
|
||||
"off": "流媒体直播已停止",
|
||||
"on": "流媒体直播中",
|
||||
"pending": "启动流媒体。。。",
|
||||
"streamIdRequired": "",
|
||||
"streamIdHelp": "在哪里找到这个",
|
||||
"unavailable": "",
|
||||
"unavailableTitle": ""
|
||||
"serviceName": "直播服务",
|
||||
"signedInAs": "",
|
||||
"signIn": "使用谷歌登录",
|
||||
"signOut": "",
|
||||
"signInCTA": "输入 YouTube 串流密钥或者登录 YouTube 帐号。",
|
||||
"start": "开始直播",
|
||||
"streamIdHelp": "这是什么?",
|
||||
"unavailableTitle": "流媒体直播不可用"
|
||||
},
|
||||
"\u0005liveStreaming": {},
|
||||
"videoSIPGW": {
|
||||
"busy": "我们正在清理和释放资源。请过几分钟后再试。",
|
||||
"busyTitle": "房间服务正忙",
|
||||
"errorInvite": "会议还未开始。请稍后再来。",
|
||||
"errorInviteTitle": "",
|
||||
"errorAlreadyInvited": "__displayName__ 已被邀请过了",
|
||||
"errorInviteFailedTitle": "邀请 __displayName__ 失败",
|
||||
"errorInviteFailed": "我们正在解决问题。请稍后再试。",
|
||||
"pending": "__displayName__ 已被邀请",
|
||||
"serviceName": "房间服务",
|
||||
"unavailableTitle": "房间服务不可用。"
|
||||
},
|
||||
"speakerStats": {
|
||||
"hours": "__count__h",
|
||||
@@ -441,69 +539,203 @@
|
||||
},
|
||||
"deviceSelection": {
|
||||
"deviceSettings": "设备设置",
|
||||
"noPermission": "未授权限 ",
|
||||
"noPermission": "未授权限",
|
||||
"previewUnavailable": "预览不可用",
|
||||
"selectADevice": "选择设备",
|
||||
"testAudio": "测试声音"
|
||||
},
|
||||
"invite": {
|
||||
"addPassword": "添加密码",
|
||||
"callNumber": "呼叫__号码__",
|
||||
"enterID": "在电话终端输入会议ID:__conferenceID__和#号拨打会议电话",
|
||||
"howToDialIn": "用以下号码和会议编号拨号。",
|
||||
"hidePassword": "隐藏密码",
|
||||
"inviteTo": "邀请人到 __会议名称__",
|
||||
"invitedYouTo": "",
|
||||
"invitePeople": "",
|
||||
"locked": "本次呼叫被锁定,新的呼叫必须有链接并输入口令后加入。",
|
||||
"showPassword": "查看口令",
|
||||
"unlocked": "本次呼叫已被锁定,用这个链接可以加入。"
|
||||
"testAudio": ""
|
||||
},
|
||||
"videoStatus": {
|
||||
"callQuality": "",
|
||||
"audioOnly": "",
|
||||
"audioOnlyExpanded": "",
|
||||
"callQuality": "通话质量",
|
||||
"hd": "高清",
|
||||
"highDefinition": "",
|
||||
"labelTooltipVideo": "",
|
||||
"labelTooltipAudioOnly": "",
|
||||
"ld": "",
|
||||
"lowDefinition": "",
|
||||
"p2pEnabled": "",
|
||||
"p2pVideoQualityDescription": "",
|
||||
"recHighDefinitionOnly": "",
|
||||
"hdTooltip": "",
|
||||
"highDefinition": "高清",
|
||||
"labelTooltipAudioOnly": "已启用仅音频模式",
|
||||
"labelTooiltipNoVideo": "",
|
||||
"labelTooltipVideo": "当前视频质量",
|
||||
"ld": "低清",
|
||||
"ldTooltip": "",
|
||||
"lowDefinition": "低清",
|
||||
"onlyAudioAvailable": "只能使用音频",
|
||||
"onlyAudioSupported": "我们只支持此浏览器的音频功能。",
|
||||
"p2pEnabled": "点对点已启用",
|
||||
"p2pVideoQualityDescription": "在点对点模式下,通话质量只有高清和仅音频两个选项。其他选项只有在点对点模式退出后才可用。",
|
||||
"recHighDefinitionOnly": "将会首选高清模式。",
|
||||
"sd": "标清",
|
||||
"standardDefinition": "",
|
||||
"qualityButtonTip": ""
|
||||
"sdTooltip": "",
|
||||
"standardDefinition": "标清",
|
||||
"qualityButtonTip": "修改接收视频质量"
|
||||
},
|
||||
"dialOut": {
|
||||
"dial": "拨号",
|
||||
"dialOut": "",
|
||||
"statusMessage": "",
|
||||
"enterPhone": "输入电话号码",
|
||||
"phoneNotAllowed": "还不支持这个目标!抱歉!"
|
||||
"statusMessage": "现在状态为 __status__"
|
||||
},
|
||||
"addPeople": {
|
||||
"add": "添加",
|
||||
"noResults": "",
|
||||
"searchPlaceholder": "",
|
||||
"title": "",
|
||||
"failedToAdd": ""
|
||||
"add": "邀请",
|
||||
"countryNotSupported": "目的国家暂时未被支持。",
|
||||
"countryReminder": "尝试在美国之外通话?请检查国家代码!",
|
||||
"disabled": "您不能邀请成员",
|
||||
"footerText": "",
|
||||
"invite": "邀请",
|
||||
"loading": "查找联系人或者电话号码",
|
||||
"loadingNumber": "验证电话号码",
|
||||
"loadingPeople": "正在搜索需要邀请的成员",
|
||||
"noResults": "没有符合要求的搜索结果",
|
||||
"noValidNumbers": "请输入一个电话号码",
|
||||
"notAvailable": "您不能邀请成员",
|
||||
"searchNumbers": "",
|
||||
"searchPeople": "",
|
||||
"searchPeopleAndNumbers": "",
|
||||
"telephone": "电话号码: __number__",
|
||||
"title": "邀请成员参加您的会议",
|
||||
"failedToAdd": "无法添加成员"
|
||||
},
|
||||
"\u0005addPeople": {},
|
||||
"inlineDialogFailure": {
|
||||
"msg": "",
|
||||
"retry": "",
|
||||
"support": "",
|
||||
"supportMsg": ""
|
||||
"msg": "貌似出了点问题。",
|
||||
"retry": "重试",
|
||||
"support": "支持",
|
||||
"supportMsg": "如果此事多次发生,请联系"
|
||||
},
|
||||
"deviceError": {
|
||||
"cameraError": "",
|
||||
"microphoneError": "",
|
||||
"cameraPermission": "",
|
||||
"microphonePermission": ""
|
||||
"cameraError": "无法访问您的摄像头",
|
||||
"microphoneError": "无法访问您的麦克风",
|
||||
"cameraPermission": "无法获得摄像头访问权限",
|
||||
"microphonePermission": "无法获得麦克风访问权限"
|
||||
},
|
||||
"feedback": {
|
||||
"average": "平均",
|
||||
"bad": "差",
|
||||
"good": "好",
|
||||
"detailsLabel": "",
|
||||
"rateExperience": "请评价您的会议体验。",
|
||||
"veryBad": "非常差",
|
||||
"veryGood": "非常好"
|
||||
},
|
||||
"\u0005feedback": {},
|
||||
"info": {
|
||||
"copy": "",
|
||||
"invite": "",
|
||||
"title": "",
|
||||
"accessibilityLabel": "",
|
||||
"addPassword": "添加密码",
|
||||
"cancelPassword": "取消密码",
|
||||
"conferenceURL": "",
|
||||
"country": "国家",
|
||||
"dialANumber": "要加入您的会议,请拨打其中之一的电话号码并输入PIN:__conferenceID__#",
|
||||
"dialInNumber": "",
|
||||
"dialInConferenceID": "",
|
||||
"dialInNotSupported": "抱歉,不支持电话呼入。",
|
||||
"genericError": "糟糕!出错了。",
|
||||
"inviteLiveStream": "",
|
||||
"invitePhone": "要想使用电话加入会议,请拨打 __number__ 并输入 PIN: __conferenceID__#",
|
||||
"invitePhoneAlternatives": "点击此链接查看更多电话号码: __url__",
|
||||
"inviteURL": "请点击此链接 __url__ 加入视频会议",
|
||||
"liveStreamURL": "流媒体直播中",
|
||||
"moreNumbers": "更多成员",
|
||||
"noNumbers": "无呼入号码。",
|
||||
"noPassword": "未发现设备",
|
||||
"noRoom": "没有指定要呼入的房间。",
|
||||
"numbers": "呼入号码",
|
||||
"password": "密码:",
|
||||
"title": "分享",
|
||||
"tooltip": ""
|
||||
}
|
||||
},
|
||||
"\u0005info": {},
|
||||
"settingsView": {
|
||||
"alertOk": "确认",
|
||||
"alertTitle": "警告",
|
||||
"alertURLText": "服务器 URL 无效",
|
||||
"conferenceSection": "会议",
|
||||
"displayName": "显示名称",
|
||||
"email": "电子邮件",
|
||||
"header": "设置",
|
||||
"profileSection": "简介",
|
||||
"serverURL": "服务器 URL",
|
||||
"startWithAudioMuted": "启动并关闭音频",
|
||||
"startWithVideoMuted": "启动并关闭视频"
|
||||
},
|
||||
"calendarSync": {
|
||||
"addMeetingURL": "",
|
||||
"confirmAddLink": "",
|
||||
"confirmAddLinkTitle": "日历",
|
||||
"join": "",
|
||||
"joinTooltip": "",
|
||||
"nextMeeting": "下次会议",
|
||||
"noEvents": "",
|
||||
"ongoingMeeting": "",
|
||||
"permissionButton": "",
|
||||
"permissionMessage": "",
|
||||
"refresh": "",
|
||||
"today": "今日"
|
||||
},
|
||||
"recentList": {
|
||||
"joinPastMeeting": ""
|
||||
},
|
||||
"sectionList": {
|
||||
"pullToRefresh": ""
|
||||
},
|
||||
"deepLinking": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"tryAgainButton": "",
|
||||
"launchWebButton": "",
|
||||
"appNotInstalled": "",
|
||||
"downloadApp": "下载应用",
|
||||
"openApp": ""
|
||||
},
|
||||
"presenceStatus": {
|
||||
"invited": "邀请",
|
||||
"ringing": "",
|
||||
"calling": "",
|
||||
"initializingCall": "",
|
||||
"connected": "已连接",
|
||||
"connecting": "连接中",
|
||||
"connecting2": "连接中",
|
||||
"disconnected": "已断开连接",
|
||||
"busy": "",
|
||||
"rejected": "",
|
||||
"ignored": "",
|
||||
"expired": ""
|
||||
},
|
||||
"\u0005presenceStatus": {},
|
||||
"dateUtils": {
|
||||
"today": "今日",
|
||||
"yesterday": "昨天",
|
||||
"earlier": "更早的"
|
||||
},
|
||||
"incomingCall": {
|
||||
"answer": "",
|
||||
"audioCallTitle": "",
|
||||
"decline": "解除,离开",
|
||||
"productLabel": "",
|
||||
"videoCallTitle": ""
|
||||
},
|
||||
"localRecording": {
|
||||
"localRecording": "",
|
||||
"dialogTitle": "",
|
||||
"start": "停止录制",
|
||||
"stop": "停止录制",
|
||||
"moderator": "管理员",
|
||||
"me": "自己",
|
||||
"duration": "",
|
||||
"durationNA": "",
|
||||
"encoding": "",
|
||||
"participantStats": "",
|
||||
"participant": "与会者",
|
||||
"sessionToken": "",
|
||||
"clientState": {
|
||||
"on": "开",
|
||||
"off": "关",
|
||||
"unknown": "未知"
|
||||
},
|
||||
"messages": {
|
||||
"engaged": "",
|
||||
"finished": "",
|
||||
"finishedModerator": "",
|
||||
"notModerator": "你不是主持人.你不能开启或关闭本地录音"
|
||||
},
|
||||
"yes": "是",
|
||||
"no": "否",
|
||||
"label": "录音",
|
||||
"labelToolTip": "本地录音被占用"
|
||||
},
|
||||
"\u0005localRecording": {}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
"poweredby": "技術支援",
|
||||
"inviteUrlDefaultMsg": "您的會議正在建立起來………",
|
||||
"me": "我",
|
||||
"speaker": "",
|
||||
"speaker": "發言者",
|
||||
"raisedHand": "請求發言",
|
||||
"defaultNickname": "例如 阿美 志明",
|
||||
"defaultLink": "例如 __url__",
|
||||
@@ -19,13 +19,13 @@
|
||||
"featureToggleDisabled": "在僅用音訊模式下,開關 __feature__ 功能是停用的"
|
||||
},
|
||||
"userMedia": {
|
||||
"react-nativeGrantPermissions": "",
|
||||
"chromeGrantPermissions": "",
|
||||
"androidGrantPermissions": "",
|
||||
"electronGrantPermissions": "",
|
||||
"react-nativeGrantPermissions": "當瀏覽器要求權限允許時,請選擇 <b><i>允許</i></b>",
|
||||
"chromeGrantPermissions": "當瀏覽器要求權限允許時,請選擇 <b><i>允許</i></b>",
|
||||
"androidGrantPermissions": "當瀏覽器要求權限允許時,請選擇 <b><i>允許</i></b>",
|
||||
"electronGrantPermissions": "請允許權限使用您的攝影裝置和麥克風",
|
||||
"firefoxGrantPermissions": "當瀏覽器要求權限允許時,請選擇<b><i>分享設備</i></b> ",
|
||||
"operaGrantPermissions": "當瀏覽器要求權限允許時,請選擇 <b><i>允許</i></b>",
|
||||
"iexplorerGrantPermissions": "",
|
||||
"iexplorerGrantPermissions": "當瀏覽器要求權限允許時,請選擇 <b><i>OK</i></b>",
|
||||
"safariGrantPermissions": "當瀏覽器要求權限允許時,請選擇 <b><i>OK</i></b>",
|
||||
"nwjsGrantPermissions": "請允許權限使用您的攝影裝置和麥克風",
|
||||
"edgeGrantPermissions": "當瀏覽器要求權限允許時,請選擇 <b><i>是的</i></b>"
|
||||
@@ -35,31 +35,42 @@
|
||||
"raiseHand": "舉手發言或不作發言",
|
||||
"pushToTalk": "按鍵通話",
|
||||
"toggleScreensharing": "在攝影鏡頭和螢幕分享之間進行切換",
|
||||
"toggleFilmstrip": "顯示或隱藏視訊",
|
||||
"toggleShortcuts": "顯示或隱藏說明選單",
|
||||
"toggleFilmstrip": "顯示或隱藏視訊影片縮圖",
|
||||
"toggleShortcuts": "顯示或顯示鍵盤快捷鍵",
|
||||
"focusLocal": "聚焦於自己的視訊",
|
||||
"focusRemote": "聚焦於另一位通話者的視訊",
|
||||
"focusRemote": "聚焦於另一人的視訊",
|
||||
"toggleChat": "開啟或關閉聊天",
|
||||
"mute": "靜音或解除靜音",
|
||||
"fullScreen": "進入或退出全螢幕",
|
||||
"fullScreen": "觀看 或 離開 全螢幕",
|
||||
"videoMute": "啟動或停止自己的攝影裝置",
|
||||
"showSpeakerStats": "顯示發言者數據"
|
||||
"showSpeakerStats": "顯示發言者數據",
|
||||
"localRecording": "顯示或顯示本地端錄影控制"
|
||||
},
|
||||
"welcomepage": {
|
||||
"accessibilityLabel": {
|
||||
"join": "輕觸即可參加",
|
||||
"roomname": "輸入會議室名稱"
|
||||
},
|
||||
"appDescription": "快來使用吧,團隊全部成員使用視訊通話,可以邀請任何您所認識的人。 __app__ 是一套完全加密、100% 開放源碼的視訊會議解決方案。無需註冊帳號,無時無刻不分日夜均可免費使用。",
|
||||
"audioVideoSwitch": {
|
||||
"audio": "語音",
|
||||
"video": "視訊"
|
||||
},
|
||||
"calendar": "日曆",
|
||||
"connectCalendarText": "連接你的行事曆,在 __app__ 查看你的會議,此外,增加 __app__ 會議到你的行事曆中,只要按一下就可以啟動。",
|
||||
"connectCalendarButton": "連接你的行事曆",
|
||||
"enterRoomTitle": "啟動新的會議",
|
||||
"go": "開始",
|
||||
"join": "加入",
|
||||
"privacy": "隱私",
|
||||
"recentList": "最近使用",
|
||||
"recentListDelete": "刪除",
|
||||
"recentListEmpty": "目前最近使用是空白的。與你的團隊成員聊天,即會在此處找到最近的會議。",
|
||||
"roomname": "輸入會議室名稱",
|
||||
"roomnameHint": "請輸入您想加入的會議室 URL 網址或名稱。您可以用個名稱來建立會議室,只要其他人輸入相同的名稱就能加入會議室喔。",
|
||||
"sendFeedback": "發送回報",
|
||||
"terms": "條款",
|
||||
"title": "更加安全、更具彈性、又完全免費的視訊會議系統"
|
||||
"title": "安全、全功能、完全免費的視訊會議"
|
||||
},
|
||||
"startupoverlay": {
|
||||
"policyText": " ",
|
||||
@@ -71,8 +82,41 @@
|
||||
"rejoinKeyTitle": "重新加入"
|
||||
},
|
||||
"toolbar": {
|
||||
"accessibilityLabel": {
|
||||
"audioOnly": "切換僅有聲音",
|
||||
"audioRoute": "",
|
||||
"callQuality": "管理通話品質",
|
||||
"chat": "切換聊天視窗",
|
||||
"cc": "切換字幕",
|
||||
"document": "切換分享的文件",
|
||||
"feedback": "留言回報",
|
||||
"fullScreen": "切換全螢幕",
|
||||
"hangup": "離開來電",
|
||||
"invite": "",
|
||||
"localRecording": "切換本地端錄影控制",
|
||||
"lockRoom": "切換會議室鎖定",
|
||||
"moreActions": "切換更多動作功能表",
|
||||
"moreActionsMenu": "更多動作功能表",
|
||||
"mute": "切換靜音",
|
||||
"pip": "切換子母畫面模式",
|
||||
"profile": "編輯您的簡介",
|
||||
"raiseHand": "切換舉手",
|
||||
"recording": "切換錄影",
|
||||
"Settings": "切換設置",
|
||||
"sharedvideo": "切換 Youtube 影片分享",
|
||||
"shareRoom": "邀請某人",
|
||||
"shareYourScreen": "切換螢幕分享",
|
||||
"shortcuts": "切換快捷鍵",
|
||||
"speakerStats": "切換發言人統計",
|
||||
"toggleCamera": "",
|
||||
"tileView": "",
|
||||
"videomute": "切換靜音視訊"
|
||||
},
|
||||
"addPeople": "新增人員到您的通話中",
|
||||
"audioonly": "啟用/停用 僅用音訊模式(節省頻寬)",
|
||||
"audioOnlyOn": "啟用僅用音訊模式(節省頻寬)",
|
||||
"audioOnlyOff": "關閉僅用音訊模式",
|
||||
"audioRoute": "選擇聲音裝置",
|
||||
"callQuality": "管理通話品質",
|
||||
"enterFullScreen": "觀看全螢幕",
|
||||
"exitFullScreen": "跳出全螢幕",
|
||||
@@ -86,52 +130,68 @@
|
||||
"etherpad": "開啟/關閉 分享文件檔案",
|
||||
"documentOpen": "開啟分享的文件檔案",
|
||||
"documentClose": "關閉分享的文件檔案",
|
||||
"shareRoom": "分享室",
|
||||
"sharedvideo": "分享 YouTube 視訊",
|
||||
"sharescreen": "螢幕分享",
|
||||
"stopSharedVideo": "停止 YouTube 視訊",
|
||||
"fullscreen": "觀看/跳出 全螢幕",
|
||||
"sip": "播打 SIP 號碼",
|
||||
"Settings": "",
|
||||
"Settings": "設置",
|
||||
"hangup": "留言",
|
||||
"login": "登入",
|
||||
"logout": "",
|
||||
"logout": "登出",
|
||||
"sharedVideoMutedPopup": "您分享的視訊已經靜音,現在可以和其他成員交談了。",
|
||||
"toggleCamera": "切換攝影機",
|
||||
"micMutedPopup": "您的麥克風已經處於靜音,可以觀看分享視訊了。",
|
||||
"talkWhileMutedPopup": "您要發言嗎? 目前您處於靜音。",
|
||||
"unableToUnmutePopup": "當分享視訊正在使用時,您不能解除靜音。",
|
||||
"cameraDisabled": "攝影裝置無法使用",
|
||||
"micDisabled": "麥克風無法使用",
|
||||
"filmstrip": "顯示/隱藏 視訊",
|
||||
"pip": "進入子母畫模式",
|
||||
"profile": "編輯您的簡介",
|
||||
"raiseHand": "舉手/取消 請求發言",
|
||||
"shortcuts": "查看快捷鍵",
|
||||
"speakerStats": "發言者數據"
|
||||
"speakerStats": "發言者數據",
|
||||
"tileViewToggle": "切換平鋪檢視",
|
||||
"invite": "邀請人員"
|
||||
},
|
||||
"chat": {
|
||||
"nickname": {
|
||||
"title": "請在下面欄位輸入暱稱",
|
||||
"popover": "選擇暱稱"
|
||||
},
|
||||
"error": "錯誤:你的訊息 \"__originalText__\" 未被送出。原因: __error__",
|
||||
"messagebox": "請輸入文字..."
|
||||
},
|
||||
"settings": {
|
||||
"title": "",
|
||||
"calendar": {
|
||||
"about": "此 __appName__ 行事曆整合是安全存取你的行事曆,所以可以讀取即將發生的事件。",
|
||||
"disconnect": "中斷連接",
|
||||
"microsoftSignIn": "使用 Microsoft 帳戶登入",
|
||||
"signedIn": "目前是以 __email__ 來存取行事曆事件。點按下方取消連接鈕可以停止存取行事曆事件。",
|
||||
"title": "日曆"
|
||||
},
|
||||
"title": "設置",
|
||||
"update": "更新",
|
||||
"name": "",
|
||||
"name": "名稱",
|
||||
"startAudioMuted": "全部人啟動時處於靜音",
|
||||
"startVideoMuted": "全部人啟動時隱藏視訊畫面",
|
||||
"selectCamera": "攝影裝置",
|
||||
"selectMic": "麥克風",
|
||||
"selectAudioOutput": "音訊輸出",
|
||||
"followMe": "全部人跟隨仿照我",
|
||||
"noDevice": "",
|
||||
"language": "語言",
|
||||
"loggedIn": "以 __name__ 登入",
|
||||
"noDevice": "無",
|
||||
"cameraAndMic": "攝影裝置和麥克風",
|
||||
"moderator": "主持人",
|
||||
"more": "更多",
|
||||
"password": "設定密碼",
|
||||
"audioVideo": "音訊和視訊"
|
||||
"audioVideo": "音訊和視訊",
|
||||
"devices": "裝置"
|
||||
},
|
||||
"profile": {
|
||||
"title": "",
|
||||
"title": "簡介",
|
||||
"setDisplayNameLabel": "設定您的顯示名稱",
|
||||
"setEmailLabel": "設置您的大頭人像電子信箱",
|
||||
"setEmailInput": "輸入您的電子信箱"
|
||||
@@ -142,13 +202,15 @@
|
||||
"mute": "成員處於靜音",
|
||||
"kick": "踢出",
|
||||
"muted": "處於靜音",
|
||||
"domute": "",
|
||||
"domute": "靜音",
|
||||
"flip": "翻轉",
|
||||
"remoteControl": "遠端控制"
|
||||
},
|
||||
"connectionindicator": {
|
||||
"header": "連接資料",
|
||||
"connectedTo": "已連接至:",
|
||||
"bitrate": "比特率:",
|
||||
"bridgeCount": "伺服器計數:",
|
||||
"packetloss": "丟包:",
|
||||
"resolution": "解析度:",
|
||||
"framerate": "影格率:",
|
||||
@@ -164,7 +226,7 @@
|
||||
"na": "一旦會議啟動,即可回到此處查看連接資訊",
|
||||
"turn": " (轉)",
|
||||
"quality": {
|
||||
"good": "",
|
||||
"good": "很好",
|
||||
"inactive": "未啟用",
|
||||
"lost": "漏失",
|
||||
"nonoptimal": "不甚理想",
|
||||
@@ -183,7 +245,6 @@
|
||||
"focus": "會議焦點",
|
||||
"focusFail": "__component__ 無法使用 - 請在 __ms__ 秒後重試",
|
||||
"grantedTo": "主持人權限已授予 __to__!",
|
||||
"grantedToUnknown": "主持人權限已經授予 $t(somebody) !",
|
||||
"muted": "您已經啟動通話,並處於靜音狀態。",
|
||||
"mutedTitle": "您目前處於靜音!",
|
||||
"raisedHand": "請求發言。",
|
||||
@@ -191,8 +252,13 @@
|
||||
"suboptimalExperienceDescription": "呃……恐怕您對 __appName__ 的體驗不是很好,我們正在嘗試找方法改進對此瀏覽器的支援。現下敬請選用 <a href='static/recommendedBrowsers.html' target='_blank'>全力支援的瀏覽器</a> 來進行。"
|
||||
},
|
||||
"dialog": {
|
||||
"accessibilityLabel": {
|
||||
"liveStreaming": "直播串流"
|
||||
},
|
||||
"allow": "允許",
|
||||
"confirm": "確認",
|
||||
"kickMessage": "您已經被踢出會議!",
|
||||
"kickTitle": "從會議踢出",
|
||||
"popupErrorTitle": "彈出視窗遭到阻攔",
|
||||
"popupError": "您的瀏覽器在此網站上阻攔彈出視窗。請在瀏覽器的安全設置中開啟它並再試一次。",
|
||||
"passwordErrorTitle": "密碼錯誤",
|
||||
@@ -201,10 +267,10 @@
|
||||
"connectError": "喔哦!發生錯誤,無法連接至會議。",
|
||||
"connectErrorWithMsg": "喔哦!發生錯誤,無法連接至會議: __msg__",
|
||||
"incorrectPassword": "錯誤的用戶名稱或密碼",
|
||||
"connecting": "",
|
||||
"connecting": "連接中",
|
||||
"copy": "複製",
|
||||
"contactSupport": "聯絡支援",
|
||||
"error": "",
|
||||
"error": "錯誤",
|
||||
"detectext": "嘗試偵測桌面分享擴充應用程式時發生錯誤。",
|
||||
"failedpermissions": "未能取得使用本地麥克風或攝影裝置的權限。",
|
||||
"conferenceReloadTitle": "不好意思,出錯了。",
|
||||
@@ -215,9 +281,10 @@
|
||||
"rejoinNow": "立即重新加入",
|
||||
"maxUsersLimitReachedTitle": "成員人數已經達到上限",
|
||||
"maxUsersLimitReached": "由於會議已達到人數上限,額滿不能加入。請聯絡會議發起人,或是稍後再次嘗試!",
|
||||
"lockRoom": "鎖定會議室",
|
||||
"lockTitle": "鎖定失敗",
|
||||
"lockMessage": "鎖定會議失敗。",
|
||||
"warning": "",
|
||||
"warning": "警告",
|
||||
"passwordNotSupportedTitle": "不支援密碼",
|
||||
"passwordNotSupported": "不支援設置會議密碼。",
|
||||
"internalErrorTitle": "內部錯誤",
|
||||
@@ -251,9 +318,9 @@
|
||||
"sessTerminated": "通話已經終止",
|
||||
"hungUp": "我方掛斷",
|
||||
"joinAgain": "再次加入",
|
||||
"Share": "",
|
||||
"Share": "分享",
|
||||
"Save": "儲存",
|
||||
"recording": "",
|
||||
"recording": "錄製作業中",
|
||||
"recordingToken": "輸入錄製標記",
|
||||
"Back": "返回",
|
||||
"serviceUnavailable": "服務無法使用",
|
||||
@@ -262,9 +329,10 @@
|
||||
"reservationError": "預約系統錯誤",
|
||||
"reservationErrorMsg": "錯誤碼: __code__, 訊息: __msg__",
|
||||
"password": "輸入密碼",
|
||||
"unlockRoom": "解鎖會議室",
|
||||
"userPassword": "用戶密碼",
|
||||
"token": "標記",
|
||||
"tokenAuthFailedTitle": "",
|
||||
"tokenAuthFailedTitle": "驗證失敗",
|
||||
"tokenAuthFailed": "對不起,您未被允許加入此會議。",
|
||||
"displayNameRequired": "顯示名稱是必須的",
|
||||
"enterDisplayName": "請輸入您的顯示名稱",
|
||||
@@ -272,9 +340,9 @@
|
||||
"feedbackQuestion": "請告訴我們本次通話體驗!",
|
||||
"thankYou": "感謝您使用 __appName__!",
|
||||
"sorryFeedback": "很抱歉聽到這些,能告訴我們更多詳情嗎?",
|
||||
"liveStreaming": "",
|
||||
"liveStreaming": "直播串流中",
|
||||
"streamKey": "直播串流密鑰",
|
||||
"startLiveStreaming": "立即開始直播",
|
||||
"startLiveStreaming": "啟動直播串流",
|
||||
"startRecording": "啟動錄製作業",
|
||||
"stopStreamingWarning": "確定要停止直播串流嗎?",
|
||||
"stopRecordingWarning": "確定要停止錄製作業嗎?",
|
||||
@@ -302,12 +370,16 @@
|
||||
"cameraNotSendingData": "我們無法取用您的攝影裝置。請檢查是否有其他程序正在使用這個設備,否則請從設置選單裡選擇其他設備或者重新裝載。",
|
||||
"goToStore": "前往應用商店",
|
||||
"externalInstallationTitle": "需要擴充應用程式",
|
||||
"externalInstallationMsg": "",
|
||||
"externalInstallationMsg": "您需要安裝桌面分享擴充應用程式。",
|
||||
"inlineInstallationMsg": "您需要安裝桌面分享擴充應用程式。",
|
||||
"inlineInstallExtension": "立即安裝",
|
||||
"muteParticipantTitle": "靜音這位成員?",
|
||||
"muteParticipantBody": "您無法對他們解除靜音,但是他們自己隨時可以解除靜音。",
|
||||
"muteParticipantButton": "靜音",
|
||||
"liveStreamingDisabledTooltip": "啟動直播串流已關閉。",
|
||||
"liveStreamingDisabledForGuestTooltip": "訪客無法啟動直播串流。",
|
||||
"recordingDisabledTooltip": "啟動錄影已關閉。",
|
||||
"recordingDisabledForGuestTooltip": "訪客無法啟動錄影。",
|
||||
"remoteControlTitle": "遠端桌面控制",
|
||||
"remoteControlRequestMessage": "您要允許 __user__ 遠端控制您的桌面嗎?",
|
||||
"remoteControlShareScreenWarning": "注意:如果按下 \"允許\" 您將分享自己的螢幕!",
|
||||
@@ -318,8 +390,11 @@
|
||||
"remoteControlStopMessage": "遠端控制階段結束!",
|
||||
"close": "關閉",
|
||||
"shareYourScreen": "分享自己的螢幕",
|
||||
"shareYourScreenDisabled": "螢幕分享已關閉。",
|
||||
"shareYourScreenDisabledForGuest": "訪客無法螢幕分享。",
|
||||
"yourEntireScreen": "自己的全螢幕",
|
||||
"applicationWindow": "應用程式視窗"
|
||||
"applicationWindow": "應用程式視窗",
|
||||
"transcribing": "轉錄中"
|
||||
},
|
||||
"email": {
|
||||
"sharedKey": [
|
||||
@@ -349,6 +424,22 @@
|
||||
],
|
||||
"and": "與"
|
||||
},
|
||||
"share": {
|
||||
"mainText": [
|
||||
"點按以下連結參加會議:",
|
||||
"__roomUrl__"
|
||||
],
|
||||
"dialInfoText": [
|
||||
"",
|
||||
"",
|
||||
"=====",
|
||||
"",
|
||||
"僅是想要由電話播入嗎?",
|
||||
"",
|
||||
"點按以下連結查看此會議的電話播入號碼",
|
||||
"__dialInfoPageUrl__"
|
||||
]
|
||||
},
|
||||
"connection": {
|
||||
"ERROR": "錯誤",
|
||||
"CONNECTING": "連接中",
|
||||
@@ -362,18 +453,42 @@
|
||||
"ATTACHED": "已經附加"
|
||||
},
|
||||
"recording": {
|
||||
"beta": "BETA",
|
||||
"busy": "我們正在釋放錄製資源。請過幾分鐘後再試。",
|
||||
"busyTitle": "全部錄製設備正在忙碌",
|
||||
"buttonTooltip": "啟動/停止 錄製作業",
|
||||
"error": "錄製作業失敗。請再次重試。",
|
||||
"expandedOff": "錄影已經停止",
|
||||
"expandedOn": "此會議目前正在錄影。",
|
||||
"expandedPending": "錄影正在啟動…",
|
||||
"failedToStart": "錄製啟動失敗",
|
||||
"live": "直播",
|
||||
"off": "錄製作業已經停止",
|
||||
"on": "錄製作業中",
|
||||
"pending": "錄製作業正在等待成員加入……",
|
||||
"pending": "準備錄影此會議…",
|
||||
"rec": "REC 錄影",
|
||||
"authDropboxText": "上傳至 Dropbox",
|
||||
"serviceName": "錄製作業服務",
|
||||
"signOut": "jde eei ",
|
||||
"signIn": "jde bp ",
|
||||
"loggedIn": "以 __userName__ 登入",
|
||||
"availableSpace": "可用空間: __spaceLeft__ MB (大約錄影時間 __duration__ 分鐘)",
|
||||
"startRecordingBody": "確定要停止錄影作業嗎?",
|
||||
"unavailable": "喔哦!__serviceName__ 目前無法使用。我們正在解決此問題,請稍後再試。",
|
||||
"unavailableTitle": "錄製作業無法使用"
|
||||
},
|
||||
"transcribing": {
|
||||
"pending": "正在準備轉錄會議…",
|
||||
"off": "轉錄已停止",
|
||||
"error": "錄影作業失敗。請重試。",
|
||||
"expandedLabel": "轉錄目前開啟",
|
||||
"failedToStart": "轉錄啟動失敗",
|
||||
"tr": "TR 轉錄",
|
||||
"labelToolTip": "此會議正被轉錄",
|
||||
"ccButtonTooltip": "啟動 / 停止顯示字幕",
|
||||
"start": "啟動顯示字幕",
|
||||
"stop": "停止顯示字幕"
|
||||
},
|
||||
"liveStreaming": {
|
||||
"busy": "我們正在釋放串流資源。請過幾分鐘後再試。",
|
||||
"busyTitle": "全部串流設備正在忙碌",
|
||||
@@ -384,12 +499,18 @@
|
||||
"enterStreamKey": "在此輸入您的 YouTube 直播串流密鑰。",
|
||||
"error": "直播串流失敗。請重試。",
|
||||
"errorAPI": "取用您的 YouTube 播出時發生錯誤。請重新登入。",
|
||||
"errorLiveStreamNotEnabled": "直播串流在 __email__ 尚未啟用。請開啟直播串流或登入有啟用直播串流的帳戶。",
|
||||
"expandedOff": "直播串流已停止",
|
||||
"expandedOn": "會議串流目前送至 YouTube 。",
|
||||
"expandedPending": "直播串流正被啟動…",
|
||||
"failedToStart": "直播串流啟動失敗",
|
||||
"off": "直播串流已經停止",
|
||||
"on": "直播串流中",
|
||||
"pending": "啟動直播串流………",
|
||||
"serviceName": "直播串流服務",
|
||||
"signedInAs": "你目前登入名為:",
|
||||
"signIn": "使用 Google 帳戶登入",
|
||||
"signOut": "登出",
|
||||
"signInCTA": "輸入 YouTube 直播串流密鑰,或登入 YouTube 帳號。",
|
||||
"start": "啟動直播串流",
|
||||
"streamIdHelp": "這是什麼?",
|
||||
@@ -420,9 +541,11 @@
|
||||
"noPermission": "未取得權限",
|
||||
"previewUnavailable": "預覽無法使用",
|
||||
"selectADevice": "選擇設備",
|
||||
"testAudio": "測試聲音"
|
||||
"testAudio": "播放測試聲音"
|
||||
},
|
||||
"videoStatus": {
|
||||
"audioOnly": "AUD 聲音",
|
||||
"audioOnlyExpanded": "你處於僅用音訊模式。這個模式節省頻寬,但無法看見他人影像。",
|
||||
"callQuality": "通話品質",
|
||||
"hd": "HD 高清",
|
||||
"hdTooltip": "觀看高清視訊 HD",
|
||||
@@ -447,10 +570,11 @@
|
||||
"statusMessage": "現在狀態為 __status__"
|
||||
},
|
||||
"addPeople": {
|
||||
"add": "",
|
||||
"add": "邀請",
|
||||
"countryNotSupported": "此目標區域尚未支援。",
|
||||
"countryReminder": "嘗試在美國外地通話?請確認開頭使用的國家代碼!",
|
||||
"disabled": "",
|
||||
"disabled": "您不可以邀請人員。",
|
||||
"footerText": "播打已關閉。",
|
||||
"invite": "邀請",
|
||||
"loading": "尋找聯絡人及電話號碼",
|
||||
"loadingNumber": "驗證電話號碼",
|
||||
@@ -487,6 +611,7 @@
|
||||
"veryGood": "極好"
|
||||
},
|
||||
"info": {
|
||||
"accessibilityLabel": "顯示資訊",
|
||||
"addPassword": "新增密碼",
|
||||
"cancelPassword": "取消密碼",
|
||||
"conferenceURL": "連結:",
|
||||
@@ -508,7 +633,7 @@
|
||||
"numbers": "播入號碼",
|
||||
"password": "密碼:",
|
||||
"title": "分享",
|
||||
"tooltip": "取得關於會議的連接使用資訊"
|
||||
"tooltip": "顯示此會議的連結及電話播入號碼"
|
||||
},
|
||||
"settingsView": {
|
||||
"alertOk": "確認",
|
||||
@@ -524,17 +649,21 @@
|
||||
"startWithVideoMuted": "啟動並視訊靜音"
|
||||
},
|
||||
"calendarSync": {
|
||||
"later": "稍後",
|
||||
"next": "即將推出",
|
||||
"addMeetingURL": "增加會議連結",
|
||||
"confirmAddLink": "你要加上 Jitsi 連結於此事件嗎?",
|
||||
"confirmAddLinkTitle": "日曆",
|
||||
"join": "參加",
|
||||
"joinTooltip": "參加會議",
|
||||
"nextMeeting": "下次會議",
|
||||
"now": "現在",
|
||||
"noEvents": "沒有預定事件排入行程。",
|
||||
"ongoingMeeting": "即將進行會議",
|
||||
"permissionButton": "開啟設定",
|
||||
"permissionMessage": "日曆允許權限是必須的,以列入您的會議於應用程式中。"
|
||||
"permissionMessage": "日曆允許權限是必須的,以查看你的會議於應用程式中。",
|
||||
"refresh": "重新整理行事曆",
|
||||
"today": "今日"
|
||||
},
|
||||
"recentList": {
|
||||
"today": "今日",
|
||||
"yesterday": "昨天",
|
||||
"earlier": "稍早"
|
||||
"joinPastMeeting": "參加過往會議"
|
||||
},
|
||||
"sectionList": {
|
||||
"pullToRefresh": "下滑以重新整理"
|
||||
@@ -547,5 +676,60 @@
|
||||
"appNotInstalled": "在您的手機上需要 __app__ 行動應用程式去加入這場會議。",
|
||||
"downloadApp": "下載應用 APP",
|
||||
"openApp": "繼續前往此應用程式"
|
||||
},
|
||||
"presenceStatus": {
|
||||
"invited": "被邀請的",
|
||||
"ringing": "鈴鈴鈴……",
|
||||
"calling": "來電…",
|
||||
"initializingCall": "播打電話…",
|
||||
"connected": "已經連接",
|
||||
"connecting": "連線中...",
|
||||
"connecting2": "通話中*...",
|
||||
"disconnected": "已經中斷連接",
|
||||
"busy": "忙線",
|
||||
"rejected": "拒接",
|
||||
"ignored": "忽略",
|
||||
"expired": "未接"
|
||||
},
|
||||
"dateUtils": {
|
||||
"today": "今日",
|
||||
"yesterday": "昨天",
|
||||
"earlier": "稍早"
|
||||
},
|
||||
"incomingCall": {
|
||||
"answer": "接通",
|
||||
"audioCallTitle": "來電",
|
||||
"decline": "解除",
|
||||
"productLabel": "來自 Jitsi Meet",
|
||||
"videoCallTitle": "視訊來電"
|
||||
},
|
||||
"localRecording": {
|
||||
"localRecording": "本地端錄影中",
|
||||
"dialogTitle": "本地端錄影控制",
|
||||
"start": "啟動錄影作業",
|
||||
"stop": "停止錄影作業",
|
||||
"moderator": "主持人",
|
||||
"me": "自己",
|
||||
"duration": "期間",
|
||||
"durationNA": "N/A",
|
||||
"encoding": "解碼中",
|
||||
"participantStats": "參與者狀態",
|
||||
"participant": "參與者",
|
||||
"sessionToken": "階段標記",
|
||||
"clientState": {
|
||||
"on": "開",
|
||||
"off": "關",
|
||||
"unknown": "不明"
|
||||
},
|
||||
"messages": {
|
||||
"engaged": "本地端錄影已使用。",
|
||||
"finished": "錄影階段 __token__ 已完成。請傳送錄影檔案至主持人。",
|
||||
"finishedModerator": "錄影階段 __token__ 已完成。本地端錄影追蹤已存檔。請要求各參與者提交其錄影檔案。",
|
||||
"notModerator": "你不是主持人,無法啟動或停止本地端錄影。"
|
||||
},
|
||||
"yes": "是的",
|
||||
"no": "沒有",
|
||||
"label": "LOR",
|
||||
"labelToolTip": "本地端錄影使用中"
|
||||
}
|
||||
}
|
||||
@@ -469,7 +469,7 @@
|
||||
"moderator": "Moderator rights granted!",
|
||||
"muted": "You have started the conversation muted.",
|
||||
"mutedTitle": "You're muted!",
|
||||
"raisedHand": "Would like to speak.",
|
||||
"raisedHand": "__name__ would like to speak.",
|
||||
"somebody": "Somebody",
|
||||
"suboptimalExperienceDescription": "Eer... we are afraid your experience with __appName__ isn't going to be that great here. We are looking for ways to improve this but, until then, please try using one of the <a href='static/recommendedBrowsers.html' target='_blank'>fully supported browsers</a>.",
|
||||
"suboptimalExperienceTitle": "Browser Warning"
|
||||
@@ -560,6 +560,7 @@
|
||||
"alertOk": "OK",
|
||||
"alertTitle": "Warning",
|
||||
"alertURLText": "The entered server URL is invalid",
|
||||
"buildInfoSection": "Build Information",
|
||||
"conferenceSection": "Conference",
|
||||
"displayName": "Display name",
|
||||
"email": "Email",
|
||||
@@ -567,7 +568,8 @@
|
||||
"profileSection": "Profile",
|
||||
"serverURL": "Server URL",
|
||||
"startWithAudioMuted": "Start with audio muted",
|
||||
"startWithVideoMuted": "Start with video muted"
|
||||
"startWithVideoMuted": "Start with video muted",
|
||||
"version": "Version"
|
||||
},
|
||||
"share": {
|
||||
"dialInfoText": "\n\n=====\n\nJust want to dial in on your phone?\n\n__defaultDialInNumber__Click this link to see the dial in phone numbers for this meeting\n__dialInfoPageUrl__",
|
||||
|
||||
@@ -11,6 +11,9 @@ import { invite } from '../../react/features/invite';
|
||||
import { getJitsiMeetTransport } from '../transport';
|
||||
|
||||
import { API_ID } from './constants';
|
||||
import {
|
||||
processExternalDeviceRequest
|
||||
} from '../../react/features/device-selection/functions';
|
||||
|
||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
|
||||
@@ -117,6 +120,12 @@ function initCommands() {
|
||||
return false;
|
||||
});
|
||||
transport.on('request', (request, callback) => {
|
||||
const { dispatch, getState } = APP.store;
|
||||
|
||||
if (processExternalDeviceRequest(dispatch, getState, request, callback)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const { name } = request;
|
||||
|
||||
switch (name) {
|
||||
@@ -377,6 +386,19 @@ class API {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify external application (if API is enabled) that the device list has
|
||||
* changed.
|
||||
*
|
||||
* @param {Object} devices - The new device list.
|
||||
* @returns {void}
|
||||
*/
|
||||
notifyDeviceListChanged(devices: Object) {
|
||||
this._sendEvent({
|
||||
name: 'device-list-changed',
|
||||
devices });
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify external application (if API is enabled) that user changed their
|
||||
* nickname.
|
||||
|
||||
105
modules/API/external/external_api.js
vendored
105
modules/API/external/external_api.js
vendored
@@ -7,6 +7,16 @@ import {
|
||||
} from '../../transport';
|
||||
|
||||
import electronPopupsConfig from './electronPopupsConfig.json';
|
||||
import {
|
||||
getAvailableDevices,
|
||||
getCurrentDevices,
|
||||
isDeviceChangeAvailable,
|
||||
isDeviceListAvailable,
|
||||
isMultipleAudioInputSupported,
|
||||
setAudioInputDevice,
|
||||
setAudioOutputDevice,
|
||||
setVideoInputDevice
|
||||
} from './functions';
|
||||
|
||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
|
||||
@@ -40,6 +50,7 @@ const events = {
|
||||
'avatar-changed': 'avatarChanged',
|
||||
'audio-availability-changed': 'audioAvailabilityChanged',
|
||||
'audio-mute-status-changed': 'audioMuteStatusChanged',
|
||||
'device-list-changed': 'deviceListChanged',
|
||||
'display-name-change': 'displayNameChange',
|
||||
'email-change': 'emailChange',
|
||||
'feedback-submitted': 'feedbackSubmitted',
|
||||
@@ -211,6 +222,8 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
* for iframe onload event.
|
||||
* @param {Array<Object>} [options.invitees] - Array of objects containing
|
||||
* information about new participants that will be invited in the call.
|
||||
* @param {Array<Object>} [options.devices] - Array of objects containing
|
||||
* information about the initial devices that will be used in the call.
|
||||
*/
|
||||
constructor(domain, ...args) {
|
||||
super();
|
||||
@@ -224,7 +237,8 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
noSSL = false,
|
||||
jwt = undefined,
|
||||
onload = undefined,
|
||||
invitees
|
||||
invitees,
|
||||
devices
|
||||
} = parseArguments(args);
|
||||
|
||||
this._parentNode = parentNode;
|
||||
@@ -233,7 +247,8 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
interfaceConfigOverwrite,
|
||||
jwt,
|
||||
noSSL,
|
||||
roomName
|
||||
roomName,
|
||||
devices
|
||||
});
|
||||
this._createIFrame(height, width, onload);
|
||||
this._transport = new Transport({
|
||||
@@ -593,6 +608,24 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Promise that resolves with a list of available devices.
|
||||
*
|
||||
* @returns {Promise}
|
||||
*/
|
||||
getAvailableDevices() {
|
||||
return getAvailableDevices(this._transport);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Promise that resolves with current selected devices.
|
||||
*
|
||||
* @returns {Promise}
|
||||
*/
|
||||
getCurrentDevices() {
|
||||
return getCurrentDevices(this._transport);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the audio is available.
|
||||
*
|
||||
@@ -605,6 +638,38 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Promise that resolves with true if the device change is available
|
||||
* and with false if not.
|
||||
*
|
||||
* @param {string} [deviceType] - Values - 'output', 'input' or undefined.
|
||||
* Default - 'input'.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
isDeviceChangeAvailable(deviceType) {
|
||||
return isDeviceChangeAvailable(this._transport, deviceType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Promise that resolves with true if the device list is available
|
||||
* and with false if not.
|
||||
*
|
||||
* @returns {Promise}
|
||||
*/
|
||||
isDeviceListAvailable() {
|
||||
return isDeviceListAvailable(this._transport);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Promise that resolves with true if multiple audio input is supported
|
||||
* and with false if not.
|
||||
*
|
||||
* @returns {Promise}
|
||||
*/
|
||||
isMultipleAudioInputSupported() {
|
||||
return isMultipleAudioInputSupported(this._transport);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invite people to the call.
|
||||
*
|
||||
@@ -771,6 +836,42 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the audio input device to the one with the label or id that is
|
||||
* passed.
|
||||
*
|
||||
* @param {string} label - The label of the new device.
|
||||
* @param {string} deviceId - The id of the new device.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
setAudioInputDevice(label, deviceId) {
|
||||
return setAudioInputDevice(this._transport, label, deviceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the audio output device to the one with the label or id that is
|
||||
* passed.
|
||||
*
|
||||
* @param {string} label - The label of the new device.
|
||||
* @param {string} deviceId - The id of the new device.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
setAudioOutputDevice(label, deviceId) {
|
||||
return setAudioOutputDevice(this._transport, label, deviceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the video input device to the one with the label or id that is
|
||||
* passed.
|
||||
*
|
||||
* @param {string} label - The label of the new device.
|
||||
* @param {string} deviceId - The id of the new device.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
setVideoInputDevice(label, deviceId) {
|
||||
return setVideoInputDevice(this._transport, label, deviceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configuration for electron for the windows that are open
|
||||
* from Jitsi Meet.
|
||||
|
||||
156
modules/API/external/functions.js
vendored
Normal file
156
modules/API/external/functions.js
vendored
Normal file
@@ -0,0 +1,156 @@
|
||||
// @flow
|
||||
|
||||
import Logger from 'jitsi-meet-logger';
|
||||
|
||||
const logger = Logger.getLogger(__filename);
|
||||
|
||||
/**
|
||||
* Returns Promise that resolves with result an list of available devices.
|
||||
*
|
||||
* @param {Transport} transport - The @code{Transport} instance responsible for
|
||||
* the external communication.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function getAvailableDevices(transport: Object) {
|
||||
return transport.sendRequest({
|
||||
type: 'devices',
|
||||
name: 'getAvailableDevices'
|
||||
}).catch(e => {
|
||||
logger.error(e);
|
||||
|
||||
return {};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Promise that resolves with current selected devices.
|
||||
*
|
||||
* @param {Transport} transport - The @code{Transport} instance responsible for
|
||||
* the external communication.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function getCurrentDevices(transport: Object) {
|
||||
return transport.sendRequest({
|
||||
type: 'devices',
|
||||
name: 'getCurrentDevices'
|
||||
}).catch(e => {
|
||||
logger.error(e);
|
||||
|
||||
return {};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Promise that resolves with true if the device change is available
|
||||
* and with false if not.
|
||||
*
|
||||
* @param {Transport} transport - The @code{Transport} instance responsible for
|
||||
* the external communication.
|
||||
* @param {string} [deviceType] - Values - 'output', 'input' or undefined.
|
||||
* Default - 'input'.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function isDeviceChangeAvailable(transport: Object, deviceType: string) {
|
||||
return transport.sendRequest({
|
||||
deviceType,
|
||||
type: 'devices',
|
||||
name: 'isDeviceChangeAvailable'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Promise that resolves with true if the device list is available
|
||||
* and with false if not.
|
||||
*
|
||||
* @param {Transport} transport - The @code{Transport} instance responsible for
|
||||
* the external communication.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function isDeviceListAvailable(transport: Object) {
|
||||
return transport.sendRequest({
|
||||
type: 'devices',
|
||||
name: 'isDeviceListAvailable'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Promise that resolves with true if multiple audio input is supported
|
||||
* and with false if not.
|
||||
*
|
||||
* @param {Transport} transport - The @code{Transport} instance responsible for
|
||||
* the external communication.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function isMultipleAudioInputSupported(transport: Object) {
|
||||
return transport.sendRequest({
|
||||
type: 'devices',
|
||||
name: 'isMultipleAudioInputSupported'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the audio input device to the one with the label or id that is passed.
|
||||
*
|
||||
* @param {Transport} transport - The @code{Transport} instance responsible for
|
||||
* the external communication.
|
||||
* @param {string} label - The label of the new device.
|
||||
* @param {string} id - The id of the new device.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function setAudioInputDevice(transport: Object, label: string, id: string) {
|
||||
return _setDevice(transport, {
|
||||
id,
|
||||
kind: 'audioinput',
|
||||
label
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the audio output device to the one with the label or id that is passed.
|
||||
*
|
||||
* @param {Transport} transport - The @code{Transport} instance responsible for
|
||||
* the external communication.
|
||||
* @param {string} label - The label of the new device.
|
||||
* @param {string} id - The id of the new device.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function setAudioOutputDevice(transport: Object, label: string, id: string) {
|
||||
return _setDevice(transport, {
|
||||
id,
|
||||
kind: 'audiooutput',
|
||||
label
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the currently used device to the one that is passed.
|
||||
*
|
||||
* @param {Transport} transport - The @code{Transport} instance responsible for
|
||||
* the external communication.
|
||||
* @param {Object} device - The new device to be used.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function _setDevice(transport: Object, device) {
|
||||
return transport.sendRequest({
|
||||
type: 'devices',
|
||||
name: 'setDevice',
|
||||
device
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the video input device to the one with the label or id that is passed.
|
||||
*
|
||||
* @param {Transport} transport - The @code{Transport} instance responsible for
|
||||
* the external communication.
|
||||
* @param {string} label - The label of the new device.
|
||||
* @param {string} id - The id of the new device.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function setVideoInputDevice(transport: Object, label: string, id: string) {
|
||||
return _setDevice(transport, {
|
||||
id,
|
||||
kind: 'videoinput',
|
||||
label
|
||||
});
|
||||
}
|
||||
@@ -175,37 +175,6 @@ UI.changeDisplayName = function(id, displayName) {
|
||||
VideoLayout.onDisplayNameChanged(id, displayName);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the "raised hand" status for a participant.
|
||||
*
|
||||
* @param {string} id - The id of the participant whose raised hand UI should
|
||||
* be updated.
|
||||
* @param {string} name - The name of the participant with the raised hand
|
||||
* update.
|
||||
* @param {boolean} raisedHandStatus - Whether the participant's hand is raised
|
||||
* or not.
|
||||
* @returns {void}
|
||||
*/
|
||||
UI.setRaisedHandStatus = (id, name, raisedHandStatus) => {
|
||||
VideoLayout.setRaisedHandStatus(id, raisedHandStatus);
|
||||
if (raisedHandStatus) {
|
||||
messageHandler.participantNotification(
|
||||
name,
|
||||
'notify.somebody',
|
||||
'connected',
|
||||
'notify.raisedHand');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the local "raised hand" status.
|
||||
*/
|
||||
UI.setLocalRaisedHandStatus
|
||||
= raisedHandStatus =>
|
||||
VideoLayout.setRaisedHandStatus(
|
||||
APP.conference.getMyUserId(),
|
||||
raisedHandStatus);
|
||||
|
||||
/**
|
||||
* Initialize conference UI.
|
||||
*/
|
||||
|
||||
@@ -4,6 +4,7 @@ const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
import jitsiLocalStorage from '../../util/JitsiLocalStorage';
|
||||
|
||||
import {
|
||||
NOTIFICATION_TIMEOUT,
|
||||
showErrorNotification,
|
||||
showNotification,
|
||||
showWarningNotification
|
||||
@@ -454,7 +455,7 @@ const messageHandler = {
|
||||
cls,
|
||||
messageKey,
|
||||
messageArguments,
|
||||
timeout = 2500) {
|
||||
timeout = NOTIFICATION_TIMEOUT) {
|
||||
APP.store.dispatch(showNotification({
|
||||
descriptionArguments: messageArguments,
|
||||
descriptionKey: messageKey,
|
||||
|
||||
@@ -824,6 +824,7 @@ SmallVideo.prototype.updateIndicators = function() {
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
<Provider store = { APP.store }>
|
||||
<I18nextProvider i18n = { i18next }>
|
||||
<div>
|
||||
<AtlasKitThemeProvider mode = 'dark'>
|
||||
@@ -839,11 +840,10 @@ SmallVideo.prototype.updateIndicators = function() {
|
||||
= { statsPopoverPosition }
|
||||
userID = { this.id } />
|
||||
: null }
|
||||
{ this._showRaisedHand
|
||||
? <RaisedHandIndicator
|
||||
iconSize = { iconSize }
|
||||
tooltipPosition = { tooltipPosition } />
|
||||
: null }
|
||||
<RaisedHandIndicator
|
||||
iconSize = { iconSize }
|
||||
participantId = { this.id }
|
||||
tooltipPosition = { tooltipPosition } />
|
||||
{ this._showDominantSpeaker
|
||||
? <DominantSpeakerIndicator
|
||||
iconSize = { iconSize }
|
||||
@@ -851,7 +851,8 @@ SmallVideo.prototype.updateIndicators = function() {
|
||||
: null }
|
||||
</AtlasKitThemeProvider>
|
||||
</div>
|
||||
</I18nextProvider>,
|
||||
</I18nextProvider>
|
||||
</Provider>,
|
||||
indicatorToolbar
|
||||
);
|
||||
};
|
||||
|
||||
@@ -672,22 +672,6 @@ const VideoLayout = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the "raised hand" status for a participant identified by 'id'.
|
||||
*/
|
||||
setRaisedHandStatus(id, raisedHandStatus) {
|
||||
const video
|
||||
= APP.conference.isLocalId(id)
|
||||
? localVideoThumbnail : remoteVideos[id];
|
||||
|
||||
if (video) {
|
||||
video.showRaisedHandIndicator(raisedHandStatus);
|
||||
if (raisedHandStatus) {
|
||||
video.showDominantSpeakerIndicator(false);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* On dominant speaker changed event.
|
||||
*
|
||||
|
||||
5
package-lock.json
generated
5
package-lock.json
generated
@@ -11955,8 +11955,9 @@
|
||||
}
|
||||
},
|
||||
"react-native-sound": {
|
||||
"version": "github:jitsi/react-native-sound#e4260ed7f641eeb0377d76eac7987aba72e1cf08",
|
||||
"from": "github:jitsi/react-native-sound#e4260ed7f641eeb0377d76eac7987aba72e1cf08"
|
||||
"version": "0.10.12",
|
||||
"resolved": "https://registry.npmjs.org/react-native-sound/-/react-native-sound-0.10.12.tgz",
|
||||
"integrity": "sha512-lhvzZ+ekNSHW9s4FjmxxGAQmd7yDT9FnHgJNP824cMyHRrTES/LgHnSOxES/1FWVoaQ8FdfbEHkWd2rN0G/L9A=="
|
||||
},
|
||||
"react-native-swipeout": {
|
||||
"version": "2.3.6",
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
"react-native-immersive": "2.0.0",
|
||||
"react-native-keep-awake": "4.0.0",
|
||||
"react-native-linear-gradient": "2.5.3",
|
||||
"react-native-sound": "github:jitsi/react-native-sound#e4260ed7f641eeb0377d76eac7987aba72e1cf08",
|
||||
"react-native-sound": "0.10.12",
|
||||
"react-native-swipeout": "2.3.6",
|
||||
"react-native-vector-icons": "6.0.2",
|
||||
"react-native-webrtc": "github:jitsi/react-native-webrtc#032ee5c90e2c5ff27ab2f952217104772fcbd155",
|
||||
|
||||
@@ -7,6 +7,8 @@ declare var config: Object;
|
||||
|
||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
|
||||
import { configureInitialDevices } from '../devices';
|
||||
|
||||
export {
|
||||
connectionEstablished,
|
||||
connectionFailed,
|
||||
@@ -25,12 +27,13 @@ export function connect() {
|
||||
|
||||
// XXX For web based version we use conference initialization logic
|
||||
// from the old app (at the moment of writing).
|
||||
return APP.conference.init({
|
||||
roomName: room
|
||||
}).catch(error => {
|
||||
APP.API.notifyConferenceLeft(APP.conference.roomName);
|
||||
logger.error(error);
|
||||
});
|
||||
return dispatch(configureInitialDevices()).then(
|
||||
() => APP.conference.init({
|
||||
roomName: room
|
||||
}).catch(error => {
|
||||
APP.API.notifyConferenceLeft(APP.conference.roomName);
|
||||
logger.error(error);
|
||||
}));
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -30,3 +30,23 @@ export const SET_VIDEO_INPUT_DEVICE = 'SET_VIDEO_INPUT_DEVICE';
|
||||
* }
|
||||
*/
|
||||
export const UPDATE_DEVICE_LIST = 'UPDATE_DEVICE_LIST';
|
||||
|
||||
/**
|
||||
* The type of Redux action which will add a pending device requests that will
|
||||
* be executed later when it is possible (when the conference is joined).
|
||||
*
|
||||
* {
|
||||
* type: ADD_PENDING_DEVICE_REQUEST,
|
||||
* request: Object
|
||||
* }
|
||||
*/
|
||||
export const ADD_PENDING_DEVICE_REQUEST = 'ADD_PENDING_DEVICE_REQUEST';
|
||||
|
||||
/**
|
||||
* The type of Redux action which will remove all pending device requests.
|
||||
*
|
||||
* {
|
||||
* type: REMOVE_PENDING_DEVICE_REQUESTS
|
||||
* }
|
||||
*/
|
||||
export const REMOVE_PENDING_DEVICE_REQUESTS = 'REMOVE_PENDING_DEVICE_REQUESTS';
|
||||
|
||||
@@ -1,10 +1,93 @@
|
||||
import JitsiMeetJS from '../lib-jitsi-meet';
|
||||
import { updateSettings } from '../settings';
|
||||
|
||||
import {
|
||||
ADD_PENDING_DEVICE_REQUEST,
|
||||
REMOVE_PENDING_DEVICE_REQUESTS,
|
||||
SET_AUDIO_INPUT_DEVICE,
|
||||
SET_VIDEO_INPUT_DEVICE,
|
||||
UPDATE_DEVICE_LIST
|
||||
} from './actionTypes';
|
||||
import {
|
||||
areDeviceLabelsInitialized,
|
||||
getDeviceIdByLabel,
|
||||
getDevicesFromURL
|
||||
} from './functions';
|
||||
|
||||
/**
|
||||
* Adds a pending device request.
|
||||
*
|
||||
* @param {Object} request - The request to be added.
|
||||
* @returns {{
|
||||
* type: ADD_PENDING_DEVICE_REQUEST,
|
||||
* request: Object
|
||||
* }}
|
||||
*/
|
||||
export function addPendingDeviceRequest(request) {
|
||||
return {
|
||||
type: ADD_PENDING_DEVICE_REQUEST,
|
||||
request
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the initial A/V devices before the conference has started.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function configureInitialDevices() {
|
||||
return (dispatch, getState) => new Promise(resolve => {
|
||||
const deviceLabels = getDevicesFromURL(getState());
|
||||
|
||||
if (deviceLabels) {
|
||||
dispatch(getAvailableDevices()).then(() => {
|
||||
const state = getState();
|
||||
|
||||
if (!areDeviceLabelsInitialized(state)) {
|
||||
// The labels are not available if the A/V permissions are
|
||||
// not yet granted.
|
||||
|
||||
Object.keys(deviceLabels).forEach(key => {
|
||||
dispatch(addPendingDeviceRequest({
|
||||
type: 'devices',
|
||||
name: 'setDevice',
|
||||
device: {
|
||||
kind: key.toLowerCase(),
|
||||
label: deviceLabels[key]
|
||||
},
|
||||
// eslint-disable-next-line no-empty-function
|
||||
responseCallback() {}
|
||||
}));
|
||||
});
|
||||
resolve();
|
||||
|
||||
return;
|
||||
}
|
||||
const newSettings = {};
|
||||
const devicesKeysToSettingsKeys = {
|
||||
audioInput: 'micDeviceId',
|
||||
audioOutput: 'audioOutputDeviceId',
|
||||
videoInput: 'cameraDeviceId'
|
||||
};
|
||||
|
||||
Object.keys(deviceLabels).forEach(key => {
|
||||
const label = deviceLabels[key];
|
||||
const deviceId = getDeviceIdByLabel(state, label);
|
||||
|
||||
if (deviceId) {
|
||||
newSettings[devicesKeysToSettingsKeys[key]] = deviceId;
|
||||
}
|
||||
});
|
||||
|
||||
dispatch(updateSettings(newSettings));
|
||||
resolve();
|
||||
});
|
||||
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries for connected A/V input and output devices and updates the redux
|
||||
@@ -29,6 +112,20 @@ export function getAvailableDevices() {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove all pending device requests.
|
||||
*
|
||||
* @returns {{
|
||||
* type: REMOVE_PENDING_DEVICE_REQUESTS
|
||||
* }}
|
||||
*/
|
||||
export function removePendingDeviceRequests() {
|
||||
return {
|
||||
type: REMOVE_PENDING_DEVICE_REQUESTS
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals to update the currently used audio input device.
|
||||
*
|
||||
@@ -77,3 +174,4 @@ export function updateDeviceList(devices) {
|
||||
devices
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,34 @@
|
||||
// @flow
|
||||
|
||||
import { parseURLParams } from '../config';
|
||||
import JitsiMeetJS from '../lib-jitsi-meet';
|
||||
import { updateSettings } from '../settings';
|
||||
|
||||
declare var APP: Object;
|
||||
|
||||
/**
|
||||
* Detects the use case when the labels are not available if the A/V permissions
|
||||
* are not yet granted.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {boolean} - True if the labels are already initialized and false
|
||||
* otherwise.
|
||||
*/
|
||||
export function areDeviceLabelsInitialized(state: Object) {
|
||||
// TODO: Replace with something that doesn't use APP when the conference.js logic is reactified.
|
||||
if (APP.conference._localTracksInitialized) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const type of [ 'audioInput', 'audioOutput', 'videoInput' ]) {
|
||||
if ((state['features/base/devices'].availableDevices[type] || []).find(d => Boolean(d.label))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get device id of the audio output device which is currently in use.
|
||||
* Empty string stands for default device.
|
||||
@@ -13,6 +39,71 @@ export function getAudioOutputDeviceId() {
|
||||
return JitsiMeetJS.mediaDevices.getAudioOutputDevice();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a device with a label that matches the passed label and returns its id.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @param {string} label - The label.
|
||||
* @returns {string|undefined}
|
||||
*/
|
||||
export function getDeviceIdByLabel(state: Object, label: string) {
|
||||
const types = [ 'audioInput', 'audioOutput', 'videoInput' ];
|
||||
|
||||
for (const type of types) {
|
||||
const device
|
||||
= (state['features/base/devices'].availableDevices[type] || [])
|
||||
.find(d => d.label === label);
|
||||
|
||||
if (device) {
|
||||
return device.deviceId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the devices set in the URL.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {Object|undefined}
|
||||
*/
|
||||
export function getDevicesFromURL(state: Object) {
|
||||
const urlParams
|
||||
= parseURLParams(state['features/base/connection'].locationURL);
|
||||
|
||||
const audioOutput = urlParams['devices.audioOutput'];
|
||||
const videoInput = urlParams['devices.videoInput'];
|
||||
const audioInput = urlParams['devices.audioInput'];
|
||||
|
||||
if (!audioOutput && !videoInput && !audioInput) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const devices = {};
|
||||
|
||||
audioOutput && (devices.audioOutput = audioOutput);
|
||||
videoInput && (devices.videoInput = videoInput);
|
||||
audioInput && (devices.audioInput = audioInput);
|
||||
|
||||
return devices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an array of media devices into an object organized by device kind.
|
||||
*
|
||||
* @param {Array<MediaDeviceInfo>} devices - Available media devices.
|
||||
* @private
|
||||
* @returns {Object} An object with the media devices split by type. The keys
|
||||
* are device type and the values are arrays with devices matching the device
|
||||
* type.
|
||||
*/
|
||||
export function groupDevicesByKind(devices: Object[]): Object {
|
||||
return {
|
||||
audioInput: devices.filter(device => device.kind === 'audioinput'),
|
||||
audioOutput: devices.filter(device => device.kind === 'audiooutput'),
|
||||
videoInput: devices.filter(device => device.kind === 'videoinput')
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Set device id of the audio output device which is currently in use.
|
||||
* Empty string stands for default device.
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
/* global APP */
|
||||
|
||||
import { CONFERENCE_JOINED } from '../conference';
|
||||
import { processExternalDeviceRequest } from '../../device-selection';
|
||||
import { MiddlewareRegistry } from '../redux';
|
||||
import UIEvents from '../../../../service/UI/UIEvents';
|
||||
|
||||
import { MiddlewareRegistry } from '../redux';
|
||||
|
||||
import { removePendingDeviceRequests } from './actions';
|
||||
import {
|
||||
SET_AUDIO_INPUT_DEVICE,
|
||||
SET_VIDEO_INPUT_DEVICE
|
||||
@@ -18,6 +20,8 @@ import {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
case CONFERENCE_JOINED:
|
||||
return _conferenceJoined(store, next, action);
|
||||
case SET_AUDIO_INPUT_DEVICE:
|
||||
APP.UI.emitEvent(UIEvents.AUDIO_DEVICE_CHANGED, action.deviceId);
|
||||
break;
|
||||
@@ -28,3 +32,34 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
|
||||
return next(action);
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Does extra sync up on properties that may need to be updated after the
|
||||
* conference was joined.
|
||||
*
|
||||
* @param {Store} store - The redux store in which the specified {@code action}
|
||||
* is being dispatched.
|
||||
* @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
|
||||
* specified {@code action} to the specified {@code store}.
|
||||
* @param {Action} action - The redux action {@code CONFERENCE_JOINED} which is
|
||||
* being dispatched in the specified {@code store}.
|
||||
* @private
|
||||
* @returns {Object} The value returned by {@code next(action)}.
|
||||
*/
|
||||
function _conferenceJoined({ dispatch, getState }, next, action) {
|
||||
const result = next(action);
|
||||
const state = getState();
|
||||
const { pendingRequests } = state['features/base/devices'];
|
||||
|
||||
pendingRequests.forEach(request => {
|
||||
processExternalDeviceRequest(
|
||||
dispatch,
|
||||
getState,
|
||||
request,
|
||||
request.responseCallback);
|
||||
});
|
||||
dispatch(removePendingDeviceRequests());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
import {
|
||||
ADD_PENDING_DEVICE_REQUEST,
|
||||
REMOVE_PENDING_DEVICE_REQUESTS,
|
||||
SET_AUDIO_INPUT_DEVICE,
|
||||
SET_VIDEO_INPUT_DEVICE,
|
||||
UPDATE_DEVICE_LIST
|
||||
} from './actionTypes';
|
||||
import { groupDevicesByKind } from './functions';
|
||||
|
||||
import { ReducerRegistry } from '../redux';
|
||||
|
||||
const DEFAULT_STATE = {
|
||||
audioInput: [],
|
||||
audioOutput: [],
|
||||
videoInput: []
|
||||
availableDevices: {
|
||||
audioInput: [],
|
||||
audioOutput: [],
|
||||
videoInput: []
|
||||
},
|
||||
pendingRequests: []
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -27,13 +33,29 @@ ReducerRegistry.register(
|
||||
(state = DEFAULT_STATE, action) => {
|
||||
switch (action.type) {
|
||||
case UPDATE_DEVICE_LIST: {
|
||||
const deviceList = _groupDevicesByKind(action.devices);
|
||||
const deviceList = groupDevicesByKind(action.devices);
|
||||
|
||||
return {
|
||||
...deviceList
|
||||
...state,
|
||||
availableDevices: deviceList
|
||||
};
|
||||
}
|
||||
|
||||
case ADD_PENDING_DEVICE_REQUEST:
|
||||
return {
|
||||
...state,
|
||||
pendingRequests: [
|
||||
...state.pendingRequests,
|
||||
action.request
|
||||
]
|
||||
};
|
||||
|
||||
case REMOVE_PENDING_DEVICE_REQUESTS:
|
||||
return {
|
||||
...state,
|
||||
pendingRequests: [ ]
|
||||
};
|
||||
|
||||
// TODO: Changing of current audio and video device id is currently
|
||||
// handled outside of react/redux. Fall through to default logic for
|
||||
// now.
|
||||
@@ -44,19 +66,3 @@ ReducerRegistry.register(
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Converts an array of media devices into an object organized by device kind.
|
||||
*
|
||||
* @param {Array<MediaDeviceInfo>} devices - Available media devices.
|
||||
* @private
|
||||
* @returns {Object} An object with the media devices split by type. The keys
|
||||
* are device type and the values are arrays with devices matching the device
|
||||
* type.
|
||||
*/
|
||||
function _groupDevicesByKind(devices) {
|
||||
return {
|
||||
audioInput: devices.filter(device => device.kind === 'audioinput'),
|
||||
audioOutput: devices.filter(device => device.kind === 'audiooutput'),
|
||||
videoInput: devices.filter(device => device.kind === 'videoinput')
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import throttle from 'lodash/throttle';
|
||||
|
||||
import { set } from '../redux';
|
||||
import { showNotification } from '../../notifications';
|
||||
import { NOTIFICATION_TIMEOUT, showNotification } from '../../notifications';
|
||||
|
||||
import {
|
||||
DOMINANT_SPEAKER_CHANGED,
|
||||
@@ -452,7 +452,7 @@ const _throttledNotifyParticipantConnected = throttle(dispatch => {
|
||||
|
||||
if (notificationProps) {
|
||||
dispatch(
|
||||
showNotification(notificationProps, 2500));
|
||||
showNotification(notificationProps, NOTIFICATION_TIMEOUT));
|
||||
}
|
||||
|
||||
joinedParticipantsNames = [];
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
// @flow
|
||||
|
||||
import UIEvents from '../../../../service/UI/UIEvents';
|
||||
|
||||
import { NOTIFICATION_TIMEOUT, showNotification } from '../../notifications';
|
||||
import { CALLING, INVITED } from '../../presence-status';
|
||||
|
||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../app';
|
||||
import {
|
||||
CONFERENCE_WILL_JOIN,
|
||||
forEachConference,
|
||||
getCurrentConference
|
||||
} from '../conference';
|
||||
import { CALLING, INVITED } from '../../presence-status';
|
||||
import { JitsiConferenceEvents } from '../lib-jitsi-meet';
|
||||
import { MiddlewareRegistry, StateListenerRegistry } from '../redux';
|
||||
import UIEvents from '../../../../service/UI/UIEvents';
|
||||
import { playSound, registerSound, unregisterSound } from '../sounds';
|
||||
|
||||
import {
|
||||
@@ -36,8 +40,8 @@ import {
|
||||
import {
|
||||
getAvatarURLByParticipantId,
|
||||
getLocalParticipant,
|
||||
getParticipantById,
|
||||
getParticipantCount
|
||||
getParticipantCount,
|
||||
getParticipantDisplayName
|
||||
} from './functions';
|
||||
import { PARTICIPANT_JOINED_FILE, PARTICIPANT_LEFT_FILE } from './sounds';
|
||||
|
||||
@@ -185,6 +189,44 @@ StateListenerRegistry.register(
|
||||
localParticipantIdChanged(LOCAL_PARTICIPANT_DEFAULT_ID));
|
||||
});
|
||||
|
||||
/**
|
||||
* Registers listeners for participant change events.
|
||||
*/
|
||||
StateListenerRegistry.register(
|
||||
state => state['features/base/conference'].conference,
|
||||
(conference, store) => {
|
||||
if (conference) {
|
||||
// We joined a conference
|
||||
conference.on(
|
||||
JitsiConferenceEvents.PARTICIPANT_PROPERTY_CHANGED,
|
||||
(participant, propertyName, oldValue, newValue) => {
|
||||
switch (propertyName) {
|
||||
case 'features_screen-sharing':
|
||||
store.dispatch(participantUpdated({
|
||||
conference,
|
||||
id: participant.getId(),
|
||||
features: { 'screen-sharing': true }
|
||||
}));
|
||||
break;
|
||||
case 'raisedHand': {
|
||||
_raiseHandUpdated(
|
||||
store, conference, participant.getId(), newValue);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
||||
// Ignore for now.
|
||||
}
|
||||
|
||||
});
|
||||
} else {
|
||||
// We left the conference, raise hand of the local participant must be updated.
|
||||
_raiseHandUpdated(
|
||||
store, conference, undefined, false);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Initializes the local participant and signals that it joined.
|
||||
*
|
||||
@@ -293,21 +335,6 @@ function _participantJoinedOrUpdated({ getState }, next, action) {
|
||||
'raisedHand',
|
||||
raisedHand);
|
||||
}
|
||||
|
||||
if (typeof APP === 'object') {
|
||||
if (local) {
|
||||
APP.UI.onLocalRaiseHandChanged(raisedHand);
|
||||
APP.UI.setLocalRaisedHandStatus(raisedHand);
|
||||
} else {
|
||||
const remoteParticipant = getParticipantById(getState(), id);
|
||||
|
||||
remoteParticipant
|
||||
&& APP.UI.setRaisedHandStatus(
|
||||
remoteParticipant.id,
|
||||
remoteParticipant.name,
|
||||
raisedHand);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Notify external listeners of potential avatarURL changes.
|
||||
@@ -332,6 +359,36 @@ function _participantJoinedOrUpdated({ getState }, next, action) {
|
||||
return next(action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a raise hand status update.
|
||||
*
|
||||
* @param {Function} dispatch - The Redux dispatch function.
|
||||
* @param {Object} conference - The conference for which we got an update.
|
||||
* @param {string?} participantId - The ID of the participant from which we got an update. If undefined,
|
||||
* we update the local participant.
|
||||
* @param {boolean} newValue - The new value of the raise hand status.
|
||||
* @returns {void}
|
||||
*/
|
||||
function _raiseHandUpdated({ dispatch, getState }, conference, participantId, newValue) {
|
||||
const raisedHand = newValue === 'true';
|
||||
const pid = participantId || getLocalParticipant(getState()).id;
|
||||
|
||||
dispatch(participantUpdated({
|
||||
conference,
|
||||
id: pid,
|
||||
raisedHand
|
||||
}));
|
||||
|
||||
if (raisedHand) {
|
||||
dispatch(showNotification({
|
||||
titleArguments: {
|
||||
name: getParticipantDisplayName(getState, pid)
|
||||
},
|
||||
titleKey: 'notify.raisedHand'
|
||||
}, NOTIFICATION_TIMEOUT));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers sounds related with the participants feature.
|
||||
*
|
||||
|
||||
@@ -469,16 +469,16 @@ export function urlObjectToString(o: Object): ?string {
|
||||
|
||||
let { hash } = url;
|
||||
|
||||
for (const configName of [ 'config', 'interfaceConfig' ]) {
|
||||
for (const urlPrefix of [ 'config', 'interfaceConfig', 'devices' ]) {
|
||||
const urlParamsArray
|
||||
= _objectToURLParamsArray(
|
||||
o[`${configName}Overwrite`]
|
||||
|| o[configName]
|
||||
|| o[`${configName}Override`]);
|
||||
o[`${urlPrefix}Overwrite`]
|
||||
|| o[urlPrefix]
|
||||
|| o[`${urlPrefix}Override`]);
|
||||
|
||||
if (urlParamsArray.length) {
|
||||
let urlParamsString
|
||||
= `${configName}.${urlParamsArray.join(`&${configName}.`)}`;
|
||||
= `${urlPrefix}.${urlParamsArray.join(`&${urlPrefix}.`)}`;
|
||||
|
||||
if (hash.length) {
|
||||
urlParamsString = `&${urlParamsString}`;
|
||||
|
||||
@@ -6,17 +6,15 @@ import {
|
||||
|
||||
import { createDeviceChangedEvent, sendAnalytics } from '../analytics';
|
||||
import {
|
||||
getAudioOutputDeviceId,
|
||||
setAudioInputDevice,
|
||||
setAudioOutputDeviceId,
|
||||
setVideoInputDevice
|
||||
} from '../base/devices';
|
||||
import { i18next } from '../base/i18n';
|
||||
import JitsiMeetJS from '../base/lib-jitsi-meet';
|
||||
import { updateSettings } from '../base/settings';
|
||||
|
||||
import { SET_DEVICE_SELECTION_POPUP_DATA } from './actionTypes';
|
||||
import { getDeviceSelectionDialogProps } from './functions';
|
||||
import { getDeviceSelectionDialogProps, processExternalDeviceRequest } from './functions';
|
||||
|
||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
|
||||
@@ -60,7 +58,7 @@ export function openDeviceSelectionPopup() {
|
||||
});
|
||||
|
||||
transport.on('request',
|
||||
_processRequest.bind(undefined, dispatch, getState));
|
||||
processExternalDeviceRequest.bind(undefined, dispatch, getState));
|
||||
transport.on('event', event => {
|
||||
if (event.type === 'devices-dialog' && event.name === 'close') {
|
||||
popup.close();
|
||||
@@ -80,75 +78,6 @@ export function openDeviceSelectionPopup() {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes device requests from external applications.
|
||||
*
|
||||
* @param {Dispatch} dispatch - The redux {@code dispatch} function.
|
||||
* @param {Function} getState - The redux function that gets/retrieves the redux
|
||||
* state.
|
||||
* @param {Object} request - The request to be processed.
|
||||
* @param {Function} responseCallback - The callback that will send the
|
||||
* response.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function _processRequest(dispatch, getState, request, responseCallback) { // eslint-disable-line max-len, max-params
|
||||
if (request.type === 'devices') {
|
||||
const state = getState();
|
||||
const settings = state['features/base/settings'];
|
||||
|
||||
switch (request.name) {
|
||||
case 'isDeviceListAvailable':
|
||||
responseCallback(JitsiMeetJS.mediaDevices.isDeviceListAvailable());
|
||||
break;
|
||||
case 'isDeviceChangeAvailable':
|
||||
responseCallback(
|
||||
JitsiMeetJS.mediaDevices.isDeviceChangeAvailable(
|
||||
request.deviceType));
|
||||
break;
|
||||
case 'isMultipleAudioInputSupported':
|
||||
responseCallback(JitsiMeetJS.isMultipleAudioInputSupported());
|
||||
break;
|
||||
case 'getCurrentDevices':
|
||||
responseCallback({
|
||||
audioInput: settings.micDeviceId,
|
||||
audioOutput: getAudioOutputDeviceId(),
|
||||
videoInput: settings.cameraDeviceId
|
||||
});
|
||||
break;
|
||||
case 'getAvailableDevices':
|
||||
responseCallback(getState()['features/base/devices']);
|
||||
break;
|
||||
case 'setDevice': {
|
||||
const { device } = request;
|
||||
|
||||
switch (device.kind) {
|
||||
case 'audioinput':
|
||||
dispatch(setAudioInputDevice(device.id));
|
||||
break;
|
||||
case 'audiooutput':
|
||||
setAudioOutputDeviceId(device.id, dispatch);
|
||||
break;
|
||||
case 'videoinput':
|
||||
dispatch(setVideoInputDevice(device.id));
|
||||
break;
|
||||
default:
|
||||
|
||||
}
|
||||
|
||||
responseCallback(true);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets information about device selection popup in the store.
|
||||
*
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
// @flow
|
||||
import { getAudioOutputDeviceId } from '../base/devices';
|
||||
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import {
|
||||
addPendingDeviceRequest,
|
||||
areDeviceLabelsInitialized,
|
||||
getAudioOutputDeviceId,
|
||||
getAvailableDevices,
|
||||
getDeviceIdByLabel,
|
||||
groupDevicesByKind,
|
||||
setAudioInputDevice,
|
||||
setAudioOutputDeviceId,
|
||||
setVideoInputDevice
|
||||
} from '../base/devices';
|
||||
import JitsiMeetJS from '../base/lib-jitsi-meet';
|
||||
import { toState } from '../base/redux';
|
||||
|
||||
@@ -15,7 +28,7 @@ export function getDeviceSelectionDialogProps(stateful: Object | Function) {
|
||||
const settings = state['features/base/settings'];
|
||||
|
||||
return {
|
||||
availableDevices: state['features/base/devices'],
|
||||
availableDevices: state['features/base/devices'].availableDevices,
|
||||
disableAudioInputChange:
|
||||
!JitsiMeetJS.isMultipleAudioInputSupported(),
|
||||
disableDeviceChange:
|
||||
@@ -29,3 +42,142 @@ export function getDeviceSelectionDialogProps(stateful: Object | Function) {
|
||||
selectedVideoInputId: settings.cameraDeviceId
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes device requests from external applications.
|
||||
*
|
||||
* @param {Dispatch} dispatch - The redux {@code dispatch} function.
|
||||
* @param {Function} getState - The redux function that gets/retrieves the redux
|
||||
* state.
|
||||
* @param {Object} request - The request to be processed.
|
||||
* @param {Function} responseCallback - The callback that will send the
|
||||
* response.
|
||||
* @returns {boolean} - True if the request has been processed and false otherwise.
|
||||
*/
|
||||
export function processExternalDeviceRequest( // eslint-disable-line max-params
|
||||
dispatch: Dispatch<any>,
|
||||
getState: Function,
|
||||
request: Object,
|
||||
responseCallback: Function) {
|
||||
if (request.type !== 'devices') {
|
||||
return false;
|
||||
}
|
||||
const state = getState();
|
||||
const settings = state['features/base/settings'];
|
||||
const { conference } = state['features/base/conference'];
|
||||
let result = true;
|
||||
|
||||
switch (request.name) {
|
||||
case 'isDeviceListAvailable':
|
||||
responseCallback(JitsiMeetJS.mediaDevices.isDeviceListAvailable());
|
||||
break;
|
||||
case 'isDeviceChangeAvailable':
|
||||
responseCallback(
|
||||
JitsiMeetJS.mediaDevices.isDeviceChangeAvailable(
|
||||
request.deviceType));
|
||||
break;
|
||||
case 'isMultipleAudioInputSupported':
|
||||
responseCallback(JitsiMeetJS.isMultipleAudioInputSupported());
|
||||
break;
|
||||
case 'getCurrentDevices':
|
||||
dispatch(getAvailableDevices()).then(devices => {
|
||||
if (areDeviceLabelsInitialized(state)) {
|
||||
let audioInput, audioOutput, videoInput;
|
||||
const audioOutputDeviceId = getAudioOutputDeviceId();
|
||||
const { cameraDeviceId, micDeviceId } = settings;
|
||||
|
||||
devices.forEach(device => {
|
||||
const { deviceId } = device;
|
||||
|
||||
switch (deviceId) {
|
||||
case micDeviceId:
|
||||
audioInput = device;
|
||||
break;
|
||||
case audioOutputDeviceId:
|
||||
audioOutput = device;
|
||||
break;
|
||||
case cameraDeviceId:
|
||||
videoInput = device;
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
responseCallback({
|
||||
audioInput,
|
||||
audioOutput,
|
||||
videoInput
|
||||
});
|
||||
} else {
|
||||
// The labels are not available if the A/V permissions are
|
||||
// not yet granted.
|
||||
dispatch(addPendingDeviceRequest({
|
||||
type: 'devices',
|
||||
name: 'getCurrentDevices',
|
||||
responseCallback
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
break;
|
||||
case 'getAvailableDevices':
|
||||
dispatch(getAvailableDevices()).then(devices => {
|
||||
if (areDeviceLabelsInitialized(state)) {
|
||||
responseCallback(groupDevicesByKind(devices));
|
||||
} else {
|
||||
// The labels are not available if the A/V permissions are
|
||||
// not yet granted.
|
||||
dispatch(addPendingDeviceRequest({
|
||||
type: 'devices',
|
||||
name: 'getAvailableDevices',
|
||||
responseCallback
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
break;
|
||||
case 'setDevice': {
|
||||
const { device } = request;
|
||||
|
||||
if (!conference) {
|
||||
dispatch(addPendingDeviceRequest({
|
||||
type: 'devices',
|
||||
name: 'setDevice',
|
||||
device,
|
||||
responseCallback
|
||||
}));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const { label, id } = device;
|
||||
const deviceId = label ? getDeviceIdByLabel(state, device.label) : id;
|
||||
|
||||
if (deviceId) {
|
||||
switch (device.kind) {
|
||||
case 'audioinput': {
|
||||
dispatch(setAudioInputDevice(deviceId));
|
||||
break;
|
||||
}
|
||||
case 'audiooutput':
|
||||
setAudioOutputDeviceId(deviceId, dispatch);
|
||||
break;
|
||||
case 'videoinput':
|
||||
dispatch(setVideoInputDevice(deviceId));
|
||||
break;
|
||||
default:
|
||||
result = false;
|
||||
}
|
||||
} else {
|
||||
result = false;
|
||||
}
|
||||
|
||||
responseCallback(result);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
// @flow
|
||||
|
||||
import { UPDATE_DEVICE_LIST } from '../base/devices';
|
||||
import { MiddlewareRegistry } from '../base/redux';
|
||||
|
||||
declare var APP: Object;
|
||||
|
||||
/**
|
||||
* Implements the middleware of the feature device-selection.
|
||||
*
|
||||
@@ -12,11 +16,19 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
const result = next(action);
|
||||
|
||||
if (action.type === UPDATE_DEVICE_LIST) {
|
||||
const { popupDialogData }
|
||||
= store.getState()['features/device-selection'];
|
||||
const state = store.getState();
|
||||
const { popupDialogData } = state['features/device-selection'];
|
||||
const { availableDevices } = state['features/base/devices'];
|
||||
|
||||
if (popupDialogData) {
|
||||
popupDialogData.transport.sendEvent({ name: 'deviceListChanged' });
|
||||
popupDialogData.transport.sendEvent({
|
||||
name: 'deviceListChanged',
|
||||
devices: availableDevices
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof APP !== 'undefined') {
|
||||
APP.API.notifyDeviceListChanged(availableDevices);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
// @flow
|
||||
|
||||
import { Component } from 'react';
|
||||
|
||||
import { getParticipantById } from '../../base/participants';
|
||||
|
||||
export type Props = {
|
||||
|
||||
/**
|
||||
* The participant id who we want to render the raised hand indicator
|
||||
* for.
|
||||
*/
|
||||
participantId: string,
|
||||
|
||||
/**
|
||||
* True if the hand is raised for this participant.
|
||||
*/
|
||||
_raisedHand?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements an abstract class for the RaisedHandIndicator component.
|
||||
*/
|
||||
export default class AbstractRaisedHandIndicator<P: Props>
|
||||
extends Component<P> {
|
||||
|
||||
/**
|
||||
* Implements {@code Component#render}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
if (!this.props._raisedHand) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this._renderIndicator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the platform specific indicator element.
|
||||
*
|
||||
* @returns {React$Element<*>}
|
||||
*/
|
||||
_renderIndicator: () => React$Element<*>
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps part of the Redux state to the props of this component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @param {Props} ownProps - The own props of the component.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function _mapStateToProps(state: Object, ownProps: Props): Object {
|
||||
const participant = getParticipantById(state, ownProps.participantId);
|
||||
|
||||
return {
|
||||
_raisedHand: participant && participant.raisedHand
|
||||
};
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { Icon } from '../../../base/font-icons';
|
||||
|
||||
import styles from './styles';
|
||||
import BaseIndicator from './BaseIndicator';
|
||||
|
||||
/**
|
||||
* Thumbnail badge for displaying the audio mute status of a participant.
|
||||
*/
|
||||
export default class AudioMutedIndicator extends Component {
|
||||
export default class AudioMutedIndicator extends Component<{}> {
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
@@ -15,9 +15,9 @@ export default class AudioMutedIndicator extends Component {
|
||||
*/
|
||||
render() {
|
||||
return (
|
||||
<Icon
|
||||
name = 'mic-disabled'
|
||||
style = { styles.thumbnailIndicator } />
|
||||
<BaseIndicator
|
||||
highlight = { false }
|
||||
icon = 'mic-disabled' />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
44
react/features/filmstrip/components/native/BaseIndicator.js
Normal file
44
react/features/filmstrip/components/native/BaseIndicator.js
Normal file
@@ -0,0 +1,44 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { View } from 'react-native';
|
||||
|
||||
import { Icon } from '../../../base/font-icons';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* True if a highlighted background has to be applied.
|
||||
*/
|
||||
highlight: boolean,
|
||||
|
||||
/**
|
||||
* The name of the icon to be used as the indicator.
|
||||
*/
|
||||
icon: string
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements a base indicator to be reused across all native indicators on
|
||||
* the filmstrip.
|
||||
*/
|
||||
export default class BaseIndicator extends Component<Props> {
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { highlight, icon } = this.props;
|
||||
|
||||
return (
|
||||
<View style = { highlight ? styles.highlightedIndicator : null }>
|
||||
<Icon
|
||||
name = { icon }
|
||||
style = { styles.indicator } />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,24 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { View } from 'react-native';
|
||||
|
||||
import { Icon } from '../../../base/font-icons';
|
||||
|
||||
import styles from './styles';
|
||||
import BaseIndicator from './BaseIndicator';
|
||||
|
||||
/**
|
||||
* Thumbnail badge showing that the participant is the dominant speaker in
|
||||
* the conference.
|
||||
*/
|
||||
export default class DominantSpeakerIndicator extends Component {
|
||||
export default class DominantSpeakerIndicator extends Component<{}> {
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
* Implements {@code Component#render}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
return (
|
||||
<View style = { styles.dominantSpeakerIndicatorBackground }>
|
||||
<Icon
|
||||
name = 'dominant-speaker'
|
||||
style = { styles.dominantSpeakerIndicator } />
|
||||
</View>
|
||||
<BaseIndicator
|
||||
highlight = { true }
|
||||
icon = 'dominant-speaker' />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { View } from 'react-native';
|
||||
import { getLocalParticipant } from '../../../base/participants';
|
||||
import { connect } from '../../../base/redux';
|
||||
|
||||
import styles from '../styles';
|
||||
import styles from './styles';
|
||||
import Thumbnail from './Thumbnail';
|
||||
|
||||
type Props = {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { Icon } from '../../../base/font-icons';
|
||||
|
||||
import styles from './styles';
|
||||
import BaseIndicator from './BaseIndicator';
|
||||
|
||||
/**
|
||||
* Thumbnail badge showing that the participant is a conference moderator.
|
||||
*/
|
||||
export default class ModeratorIndicator extends Component {
|
||||
export default class ModeratorIndicator extends Component<{}> {
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
@@ -15,9 +15,9 @@ export default class ModeratorIndicator extends Component {
|
||||
*/
|
||||
render() {
|
||||
return (
|
||||
<Icon
|
||||
name = 'star'
|
||||
style = { styles.moderatorIndicator } />
|
||||
<BaseIndicator
|
||||
highlight = { false }
|
||||
icon = 'star' />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { connect } from '../../../base/redux';
|
||||
|
||||
import AbstractRaisedHandIndicator, {
|
||||
type Props,
|
||||
_mapStateToProps
|
||||
} from '../AbstractRaisedHandIndicator';
|
||||
|
||||
import BaseIndicator from './BaseIndicator';
|
||||
|
||||
/**
|
||||
* Thumbnail badge showing that the participant would like to speak.
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
class RaisedHandIndicator extends AbstractRaisedHandIndicator<Props> {
|
||||
/**
|
||||
* Renders the platform specific indicator element.
|
||||
*
|
||||
* @returns {React$Element<*>}
|
||||
*/
|
||||
_renderIndicator() {
|
||||
return (
|
||||
<BaseIndicator
|
||||
highlight = { true }
|
||||
icon = 'raised-hand' />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps)(RaisedHandIndicator);
|
||||
@@ -1,6 +1,7 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { View } from 'react-native';
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { ColorSchemeRegistry } from '../../../base/color-scheme';
|
||||
@@ -22,8 +23,8 @@ import { RemoteVideoMenu } from '../../../remote-video-menu';
|
||||
import AudioMutedIndicator from './AudioMutedIndicator';
|
||||
import DominantSpeakerIndicator from './DominantSpeakerIndicator';
|
||||
import ModeratorIndicator from './ModeratorIndicator';
|
||||
import { AVATAR_SIZE } from '../styles';
|
||||
import styles from './styles';
|
||||
import RaisedHandIndicator from './RaisedHandIndicator';
|
||||
import styles, { AVATAR_SIZE } from './styles';
|
||||
import VideoMutedIndicator from './VideoMutedIndicator';
|
||||
|
||||
/**
|
||||
@@ -163,10 +164,15 @@ class Thumbnail extends Component<Props> {
|
||||
zOrder = { 1 } />
|
||||
|
||||
{ participant.role === PARTICIPANT_ROLE.MODERATOR
|
||||
&& <ModeratorIndicator /> }
|
||||
&& <View style = { styles.moderatorIndicatorContainer }>
|
||||
<ModeratorIndicator />
|
||||
</View> }
|
||||
|
||||
{ participant.dominantSpeaker
|
||||
&& <DominantSpeakerIndicator /> }
|
||||
<View style = { styles.thumbnailTopIndicatorContainer }>
|
||||
<RaisedHandIndicator participantId = { participant.id } />
|
||||
{ participant.dominantSpeaker
|
||||
&& <DominantSpeakerIndicator /> }
|
||||
</View>
|
||||
|
||||
<Container style = { styles.thumbnailIndicatorContainer }>
|
||||
{ audioMuted
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { Icon } from '../../../base/font-icons';
|
||||
|
||||
import styles from './styles';
|
||||
import BaseIndicator from './BaseIndicator';
|
||||
|
||||
/**
|
||||
* Thumbnail badge for displaying the video mute status of a participant.
|
||||
*/
|
||||
export default class VideoMutedIndicator extends Component {
|
||||
export default class VideoMutedIndicator extends Component<{}> {
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
@@ -15,9 +15,9 @@ export default class VideoMutedIndicator extends Component {
|
||||
*/
|
||||
render() {
|
||||
return (
|
||||
<Icon
|
||||
name = 'camera-disabled'
|
||||
style = { styles.thumbnailIndicator } />
|
||||
<BaseIndicator
|
||||
highlight = { false }
|
||||
icon = 'camera-disabled' />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,50 +1,175 @@
|
||||
import { ColorPalette, createStyleSheet } from '../../../base/styles';
|
||||
// @flow
|
||||
|
||||
import { default as platformIndependentStyles } from '../styles';
|
||||
import { ColorSchemeRegistry, schemeColor } from '../../../base/color-scheme';
|
||||
import { ColorPalette } from '../../../base/styles';
|
||||
|
||||
import { FILMSTRIP_SIZE } from '../../constants';
|
||||
|
||||
/**
|
||||
* The base/default style of indicators such as audioMutedIndicator,
|
||||
* moderatorIndicator, and videoMutedIndicator.
|
||||
* Size for the Avatar.
|
||||
*/
|
||||
const indicator = {
|
||||
textShadowColor: ColorPalette.black,
|
||||
textShadowOffset: {
|
||||
height: -1,
|
||||
width: 0
|
||||
}
|
||||
};
|
||||
export const AVATAR_SIZE = 50;
|
||||
|
||||
/**
|
||||
* The styles of the feature filmstrip.
|
||||
*/
|
||||
export default createStyleSheet(platformIndependentStyles, {
|
||||
dominantSpeakerIndicator: {
|
||||
fontSize: 12
|
||||
},
|
||||
|
||||
export default {
|
||||
/**
|
||||
* Dominant speaker indicator background style.
|
||||
* Highlighted indicator additional style.
|
||||
*/
|
||||
dominantSpeakerIndicatorBackground: {
|
||||
highlightedIndicator: {
|
||||
backgroundColor: ColorPalette.blue,
|
||||
borderRadius: 16,
|
||||
padding: 4
|
||||
},
|
||||
|
||||
/**
|
||||
* Moderator indicator style.
|
||||
* Dominant speaker indicator style.
|
||||
*/
|
||||
moderatorIndicator: indicator,
|
||||
indicator: {
|
||||
backgroundColor: ColorPalette.transparent,
|
||||
color: ColorPalette.white,
|
||||
fontSize: 12,
|
||||
textShadowColor: ColorPalette.black,
|
||||
textShadowOffset: {
|
||||
height: -1,
|
||||
width: 0
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Video thumbnail style.
|
||||
* The style of the narrow {@link Filmstrip} version which displays
|
||||
* thumbnails in a row at the bottom of the screen.
|
||||
*/
|
||||
filmstripNarrow: {
|
||||
flexDirection: 'row',
|
||||
flexGrow: 0,
|
||||
justifyContent: 'flex-end',
|
||||
height: FILMSTRIP_SIZE
|
||||
},
|
||||
|
||||
/**
|
||||
* The style of the wide {@link Filmstrip} version which displays thumbnails
|
||||
* in a column on the short size of the screen.
|
||||
*
|
||||
* NOTE: width is calculated based on the children, but it should also align
|
||||
* to {@code FILMSTRIP_SIZE}.
|
||||
*/
|
||||
filmstripWide: {
|
||||
bottom: 0,
|
||||
flexDirection: 'column',
|
||||
flexGrow: 0,
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
top: 0
|
||||
},
|
||||
|
||||
/**
|
||||
* Container of the {@link LocalThumbnail}.
|
||||
*/
|
||||
localThumbnail: {
|
||||
alignContent: 'stretch',
|
||||
alignSelf: 'stretch',
|
||||
aspectRatio: 1,
|
||||
flexShrink: 0,
|
||||
flexDirection: 'row'
|
||||
},
|
||||
|
||||
moderatorIndicatorContainer: {
|
||||
bottom: 4,
|
||||
position: 'absolute',
|
||||
right: 4
|
||||
},
|
||||
|
||||
/**
|
||||
* The style of the scrollview containing the remote thumbnails.
|
||||
*/
|
||||
scrollView: {
|
||||
flexGrow: 0
|
||||
},
|
||||
|
||||
/**
|
||||
* The style of a participant's Thumbnail which renders either the video or
|
||||
* the avatar of the associated participant.
|
||||
*/
|
||||
thumbnail: {
|
||||
alignItems: 'stretch',
|
||||
backgroundColor: ColorPalette.appBackground,
|
||||
borderColor: '#424242',
|
||||
borderRadius: 3,
|
||||
borderStyle: 'solid',
|
||||
borderWidth: 1,
|
||||
flex: 1,
|
||||
height: 80,
|
||||
justifyContent: 'center',
|
||||
margin: 2,
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
width: 80
|
||||
},
|
||||
|
||||
/**
|
||||
* Audio muted indicator style.
|
||||
* The thumbnails indicator container.
|
||||
*/
|
||||
thumbnailIndicator: indicator
|
||||
thumbnailIndicatorContainer: {
|
||||
alignSelf: 'stretch',
|
||||
bottom: 4,
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
left: 4,
|
||||
position: 'absolute'
|
||||
},
|
||||
|
||||
thumbnailTopIndicatorContainer: {
|
||||
left: 0,
|
||||
padding: 4,
|
||||
position: 'absolute',
|
||||
top: 0
|
||||
},
|
||||
|
||||
tileView: {
|
||||
alignSelf: 'center'
|
||||
},
|
||||
|
||||
tileViewRows: {
|
||||
justifyContent: 'center'
|
||||
},
|
||||
|
||||
tileViewRow: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center'
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Color schemed styles for the @{code Thumbnail} component.
|
||||
*/
|
||||
ColorSchemeRegistry.register('Thumbnail', {
|
||||
|
||||
/**
|
||||
* Tinting style of the on-stage participant thumbnail.
|
||||
*/
|
||||
activeThumbnailTint: {
|
||||
backgroundColor: schemeColor('activeParticipantTint')
|
||||
},
|
||||
|
||||
/**
|
||||
* Coloring if the thumbnail background.
|
||||
*/
|
||||
participantViewStyle: {
|
||||
backgroundColor: schemeColor('background')
|
||||
},
|
||||
|
||||
/**
|
||||
* Pinned video thumbnail style.
|
||||
*/
|
||||
thumbnailPinned: {
|
||||
borderColor: schemeColor('activeParticipantHighlight'),
|
||||
shadowColor: schemeColor('activeParticipantHighlight'),
|
||||
shadowOffset: {
|
||||
height: 5,
|
||||
width: 5
|
||||
},
|
||||
shadowRadius: 5
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,185 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import { ColorSchemeRegistry, schemeColor } from '../../base/color-scheme';
|
||||
import { ColorPalette } from '../../base/styles';
|
||||
import { FILMSTRIP_SIZE } from '../constants';
|
||||
|
||||
/**
|
||||
* Size for the Avatar.
|
||||
*/
|
||||
export const AVATAR_SIZE = 50;
|
||||
|
||||
/**
|
||||
* The base style of {@link Filmstrip} shared between narrow and wide versions.
|
||||
*/
|
||||
const filmstrip = {
|
||||
flexGrow: 0
|
||||
};
|
||||
|
||||
/**
|
||||
* The styles of the feature filmstrip common to both Web and native.
|
||||
*/
|
||||
export default {
|
||||
/**
|
||||
* Dominant speaker indicator style.
|
||||
*/
|
||||
dominantSpeakerIndicator: {
|
||||
color: ColorPalette.white,
|
||||
fontSize: 15
|
||||
},
|
||||
|
||||
/**
|
||||
* Dominant speaker indicator background style.
|
||||
*/
|
||||
dominantSpeakerIndicatorBackground: {
|
||||
backgroundColor: ColorPalette.blue,
|
||||
borderRadius: 15,
|
||||
left: 4,
|
||||
padding: 5,
|
||||
position: 'absolute',
|
||||
top: 4
|
||||
},
|
||||
|
||||
/**
|
||||
* The style of the narrow {@link Filmstrip} version which displays
|
||||
* thumbnails in a row at the bottom of the screen.
|
||||
*/
|
||||
filmstripNarrow: {
|
||||
...filmstrip,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-end',
|
||||
height: FILMSTRIP_SIZE
|
||||
},
|
||||
|
||||
/**
|
||||
* The style of the wide {@link Filmstrip} version which displays thumbnails
|
||||
* in a column on the short size of the screen.
|
||||
*
|
||||
* NOTE: width is calculated based on the children, but it should also align
|
||||
* to {@code FILMSTRIP_SIZE}.
|
||||
*/
|
||||
filmstripWide: {
|
||||
...filmstrip,
|
||||
bottom: 0,
|
||||
flexDirection: 'column',
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
top: 0
|
||||
},
|
||||
|
||||
/**
|
||||
* Container of the {@link LocalThumbnail}.
|
||||
*/
|
||||
localThumbnail: {
|
||||
alignContent: 'stretch',
|
||||
alignSelf: 'stretch',
|
||||
aspectRatio: 1,
|
||||
flexShrink: 0,
|
||||
flexDirection: 'row'
|
||||
},
|
||||
|
||||
/**
|
||||
* Moderator indicator style.
|
||||
*/
|
||||
moderatorIndicator: {
|
||||
backgroundColor: 'transparent',
|
||||
bottom: 4,
|
||||
color: ColorPalette.white,
|
||||
position: 'absolute',
|
||||
right: 4
|
||||
},
|
||||
|
||||
/**
|
||||
* The style of the scrollview containing the remote thumbnails.
|
||||
*/
|
||||
scrollView: {
|
||||
flexGrow: 0
|
||||
},
|
||||
|
||||
/**
|
||||
* The style of a participant's Thumbnail which renders either the video or
|
||||
* the avatar of the associated participant.
|
||||
*/
|
||||
thumbnail: {
|
||||
alignItems: 'stretch',
|
||||
backgroundColor: ColorPalette.appBackground,
|
||||
borderColor: '#424242',
|
||||
borderRadius: 3,
|
||||
borderStyle: 'solid',
|
||||
borderWidth: 1,
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
margin: 2,
|
||||
overflow: 'hidden',
|
||||
position: 'relative'
|
||||
},
|
||||
|
||||
/**
|
||||
* The thumbnail audio and video muted indicator style.
|
||||
*/
|
||||
thumbnailIndicator: {
|
||||
backgroundColor: 'transparent',
|
||||
color: ColorPalette.white,
|
||||
paddingLeft: 1,
|
||||
paddingRight: 1,
|
||||
position: 'relative'
|
||||
},
|
||||
|
||||
/**
|
||||
* The thumbnails indicator container.
|
||||
*/
|
||||
thumbnailIndicatorContainer: {
|
||||
alignSelf: 'stretch',
|
||||
bottom: 4,
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
left: 4,
|
||||
position: 'absolute'
|
||||
},
|
||||
|
||||
tileView: {
|
||||
alignSelf: 'center'
|
||||
},
|
||||
|
||||
tileViewRows: {
|
||||
justifyContent: 'center'
|
||||
},
|
||||
|
||||
tileViewRow: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center'
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Color schemed styles for the @{code Thumbnail} component.
|
||||
*/
|
||||
ColorSchemeRegistry.register('Thumbnail', {
|
||||
|
||||
/**
|
||||
* Tinting style of the on-stage participant thumbnail.
|
||||
*/
|
||||
activeThumbnailTint: {
|
||||
backgroundColor: schemeColor('activeParticipantTint')
|
||||
},
|
||||
|
||||
/**
|
||||
* Coloring if the thumbnail background.
|
||||
*/
|
||||
participantViewStyle: {
|
||||
backgroundColor: schemeColor('background')
|
||||
},
|
||||
|
||||
/**
|
||||
* Pinned video thumbnail style.
|
||||
*/
|
||||
thumbnailPinned: {
|
||||
borderColor: schemeColor('activeParticipantHighlight'),
|
||||
shadowColor: schemeColor('activeParticipantHighlight'),
|
||||
shadowOffset: {
|
||||
height: 5,
|
||||
width: 5
|
||||
},
|
||||
shadowRadius: 5
|
||||
}
|
||||
});
|
||||
@@ -1,13 +1,20 @@
|
||||
/* @flow */
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import { connect } from '../../../base/redux';
|
||||
|
||||
import AbstractRaisedHandIndicator, {
|
||||
type Props as AbstractProps,
|
||||
_mapStateToProps
|
||||
} from '../AbstractRaisedHandIndicator';
|
||||
|
||||
import BaseIndicator from './BaseIndicator';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link RaisedHandIndicator}.
|
||||
*/
|
||||
type Props = {
|
||||
type Props = AbstractProps & {
|
||||
|
||||
/**
|
||||
* The font-size for the icon.
|
||||
@@ -25,13 +32,13 @@ type Props = {
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
class RaisedHandIndicator extends Component<Props> {
|
||||
class RaisedHandIndicator extends AbstractRaisedHandIndicator<Props> {
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
* Renders the platform specific indicator element.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {React$Element<*>}
|
||||
*/
|
||||
render() {
|
||||
_renderIndicator() {
|
||||
return (
|
||||
<BaseIndicator
|
||||
className = 'raisehandindicator indicator show-inline'
|
||||
@@ -43,4 +50,4 @@ class RaisedHandIndicator extends Component<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
export default RaisedHandIndicator;
|
||||
export default connect(_mapStateToProps)(RaisedHandIndicator);
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { translate } from '../../base/i18n';
|
||||
import { connect } from '../../base/redux';
|
||||
import { AbstractButton } from '../../base/toolbox';
|
||||
import type { AbstractButtonProps } from '../../base/toolbox';
|
||||
import { beginShareRoom } from '../../share-room';
|
||||
|
||||
import { setAddPeopleDialogVisible } from '../actions';
|
||||
import { isAddPeopleEnabled, isDialOutEnabled } from '../functions';
|
||||
|
||||
type Props = AbstractButtonProps & {
|
||||
|
||||
/**
|
||||
* Whether or not the feature to invite people to join the
|
||||
* conference is available.
|
||||
*/
|
||||
_addPeopleEnabled: boolean,
|
||||
|
||||
/**
|
||||
* Opens the add people dialog.
|
||||
*/
|
||||
_onOpenAddPeopleDialog: Function,
|
||||
|
||||
/**
|
||||
* Begins the UI procedure to share the conference/room URL.
|
||||
*/
|
||||
_onShareRoom: Function
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements an {@link AbstractButton} to enter add/invite people to the
|
||||
* current call/conference/meeting.
|
||||
*/
|
||||
class InviteButton extends AbstractButton<Props, *> {
|
||||
accessibilityLabel = 'toolbar.accessibilityLabel.shareRoom';
|
||||
iconName = 'icon-link';
|
||||
label = 'toolbar.shareRoom';
|
||||
|
||||
/**
|
||||
* Handles clicking / pressing the button, and opens the appropriate dialog.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
const {
|
||||
_addPeopleEnabled,
|
||||
_onOpenAddPeopleDialog,
|
||||
_onShareRoom
|
||||
} = this.props;
|
||||
|
||||
if (_addPeopleEnabled) {
|
||||
_onOpenAddPeopleDialog();
|
||||
} else {
|
||||
_onShareRoom();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps redux actions to {@link InviteButton}'s React
|
||||
* {@code Component} props.
|
||||
*
|
||||
* @param {Function} dispatch - The redux action {@code dispatch} function.
|
||||
* @returns {{
|
||||
* _onOpenAddPeopleDialog,
|
||||
* _onShareRoom
|
||||
* }}
|
||||
* @private
|
||||
*/
|
||||
function _mapDispatchToProps(dispatch: Dispatch<any>) {
|
||||
return {
|
||||
|
||||
/**
|
||||
* Opens the add people dialog.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
* @type {Function}
|
||||
*/
|
||||
_onOpenAddPeopleDialog() {
|
||||
dispatch(setAddPeopleDialogVisible(true));
|
||||
},
|
||||
|
||||
/**
|
||||
* Begins the UI procedure to share the conference/room URL.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
* @type {Function}
|
||||
*/
|
||||
_onShareRoom() {
|
||||
dispatch(beginShareRoom());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the redux state to {@link Toolbox}'s React {@code Component}
|
||||
* props.
|
||||
*
|
||||
* @param {Object} state - The redux store/state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _addPeopleEnabled: boolean
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
return {
|
||||
_addPeopleEnabled: isAddPeopleEnabled(state) || isDialOutEnabled(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(
|
||||
connect(_mapStateToProps, _mapDispatchToProps)(InviteButton));
|
||||
@@ -0,0 +1,73 @@
|
||||
// @flow
|
||||
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { translate } from '../../../../base/i18n';
|
||||
import { connect } from '../../../../base/redux';
|
||||
import { AbstractButton } from '../../../../base/toolbox';
|
||||
import type { AbstractButtonProps } from '../../../../base/toolbox';
|
||||
|
||||
import { setAddPeopleDialogVisible } from '../../../actions';
|
||||
import { isAddPeopleEnabled, isDialOutEnabled } from '../../../functions';
|
||||
|
||||
type Props = AbstractButtonProps & {
|
||||
|
||||
/**
|
||||
* Whether or not the feature to invite people to join the
|
||||
* conference is available.
|
||||
*/
|
||||
_addPeopleEnabled: boolean,
|
||||
|
||||
/**
|
||||
* The Redux dispatch function.
|
||||
*/
|
||||
dispatch: Dispatch<any>
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements an {@link AbstractButton} to enter add/invite people to the
|
||||
* current call/conference/meeting.
|
||||
*/
|
||||
class InviteButton extends AbstractButton<Props, *> {
|
||||
accessibilityLabel = 'toolbar.accessibilityLabel.shareRoom';
|
||||
iconName = 'icon-link';
|
||||
label = 'toolbar.shareRoom';
|
||||
|
||||
/**
|
||||
* Handles clicking / pressing the button, and opens the appropriate dialog.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
this.props.dispatch(setAddPeopleDialogVisible(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if none of the invite methods are available.
|
||||
*
|
||||
* @protected
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_isDisabled() {
|
||||
return !this.props._addPeopleEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the redux state to {@link InviteButton}'s React {@code Component}
|
||||
* props.
|
||||
*
|
||||
* @param {Object} state - The redux store/state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _addPeopleEnabled: boolean
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
return {
|
||||
_addPeopleEnabled: isAddPeopleEnabled(state) || isDialOutEnabled(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(InviteButton));
|
||||
@@ -1,3 +1,4 @@
|
||||
// @flow
|
||||
|
||||
export { default as AddPeopleDialog } from './AddPeopleDialog';
|
||||
export { default as InviteButton } from './InviteButton';
|
||||
|
||||
@@ -2,6 +2,5 @@
|
||||
|
||||
export * from './add-people-dialog';
|
||||
export { DialInSummary } from './dial-in-summary';
|
||||
export { default as InfoDialogButton } from './InfoDialogButton';
|
||||
export { default as InviteButton } from './InviteButton';
|
||||
export * from './info-dialog';
|
||||
export * from './callee-info';
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export { default as InfoDialog } from './InfoDialog';
|
||||
@@ -0,0 +1,3 @@
|
||||
// @flow
|
||||
|
||||
export * from './native';
|
||||
@@ -0,0 +1,3 @@
|
||||
// @flow
|
||||
|
||||
export * from './web';
|
||||
@@ -0,0 +1,38 @@
|
||||
// @flow
|
||||
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { translate } from '../../../../base/i18n';
|
||||
import { connect } from '../../../../base/redux';
|
||||
import { AbstractButton } from '../../../../base/toolbox';
|
||||
import type { AbstractButtonProps } from '../../../../base/toolbox';
|
||||
import { beginShareRoom } from '../../../../share-room';
|
||||
|
||||
type Props = AbstractButtonProps & {
|
||||
|
||||
/**
|
||||
* The Redux dispatch function.
|
||||
*/
|
||||
dispatch: Dispatch<any>
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements an {@link AbstractButton} to open the info dialog of the meeting.
|
||||
*/
|
||||
class InfoDialogButton extends AbstractButton<Props, *> {
|
||||
accessibilityLabel = 'info.accessibilityLabel';
|
||||
iconName = 'icon-info';
|
||||
label = 'info.label';
|
||||
|
||||
/**
|
||||
* Handles clicking / pressing the button, and opens the appropriate dialog.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
this.props.dispatch(beginShareRoom());
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(connect()(InfoDialogButton));
|
||||
@@ -0,0 +1,3 @@
|
||||
// @flow
|
||||
|
||||
export { default as InfoDialogButton } from './InfoDialogButton';
|
||||
@@ -1,8 +1,8 @@
|
||||
/* @flow */
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { translate } from '../../../../base/i18n';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link DialInNumber}.
|
||||
@@ -1,16 +1,16 @@
|
||||
/* @flow */
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { setPassword } from '../../../base/conference';
|
||||
import { getInviteURL } from '../../../base/connection';
|
||||
import { Dialog } from '../../../base/dialog';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { isLocalParticipantModerator } from '../../../base/participants';
|
||||
import { setPassword } from '../../../../base/conference';
|
||||
import { getInviteURL } from '../../../../base/connection';
|
||||
import { Dialog } from '../../../../base/dialog';
|
||||
import { translate } from '../../../../base/i18n';
|
||||
import { connect } from '../../../../base/redux';
|
||||
import { isLocalParticipantModerator } from '../../../../base/participants';
|
||||
|
||||
import { _getDefaultPhoneNumber, getDialInfoPageURL } from '../../functions';
|
||||
import { _getDefaultPhoneNumber, getDialInfoPageURL } from '../../../functions';
|
||||
import DialInNumber from './DialInNumber';
|
||||
import PasswordForm from './PasswordForm';
|
||||
|
||||
@@ -4,18 +4,18 @@ import InlineDialog from '@atlaskit/inline-dialog';
|
||||
import React, { Component } from 'react';
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { createToolbarEvent, sendAnalytics } from '../../analytics';
|
||||
import { openDialog } from '../../base/dialog';
|
||||
import { translate } from '../../base/i18n';
|
||||
import { JitsiRecordingConstants } from '../../base/lib-jitsi-meet';
|
||||
import { getParticipantCount } from '../../base/participants';
|
||||
import { OverflowMenuItem } from '../../base/toolbox';
|
||||
import { connect } from '../../base/redux';
|
||||
import { getActiveSession } from '../../recording';
|
||||
import { ToolbarButton } from '../../toolbox';
|
||||
import { updateDialInNumbers } from '../actions';
|
||||
import { createToolbarEvent, sendAnalytics } from '../../../../analytics';
|
||||
import { openDialog } from '../../../../base/dialog';
|
||||
import { translate } from '../../../../base/i18n';
|
||||
import { JitsiRecordingConstants } from '../../../../base/lib-jitsi-meet';
|
||||
import { getParticipantCount } from '../../../../base/participants';
|
||||
import { OverflowMenuItem } from '../../../../base/toolbox';
|
||||
import { connect } from '../../../../base/redux';
|
||||
import { getActiveSession } from '../../../../recording';
|
||||
import { ToolbarButton } from '../../../../toolbox';
|
||||
import { updateDialInNumbers } from '../../../actions';
|
||||
|
||||
import { InfoDialog } from './info-dialog';
|
||||
import InfoDialog from './InfoDialog';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link InfoDialogButton}.
|
||||
@@ -1,9 +1,9 @@
|
||||
/* @flow */
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { LOCKED_LOCALLY } from '../../../room-lock';
|
||||
import { translate } from '../../../../base/i18n';
|
||||
import { LOCKED_LOCALLY } from '../../../../room-lock';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link PasswordForm}.
|
||||
@@ -0,0 +1,4 @@
|
||||
// @flow
|
||||
|
||||
export { default as InfoDialog } from './InfoDialog';
|
||||
export { default as InfoDialogButton } from './InfoDialogButton';
|
||||
@@ -1,3 +1,5 @@
|
||||
// @flow
|
||||
|
||||
/**
|
||||
* The type of (redux) action which signals that all the stored notifications
|
||||
* need to be cleared.
|
||||
|
||||
3
react/features/notifications/components/index.native.js
Normal file
3
react/features/notifications/components/index.native.js
Normal file
@@ -0,0 +1,3 @@
|
||||
// @flow
|
||||
|
||||
export * from './native';
|
||||
3
react/features/notifications/components/index.web.js
Normal file
3
react/features/notifications/components/index.web.js
Normal file
@@ -0,0 +1,3 @@
|
||||
// @flow
|
||||
|
||||
export * from './web';
|
||||
@@ -3,12 +3,13 @@
|
||||
import React from 'react';
|
||||
import { Text, TouchableOpacity, View } from 'react-native';
|
||||
|
||||
import { Icon } from '../../base/font-icons';
|
||||
import { translate } from '../../base/i18n';
|
||||
import { Icon } from '../../../base/font-icons';
|
||||
import { translate } from '../../../base/i18n';
|
||||
|
||||
import AbstractNotification, {
|
||||
type Props
|
||||
} from './AbstractNotification';
|
||||
} from '../AbstractNotification';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
/**
|
||||
@@ -3,12 +3,13 @@
|
||||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
|
||||
import { connect } from '../../base/redux';
|
||||
import { connect } from '../../../base/redux';
|
||||
|
||||
import AbstractNotificationsContainer, {
|
||||
_abstractMapStateToProps,
|
||||
type Props as AbstractProps
|
||||
} from './AbstractNotificationsContainer';
|
||||
} from '../AbstractNotificationsContainer';
|
||||
|
||||
import Notification from './Notification';
|
||||
import styles from './styles';
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
// @flow
|
||||
|
||||
export { default as Notification } from './Notification';
|
||||
export { default as NotificationsContainer } from './NotificationsContainer';
|
||||
@@ -1,11 +1,11 @@
|
||||
// @flow
|
||||
|
||||
import { BoxModel, createStyleSheet, ColorPalette } from '../../base/styles';
|
||||
import { BoxModel, ColorPalette } from '../../../base/styles';
|
||||
|
||||
/**
|
||||
* The styles of the React {@code Components} of the feature notifications.
|
||||
*/
|
||||
export default createStyleSheet({
|
||||
export default {
|
||||
|
||||
/**
|
||||
* The content (left) column of the notification.
|
||||
@@ -58,4 +58,4 @@ export default createStyleSheet({
|
||||
notificationContent: {
|
||||
flexDirection: 'column'
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -7,13 +7,13 @@ import WarningIcon from '@atlaskit/icon/glyph/warning';
|
||||
import { colors } from '@atlaskit/theme';
|
||||
import React from 'react';
|
||||
|
||||
import { translate } from '../../base/i18n';
|
||||
import { translate } from '../../../base/i18n';
|
||||
|
||||
import { NOTIFICATION_TYPE } from '../constants';
|
||||
import { NOTIFICATION_TYPE } from '../../constants';
|
||||
|
||||
import AbstractNotification, {
|
||||
type Props
|
||||
} from './AbstractNotification';
|
||||
} from '../AbstractNotification';
|
||||
|
||||
declare var interfaceConfig: Object;
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
import { FlagGroup } from '@atlaskit/flag';
|
||||
import React from 'react';
|
||||
|
||||
import { connect } from '../../base/redux';
|
||||
import { connect } from '../../../base/redux';
|
||||
|
||||
import AbstractNotificationsContainer, {
|
||||
_abstractMapStateToProps as _mapStateToProps,
|
||||
type Props
|
||||
} from './AbstractNotificationsContainer';
|
||||
} from '../AbstractNotificationsContainer';
|
||||
|
||||
import Notification from './Notification';
|
||||
|
||||
/**
|
||||
4
react/features/notifications/components/web/index.js
Normal file
4
react/features/notifications/components/web/index.js
Normal file
@@ -0,0 +1,4 @@
|
||||
// @flow
|
||||
|
||||
export { default as Notification } from './Notification';
|
||||
export { default as NotificationsContainer } from './NotificationsContainer';
|
||||
@@ -1,3 +1,10 @@
|
||||
// @flow
|
||||
|
||||
/**
|
||||
* The standard time when auto-disappearing notifications should disappear.
|
||||
*/
|
||||
export const NOTIFICATION_TIMEOUT = 2500;
|
||||
|
||||
/**
|
||||
* The set of possible notification types.
|
||||
*
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// @flow
|
||||
|
||||
import { toState } from '../base/redux';
|
||||
|
||||
/**
|
||||
@@ -7,7 +9,7 @@ import { toState } from '../base/redux';
|
||||
* @param {Object|Function} stateful - The redux store state.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function areThereNotifications(stateful) {
|
||||
export function areThereNotifications(stateful: Object | Function) {
|
||||
const state = toState(stateful);
|
||||
const { enabled, notifications } = state['features/notifications'];
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
// @flow
|
||||
|
||||
export * from './actions';
|
||||
export * from './actionTypes';
|
||||
export * from './components';
|
||||
export * from './constants';
|
||||
export * from './functions';
|
||||
|
||||
import './middleware';
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// @flow
|
||||
|
||||
import { ReducerRegistry } from '../base/redux';
|
||||
|
||||
import {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import JitsiMeetJS, { JitsiRecordingConstants } from '../base/lib-jitsi-meet';
|
||||
import {
|
||||
NOTIFICATION_TIMEOUT,
|
||||
hideNotification,
|
||||
showErrorNotification,
|
||||
showNotification
|
||||
@@ -125,7 +126,7 @@ export function showStoppedRecordingNotification(streamType: string) {
|
||||
titleKey: 'dialog.recording'
|
||||
};
|
||||
|
||||
return showNotification(dialogProps, 2500);
|
||||
return showNotification(dialogProps, NOTIFICATION_TIMEOUT);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
Alert,
|
||||
SafeAreaView,
|
||||
ScrollView,
|
||||
Switch,
|
||||
TextInput,
|
||||
View
|
||||
} from 'react-native';
|
||||
import { Alert, NativeModules, SafeAreaView, ScrollView, Switch, Text, TextInput, View } from 'react-native';
|
||||
|
||||
import { ColorSchemeRegistry } from '../../../base/color-scheme';
|
||||
import { translate } from '../../../base/i18n';
|
||||
@@ -27,6 +20,11 @@ import { normalizeUserInputURL } from '../../functions';
|
||||
import styles from './styles';
|
||||
import { HeaderLabel } from '../../../base/react/components/native';
|
||||
|
||||
/**
|
||||
* Application information module.
|
||||
*/
|
||||
const { AppInfo } = NativeModules;
|
||||
|
||||
type Props = AbstractProps & {
|
||||
|
||||
/**
|
||||
@@ -193,6 +191,15 @@ class SettingsView extends AbstractSettingsView<Props> {
|
||||
onValueChange = { this._onStartVideoMutedChange }
|
||||
value = { _settings.startWithVideoMuted } />
|
||||
</FormRow>
|
||||
<FormSectionHeader
|
||||
label = 'settingsView.buildInfoSection' />
|
||||
<FormRow
|
||||
fieldSeparator = { true }
|
||||
label = 'settingsView.version'>
|
||||
<Text>
|
||||
{ `${AppInfo.version} build ${AppInfo.buildNumber}` }
|
||||
</Text>
|
||||
</FormRow>
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/* global JitsiMeetJS */
|
||||
|
||||
import { AtlasKitThemeProvider } from '@atlaskit/theme';
|
||||
import Logger from 'jitsi-meet-logger';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { I18nextProvider } from 'react-i18next';
|
||||
@@ -10,13 +9,21 @@ import {
|
||||
PostMessageTransportBackend,
|
||||
Transport
|
||||
} from '../../../../../modules/transport';
|
||||
import {
|
||||
getAvailableDevices,
|
||||
getCurrentDevices,
|
||||
isDeviceChangeAvailable,
|
||||
isDeviceListAvailable,
|
||||
isMultipleAudioInputSupported,
|
||||
setAudioInputDevice,
|
||||
setAudioOutputDevice,
|
||||
setVideoInputDevice
|
||||
} from '../../../../../modules/API/external/functions';
|
||||
|
||||
import { parseURLParams } from '../../../base/config';
|
||||
import { DialogWithTabs } from '../../../base/dialog';
|
||||
import { DeviceSelection } from '../../../device-selection';
|
||||
|
||||
const logger = Logger.getLogger(__filename);
|
||||
|
||||
/**
|
||||
* Implements a class that renders the React components for the device selection
|
||||
* popup page and handles the communication between the components and Jitsi
|
||||
@@ -102,14 +109,7 @@ export default class DeviceSelectionPopup {
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_getAvailableDevices() {
|
||||
return this._transport.sendRequest({
|
||||
type: 'devices',
|
||||
name: 'getAvailableDevices'
|
||||
}).catch(e => {
|
||||
logger.error(e);
|
||||
|
||||
return {};
|
||||
});
|
||||
return getAvailableDevices(this._transport);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -118,13 +118,18 @@ export default class DeviceSelectionPopup {
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_getCurrentDevices() {
|
||||
return this._transport.sendRequest({
|
||||
type: 'devices',
|
||||
name: 'getCurrentDevices'
|
||||
}).catch(e => {
|
||||
logger.error(e);
|
||||
return getCurrentDevices(this._transport).then(currentDevices => {
|
||||
const {
|
||||
audioInput = {},
|
||||
audioOutput = {},
|
||||
videoInput = {}
|
||||
} = currentDevices;
|
||||
|
||||
return {};
|
||||
return {
|
||||
audioInput: audioInput.deviceId,
|
||||
audioOutput: audioOutput.deviceId,
|
||||
videoInput: videoInput.deviceId
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -170,15 +175,7 @@ export default class DeviceSelectionPopup {
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_isDeviceChangeAvailable(deviceType) {
|
||||
return this._transport.sendRequest({
|
||||
deviceType,
|
||||
type: 'devices',
|
||||
name: 'isDeviceChangeAvailable'
|
||||
}).catch(e => {
|
||||
logger.error(e);
|
||||
|
||||
return false;
|
||||
});
|
||||
return isDeviceChangeAvailable(this._transport, deviceType).catch(() => false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -188,31 +185,17 @@ export default class DeviceSelectionPopup {
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_isDeviceListAvailable() {
|
||||
return this._transport.sendRequest({
|
||||
type: 'devices',
|
||||
name: 'isDeviceListAvailable'
|
||||
}).catch(e => {
|
||||
logger.error(e);
|
||||
|
||||
return false;
|
||||
});
|
||||
return isDeviceListAvailable(this._transport).catch(() => false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Promise that resolves with true if the device list is available
|
||||
* Returns Promise that resolves with true if multiple audio input is supported
|
||||
* and with false if not.
|
||||
*
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_isMultipleAudioInputSupported() {
|
||||
return this._transport.sendRequest({
|
||||
type: 'devices',
|
||||
name: 'isMultipleAudioInputSupported'
|
||||
}).catch(e => {
|
||||
logger.error(e);
|
||||
|
||||
return false;
|
||||
});
|
||||
return isMultipleAudioInputSupported(this._transport).catch(() => false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -281,10 +264,7 @@ export default class DeviceSelectionPopup {
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_setAudioInputDevice(id) {
|
||||
return this._setDevice({
|
||||
id,
|
||||
kind: 'audioinput'
|
||||
});
|
||||
return setAudioInputDevice(this._transport, undefined, id);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -294,24 +274,7 @@ export default class DeviceSelectionPopup {
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_setAudioOutputDevice(id) {
|
||||
return this._setDevice({
|
||||
id,
|
||||
kind: 'audiooutput'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the currently used device to the one that is passed.
|
||||
*
|
||||
* @param {Object} device - The new device to be used.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_setDevice(device) {
|
||||
return this._transport.sendRequest({
|
||||
type: 'devices',
|
||||
name: 'setDevice',
|
||||
device
|
||||
});
|
||||
return setAudioOutputDevice(this._transport, undefined, id);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -321,10 +284,7 @@ export default class DeviceSelectionPopup {
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_setVideoInputDevice(id) {
|
||||
return this._setDevice({
|
||||
id,
|
||||
kind: 'videoinput'
|
||||
});
|
||||
return setVideoInputDevice(this._transport, undefined, id);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
} from '../../../base/dialog';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { StyleType } from '../../../base/styles';
|
||||
import { InviteButton } from '../../../invite';
|
||||
import { InfoDialogButton, InviteButton } from '../../../invite';
|
||||
import { AudioRouteButton } from '../../../mobile/audio-mode';
|
||||
import { LiveStreamButton, RecordButton } from '../../../recording';
|
||||
import { RoomLockButton } from '../../../room-lock';
|
||||
@@ -18,6 +18,7 @@ import { ClosedCaptionButton } from '../../../subtitles';
|
||||
import { TileViewButton } from '../../../video-layout';
|
||||
|
||||
import AudioOnlyButton from './AudioOnlyButton';
|
||||
import RaiseHandButton from './RaiseHandButton';
|
||||
import ToggleCameraButton from './ToggleCameraButton';
|
||||
|
||||
declare var __DEV__;
|
||||
@@ -95,6 +96,8 @@ class OverflowMenu extends Component<Props> {
|
||||
<LiveStreamButton { ...buttonProps } />
|
||||
<TileViewButton { ...buttonProps } />
|
||||
<InviteButton { ...buttonProps } />
|
||||
<InfoDialogButton { ...buttonProps } />
|
||||
<RaiseHandButton { ...buttonProps } />
|
||||
</BottomSheet>
|
||||
);
|
||||
}
|
||||
|
||||
113
react/features/toolbox/components/native/RaiseHandButton.js
Normal file
113
react/features/toolbox/components/native/RaiseHandButton.js
Normal file
@@ -0,0 +1,113 @@
|
||||
// @flow
|
||||
|
||||
import { type Dispatch } from 'redux';
|
||||
|
||||
import {
|
||||
createToolbarEvent,
|
||||
sendAnalytics
|
||||
} from '../../../analytics';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import {
|
||||
getLocalParticipant,
|
||||
participantUpdated
|
||||
} from '../../../base/participants';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { AbstractButton } from '../../../base/toolbox';
|
||||
import type { AbstractButtonProps } from '../../../base/toolbox';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link RaiseHandButton}.
|
||||
*/
|
||||
type Props = AbstractButtonProps & {
|
||||
|
||||
/**
|
||||
* The local participant.
|
||||
*/
|
||||
_localParticipant: Object,
|
||||
|
||||
/**
|
||||
* Whether the participant raused their hand or not.
|
||||
*/
|
||||
_raisedHand: boolean,
|
||||
|
||||
/**
|
||||
* The redux {@code dispatch} function.
|
||||
*/
|
||||
dispatch: Dispatch<any>
|
||||
};
|
||||
|
||||
/**
|
||||
* An implementation of a button to raise or lower hand.
|
||||
*/
|
||||
class RaiseHandButton extends AbstractButton<Props, *> {
|
||||
accessibilityLabel = 'toolbar.accessibilityLabel.raiseHand';
|
||||
iconName = 'raised-hand';
|
||||
label = 'toolbar.raiseYourHand';
|
||||
toggledIconName = 'raised-hand';
|
||||
toggledLabel = 'toolbar.lowerYourHand';
|
||||
|
||||
/**
|
||||
* Handles clicking / pressing the button.
|
||||
*
|
||||
* @override
|
||||
* @protected
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
this._toggleRaisedHand();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this button is in toggled state or not.
|
||||
*
|
||||
* @override
|
||||
* @protected
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_isToggled() {
|
||||
return this.props._raisedHand;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the rased hand status of the local participant.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_toggleRaisedHand() {
|
||||
const enable = !this.props._raisedHand;
|
||||
|
||||
sendAnalytics(createToolbarEvent('raise.hand', { enable }));
|
||||
|
||||
this.props.dispatch(participantUpdated({
|
||||
// XXX Only the local participant is allowed to update without
|
||||
// stating the JitsiConference instance (i.e. participant property
|
||||
// `conference` for a remote participant) because the local
|
||||
// participant is uniquely identified by the very fact that there is
|
||||
// only one local participant.
|
||||
|
||||
id: this.props._localParticipant.id,
|
||||
local: true,
|
||||
raisedHand: enable
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps part of the Redux state to the props of this component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _raisedHand: boolean
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state): Object {
|
||||
const _localParticipant = getLocalParticipant(state);
|
||||
|
||||
return {
|
||||
_localParticipant,
|
||||
_raisedHand: _localParticipant.raisedHand
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(RaiseHandButton));
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
STOP_TRANSCRIBING
|
||||
} from './actionTypes';
|
||||
import {
|
||||
NOTIFICATION_TIMEOUT,
|
||||
hideNotification,
|
||||
showErrorNotification,
|
||||
showNotification
|
||||
@@ -161,7 +162,7 @@ export function hidePendingTranscribingNotification() {
|
||||
|
||||
/**
|
||||
* Signals that the stopped transcribing notification should be shown on the
|
||||
* screen for a 2500 ms.
|
||||
* screen.
|
||||
*
|
||||
* @returns {showNotification}
|
||||
*/
|
||||
@@ -169,7 +170,7 @@ export function showStoppedTranscribingNotification() {
|
||||
return showNotification({
|
||||
descriptionKey: 'transcribing.off',
|
||||
titleKey: 'dialog.transcribing'
|
||||
}, 2500);
|
||||
}, NOTIFICATION_TIMEOUT);
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user