Compare commits

...

12 Commits

7 changed files with 10759 additions and 10570 deletions

View File

@ -1,3 +0,0 @@
<svg height="1024" width="1024" xmlns="http://www.w3.org/2000/svg">
<path d="M512 0C229.25 0 0 229.25 0 512c0 226.25 146.688 418.125 350.156 485.812 25.594 4.688 34.938-11.125 34.938-24.625 0-12.188-0.469-52.562-0.719-95.312C242 908.812 211.906 817.5 211.906 817.5c-23.312-59.125-56.844-74.875-56.844-74.875-46.531-31.75 3.53-31.125 3.53-31.125 51.406 3.562 78.47 52.75 78.47 52.75 45.688 78.25 119.875 55.625 149 42.5 4.654-33 17.904-55.625 32.5-68.375C304.906 725.438 185.344 681.5 185.344 485.312c0-55.938 19.969-101.562 52.656-137.406-5.219-13-22.844-65.094 5.062-135.562 0 0 42.938-13.75 140.812 52.5 40.812-11.406 84.594-17.031 128.125-17.219 43.5 0.188 87.312 5.875 128.188 17.281 97.688-66.312 140.688-52.5 140.688-52.5 28 70.531 10.375 122.562 5.125 135.5 32.812 35.844 52.625 81.469 52.625 137.406 0 196.688-119.75 240-233.812 252.688 18.438 15.875 34.75 47 34.75 94.75 0 68.438-0.688 123.625-0.688 140.5 0 13.625 9.312 29.562 35.25 24.562C877.438 930 1024 738.125 1024 512 1024 229.25 794.75 0 512 0z" />
</svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

180
README.md Normal file
View File

@ -0,0 +1,180 @@
# mumble-web
mumble-web is an HTML5 [Mumble] client for use in modern browsers.
A live demo is running [here](https://voice.johni0702.de/?address=voice.johni0702.de&port=443/demo).
The Mumble protocol uses TCP for control and UDP for voice.
Running in a browser, both are unavailable to this client.
Instead Websockets are used for all communications.
libopus, libcelt (0.7.1) and libsamplerate, compiled to JS via emscripten, are used for audio decoding.
Therefore, at the moment only the Opus and CELT Alpha codecs are supported.
Quite a few features, most noticeably all
administrative functionallity, are still missing.
### Installing
#### Download
mumble-web can either be installed directly from npm with `npm install -g mumble-web`
or from git:
```
git clone https://github.com/johni0702/mumble-web
cd mumble-web
npm install
npm run build
```
The npm version is prebuilt and ready to use whereas the git version allows you
to e.g. customize the theme before building it.
Either way you will end up with a `dist` folder that contains the static page.
#### Setup
At the time of writing this there do not seem to be any Mumble servers
which natively support Websockets. To use this client with any standard mumble
server, websockify must be set up (preferably on the same machine that the
Mumble server is running on).
You can install websockify via your package manager `apt install websockify` or
manually from the [websockify GitHub page]. Note that while some versions might
function better than others, the python version generally seems to be the best.
There are two basic ways you can use websockify with mumble-web:
- Standalone, use websockify for both, websockets and serving static files
- Proxied, let your favorite web server serve static files and proxy websocket connections to websockify
##### Standalone
This is the simplest but at the same time least flexible configuration. Replace `<mumbleserver>` with the URI of your mumble server. If `websockify` is running on the same machine as `mumble-server`, use `localhost`.
```
websockify --cert=mycert.crt --key=mykey.key --ssl-only --ssl-target --web=path/to/dist 443 <mumbleserver>:64738
```
##### Proxied
This configuration allows you to run websockify on a machine that already has
another webserver running. Replace `<mumbleserver>` with the URI of your mumble server. If `websockify` is running on the same machine as `mumble-server`, use `localhost`.
```
websockify --ssl-target 64737 <mumbleserver>:64738
```
Here are two web server configuration files (one for [NGINX](https://www.nginx.com/) and one for [Caddy server](https://caddyserver.com/)) which will serve the mumble-web interface at `https://voice.example.com` and allow the websocket to connect at `wss://voice.example.com/demo` (similar to the demo server). Replace `<websockify>` with the URI to the machine where `websockify` is running. If `websockify` is running on the same machine as your web server, use `localhost`.
* NGINX configuration file
```Nginx
server {
listen 443 ssl;
server_name voice.example.com;
ssl_certificate /etc/letsencrypt/live/voice.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/voice.example.com/privkey.pem;
location / {
root /path/to/dist;
}
location /demo {
proxy_pass http://<websockify>:64737;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
}
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
```
* Caddy configuration file (`Caddyfile`)
```
http://voice.example.com {
redir https://voice.example.com
}
https://voice.example.com {
tls "/etc/letsencrypt/live/voice.example.com/fullchain.pem" "/etc/letsencrypt/live/voice.example.com/privkey.pem"
root /path/to/dist
proxy /demo http://<websockify>:64737 {
websocket
}
}
```
Make sure that your Mumble server is running. You may now open may now open `https://voice.example.com` in a web browser. You will be prompted for server details: choose either `address: voice.example.com/demo` with `port: 443` or `address: voice.example.com` with `port: 443/demo`. You may prefill these values by appending `?address=voice.example.com/demo&port=443`. Choose a username, and click `Connect`: you should now be able to talk and use the chat.
Here is an example of systemd service, put it in `/etc/systemd/system/mumble-web.service` and adapt it to your needs:
```
[Unit]
Description=Mumble web interface
Documentation=https://github.com/johni0702/mumble-web
Requires=network.target mumble-server.service
After=network.target mumble-server.service
[Service]
Type=simple
User=www-data
ExecStart=/usr/bin/websockify --web=/usr/lib/node_modules/mumble-web/dist --ssl-target localhost:64737 localhost:64738
[Install]
WantedBy=multi-user.target
```
Then
```
systemctl daemon-reload
systemctl start mumble-web
systemctl enable mumble-web
```
### Configuration
The `app/config.js` file contains default values and descriptions for all configuration options.
You can overwrite those by editing the `config.local.js` file within your `dist` folder. Make sure to back up and restore the file whenever you update to a new version.
### Themes
The default theme of mumble-web tries to mimic the excellent [MetroMumble]Light theme.
mumble-web also includes a dark version, named MetroMumbleDark, which is heavily inspired by [MetroMumble]'s dark version.
To select a theme other than the default one, append a `theme=dark` query parameter (where `dark` is the name of the theme) when accessing the mumble-web page.
E.g. [this](https://voice.johni0702.de/?address=voice.johni0702.de&port=443/demo&theme=dark)is the live demo linked above but using the dark theme (`dark` is an alias for `MetroMumbleDark`).
Custom themes can be created by deriving them from the MetroMumbleLight/Dark themes just like the MetroMumbleDark theme is derived from the MetroMumbleLight theme.
### Matrix Widget
mumble-web has specific support for running as a widget in a [Matrix] room.
While just using the URL to a mumble-web instance in a Custom Widget should work for most cases, making full use of all supported features will require some additional trickery. Also note that audio may not be functioning properly on newer Chrome versions without these extra steps.
This assumes you are using the Riot Web or Desktop client. Other clients will probably require different steps.
1. Type `/devtools` into the message box of the room and press Enter
2. Click on `Send Custom Event`
3. Click on `Event` in the bottom right corner (it should change to `State Event`)
4. Enter `im.vector.modular.widgets` for `Event Type`
5. Enter `mumble` for `State Key` (this value may be arbitrary but must be unique per room)
6. For `Event Content` enter (make sure to replace the example values):
```
{
"waitForIframeLoad": true,
"name": "Mumble",
"creatorUserId": "@your_user_id:your_home_server.example",
"url": "https://voice.johni0702.de/?address=voice.johni0702.de&port=443/mumble&matrix=true&username=$matrix_display_name&theme=$theme&avatarurl=$matrix_avatar_url",
"data": {},
"type": "jitsi",
"id": "mumble"
}
```
The `$var` parts of the `url` are intentional and will be replaced by Riot whenever a widget is loaded (i.e. they will be different for every user). The `username` query parameter sets the default username to the user's Matrix display name, the `theme` parameter automatically uses the dark theme if it's used in Riot, and the `avatarurl` will automatically download the user's avatar on Matrix and upload it as the avatar in Mumble.
Finally, the `matrix=true` query parameter replaces the whole `Connect to Server` dialog with a single `Join Conference` button, so make sure to remove it if you do not supply default values for all connection parameters as above.
The `type` needs to be `jitsi` to allow the widget to use audio and to stay open when switching to a different room (this will hopefully change once Riot is able to ask for permission from the user by itself).
The `id` should be the same as the `State Key` from step 5.
See [here](https://docs.google.com/document/d/1uPF7XWY_dXTKVKV7jZQ2KmsI19wn9-kFRgQ1tFQP7wQ/edit) for more information on the values of these fields.
7. Press `Send`
### License
ISC
[Mumble]: https://wiki.mumble.info/wiki/Main_Page
[websockify GitHub page]: https://github.com/novnc/websockify
[MetroMumble]: https://github.com/xPoke/MetroMumble
[Matrix]: https://matrix.org

BIN
chapril-logo-mini-200x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -7,3 +7,16 @@ let config = window.mumbleWebConfig // eslint-disable-line no-unused-vars
// E.g. changing default address and theme: // E.g. changing default address and theme:
// config.defaults.address = 'voice.example.com' // config.defaults.address = 'voice.example.com'
// config.defaults.theme = 'MetroMumbleDark' // config.defaults.theme = 'MetroMumbleDark'
config.defaults.address = 'mumble.chapril.org/ws';
config.connectDialog.address = false;
config.connectDialog.port = false;
config.connectDialog.token = true;
config.connectDialog.password = false;
config.connectDialog.channelName = false;
config.settings.voiceMode = 'ptt';
config.settings.pttKey = 'ctrl + shift';
config.settings.joinDialog = true;

BIN
favicon-32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 972 B

View File

@ -3,12 +3,8 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<!-- Favicon as generated by realfavicongenerator.net (slightly modified for webpack) --> <!-- Favicon as generated by realfavicongenerator.net (slightly modified for webpack) -->
<link rel="apple-touch-icon" sizes="180x180" href="ee7a19054eb87597c4b8e4664823ebfd.png"> <link rel="icon" type="image/png" href="favicon-32x32.png" sizes="32x32">
<link rel="icon" type="image/png" href="195ad531e2d729dbb80bb4524be18d78.png" sizes="32x32">
<link rel="icon" type="image/png" href="cf02aba975e911ef9823610d908b32f5.png" sizes="16x16">
<link rel="manifest" href="e72996a6d1ed5d1d7962c27c10c08fa6.json"> <link rel="manifest" href="e72996a6d1ed5d1d7962c27c10c08fa6.json">
<link rel="mask-icon" href="18e00168e2eeff800227594e26818681.svg" color="#5bbad5">
<link rel="shortcut icon" href="7e20ba1e3b8980a164c5f557a0470919.ico">
<meta name="apple-mobile-web-app-title" content="Mumble"> <meta name="apple-mobile-web-app-title" content="Mumble">
<meta name="application-name" content="Mumble"> <meta name="application-name" content="Mumble">
<meta name="msapplication-config" content="8b61e99b17ef88afd479b3fc990e5b9d.xml"> <meta name="msapplication-config" content="8b61e99b17ef88afd479b3fc990e5b9d.xml">
@ -28,12 +24,12 @@
<!-- ko with: connectDialog --> <!-- ko with: connectDialog -->
<div class="connect-dialog dialog" data-bind="visible: visible() && !joinOnly()"> <div class="connect-dialog dialog" data-bind="visible: visible() && !joinOnly()">
<div class="dialog-header"> <div class="dialog-header">
Connect to Server Se connecter au serveur
</div> </div>
<form data-bind="submit: connect"> <form data-bind="submit: connect">
<table> <table>
<tr data-bind="if: $root.config.connectDialog.address"> <tr data-bind="if: $root.config.connectDialog.address">
<td>Address</td> <td>Adresse</td>
<td><input id="address" type="text" data-bind="value: address" required></td> <td><input id="address" type="text" data-bind="value: address" required></td>
</tr> </tr>
<tr data-bind="if: $root.config.connectDialog.port"> <tr data-bind="if: $root.config.connectDialog.port">
@ -41,15 +37,15 @@
<td><input id="port" type="text" data-bind="value: port" required></td> <td><input id="port" type="text" data-bind="value: port" required></td>
</tr> </tr>
<tr data-bind="if: $root.config.connectDialog.username"> <tr data-bind="if: $root.config.connectDialog.username">
<td>Username</td> <td>Pseudo ou nom</td>
<td><input id="username" type="text" data-bind="value: username" required></td> <td><input id="username" type="text" data-bind="value: username" required></td>
</tr> </tr>
<tr data-bind="if: $root.config.connectDialog.password"> <tr data-bind="if: $root.config.connectDialog.password">
<td>Password</td> <td>Mot de passe</td>
<td><input id="password" type="password" data-bind="value: password"></td> <td><input id="password" type="password" data-bind="value: password"></td>
</tr> </tr>
<tr data-bind="if: $root.config.connectDialog.token"> <tr data-bind="if: $root.config.connectDialog.token">
<td>Tokens</td> <td><small>Mot de passe de canal</small><br><small><small>optionnel, uniquement pour<br>accéder aux canaux réservés</small></small></td>
<td> <td>
<input type="text" data-bind='value: tokenToAdd, valueUpdate: "afterkeydown"'> <input type="text" data-bind='value: tokenToAdd, valueUpdate: "afterkeydown"'>
</td> </td>
@ -57,8 +53,8 @@
<tr data-bind="if: $root.config.connectDialog.token"> <tr data-bind="if: $root.config.connectDialog.token">
<td></td> <td></td>
<td> <td>
<button class="dialog-submit" type="button" data-bind="enable: selectedTokens().length > 0, click: removeSelectedTokens()">Remove</button> <button class="dialog-submit" type="button" data-bind="enable: selectedTokens().length > 0, click: removeSelectedTokens()"><small><small>Supprimer</small></small></button>
<button class="dialog-submit" type="button" data-bind="enable: tokenToAdd().length > 0, click: addToken()">Add</button> <button class="dialog-submit" type="button" data-bind="enable: tokenToAdd().length > 0, click: addToken()"><small><small>Ajouter</small></small></button>
</td> </td>
</tr> </tr>
<tr data-bind="if: $root.config.connectDialog.token, visible: tokens().length > 0"> <tr data-bind="if: $root.config.connectDialog.token, visible: tokens().length > 0">
@ -66,13 +62,13 @@
<td><select id="token" multiple="multiple" height="5" data-bind="options:tokens, selectedOptions:selectedTokens"></select></td> <td><select id="token" multiple="multiple" height="5" data-bind="options:tokens, selectedOptions:selectedTokens"></select></td>
</tr> </tr>
<tr data-bind="if: $root.config.connectDialog.channelName"> <tr data-bind="if: $root.config.connectDialog.channelName">
<td>Channel</td> <td>Canal</td>
<td><input id="channelName" type="text" data-bind="value: channelName"></td> <td><input id="channelName" type="text" data-bind="value: channelName"></td>
</tr> </tr>
</table> </table>
<div class="dialog-footer"> <div class="dialog-footer">
<input class="dialog-close" type="button" data-bind="click: hide" value="Cancel"> <input class="dialog-close" type="button" data-bind="click: hide" value="Annuler">
<input class="dialog-submit" type="submit" value="Connect"> <input class="dialog-submit" type="submit" value="Se connecter">
</div> </div>
</form> </form>
</div> </div>
@ -80,7 +76,7 @@
<!-- ko with: connectDialog --> <!-- ko with: connectDialog -->
<div class="join-dialog dialog" data-bind="visible: visible() && joinOnly()"> <div class="join-dialog dialog" data-bind="visible: visible() && joinOnly()">
<div class="dialog-header"> <div class="dialog-header">
Mumble Voice Conference Conférence vocale Mumble
</div> </div>
<form data-bind="submit: connect"> <form data-bind="submit: connect">
<input class="dialog-submit" type="submit" value="Join Conference"> <input class="dialog-submit" type="submit" value="Join Conference">
@ -90,57 +86,56 @@
<!-- ko with: connectErrorDialog --> <!-- ko with: connectErrorDialog -->
<div class="connect-dialog error-dialog dialog" data-bind="visible: visible()"> <div class="connect-dialog error-dialog dialog" data-bind="visible: visible()">
<div class="dialog-header"> <div class="dialog-header">
Failed to connect Échec de la connexion.
</div> </div>
<form data-bind="submit: connect"> <form data-bind="submit: connect">
<table> <table>
<tr> <tr>
<td colspan=2> <td colspan=2>
<!-- ko if: type() == 0 || type() == 8 --> <!-- ko if: type() == 0 || type() == 8 -->
The connection has been refused. La connection a été refusée.
<!-- /ko --> <!-- /ko -->
<!-- ko if: type() == 1 --> <!-- ko if: type() == 1 -->
The server uses an incompatible version. Le serveur utilise une version incompatible.
<!-- /ko --> <!-- /ko -->
<!-- ko if: type() == 2 --> <!-- ko if: type() == 2 -->
Your user name was rejected. Maybe try a different one? Votre nom d'utilisateur a été rejeté. Pourriez-vous en essayer un autre ?
<!-- /ko --> <!-- /ko -->
<!-- ko if: type() == 3 --> <!-- ko if: type() == 3 -->
The given password is incorrect. Le mot de passe donné est incorrect.
The user name you have chosen requires a special one. Le nom d'utilisateur donné en nécessite un.
<!-- /ko --> <!-- /ko -->
<!-- ko if: type() == 4 --> <!-- ko if: type() == 4 -->
The given password is incorrect. Le mot de passe donné est incorrect.
<!-- /ko --> <!-- /ko -->
<!-- ko if: type() == 5 --> <!-- ko if: type() == 5 -->
The user name you have chosen is already in use. Le nom que vous avez choisi est déjà utilisé.
<!-- /ko --> <!-- /ko -->
<!-- ko if: type() == 6 --> <!-- ko if: type() == 6 -->
The server is full. Le serveur est complet.
<!-- /ko --> <!-- /ko -->
<!-- ko if: type() == 7 --> <!-- ko if: type() == 7 -->
The server requires you to provide a client certificate Le serveur requiert un certificat client non supporté par cette application web.
which is not supported by this web application.
<!-- /ko --> <!-- /ko -->
<br> <br>
The server reports: Le serveur indique :
<br> <br>
"<span class="connect-error-reason" data-bind="text: reason"></span>" "<span class="connect-error-reason" data-bind="text: reason"></span>"
</td> </td>
</tr> </tr>
<tr data-bind="if: type() == 2 || type() == 3 || type() == 5"> <tr data-bind="if: type() == 2 || type() == 3 || type() == 5">
<td>Username</td> <td>Utilisateur</td>
<td><input id="username" type="text" data-bind="value: username" required></td> <td><input id="username" type="text" data-bind="value: username" required></td>
</tr> </tr>
<tr data-bind="if: type() == 3 || type() == 4"> <tr data-bind="if: type() == 3 || type() == 4">
<td>Password</td> <td>Mot de passe</td>
<td><input id="password" type="password" data-bind="value: password" required></td> <td><input id="password" type="password" data-bind="value: password" required></td>
</tr> </tr>
</table> </table>
<div class="dialog-footer"> <div class="dialog-footer">
<input class="dialog-close" type="button" value="Cancel" <input class="dialog-close" type="button" value="Annuler"
data-bind="click: hide, visible: !joinOnly()"> data-bind="click: hide, visible: !joinOnly()">
<input class="dialog-submit" type="submit" value="Retry"> <input class="dialog-submit" type="submit" value="Réessayer">
</div> </div>
</form> </form>
</div> </div>
@ -148,12 +143,12 @@
<!-- ko with: connectionInfo --> <!-- ko with: connectionInfo -->
<div class="connection-info-dialog dialog" data-bind="visible: visible"> <div class="connection-info-dialog dialog" data-bind="visible: visible">
<div class="dialog-header"> <div class="dialog-header">
Connection Information Informations de connexion
</div> </div>
<div class="dialog-content"> <div class="dialog-content">
<h3>Version</h3> <h3>Version</h3>
<!-- ko with: serverVersion --> <!-- ko with: serverVersion -->
Protocol Protocole
<span data-bind="text: major + '.' + minor + '.' + patch"></span>. <span data-bind="text: major + '.' + minor + '.' + patch"></span>.
<br> <br>
<br> <br>
@ -164,24 +159,24 @@
<br> <br>
<!-- /ko --> <!-- /ko -->
<!-- ko if: !serverVersion() --> <!-- ko if: !serverVersion() -->
Unknown Inconnu
<!-- /ko --> <!-- /ko -->
<h3>Control channel</h3> <h3>Canal de contrôle</h3>
<span data-bind="text: latencyMs().toFixed(2)"></span> ms average latency <span data-bind="text: latencyMs().toFixed(2)"></span> ms de latence moyenne
(<span data-bind="text: latencyDeviation().toFixed(2)"></span> deviation) (<span data-bind="text: latencyDeviation().toFixed(2)"></span> déviation)
<br> <br>
<br> <br>
Remote host <span data-bind="text: remoteHost"></span> Hôte distant <span data-bind="text: remoteHost"></span>
(port <span data-bind="text: remotePort"></span>) (port <span data-bind="text: remotePort"></span>)
<br> <br>
<h3>Audio bandwidth</h3> <h3>Bande passante audioh</h3>
Maximum <span data-bind="text: (maxBitrate()/1000).toFixed(1)"></span> kbits/s Maximum <span data-bind="text: (maxBitrate()/1000).toFixed(1)"></span> kbits/s
(<span data-bind="text: (maxBandwidth()/1000).toFixed(1)"></span> kbits/s with overhead) (<span data-bind="text: (maxBandwidth()/1000).toFixed(1)"></span> kbits/s avec overhead)
<br> <br>
Current <span data-bind="text: (currentBitrate()/1000).toFixed(1)"></span> kbits/s Courante <span data-bind="text: (currentBitrate()/1000).toFixed(1)"></span> kbits/s
(<span data-bind="text: (currentBandwidth()/1000).toFixed(1)"></span> kbits/s with overhead) (<span data-bind="text: (currentBandwidth()/1000).toFixed(1)"></span> kbits/s avec overhead)
<br> <br>
Codec: <span data-bind="text: codec"></span> Codec: <span data-bind="text: codec"></span>
</div> </div>
@ -193,7 +188,7 @@
<!-- ko with: settingsDialog --> <!-- ko with: settingsDialog -->
<div class="settings-dialog dialog" data-bind="visible: $data"> <div class="settings-dialog dialog" data-bind="visible: $data">
<div class="dialog-header"> <div class="dialog-header">
Settings Paramètres
</div> </div>
<form data-bind="submit: $root.applySettings"> <form data-bind="submit: $root.applySettings">
<table> <table>
@ -201,9 +196,9 @@
<td>Transmission</td> <td>Transmission</td>
<td> <td>
<select data-bind='value: voiceMode'> <select data-bind='value: voiceMode'>
<option value="cont">Continuous</option> <option value="cont">Continue</option>
<option value="vad">Voice Activity</option> <option value="vad">Activité vocale</option>
<option value="ptt">Push To Talk</option> <option value="ptt">Appuyer pour parler</option>
</td> </td>
</tr> </tr>
<tr data-bind="visible: voiceMode() == 'vad'"> <tr data-bind="visible: voiceMode() == 'vad'">
@ -219,13 +214,13 @@
</td> </td>
</tr> </tr>
<tr data-bind="visible: voiceMode() == 'ptt'"> <tr data-bind="visible: voiceMode() == 'ptt'">
<td>PTT Key</td> <td>Touche d'activation</td>
<td> <td>
<input type="button" data-bind="value: pttKeyDisplay, click: recordPttKey"> <input type="button" data-bind="value: pttKeyDisplay, click: recordPttKey">
</td> </td>
</tr> </tr>
<tr> <tr>
<td>Audio Quality</td> <td>Qualité audio</td>
<td><span data-bind="text: (audioBitrate()/1000).toFixed(1)"></span> kbit/s</td> <td><span data-bind="text: (audioBitrate()/1000).toFixed(1)"></span> kbit/s</td>
</tr> </tr>
<tr> <tr>
@ -235,7 +230,7 @@
</td> </td>
</tr> </tr>
<tr> <tr>
<td>Audio per packet</td> <td>Audio par paquet</td>
<td><span data-bind="text: msPerPacket"></span> ms</td> <td><span data-bind="text: msPerPacket"></span> ms</td>
</tr> </tr>
<tr> <tr>
@ -257,26 +252,26 @@
</td> </td>
</tr> </tr>
<tr> <tr>
<td>Show Avatars</td> <td>Montrer les avatars</td>
<td> <td>
<select data-bind='value: showAvatars'> <select data-bind='value: showAvatars'>
<option value="always">Always</option> <option value="always">Toujours</option>
<option value="own_channel">Same Channel</option> <option value="own_channel">Même canal</option>
<option value="linked_channel">Linked Channels</option> <option value="linked_channel">Canaux liés</option>
<option value="minimal_only">Minimal View</option> <option value="minimal_only">Vue minimale</option>
<option value="never">Never</option> <option value="never">Jamais</option>
</td> </td>
</tr> </tr>
<tr> <tr>
<td colspan="2"> <td colspan="2">
<input type="checkbox" data-bind="checked: userCountInChannelName"> <input type="checkbox" data-bind="checked: userCountInChannelName">
Show user count after channel name Montrer le nombre d'utilisateurs du canal
</td> </td>
</tr> </tr>
</table> </table>
<div class="dialog-footer"> <div class="dialog-footer">
<input class="dialog-close" type="button" data-bind="click: $root.closeSettings" value="Cancel"> <input class="dialog-close" type="button" data-bind="click: $root.closeSettings" value="Annuler">
<input class="dialog-submit" type="submit" value="Apply"> <input class="dialog-submit" type="submit" value="Appliquer">
</div> </div>
</form> </form>
</div> </div>
@ -290,75 +285,75 @@
<!-- ko with: target --> <!-- ko with: target -->
<li data-bind="css: { disabled: !canChangeMute() }"> <li data-bind="css: { disabled: !canChangeMute() }">
Mute Silence
</li> </li>
<li data-bind="css: { disabled: !canChangeDeafen() }"> <li data-bind="css: { disabled: !canChangeDeafen() }">
Deafen Sourdine
</li> </li>
<li data-bind="css: { disabled: !canChangePrioritySpeaker() }"> <li data-bind="css: { disabled: !canChangePrioritySpeaker() }">
Priority Speaker Priorité haut-parleurs
</li> </li>
<li data-bind="css: { disabled: !canLocalMute() }"> <li data-bind="css: { disabled: !canLocalMute() }">
Local Mute Silence local
</li> </li>
<li data-bind="css: { disabled: !canIgnoreMessages() }"> <li data-bind="css: { disabled: !canIgnoreMessages() }">
Ignore Messages Ignorer les messages
</li> </li>
<li data-bind="css: { disabled: !canChangeComment() }, visible: comment"> <li data-bind="css: { disabled: !canChangeComment() }, visible: comment">
View Comment Voir le commentaire
</li> </li>
<!-- ko if: $data === $root.thisUser() --> <!-- ko if: $data === $root.thisUser() -->
<li data-bind="css: { disabled: !canChangeComment() }, visible: true"> <li data-bind="css: { disabled: !canChangeComment() }, visible: true">
Change Comment Modifier le commentaire
</li> </li>
<!-- /ko --> <!-- /ko -->
<!-- ko if: $data !== $root.thisUser() --> <!-- ko if: $data !== $root.thisUser() -->
<li data-bind="css: { disabled: !canChangeComment() }, visible: comment"> <li data-bind="css: { disabled: !canChangeComment() }, visible: comment">
Reset Comment Effacer le commentaire
</li> </li>
<!-- /ko --> <!-- /ko -->
<li data-bind="css: { disabled: !canChangeAvatar() }, visible: texture, <li data-bind="css: { disabled: !canChangeAvatar() }, visible: texture,
click: viewAvatar"> click: viewAvatar">
View Avatar Voir l'avatar
</li> </li>
<!-- ko if: $data === $root.thisUser() --> <!-- ko if: $data === $root.thisUser() -->
<li data-bind="css: { disabled: !canChangeAvatar() }, visible: true, <li data-bind="css: { disabled: !canChangeAvatar() }, visible: true,
click: changeAvatar"> click: changeAvatar">
Change Avatar Modifier l'avatar
</li> </li>
<!-- /ko --> <!-- /ko -->
<li data-bind="css: { disabled: !canChangeAvatar() }, visible: texture, <li data-bind="css: { disabled: !canChangeAvatar() }, visible: texture,
click: removeAvatar"> click: removeAvatar">
Reset Avatar Effacer l'avatar
</li> </li>
<li data-bind="css: { disabled: true }, visible: true"> <li data-bind="css: { disabled: true }, visible: true">
Send Message Envoyer un message
</li> </li>
<li data-bind="css: { disabled: true }, visible: true"> <li data-bind="css: { disabled: true }, visible: true">
Information Informations
</li> </li>
<li data-bind="visible: $data === $root.thisUser(), <li data-bind="visible: $data === $root.thisUser(),
css: { checked: selfMute }, css: { checked: selfMute },
click: toggleMute"> click: toggleMute">
Self Mute Se rendre muet
</li> </li>
<li data-bind="visible: $data === $root.thisUser(), <li data-bind="visible: $data === $root.thisUser(),
css: { checked: selfDeaf }, css: { checked: selfDeaf },
click: toggleDeaf"> click: toggleDeaf">
Self Deafen Se mettre en sourdine
</li> </li>
<!-- ko if: $data !== $root.thisUser() --> <!-- ko if: $data !== $root.thisUser() -->
<li data-bind="css: { disabled: true }, visible: true"> <li data-bind="css: { disabled: true }, visible: true">
Add Friend Ajouter un ami
</li> </li>
<li data-bind="css: { disabled: true }, visible: false"> <li data-bind="css: { disabled: true }, visible: false">
Remove Friend Supprimer un ami
</li> </li>
<!-- /ko --> <!-- /ko -->
@ -374,36 +369,36 @@
<li data-bind="visible: users.indexOf($root.thisUser()) === -1, <li data-bind="visible: users.indexOf($root.thisUser()) === -1,
css: { disabled: !canJoin() }, css: { disabled: !canJoin() },
click: $root.requestMove.bind($root, $root.thisUser())"> click: $root.requestMove.bind($root, $root.thisUser())">
Join Channel Rejoindre le canal
</li> </li>
<li data-bind="css: { disabled: !canAdd() }"> <li data-bind="css: { disabled: !canAdd() }">
Add Ajouter
</li> </li>
<li data-bind="css: { disabled: !canEdit() }"> <li data-bind="css: { disabled: !canEdit() }">
Edit Éditer
</li> </li>
<li data-bind="css: { disabled: !canRemove() }"> <li data-bind="css: { disabled: !canRemove() }">
Remove Supprimer
</li> </li>
<li data-bind="css: { disabled: !canLink() }"> <li data-bind="css: { disabled: !canLink() }">
Link Lier
</li> </li>
<li data-bind="css: { disabled: !canUnlink() }"> <li data-bind="css: { disabled: !canUnlink() }">
Unlink Délier
</li> </li>
<li data-bind="css: { disabled: !canUnlink() }"> <li data-bind="css: { disabled: !canUnlink() }">
Unlink All Tout délier
</li> </li>
<li data-bind="css: { disabled: true }"> <li data-bind="css: { disabled: true }">
Copy Mumble URL Copier l'URL de Mumble
</li> </li>
<li data-bind="css: { disabled: true }"> <li data-bind="css: { disabled: true }">
Copy Mumble-Web URL Copier l'URL de Mumble-Web
</li> </li>
<li data-bind="css: { disabled: !canSendMessage() }"> <li data-bind="css: { disabled: !canSendMessage() }">
Send Message Envoyer un message
</li> </li>
<!-- /ko --> <!-- /ko -->
@ -440,8 +435,6 @@
<img class="tb-undeaf tb-active" data-bind="visible: selfDeaf, <img class="tb-undeaf tb-active" data-bind="visible: selfDeaf,
click: function () { requestUndeaf(thisUser()) }" click: function () { requestUndeaf(thisUser()) }"
rel="undeaf" src="d5ba30b381ebc262ba3871eaed9d7102.svg"> rel="undeaf" src="d5ba30b381ebc262ba3871eaed9d7102.svg">
<img class="tb-record" data-bind="click: function(){}"
rel="record" src="7b86c879d50808c66816ed3338f26557.svg">
<div class="divider"></div> <div class="divider"></div>
<img class="tb-comment" data-bind="click: commentDialog.show" <img class="tb-comment" data-bind="click: commentDialog.show"
rel="comment" src="077f1c5bd335be073c48c340b01f58bc.svg"> rel="comment" src="077f1c5bd335be073c48c340b01f58bc.svg">
@ -450,24 +443,30 @@
rel="settings" src="50dddae19e7bf601b168f46a1303674b.svg"> rel="settings" src="50dddae19e7bf601b168f46a1303674b.svg">
<div class="divider"></div> <div class="divider"></div>
<img class="tb-sourcecode" data-bind="click: openSourceCode" <img class="tb-sourcecode" data-bind="click: openSourceCode"
rel="Source Code" src="71edeaefdc2f5a19dc84298669af6962.svg"> rel="Source Code" src="chapril-logo-mini-200x.png">
</div> </div>
<div class="chat"> <div class="chat">
<script type="text/html" id="log-generic"> <script type="text/html" id="log-generic">
<span data-bind="text: value"></span> <span data-bind="text: value"></span>
</script> </script>
<script type="text/html" id="log-welcome-message"> <script type="text/html" id="log-welcome-message">
Welcome message: <span data-bind="html: message"></span> Message de bienvenue: <span data-bind="html: message"></span>
<hr />
<center>Tutoriel</center>
<p>Par défaut, pour pouvoir parler, il faut appuyer et maintenir appuyées les touches <b>Ctrl et Majuscule</b> (shift) (c&apos;est le mode <i>Appuyer pour parler</i>). Pour changer ce mode de transmission, pour passer par exemple en mode transmission continue, vous pouvez aller dans les paramètres (en cliquant sur la roue crantée).</p>
<p>Pour rejoindre un salon, double-cliquer sur le nom du salon, ou faire clic-droit sur le nom du salon et choisir «&#160;Rejoindre le salon&#160;». Pour quitter un salon, il faut double-cliquer sur Chapril tout en haut.</p>
<p>Vous pouvez consulter <a href='https://www.chapril.org/Mumble.html'>une documentation</a>.</p>
<hr />
</script> </script>
<script type="text/html" id="log-chat-message"> <script type="text/html" id="log-chat-message">
<span data-bind="visible: channel"> <span data-bind="visible: channel">
(Channel) (Canal)
</span> </span>
<span data-bind="template: { name: 'user-tag', data: user }"></span>: <span data-bind="template: { name: 'user-tag', data: user }"></span>:
<span class="message-content" data-bind="html: message"></span> <span class="message-content" data-bind="html: message"></span>
</script> </script>
<script type="text/html" id="log-chat-message-self"> <script type="text/html" id="log-chat-message-self">
To À
<span data-bind="template: { if: $data.channel, name: 'channel-tag', data: $data.channel }"> <span data-bind="template: { if: $data.channel, name: 'channel-tag', data: $data.channel }">
</span><span data-bind="template: { if: $data.user, name: 'user-tag', data: $data.user }"> </span><span data-bind="template: { if: $data.user, name: 'user-tag', data: $data.user }">
</span>: </span>:

20950
index.js

File diff suppressed because it is too large Load Diff