Browse Source

Merge branch 'develop' into master

pull/459/head
Mathieu Brunot 7 years ago committed by GitHub
parent
commit
de63d8b14c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      .travis.yml
  2. 3
      AUTHORS
  3. 52
      CHANGELOG.md
  4. 11
      Makefile.am
  5. 15
      README.md
  6. 5
      dependencies.tsv
  7. 79
      doc/CHANNELING-API.txt
  8. 51
      doc/turn/block-udp-for-turn-test.sh
  9. 6
      go/channelling/api/api.go
  10. 3
      go/channelling/api/api_test.go
  11. 4
      go/channelling/api/handle_authentication.go
  12. 4
      go/channelling/api/handle_self.go
  13. 65
      go/channelling/bus_manager.go
  14. 7
      go/channelling/client.go
  15. 5
      go/channelling/config.go
  16. 19
      go/channelling/data.go
  17. 48
      go/channelling/hub.go
  18. 4
      go/channelling/pipeline.go
  19. 34
      go/channelling/room_manager.go
  20. 50
      go/channelling/room_manager_test.go
  21. 16
      go/channelling/server/config.go
  22. 6
      go/channelling/server/rooms.go
  23. 2
      go/channelling/turndata.go
  24. 140
      go/channelling/turnservice_manager.go
  25. 62
      go/natsconnection/natsconnection.go
  26. 2
      html/head.html
  27. 8
      scripts/docker_entrypoint.sh
  28. 47
      server.conf.in
  29. 76
      src/app/spreed-webrtc-server/main.go
  30. 39
      src/app/spreed-webrtc-server/template.go
  31. 50
      src/app/spreed-webrtc-server/template_test.go
  32. 24
      src/audio/_build.py
  33. 2
      src/audio/_encode.sh
  34. 34
      src/audio/sprite1.json
  35. BIN
      src/audio/sprite1.mp3
  36. BIN
      src/audio/sprite1.ogg
  37. BIN
      src/audio/sprite1.wav
  38. 4
      src/i18n/helpers/languages.py
  39. 19
      src/i18n/messages-de.po
  40. 892
      src/i18n/messages-es.po
  41. 321
      src/i18n/messages-fr.po
  42. 886
      src/i18n/messages-it.po
  43. 17
      src/i18n/messages-ja.po
  44. 17
      src/i18n/messages-ko.po
  45. 20
      src/i18n/messages-ru.po
  46. 17
      src/i18n/messages-zh-cn.po
  47. 17
      src/i18n/messages-zh-tw.po
  48. 14
      src/i18n/messages.pot
  49. 4
      src/styles/components/_social.scss
  50. 4
      static/css/main.min.css
  51. 1
      static/js/app.js
  52. 3
      static/js/controllers/appcontroller.js
  53. 7
      static/js/controllers/chatroomcontroller.js
  54. 59
      static/js/controllers/uicontroller.js
  55. 6
      static/js/directives/audiovideo.js
  56. 20
      static/js/directives/settings.js
  57. 5
      static/js/directives/socialshare.js
  58. 4
      static/js/mediastream/api.js
  59. 5
      static/js/mediastream/peercall.js
  60. 2
      static/js/mediastream/peerconnection.js
  61. 47
      static/js/mediastream/usermedia.js
  62. 6
      static/js/mediastream/webrtc.js
  63. 2
      static/js/services/constraints.js
  64. 79
      static/js/services/roompin.js
  65. 9
      static/js/services/rooms.js
  66. 9
      static/js/services/services.js
  67. 213
      static/js/services/turndata.js
  68. 59
      static/js/services/videowaiter.js
  69. 9
      static/partials/settings.html
  70. 1
      static/partials/socialshare.html
  71. 34
      static/sounds/sprite1.json
  72. BIN
      static/sounds/sprite1.mp3
  73. BIN
      static/sounds/sprite1.ogg
  74. 2
      static/translation/languages.js
  75. 2
      static/translation/messages-de.json
  76. 1
      static/translation/messages-es.json
  77. 2
      static/translation/messages-fr.json
  78. 1
      static/translation/messages-it.json
  79. 2
      static/translation/messages-ja.json
  80. 2
      static/translation/messages-ko.json
  81. 2
      static/translation/messages-ru.json
  82. 2
      static/translation/messages-zh-cn.json
  83. 2
      static/translation/messages-zh-tw.json

1
.travis.yml

@ -6,6 +6,7 @@ go: @@ -6,6 +6,7 @@ go:
- 1.4
- 1.5
- 1.6
- 1.7
- tip
matrix:

3
AUTHORS

@ -11,3 +11,6 @@ Leon Klingele <klingele@struktur.de> @@ -11,3 +11,6 @@ Leon Klingele <klingele@struktur.de>
Translation:
Curt Frisemo <curt.frisemo@spreed.com>
Irek Zaripov <iazaripov@gmail.com>
Florent BEAUCHAMP <fbeauchamp@sdis71.fr>
Jhon Felipe Urrego Mejia <ingenierofelipeurrego@gmail.com>
Riccardo Olivo <olivo@arkitech.it>

52
CHANGELOG.md

@ -1,3 +1,55 @@ @@ -1,3 +1,55 @@
## 0.29.4
* Fixed regression introduced in 0.29.3 that could trigger a ring timeout in conference rooms.
* Handle getUserMedia failures on FF 49 and newer when no audio or video device is available (#394).
* Update spreed-turnservercli to return credentials that are about to expire (but not expired yet).
* Update Italian translation.
* Improve support for building on FreeBSD.
## 0.29.3
* Added Italian translation (#382)
* Fix XSS in room PIN dialog (#386)
* Reduce volume of "end1" sound by 50% (#379)
* Fix missing ringing sound / timeout on outgoing calls (#377/#378)
## 0.29.2
* Honor case insensitive config for auto generated room names
* Do not encode the body of a desktop notification
* Make room names case insensitive by default
* Update dependencies to phoenix with Update support
* Implement config loading via NATS
* Don`t run target binary before get is run
* Docker: Use /dev/urandom instead of /dev/random
## 0.29.1
* Filter rtx support from remote SDP for Chrone <= 38
* Fix Go 1.4 detection for minor versions
## 0.29.0
* Add Spanish translation (#307)
* Add minimal TURN refresh interval
* Add script to block UDP for TURN testing
* Implement TURN service client
* Add french translation (#325)
* Make room locking configurable
* Add UI to lock/unlock a room (#239)
* Fix syntax error in example
* Bump minimal Go version
* Fix Go 1.4 release target
* Check if system GOPATH exists
* Allow dist_gopath to fail
* Add make tarball to travis
* Allow specifying docker secrets with enviroment variables
## 0.28.1
* Filter rtx support from remote SDP for Chrome <= 38

11
Makefile.am

@ -40,7 +40,7 @@ OUTPUT := $(CURDIR)/bin @@ -40,7 +40,7 @@ OUTPUT := $(CURDIR)/bin
OUTPUT_JS := $(CURDIR)/build/out
BUILD_ARCH := $(shell $(GO) env GOARCH)
BUILD_OS := $(shell go env GOOS)
BUILD_OS := $(shell $(GO) env GOOS)
DIST := $(CURDIR)/dist_$(BUILD_ARCH)
DIST_SRC := $(DIST)/src
DIST_BIN := $(DIST)/bin
@ -56,9 +56,9 @@ gopath: @@ -56,9 +56,9 @@ gopath:
@echo GOPATH=$(GOPATH)
goget:
if [ -z "$(DEB_BUILDING)" ]; then GOPATH=$(GOPATH) go get github.com/rogpeppe/godeps; fi
if [ -z "$(DEB_BUILDING)" ]; then GOPATH=$(GOPATH) $(GO) get github.com/rogpeppe/godeps; fi
if [ -z "$(DEB_BUILDING)" ]; then GOPATH=$(GOPATH) $(CURDIR)/vendor/bin/godeps -u dependencies.tsv; fi
mkdir -p $(shell dirname "$(CURDIR)/vendor/src/$(GOPKG)")
$(MKDIR_P) $(shell dirname "$(CURDIR)/vendor/src/$(GOPKG)")
rm -f $(CURDIR)/vendor/src/$(GOPKG)
ln -sfn $(PWD) $(CURDIR)/vendor/src/$(GOPKG)
@ -163,6 +163,7 @@ install-assets: @@ -163,6 +163,7 @@ install-assets:
$(INSTALL) -d $(DESTDIR)$(SHARE)/www/static/translation
$(INSTALL) -d $(DESTDIR)$(SHARE)/www/static/css
$(INSTALL) -d $(DESTDIR)$(SHARE)/www/static/js/libs/pdf
$(INSTALL) -d $(DESTDIR)$(SHARE)/www/static/js/libs/require
$(INSTALL) -d $(DESTDIR)$(SHARE)/www/static/js/sandboxes
$(INSTALL) html/*.html $(DESTDIR)$(SHARE)/www/html
$(INSTALL) html/sandboxes/*.html $(DESTDIR)$(SHARE)/www/html/sandboxes
@ -171,10 +172,10 @@ install-assets: @@ -171,10 +172,10 @@ install-assets:
$(INSTALL) static/fonts/* $(DESTDIR)$(SHARE)/www/static/fonts
$(INSTALL) static/translation/* $(DESTDIR)$(SHARE)/www/static/translation
$(INSTALL) static/css/* $(DESTDIR)$(SHARE)/www/static/css
$(INSTALL) -D static/js/libs/require/require.js $(DESTDIR)$(SHARE)/www/static/js/libs/require/require.js
$(INSTALL) static/js/libs/require/require.js $(DESTDIR)$(SHARE)/www/static/js/libs/require
$(INSTALL) $(OUTPUT_JS)/*.js $(DESTDIR)$(SHARE)/www/static/js
$(INSTALL) $(OUTPUT_JS)/libs/pdf/*.js $(DESTDIR)$(SHARE)/www/static/js/libs/pdf
$(INSTALL) -D static/js/libs/webodf.js $(DESTDIR)$(SHARE)/www/static/js/libs/webodf.js
$(INSTALL) static/js/libs/webodf.js $(DESTDIR)$(SHARE)/www/static/js/libs
$(INSTALL) $(OUTPUT_JS)/sandboxes/*.js $(DESTDIR)$(SHARE)/www/static/js/sandboxes
install: install-binary install-assets

15
README.md

@ -13,6 +13,7 @@ The latest source of Spreed WebRTC can be found on [GitHub](https://github.com/s @@ -13,6 +13,7 @@ The latest source of Spreed WebRTC can be found on [GitHub](https://github.com/s
- [NodeJS](http://nodejs.org/) >= 0.6.0
- [autoconf](http://www.gnu.org/software/autoconf/)
- [automake](http://www.gnu.org/software/automake/)
- [git](https://git-scm.com/)
## Runtime dependencies
@ -39,6 +40,9 @@ $ ./configure @@ -39,6 +40,9 @@ $ ./configure
$ make
```
On FreeBSD, the default `make` has a different syntax, so `gmake` must be used
there.
## Build separately
@ -155,13 +159,16 @@ docker run --rm --name my-spreed-webrtc -p 8080:8080 -p 8443:8443 \ @@ -155,13 +159,16 @@ docker run --rm --name my-spreed-webrtc -p 8080:8080 -p 8443:8443 \
## Setup Screensharing
### Chrome
Chrome should work out of the box.
Chromium-based browsers (e.g. Google Chrome) require the [Spreed.ME screen sharing
extension](https://www.spreed.me/extension/).
### Firefox
As of Firefox >= 36 you must append the domain being used to the allowed domains
to access your screen. You do this by navigating to `about:config`, search for
'media.getusermedia.screensharing.allowed_domains', and append the domain
Screensharing with Firefox >= 52 should work out of the box.
When using Firefox 36 – 51 you must append the domain being used to the allowed
domains to access your screen. You do this by navigating to `about:config`, search
for 'media.getusermedia.screensharing.allowed_domains', and append the domain
to the list of strings. You can edit the field simply by double clicking on it.
Ensure that you follow the syntax rules of the field. If you are using an `ip:port`
url, simply append `ip` to the list. Also ensure that you are using `https`,

5
dependencies.tsv

@ -3,10 +3,11 @@ github.com/gorilla/context git 215affda49addc4c8ef7e2534915df2c8c35c6cd 2014-12- @@ -3,10 +3,11 @@ github.com/gorilla/context git 215affda49addc4c8ef7e2534915df2c8c35c6cd 2014-12-
github.com/gorilla/mux git ba336c9cfb43552c90de6cb2ceedd3271c747558 2015-07-17T15:03:03Z
github.com/gorilla/securecookie git aeade84400a85c6875264ae51c7a56ecdcb61751 2015-07-16T23:32:44Z
github.com/gorilla/websocket git a69d25be2fe2923a97c2af6849b2f52426f68fc0 2016-08-02T13:32:03Z
github.com/longsleep/pkac git 68bf8859f58dd84332ee41c07eba357fb3818ba3 2014-05-01T18:13:13Z
github.com/longsleep/pkac git 302922ac7627196c8cf9e4384cccadf9aa008b3e 2017-02-16T19:00:44Z
github.com/nats-io/nats git 355b5b97e0842dc94f1106729aa88e33e06317ca 2015-12-09T21:13:14Z
github.com/satori/go.uuid git 879c5887cd475cd7864858769793b2ceb0d44feb 2016-06-07T14:43:47Z
github.com/strukturag/goacceptlanguageparser git 68066e68c2940059aadc6e19661610cf428b6647 2014-02-13T13:31:23Z
github.com/strukturag/httputils git afbf05c71ac03ee7989c96d033a9571ba4ded468 2014-07-02T01:35:33Z
github.com/strukturag/phoenix git 31b7f25f4815e6e0b8e7c4010f6e9a71c4165b19 2016-06-01T11:34:58Z
github.com/strukturag/phoenix git 8c65e1692d19e1ea84c79d8346bee5747c8ef69c 2016-10-05T15:12:02Z
github.com/strukturag/sloth git 74a8bcf67368de59baafe5d3e17aee9875564cfc 2015-04-22T08:59:42Z
github.com/strukturag/spreed-turnservicecli git b8e469d5ef70aebcfbf24555febe96467892f30f 2016-11-17T16:24:08Z

1 github.com/dlintw/goconf git dcc070983490608a14480e3bf943bad464785df5 2012-02-28T08:26:10Z
3 github.com/gorilla/mux git ba336c9cfb43552c90de6cb2ceedd3271c747558 2015-07-17T15:03:03Z
4 github.com/gorilla/securecookie git aeade84400a85c6875264ae51c7a56ecdcb61751 2015-07-16T23:32:44Z
5 github.com/gorilla/websocket git a69d25be2fe2923a97c2af6849b2f52426f68fc0 2016-08-02T13:32:03Z
6 github.com/longsleep/pkac git 68bf8859f58dd84332ee41c07eba357fb3818ba3 302922ac7627196c8cf9e4384cccadf9aa008b3e 2014-05-01T18:13:13Z 2017-02-16T19:00:44Z
7 github.com/nats-io/nats git 355b5b97e0842dc94f1106729aa88e33e06317ca 2015-12-09T21:13:14Z
8 github.com/satori/go.uuid git 879c5887cd475cd7864858769793b2ceb0d44feb 2016-06-07T14:43:47Z
9 github.com/strukturag/goacceptlanguageparser git 68066e68c2940059aadc6e19661610cf428b6647 2014-02-13T13:31:23Z
10 github.com/strukturag/httputils git afbf05c71ac03ee7989c96d033a9571ba4ded468 2014-07-02T01:35:33Z
11 github.com/strukturag/phoenix git 31b7f25f4815e6e0b8e7c4010f6e9a71c4165b19 8c65e1692d19e1ea84c79d8346bee5747c8ef69c 2016-06-01T11:34:58Z 2016-10-05T15:12:02Z
12 github.com/strukturag/sloth git 74a8bcf67368de59baafe5d3e17aee9875564cfc 2015-04-22T08:59:42Z
13 github.com/strukturag/spreed-turnservicecli git b8e469d5ef70aebcfbf24555febe96467892f30f 2016-11-17T16:24:08Z

79
doc/CHANNELING-API.txt

@ -122,11 +122,11 @@ Special purpose documents for channling @@ -122,11 +122,11 @@ Special purpose documents for channling
"username": "turn-username",
"password": "turn-password",
"ttl": 3600
"urls": {
"urls": [
"turn:213.203.211.154:3478?transport=udp",
"turn:213.203.211.154:3479?transport=tcp",
"turns:213.203.211.154:443?transport=tcp"
}
]
},
"Stun": [
"stun:213.203.211.154:443"
@ -150,12 +150,8 @@ Special purpose documents for channling @@ -150,12 +150,8 @@ Special purpose documents for channling
Version : Server version number. Use this to detect server upgrades.
ApiVersion : Server channeling API base version. Use this version to select
client side compatibility with the connected server.
Turn : Mapping (interface{}) to contain TURN server details, like
urls, password and username. See
http://tools.ietf.org/html/draft-uberti-behave-turn-rest-00
and TURN REST API section in
https://code.google.com/p/rfc5766-turn-server/wiki/turnserver
for details.
Turn : Mapping (interface{}) to contain TURN server details. See
TURN credentials and URNs section below for specification.
Stun : Array with STUN server URLs.
You can also send an empty Self document to the server to make the server
@ -524,6 +520,19 @@ Additional types for session listing and notifications @@ -524,6 +520,19 @@ Additional types for session listing and notifications
The Alive value is a timestamp integer in milliseconds (unix time).
TurnUpdate
{
"Type": "TurnUpdate",
"TurnUpdate": {
"Turn": {...}
}
}
The server might send a TurnUpdate document at any time. If received by the
client, the Turn details from the Self document have changed and the client
should use the updated data as received in the TurnUpdate document.
User authorization and session authentication
@ -1035,6 +1044,58 @@ File sharing data channel protocol @@ -1035,6 +1044,58 @@ File sharing data channel protocol
message was received.
TURN cedentials and URNs document
TURN example data
{
"username": "turn-username",
"password": "turn-password",
"ttl": 3600
"urls": [
"turn:213.203.211.154:3478?transport=udp",
"turn:213.203.211.154:3479?transport=tcp",
"turns:213.203.211.154:443?transport=tcp"
]
}
TURN data is provided as a mapping of key value pairs like `urls`, `password`
and `username`. See http://tools.ietf.org/html/draft-uberti-behave-turn-rest-00
and the TURN REST API section in https://github.com/coturn/coturn for details.
In addition, if the service is configured to use a TURN service to provide
credentials for TURN, it can can contain `geo_uri` and `servers` as returned
by the TURN service client. See https://github.com/strukturag/spreed-turnservicecli
for documentation.
TURN example data with TURN service
{
"geo_uri": "https://turnservice.spreed.me/api/v1/turn/geo",
"servers": [
{
"prio": 10,
"urns": [
"turn:213.203.211.154:3478?transport=udp",
"turn:213.203.211.154:3479?transport=tcp",
"turns:213.203.211.154:443?transport=tcp"
],
"id": "zone-id"
}
],
"ttl": 3600,
"username": "turn-username",
"password": "turn-password"
}
The TURN credentials data does expire after a while and needs to be refreshed
before the `ttl` is reached. The `ttl` is the number of seconds from now until
the data expires. To refresh the TURN data, a `Self` document with empty data
should be sent.
All values in the TURN data except the `ttl` are optional.
End of Channeling API.
For latest version of Spreed WebRTC check
@ -1043,4 +1104,4 @@ https://github.com/strukturag/spreed-webrtc @@ -1043,4 +1104,4 @@ https://github.com/strukturag/spreed-webrtc
For questions, contact mailto:opensource@struktur.de.
(c)2014 struktur AG
(c)2016 struktur AG

51
doc/turn/block-udp-for-turn-test.sh

@ -0,0 +1,51 @@ @@ -0,0 +1,51 @@
#!/bin/sh
#
# This script blocks all outbound and inbound DNS except DNS. If all UDP is
# blocked, the only way to do a peer to peer connection is with a TURN server
# which supports tcp.
#
# NOTE: this script requires Linux and must be run as root/sudo.
#
# (c)2016 struktur AG
# http://www.struktur.de
set -e
RETVAL=0
run() {
set -x
local mode=$1
iptables $mode INPUT -p udp --sport 53 -j ACCEPT
iptables $mode INPUT -p udp --dport 53 -j ACCEPT
iptables $mode OUTPUT -p udp --sport 53 -j ACCEPT
iptables $mode OUTPUT -p udp --dport 53 -j ACCEPT
iptables $mode INPUT -p udp -j DROP
iptables $mode OUTPUT -p udp -j DROP
set +x
}
start() {
run -A
}
stop() {
set +e
run -D
set -e
}
case "$1" in
start)
start
;;
stop)
stop
;;
*)
echo "Usage: $0 [start|stop]"
RETVAL=1
;;
esac
exit $RETVAL

6
go/channelling/api/api.go

@ -74,7 +74,7 @@ func New(config *channelling.Config, @@ -74,7 +74,7 @@ func New(config *channelling.Config,
func (api *channellingAPI) OnConnect(client *channelling.Client, session *channelling.Session) (interface{}, error) {
api.Unicaster.OnConnect(client, session)
self, err := api.HandleSelf(session)
self, err := api.HandleSelf(client, session)
if err == nil {
api.BusManager.Trigger(channelling.BusManagerConnect, session.Id, "", nil, nil)
}
@ -90,7 +90,7 @@ func (api *channellingAPI) OnIncoming(sender channelling.Sender, session *channe @@ -90,7 +90,7 @@ func (api *channellingAPI) OnIncoming(sender channelling.Sender, session *channe
var pipeline *channelling.Pipeline
switch msg.Type {
case "Self":
return api.HandleSelf(session)
return api.HandleSelf(sender, session)
case "Hello":
if msg.Hello == nil {
return nil, channelling.NewDataError("bad_request", "message did not contain Hello")
@ -138,7 +138,7 @@ func (api *channellingAPI) OnIncoming(sender channelling.Sender, session *channe @@ -138,7 +138,7 @@ func (api *channellingAPI) OnIncoming(sender channelling.Sender, session *channe
return nil, channelling.NewDataError("bad_request", "message did not contain Authentication")
}
return api.HandleAuthentication(session, msg.Authentication.Authentication)
return api.HandleAuthentication(sender, session, msg.Authentication.Authentication)
case "Bye":
if msg.Bye == nil {
log.Println("Received invalid bye message.", msg)

3
go/channelling/api/api_test.go

@ -42,6 +42,9 @@ func (fake *fakeClient) Index() uint64 { @@ -42,6 +42,9 @@ func (fake *fakeClient) Index() uint64 {
func (fake *fakeClient) Send(_ buffercache.Buffer) {
}
func (fake *fakeClient) Outgoing(_ interface{}) {
}
type fakeRoomManager struct {
joinedRoomID string
leftRoomID string

4
go/channelling/api/handle_authentication.go

@ -27,14 +27,14 @@ import ( @@ -27,14 +27,14 @@ import (
"github.com/strukturag/spreed-webrtc/go/channelling"
)
func (api *channellingAPI) HandleAuthentication(session *channelling.Session, st *channelling.SessionToken) (*channelling.DataSelf, error) {
func (api *channellingAPI) HandleAuthentication(sender channelling.Sender, session *channelling.Session, st *channelling.SessionToken) (*channelling.DataSelf, error) {
if err := api.SessionManager.Authenticate(session, st, ""); err != nil {
log.Println("Authentication failed", err, st.Userid, st.Nonce)
return nil, err
}
log.Println("Authentication success", session.Userid())
self, err := api.HandleSelf(session)
self, err := api.HandleSelf(sender, session)
if err == nil {
session.BroadcastStatus()
}

4
go/channelling/api/handle_self.go

@ -27,7 +27,7 @@ import ( @@ -27,7 +27,7 @@ import (
"github.com/strukturag/spreed-webrtc/go/channelling"
)
func (api *channellingAPI) HandleSelf(session *channelling.Session) (*channelling.DataSelf, error) {
func (api *channellingAPI) HandleSelf(sender channelling.Sender, session *channelling.Session) (*channelling.DataSelf, error) {
token, err := api.SessionEncoder.EncodeSessionToken(session)
if err != nil {
log.Println("Error in OnRegister", err)
@ -44,7 +44,7 @@ func (api *channellingAPI) HandleSelf(session *channelling.Session) (*channellin @@ -44,7 +44,7 @@ func (api *channellingAPI) HandleSelf(session *channelling.Session) (*channellin
Token: token,
Version: api.config.Version,
ApiVersion: apiVersion,
Turn: api.TurnDataCreator.CreateTurnData(session),
Turn: api.TurnDataCreator.CreateTurnData(sender, session),
Stun: api.config.StunURIs,
}
api.BusManager.Trigger(channelling.BusManagerSession, session.Id, session.Userid(), nil, nil)

65
go/channelling/bus_manager.go

@ -49,6 +49,8 @@ type BusManager interface { @@ -49,6 +49,8 @@ type BusManager interface {
Start()
Publish(subject string, v interface{}) error
Request(subject string, v interface{}, vPtr interface{}, timeout time.Duration) error
BusRequest(subject string, v *BusRequest, vPtr interface{}, timeout time.Duration) error
BusRequestWithRetry(subject string, v *BusRequest, vPtr interface{}, timeout time.Duration, singleTimeout *time.Duration) error
Trigger(name, from, payload string, data interface{}, pipeline *Pipeline) error
Subscribe(subject string, cb nats.Handler) (*nats.Subscription, error)
BindRecvChan(subject string, channel interface{}) (*nats.Subscription, error)
@ -68,6 +70,14 @@ type BusTrigger struct { @@ -68,6 +70,14 @@ type BusTrigger struct {
Pipeline string `json:",omitempty"`
}
// A BusRequest is a simple generic to allow sending arbitrary
// Requests to the bus.
type BusRequest struct {
Id string
Type string
Data interface{} `json:",omitempty"`
}
// BusSubjectTrigger returns the bus subject for trigger payloads.
func BusSubjectTrigger(prefix, suffix string) string {
return fmt.Sprintf("%s.%s", prefix, suffix)
@ -76,11 +86,12 @@ func BusSubjectTrigger(prefix, suffix string) string { @@ -76,11 +86,12 @@ func BusSubjectTrigger(prefix, suffix string) string {
// NewBusManager creates and initializes a new BusMager with the
// provided flags for NATS support. It is intended to connect the
// backend bus with a easy to use API to send and receive bus data.
func NewBusManager(apiConsumer ChannellingAPIConsumer, id string, useNats bool, subjectPrefix string) BusManager {
func NewBusManager(apiConsumer ChannellingAPIConsumer, id string, useNats bool, triggerSubjectPrefix string) BusManager {
var b BusManager
var err error
if useNats {
b, err = newNatsBus(apiConsumer, id, subjectPrefix)
log.Println("NATS enabled", useNats, id)
b, err = newNatsBus(apiConsumer, id, triggerSubjectPrefix)
if err == nil {
log.Println("NATS bus connected")
} else {
@ -111,6 +122,16 @@ func (bus *noopBus) Request(subject string, v interface{}, vPtr interface{}, tim @@ -111,6 +122,16 @@ func (bus *noopBus) Request(subject string, v interface{}, vPtr interface{}, tim
return nil
}
func (bus *noopBus) BusRequest(subject string, v *BusRequest, vPtr interface{}, timeout time.Duration) error {
v.Id = bus.id
return nil
}
func (bus *noopBus) BusRequestWithRetry(subject string, v *BusRequest, vPtr interface{}, timeout time.Duration, singleTimeout *time.Duration) error {
v.Id = bus.id
return nil
}
func (bus *noopBus) Trigger(name, from, payload string, data interface{}, pipeline *Pipeline) error {
return nil
}
@ -137,24 +158,21 @@ func (bus *noopBus) CreateSink(id string) Sink { @@ -137,24 +158,21 @@ func (bus *noopBus) CreateSink(id string) Sink {
type natsBus struct {
ChannellingAPIConsumer
id string
prefix string
ec *natsconnection.EncodedConnection
triggerQueue chan *busQueueEntry
id string
triggerPrefix string
ec *natsconnection.EncodedConnection
triggerQueue chan *busQueueEntry
}
func newNatsBus(apiConsumer ChannellingAPIConsumer, id, prefix string) (*natsBus, error) {
func newNatsBus(apiConsumer ChannellingAPIConsumer, id, triggerPrefix string) (*natsBus, error) {
ec, err := natsconnection.EstablishJSONEncodedConnection(nil)
if err != nil {
return nil, err
}
if prefix == "" {
prefix = "channelling.trigger"
}
// Create buffered channel for outbound NATS data.
triggerQueue := make(chan *busQueueEntry, 50)
return &natsBus{apiConsumer, id, prefix, ec, triggerQueue}, nil
return &natsBus{apiConsumer, id, triggerPrefix, ec, triggerQueue}, nil
}
func (bus *natsBus) Start() {
@ -171,7 +189,28 @@ func (bus *natsBus) Request(subject string, v interface{}, vPtr interface{}, tim @@ -171,7 +189,28 @@ func (bus *natsBus) Request(subject string, v interface{}, vPtr interface{}, tim
return bus.ec.Request(subject, v, vPtr, timeout)
}
func (bus *natsBus) BusRequest(subject string, v *BusRequest, vPtr interface{}, timeout time.Duration) error {
v.Id = bus.id
return bus.Request(subject, v, vPtr, timeout)
}
func (bus *natsBus) BusRequestWithRetry(subject string, v *BusRequest, vPtr interface{}, timeout time.Duration, singleTimeout *time.Duration) error {
if singleTimeout == nil {
singleTimeout = &natsconnection.DefaultRequestTimeout
}
v.Id = bus.id
err := natsconnection.CallFuncWithRetry(func() error {
return bus.Request(subject, v, vPtr, *singleTimeout)
}, timeout)
return err
}
func (bus *natsBus) Trigger(name, from, payload string, data interface{}, pipeline *Pipeline) (err error) {
if bus.triggerPrefix == "" {
// Trigger disabled.
return nil
}
trigger := &BusTrigger{
Id: bus.id,
Name: name,
@ -182,7 +221,7 @@ func (bus *natsBus) Trigger(name, from, payload string, data interface{}, pipeli @@ -182,7 +221,7 @@ func (bus *natsBus) Trigger(name, from, payload string, data interface{}, pipeli
if pipeline != nil {
trigger.Pipeline = pipeline.GetID()
}
entry := &busQueueEntry{BusSubjectTrigger(bus.prefix, name), trigger}
entry := &busQueueEntry{BusSubjectTrigger(bus.triggerPrefix, name), trigger}
select {
case bus.triggerQueue <- entry:
// sent ok
@ -195,7 +234,7 @@ func (bus *natsBus) Trigger(name, from, payload string, data interface{}, pipeli @@ -195,7 +234,7 @@ func (bus *natsBus) Trigger(name, from, payload string, data interface{}, pipeli
}
func (bus *natsBus) PrefixSubject(sub string) string {
return fmt.Sprintf("%s.%s", bus.prefix, sub)
return fmt.Sprintf("%s.%s", bus.triggerPrefix, sub)
}
func (bus *natsBus) Subscribe(subject string, cb nats.Handler) (*nats.Subscription, error) {

7
go/channelling/client.go

@ -30,6 +30,7 @@ import ( @@ -30,6 +30,7 @@ import (
type Sender interface {
Index() uint64
Send(buffercache.Buffer)
Outgoing(interface{})
}
type Client struct {
@ -50,7 +51,7 @@ func NewClient(codec Codec, api ChannellingAPI, session *Session) *Client { @@ -50,7 +51,7 @@ func NewClient(codec Codec, api ChannellingAPI, session *Session) *Client {
func (client *Client) OnConnect(conn Connection) {
client.Connection = conn
if reply, err := client.ChannellingAPI.OnConnect(client, client.session); err == nil {
client.reply("", reply)
client.Outgoing(reply)
} else {
log.Println("OnConnect error", err)
}
@ -85,6 +86,10 @@ func (client *Client) reply(iid string, m interface{}) { @@ -85,6 +86,10 @@ func (client *Client) reply(iid string, m interface{}) {
}
}
func (client *Client) Outgoing(m interface{}) {
client.reply("", m)
}
func (client *Client) Session() *Session {
return client.session
}

5
go/channelling/config.go

@ -14,6 +14,8 @@ type Config struct { @@ -14,6 +14,8 @@ type Config struct {
Renegotiation bool // Renegotiation flag
StunURIs []string // STUN server URIs
TurnURIs []string // TURN server URIs
TurnUsername string // Username for TURN server
TurnPassword string // Password for TURN server
Tokens bool // True when we got a tokens file
Version string // Server version number
UsersEnabled bool // Flag if users are enabled
@ -30,6 +32,9 @@ type Config struct { @@ -30,6 +32,9 @@ type Config struct {
ContentSecurityPolicyReportOnly string `json:"-"` // HTML content security policy in report only mode
RoomTypeDefault string `json:"-"` // New rooms default to this type
RoomTypes map[*regexp.Regexp]string `json:"-"` // Map of regular expression -> room type
RoomNameCaseSensitive bool // Whether the room names are case sensitive.
LockedRoomJoinableWithPIN bool // Whether locked rooms should be joinable by providing the PIN the room was locked with
PublicRoomNames *regexp.Regexp `json:"-"` // Regular expression that specifies room paths that may be created/joined without a user account.
}
func (config *Config) WithModule(m string) bool {

19
go/channelling/data.go

@ -21,6 +21,10 @@ @@ -21,6 +21,10 @@
package channelling
import (
"github.com/strukturag/spreed-turnservicecli/turnservicecli"
)
type DataError struct {
Type string
Code string
@ -91,11 +95,18 @@ type DataSelf struct { @@ -91,11 +95,18 @@ type DataSelf struct {
Stun []string
}
type DataTurnUpdate struct {
Type string
Turn *DataTurn
}
type DataTurn struct {
Username string `json:"username"`
Password string `json:"password"`
Ttl int `json:"ttl"`
Urls []string `json:"urls"`
Username string `json:"username"`
Password string `json:"password"`
Ttl int `json:"ttl"`
Urls []string `json:"urls,omitempty"`
Servers []*turnservicecli.URNsWithID `json:"servers,omitempty"`
GeoURI string `json:"geo_uri,omitempty"`
}
type DataSession struct {

48
go/channelling/hub.go

@ -91,25 +91,39 @@ func (h *hub) ClientInfo(details bool) (clientCount int, sessions map[string]*Da @@ -91,25 +91,39 @@ func (h *hub) ClientInfo(details bool) (clientCount int, sessions map[string]*Da
return
}
func (h *hub) CreateTurnData(session *Session) *DataTurn {
// Create turn data credentials for shared secret auth with TURN
// server. See http://tools.ietf.org/html/draft-uberti-behave-turn-rest-00
// and https://code.google.com/p/rfc5766-turn-server/ REST API auth
// and set shared secret in TURN server with static-auth-secret.
if len(h.turnSecret) == 0 {
return &DataTurn{}
func (h *hub) CreateTurnData(sender Sender, session *Session) *DataTurn {
if len(h.turnSecret) > 0 {
// Create turn data credentials for shared secret auth with TURN
// server. See http://tools.ietf.org/html/draft-uberti-behave-turn-rest-00
// and https://code.google.com/p/rfc5766-turn-server/ REST API auth
// and set shared secret in TURN server with static-auth-secret.
id := session.Id
bar := sha256.New()
bar.Write([]byte(id))
id = base64.StdEncoding.EncodeToString(bar.Sum(nil))
foo := hmac.New(sha1.New, h.turnSecret)
expiration := int32(time.Now().Unix()) + turnTTL
user := fmt.Sprintf("%d:%s", expiration, id)
foo.Write([]byte(user))
password := base64.StdEncoding.EncodeToString(foo.Sum(nil))
return &DataTurn{
Username: user,
Password: password,
Ttl: turnTTL,
Urls: h.config.TurnURIs,
}
}
if h.config.TurnUsername != "" && h.config.TurnPassword != "" {
return &DataTurn{
Username: h.config.TurnUsername,
Password: h.config.TurnPassword,
Urls: h.config.TurnURIs,
}
}
id := session.Id
bar := sha256.New()
bar.Write([]byte(id))
id = base64.StdEncoding.EncodeToString(bar.Sum(nil))
foo := hmac.New(sha1.New, h.turnSecret)
expiration := int32(time.Now().Unix()) + turnTTL
user := fmt.Sprintf("%d:%s", expiration, id)
foo.Write([]byte(user))
password := base64.StdEncoding.EncodeToString(foo.Sum(nil))
return &DataTurn{user, password, turnTTL, h.config.TurnURIs}
return &DataTurn{}
}
func (h *hub) GetSession(id string) (session *Session, ok bool) {

4
go/channelling/pipeline.go

@ -112,6 +112,10 @@ func (pipeline *Pipeline) Send(b buffercache.Buffer) { @@ -112,6 +112,10 @@ func (pipeline *Pipeline) Send(b buffercache.Buffer) {
// Noop.
}
func (pipeline *Pipeline) Outgoing(m interface{}) {
// Noop.
}
func (pipeline *Pipeline) Index() uint64 {
return 0
}

34
go/channelling/room_manager.go

@ -24,6 +24,7 @@ package channelling @@ -24,6 +24,7 @@ package channelling
import (
"fmt"
"log"
"strings"
"sync"
"github.com/nats-io/nats"
@ -63,6 +64,7 @@ type roomManager struct { @@ -63,6 +64,7 @@ type roomManager struct {
roomTypes map[string]string
globalRoomID string
defaultRoomID string
caseSensitive bool
}
type roomTypeMessage struct {
@ -77,6 +79,7 @@ func NewRoomManager(config *Config, encoder OutgoingEncoder) RoomManager { @@ -77,6 +79,7 @@ func NewRoomManager(config *Config, encoder OutgoingEncoder) RoomManager {
OutgoingEncoder: encoder,
roomTable: make(map[string]RoomWorker),
roomTypes: make(map[string]string),
caseSensitive: config.RoomNameCaseSensitive,
}
if config.GlobalRoomID != "" {
rm.globalRoomID = rm.MakeRoomID(config.GlobalRoomID, "")
@ -106,6 +109,9 @@ func (rooms *roomManager) setNatsRoomType(msg *roomTypeMessage) { @@ -106,6 +109,9 @@ func (rooms *roomManager) setNatsRoomType(msg *roomTypeMessage) {
return
}
// TODO(fancycode): Should we use a separate mutex for this?
rooms.Lock()
defer rooms.Unlock()
if msg.Type != "" {
log.Printf("Setting room type for %s to %s\n", msg.Path, msg.Type)
rooms.roomTypes[msg.Path] = msg.Type
@ -203,9 +209,17 @@ func (rooms *roomManager) Get(roomID string) (room RoomWorker, ok bool) { @@ -203,9 +209,17 @@ func (rooms *roomManager) Get(roomID string) (room RoomWorker, ok bool) {
return
}
func (rooms *roomManager) isPublicRoom(roomName string) bool {
return rooms.PublicRoomNames != nil &&
rooms.PublicRoomNames.MatchString(roomName)
}
func (rooms *roomManager) GetOrCreate(roomID, roomName, roomType string, credentials *DataRoomCredentials, sessionAuthenticated bool) (RoomWorker, error) {
isPublic := false
if rooms.AuthorizeRoomJoin && rooms.UsersEnabled && !sessionAuthenticated {
return nil, NewDataError("room_join_requires_account", "Room join requires a user account")
if isPublic = rooms.isPublicRoom(roomName); !isPublic {
return nil, NewDataError("room_join_requires_account", "Room join requires a user account")
}
}
if room, ok := rooms.Get(roomID); ok {
@ -225,8 +239,13 @@ func (rooms *roomManager) GetOrCreate(roomID, roomName, roomType string, credent @@ -225,8 +239,13 @@ func (rooms *roomManager) GetOrCreate(roomID, roomName, roomType string, credent
}
if rooms.UsersEnabled && rooms.AuthorizeRoomCreation && !sessionAuthenticated {
rooms.Unlock()
return nil, NewDataError("room_join_requires_account", "Room creation requires a user account")
// Only need to check for public room if not checked above.
if !isPublic {
if isPublic = rooms.isPublicRoom(roomName); !isPublic {
rooms.Unlock()
return nil, NewDataError("room_join_requires_account", "Room creation requires a user account")
}
}
}
room := NewRoomWorker(rooms, roomID, roomName, roomType, credentials)
@ -264,11 +283,18 @@ func (rooms *roomManager) MakeRoomID(roomName, roomType string) string { @@ -264,11 +283,18 @@ func (rooms *roomManager) MakeRoomID(roomName, roomType string) string {
roomType = rooms.getConfiguredRoomType(roomName)
}
if !rooms.caseSensitive {
roomName = strings.ToLower(roomName)
}
return fmt.Sprintf("%s:%s", roomType, roomName)
}
func (rooms *roomManager) getConfiguredRoomType(roomName string) string {
if roomType, found := rooms.roomTypes[roomName]; found {
// TODO(fancycode): Should we use a separate mutex for this?
rooms.RLock()
roomType, found := rooms.roomTypes[roomName]
rooms.RUnlock()
if found {
// Type of this room was overwritten through NATS.
return roomType
}

50
go/channelling/room_manager_test.go

@ -22,6 +22,7 @@ @@ -22,6 +22,7 @@
package channelling
import (
"regexp"
"testing"
"github.com/strukturag/spreed-webrtc/go/channelling"
@ -74,6 +75,55 @@ func Test_RoomManager_JoinRoom_ReturnsAnErrorForUnauthenticatedSessionsWhenJoinR @@ -74,6 +75,55 @@ func Test_RoomManager_JoinRoom_ReturnsAnErrorForUnauthenticatedSessionsWhenJoinR
assertDataError(t, err, "room_join_requires_account")
}
func Test_RoomManager_JoinPublicRoom_ForUnauthenticatedSessionsWhenCreationRequiresAnAccount(t *testing.T) {
roomManager, config := NewTestRoomManager()
config.UsersEnabled = true
config.AuthorizeRoomCreation = true
unauthenticatedSession := &Session{}
_, err := roomManager.JoinRoom(channelling.RoomTypeRoom+":public", "public", channelling.RoomTypeRoom, nil, unauthenticatedSession, false, nil)
assertDataError(t, err, "room_join_requires_account")
config.PublicRoomNames = regexp.MustCompile("^public$")
_, err = roomManager.JoinRoom(channelling.RoomTypeRoom+":public", "public", channelling.RoomTypeRoom, nil, unauthenticatedSession, false, nil)
if err != nil {
t.Fatalf("Unexpected error %v joining public room", err)
}
_, err = roomManager.JoinRoom(channelling.RoomTypeRoom+":private", "private", channelling.RoomTypeRoom, nil, unauthenticatedSession, false, nil)
assertDataError(t, err, "room_join_requires_account")
}
func Test_RoomManager_JoinPublicRoom_ForUnauthenticatedSessionsWhenJoinRequiresAnAccount(t *testing.T) {
roomManager, config := NewTestRoomManager()
config.UsersEnabled = true
config.AuthorizeRoomJoin = true
authenticatedSession := &Session{userid: "9870457"}
_, err := roomManager.JoinRoom(channelling.RoomTypeRoom+":public", "public", channelling.RoomTypeRoom, nil, authenticatedSession, true, nil)
if err != nil {
t.Fatalf("Unexpected error %v joining room while authenticated", err)
}
unauthenticatedSession := &Session{}
_, err = roomManager.JoinRoom(channelling.RoomTypeRoom+":public", "public", channelling.RoomTypeRoom, nil, unauthenticatedSession, false, nil)
assertDataError(t, err, "room_join_requires_account")
config.PublicRoomNames = regexp.MustCompile("^public$")
_, err = roomManager.JoinRoom(channelling.RoomTypeRoom+":public", "public", channelling.RoomTypeRoom, nil, unauthenticatedSession, false, nil)
if err != nil {
t.Fatalf("Unexpected error %v joining public room", err)
}
_, err = roomManager.JoinRoom(channelling.RoomTypeRoom+":private", "private", channelling.RoomTypeRoom, nil, authenticatedSession, true, nil)
if err != nil {
t.Fatalf("Unexpected error %v joining room while authenticated", err)
}
_, err = roomManager.JoinRoom(channelling.RoomTypeRoom+":private", "private", channelling.RoomTypeRoom, nil, unauthenticatedSession, false, nil)
assertDataError(t, err, "room_join_requires_account")
}
func Test_RoomManager_UpdateRoom_ReturnsAnErrorIfNoRoomHasBeenJoined(t *testing.T) {
roomManager, _ := NewTestRoomManager()
_, err := roomManager.UpdateRoom(&Session{}, nil)

16
go/channelling/server/config.go

@ -83,6 +83,7 @@ func NewConfig(container phoenix.Container, tokens bool) (*channelling.Config, e @@ -83,6 +83,7 @@ func NewConfig(container phoenix.Container, tokens bool) (*channelling.Config, e
"youtube": true,
"presentation": true,
"contacts": true,
"roomlocking": true,
}
modules := []string{}
for module := range modulesTable {
@ -118,6 +119,16 @@ func NewConfig(container phoenix.Container, tokens bool) (*channelling.Config, e @@ -118,6 +119,16 @@ func NewConfig(container phoenix.Container, tokens bool) (*channelling.Config, e
}
}
publicRoomNamesString := container.GetStringDefault("app", "publicRooms", "")
var publicRoomNames *regexp.Regexp
if publicRoomNamesString != "" {
var err error
if publicRoomNames, err = regexp.Compile(publicRoomNamesString); err != nil {
return nil, fmt.Errorf("Invalid regular expression '%s': %s", publicRoomNamesString, err)
}
log.Printf("Allowed public rooms: %s\n", publicRoomNamesString)
}
return &channelling.Config{
Title: container.GetStringDefault("app", "title", "Spreed WebRTC"),
Ver: ver,
@ -127,6 +138,8 @@ func NewConfig(container phoenix.Container, tokens bool) (*channelling.Config, e @@ -127,6 +138,8 @@ func NewConfig(container phoenix.Container, tokens bool) (*channelling.Config, e
Renegotiation: container.GetBoolDefault("app", "renegotiation", false),
StunURIs: stunURIs,
TurnURIs: turnURIs,
TurnUsername: container.GetStringDefault("app", "turnUsername", ""),
TurnPassword: container.GetStringDefault("app", "turnPassword", ""),
Tokens: tokens,
Version: version,
UsersEnabled: container.GetBoolDefault("users", "enabled", false),
@ -143,6 +156,9 @@ func NewConfig(container phoenix.Container, tokens bool) (*channelling.Config, e @@ -143,6 +156,9 @@ func NewConfig(container phoenix.Container, tokens bool) (*channelling.Config, e
ContentSecurityPolicyReportOnly: container.GetStringDefault("app", "contentSecurityPolicyReportOnly", ""),
RoomTypeDefault: defaultRoomType,
RoomTypes: roomTypes,
RoomNameCaseSensitive: container.GetBoolDefault("app", "caseSensitiveRooms", false),
LockedRoomJoinableWithPIN: container.GetBoolDefault("app", "lockedRoomJoinableWithPIN", true),
PublicRoomNames: publicRoomNames,
}, nil
}

6
go/channelling/server/rooms.go

@ -24,6 +24,7 @@ package server @@ -24,6 +24,7 @@ package server
import (
"fmt"
"net/http"
"strings"
"github.com/strukturag/spreed-webrtc/go/randomstring"
)
@ -34,11 +35,16 @@ type Room struct { @@ -34,11 +35,16 @@ type Room struct {
}
type Rooms struct {
CaseSensitive bool
}
func (rooms *Rooms) Post(request *http.Request) (int, interface{}, http.Header) {
name := randomstring.NewRandomString(11)
if !rooms.CaseSensitive {
name = strings.ToLower(name)
}
return 200, &Room{name, fmt.Sprintf("/%s", name)}, http.Header{"Content-Type": {"application/json"}}
}

2
go/channelling/turndata.go

@ -22,5 +22,5 @@ @@ -22,5 +22,5 @@
package channelling
type TurnDataCreator interface {
CreateTurnData(*Session) *DataTurn
CreateTurnData(Sender, *Session) *DataTurn
}

140
go/channelling/turnservice_manager.go

@ -0,0 +1,140 @@ @@ -0,0 +1,140 @@
/*
* Spreed WebRTC.
* Copyright (C) 2013-2016 struktur AG
*
* This file is part of Spreed WebRTC.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package channelling
import (
"log"
"sync"
"time"
"github.com/strukturag/spreed-turnservicecli/turnservicecli"
)
type TURNServiceManager interface {
TurnDataCreator
}
type turnServiceManager struct {
sync.Mutex
pleaders map[uint64]Sender // Mapping of clients waiting to receive TURN data.
uri string
accessToken string
clientID string
turnService *turnservicecli.TURNService
}
func NewTURNServiceManager(uri string, accessToken string, clientID string) TURNServiceManager {
turnService := turnservicecli.NewTURNService(uri, 0, nil)
mgr := &turnServiceManager{
uri: uri,
accessToken: accessToken,
clientID: clientID,
turnService: turnService,
pleaders: make(map[uint64]Sender),
}
turnService.Open(accessToken, clientID, "")
turnService.BindOnCredentials(mgr.onCredentials)
log.Println("Fetching TURN credentials from service")
go func() {
//time.Sleep(10000 * time.Millisecond)
turnService.Autorefresh(true)
}()
// Wait a bit, to give TURN service some time to populate credentials, so
// we avoid to have send them as an update for fast reconnecting clients.
time.Sleep(500 * time.Millisecond)
if mgr.turnService.Credentials(false) == nil {
log.Println("No TURN credentials from service on startup - extra traffic for clients connecting before credentials have been received")
}
return mgr
}
func (mgr *turnServiceManager) CreateTurnData(sender Sender, session *Session) *DataTurn {
credentials := mgr.turnService.Credentials(false)
turn, err := mgr.turnData(credentials)
if err != nil || turn.Ttl == 0 {
// When no data was return from service, refresh quickly.
mgr.Lock()
mgr.pleaders[sender.Index()] = sender
mgr.Unlock()
// Have client come back early.
turn.Ttl = 300
}
return turn
}
func (mgr *turnServiceManager) turnData(credentials *turnservicecli.CachedCredentialsData) (*DataTurn, error) {
turn := &DataTurn{}
if credentials != nil {
ttl := credentials.TTL()
if ttl > 0 {
turn.Username = credentials.Turn.Username
turn.Password = credentials.Turn.Password
turn.Servers = credentials.Turn.Servers
turn.Ttl = int(ttl)
turn.GeoURI = credentials.Turn.GeoURI
if len(turn.Servers) > 0 {
// For backwards compatibility with clients which do not
// understand turn.Servers, directly deliver the TURN
// server zone URNs with the lowest priority.
minPrio := 0
minPrioIdx := -1
for idx, server := range turn.Servers {
if minPrioIdx == -1 || server.Prio < minPrio {
minPrio = server.Prio
minPrioIdx = idx
}
}
turn.Urls = turn.Servers[minPrioIdx].URNs
}
}
}
return turn, nil
}
func (mgr *turnServiceManager) onCredentials(credentials *turnservicecli.CachedCredentialsData, err error) {
if err != nil {
log.Printf("TURN credentials service error: %s\n", err.Error())
return
}
log.Println("Received TURN credentials from service", credentials.Turn.Username)
mgr.Lock()
for _, sender := range mgr.pleaders {
if turn, err := mgr.turnData(credentials); err == nil {
sender.Outgoing(&DataTurnUpdate{
Type: "TurnUpdate",
Turn: turn,
})
}
}
mgr.pleaders = make(map[uint64]Sender) // Clear.
mgr.Unlock()
}

62
go/natsconnection/natsconnection.go

@ -8,11 +8,14 @@ import ( @@ -8,11 +8,14 @@ import (
"github.com/nats-io/nats"
)
// DefaultNatsEstablishTimeout is the default timeout for
// DefaultEstablishTimeout is the default timeout for
// calls to EstablishNatsConnection.
var DefaultEstablishTimeout = 60 * time.Second
// DefaultNatsURL is the default NATS server URL used for
// DefaultRequestTimeout is the default timeout for Request() calls.
var DefaultRequestTimeout = 5 * time.Second
// DefaultURL is the default NATS server URL used for
// calls to NewConnection and EstablishConnection.
var DefaultURL = nats.DefaultURL
@ -138,3 +141,58 @@ func EstablishJSONEncodedConnection(timeout *time.Duration) (*EncodedConnection, @@ -138,3 +141,58 @@ func EstablishJSONEncodedConnection(timeout *time.Duration) (*EncodedConnection,
}
return &EncodedConnection{ec}, nil
}
// CallFuncWithRetry retries the given func when it does not return nil
// and the timeout duration has not been reached. It sleeps 1 second between
// each call. If the timeout is 0, the function will be retried forever.
func CallFuncWithRetry(f func() error, timeout time.Duration) error {
errch := make(chan error, 1)
quitch := make(chan bool)
var lastErr error
// Start our worker loop.
go func() {
for {
select {
case <-quitch:
// Quit requested.
return
default:
// Call our target function.
err := f()
switch err {
case nil:
// No error, success.
errch <- err
return
default:
// Remember last error.
lastErr = err
}
time.Sleep(1 * time.Second)
}
}
}()
// Create our wait channel. It either is an empty channel or
// it is filled when the timeout gets reached.
var waitch <-chan time.Time
if timeout == 0 {
// Create empty channel to wait forever.
waitch = make(<-chan time.Time)
} else {
waitch = time.After(timeout)
}
// Wait until something happens, either nil result or timeout.
select {
case err := <-errch:
return err
case <-waitch:
quitch <- true
if lastErr != nil {
return lastErr
}
return errors.New("Call with retry: timeout")
}
}

2
html/head.html

@ -12,4 +12,4 @@ @@ -12,4 +12,4 @@
<link rel="stylesheet" type="text/css" href="<%.Cfg.S%>/css/main.min.css">
<%template "extra-head" .%>
<%.ExtraDHead%>
<script id="globalcontext" type="application/json"><%$%></script><%end%>
<script id="globalcontext" type="application/json"><%json .%></script><%end%>

8
scripts/docker_entrypoint.sh

@ -28,10 +28,10 @@ openssl x509 -in /srv/cert.pem -text @@ -28,10 +28,10 @@ openssl x509 -in /srv/cert.pem -text
if [ "$NEWSECRETS" = "1" -o ! -s /srv/secrets.conf ]; then
echo "Creating new server secrets ..."
rm -f /srv/secrets.conf.tmp
echo "SESSION_SECRET=$(randomhex 32)" >>/srv/secrets.conf.tmp
echo "ENCRYPTION_SECRET=$(randomhex 32)" >>/srv/secrets.conf.tmp
echo "SERVER_TOKEN=$(randomhex 32)" >>/srv/secrets.conf.tmp
echo "SHARED_SECRET=$(randomhex 32)" >>/srv/secrets.conf.tmp
echo "SESSION_SECRET=${SESSION_SECRET:-$(randomhex 32)}" >>/srv/secrets.conf.tmp
echo "ENCRYPTION_SECRET=${ENCRYPTION_SECRET:-$(randomhex 32)}" >>/srv/secrets.conf.tmp
echo "SERVER_TOKEN=${SERVER_TOKEN:-$(randomhex 32)}" >>/srv/secrets.conf.tmp
echo "SHARED_SECRET=${SHARED_SECRET:-$(randomhex 32)}" >>/srv/secrets.conf.tmp
. /srv/secrets.conf.tmp
sed -i -e "s/sessionSecret =.*/sessionSecret = $SESSION_SECRET/" /srv/spreed-webrtc/default.conf
sed -i -e "s/encryptionSecret =.*/encryptionSecret = $ENCRYPTION_SECRET/" /srv/spreed-webrtc/default.conf

47
server.conf.in

@ -57,6 +57,12 @@ listen = 127.0.0.1:8080 @@ -57,6 +57,12 @@ listen = 127.0.0.1:8080
; See http://tools.ietf.org/html/draft-uberti-behave-turn-rest-00 for details.
; A supported TURN server is https://code.google.com/p/rfc5766-turn-server/.
;turnSecret = the-default-turn-shared-secret-do-not-keep
; Fixed username/password credentials to be used for the TURN server.
; IMPORTANT: This will give all users connected to the spreed-webrtc service
; access to the credentials, so in almost all cases the shared secret mode
; should be used instead!!
;turnUsername = the-turn-username
;turnPassword = the-turn-secret
; Enable renegotiation support. Set to true to tell clients that they can
; renegotiate peer connections when required. Firefox support is not complete,
; so do not enable if you want compatibility with Firefox clients.
@ -82,13 +88,23 @@ encryptionSecret = tne-default-encryption-block-key @@ -82,13 +88,23 @@ encryptionSecret = tne-default-encryption-block-key
; all users will join this room if enabled. If it is disabled then a room join
; form will be shown instead.
;defaultRoomEnabled = true
; Whether the room names are case sensitive. If enabled, different casing
; of room names are different rooms. Optional. Defaults to false.
;caseSensitiveRooms = false
; Whether a user account is required to join a room. This only has an effect
; if user accounts are enabled. Optional, defaults to false.
;authorizeRoomJoin = false
; Whether a user account is required to create a room. This only has an effect
; if user accounts are enabled. Optional, defaults to false.
;authorizeRoomCreation = false
; Wether the pipelines API should be enabled. Optional, defaults to false.
; Whether locked rooms should be joinable by providing the PIN the room was
; locked with. Optional, defaults to true.
;lockedRoomJoinableWithPIN = true
; Regular expression specifying room names that can be created / joined without
; a valid user account (even if "authorizeRoomJoin" or "authorizeRoomCreation"
; is enabled).
;publicRooms =
; Whether the pipelines API should be enabled. Optional, defaults to false.
;pipelinesEnabled = false
; Server token is a public random string which is used to enhance security of
; server generated security tokens. When the serverToken is changed all existing
@ -139,6 +155,7 @@ serverRealm = local @@ -139,6 +155,7 @@ serverRealm = local
;youtube = true
;presentation = true
;contacts = true
;roomlocking = true
[log]
;logfile = /var/log/spreed-webrtc-server.log
@ -202,16 +219,29 @@ enabled = false @@ -202,16 +219,29 @@ enabled = false
;allowRegistration = false
[nats]
; Set to true, to connect to NATS on startup. If false, all other settins in the
; [nats] section are ignored. Defaults to false.
;useNATS = false
; Set to true, to enable triggering channelling events via NATS
;channelling_trigger = false
; NATS channel trigger subject. Defaults to 'channelling.trigger'.
;channelling_trigger_subject = channelling.trigger
; NATS server URL
; NATS server URL.
;url = nats://127.0.0.1:4222
; NATS connect establish timeout in seconds
; NATS connect establish timeout in seconds.
;establishTimeout = 60
; Use client_id to distinguish between multipe servers. The value is sent
; together with every NATS request. Defaults to empty.
;client_id =
; Set to true, to load additional configuration settings via NATS. The config
; loaded is applied after loading the [nats] section and all entries extend the
; configuration files with overwrite. Only takes effect when a NATS server URL
; is configured. Defaults to false.
;configFromNATS = false
;configFromNATSSubject = spreed-webrtc.config.get
; NATS config load timeout. If 0, then it waits forever, blocking the startup
; until the configuration was loaded from NATS. Defaults to 0.
;configFromNATSTimeout = 0
[roomtypes]
; You can define room types that should be used for given room names instead of
@ -226,3 +256,14 @@ enabled = false @@ -226,3 +256,14 @@ enabled = false
; Example (all rooms below "conference/" are conference rooms):
;^conference/.+ = Conference
;
[turnService]
; To avoid the setup of a self-owned TURN server, a TURN service can be used.
; When a turnServiceURI is set, TurnURIs in the [app] section are ignored.
;turnServiceURI = https://turnservice.spreed.me
; Access token for the TURN service. This is usually required and provided with
; your TURN service subscription.
;turnServiceAccessToken = some-secret-value
; The ClientID can be used to specify additional credentials if required
; by the TURN service. If empty, the value of turnServiceAccessToken is used.
;turnServiceClientID =

76
src/app/spreed-webrtc-server/main.go

@ -62,6 +62,49 @@ var config *channelling.Config @@ -62,6 +62,49 @@ var config *channelling.Config
func runner(runtime phoenix.Runtime) error {
log.SetFlags(log.LstdFlags | log.Lmicroseconds)
// Nats pub/sub supports.
useNats := runtime.GetBoolDefault("nats", "useNATS", false)
if useNats {
if natsURL, err := runtime.GetString("nats", "url"); err == nil {
if natsURL != "" {
natsconnection.DefaultURL = natsURL
}
}
if natsEstablishTimeout, err := runtime.GetInt("nats", "establishTimeout"); err == nil {
if natsEstablishTimeout != 0 {
natsconnection.DefaultEstablishTimeout = time.Duration(natsEstablishTimeout) * time.Second
}
}
}
natsClientId := runtime.GetStringDefault("nats", "client_id", "")
var natsChannellingTriggerSubject string
if runtime.GetBoolDefault("nats", "channelling_trigger", false) {
natsChannellingTriggerSubject = runtime.GetStringDefault("nats", "channelling_trigger_subject", "channelling.trigger")
}
// Base services.
apiConsumer := channelling.NewChannellingAPIConsumer()
busManager := channelling.NewBusManager(apiConsumer, natsClientId, useNats, natsChannellingTriggerSubject)
// Update configuration from NATS.
if useNats && runtime.GetBoolDefault("nats", "configFromNATS", false) {
log.Println("Fetching configuration from NATS")
configFromNATSSubject := runtime.GetStringDefault("nats", "configFromNATSSubject", "spreed-webrtc.config.get")
configFromNATSTimeout := time.Duration(runtime.GetIntDefault("nats", "configFromNATSTimeout", 0)) * time.Second
// Receive config from bus.
var req = &channelling.BusRequest{}
var res map[string]map[string]string
if err := busManager.BusRequestWithRetry(configFromNATSSubject, req, &res, configFromNATSTimeout, nil); err == nil {
if updateErr := runtime.Update(res); updateErr == nil {
log.Println("Updated configuration from NATS")
} else {
log.Println("Failed to update config with NATS updates", updateErr)
}
} else {
log.Println("Failed to fetch config from NATS", err)
}
}
rootFolder, err := runtime.GetString("http", "root")
if err != nil {
cwd, err2 := os.Getwd()
@ -158,21 +201,6 @@ func runner(runtime phoenix.Runtime) error { @@ -158,21 +201,6 @@ func runner(runtime phoenix.Runtime) error {
tokenProvider = channelling.TokenFileProvider(tokenFile)
}
// Nats pub/sub supports.
natsChannellingTrigger, _ := runtime.GetBool("nats", "channelling_trigger")
natsChannellingTriggerSubject, _ := runtime.GetString("nats", "channelling_trigger_subject")
if natsURL, err := runtime.GetString("nats", "url"); err == nil {
if natsURL != "" {
natsconnection.DefaultURL = natsURL
}
}
if natsEstablishTimeout, err := runtime.GetInt("nats", "establishTimeout"); err == nil {
if natsEstablishTimeout != 0 {
natsconnection.DefaultEstablishTimeout = time.Duration(natsEstablishTimeout) * time.Second
}
}
natsClientId, _ := runtime.GetString("nats", "client_id")
// Load remaining configuration items.
config, err = server.NewConfig(runtime, tokenProvider != nil)
if err != nil {
@ -181,7 +209,7 @@ func runner(runtime phoenix.Runtime) error { @@ -181,7 +209,7 @@ func runner(runtime phoenix.Runtime) error {
// Load templates.
templates = template.New("")
templates.Delims("<%", "%>")
templates.Delims("<%", "%>").Funcs(templateFuncMap())
// Load html templates folder
err = filepath.Walk(path.Join(rootFolder, "html"), func(path string, info os.FileInfo, err error) error {
@ -282,7 +310,6 @@ func runner(runtime phoenix.Runtime) error { @@ -282,7 +310,6 @@ func runner(runtime phoenix.Runtime) error {
}
// Prepare services.
apiConsumer := channelling.NewChannellingAPIConsumer()
buddyImages := channelling.NewImageCache()
codec := channelling.NewCodec(incomingCodecLimit)
roomManager := channelling.NewRoomManager(config, codec)
@ -290,14 +317,23 @@ func runner(runtime phoenix.Runtime) error { @@ -290,14 +317,23 @@ func runner(runtime phoenix.Runtime) error {
tickets := channelling.NewTickets(sessionSecret, encryptionSecret, computedRealm)
sessionManager := channelling.NewSessionManager(config, tickets, hub, roomManager, roomManager, buddyImages, sessionSecret)
statsManager := channelling.NewStatsManager(hub, roomManager, sessionManager)
busManager := channelling.NewBusManager(apiConsumer, natsClientId, natsChannellingTrigger, natsChannellingTriggerSubject)
pipelineManager := channelling.NewPipelineManager(busManager, sessionManager, sessionManager, sessionManager)
if err := roomManager.SetBusManager(busManager); err != nil {
return err
}
// TURN data support.
var turnDataCreator channelling.TurnDataCreator
if turnServiceURI, _ := runtime.GetString("turnService", "turnServiceURI"); turnServiceURI != "" {
log.Printf("Using TURN service: %s\n", turnServiceURI)
turnServiceManager := channelling.NewTURNServiceManager(turnServiceURI, runtime.GetStringDefault("turnService", "turnServiceAccessToken", ""), runtime.GetStringDefault("turnService", "turnServiceClientID", ""))
turnDataCreator = turnServiceManager
} else {
turnDataCreator = hub
}
// Create API.
channellingAPI := api.New(config, roomManager, tickets, sessionManager, statsManager, hub, hub, hub, busManager, pipelineManager)
channellingAPI := api.New(config, roomManager, tickets, sessionManager, statsManager, hub, turnDataCreator, hub, busManager, pipelineManager)
apiConsumer.SetChannellingAPI(channellingAPI)
// Start bus.
@ -317,7 +353,7 @@ func runner(runtime phoenix.Runtime) error { @@ -317,7 +353,7 @@ func runner(runtime phoenix.Runtime) error {
// Add RESTful API end points.
rest := sloth.NewAPI()
rest.SetMux(r.PathPrefix("/api/v1/").Subrouter())
rest.AddResource(&server.Rooms{}, "/rooms")
rest.AddResource(&server.Rooms{config.RoomNameCaseSensitive}, "/rooms")
rest.AddResource(config, "/config")
rest.AddResourceWithWrapper(&server.Tokens{tokenProvider}, httputils.MakeGzipHandler, "/tokens")

39
src/app/spreed-webrtc-server/template.go

@ -0,0 +1,39 @@ @@ -0,0 +1,39 @@
/*
* Spreed WebRTC.
* Copyright (C) 2013-2016 struktur AG
*
* This file is part of Spreed WebRTC.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package main
import (
"encoding/json"
"html/template"
)
func templateFuncMap() template.FuncMap {
return template.FuncMap{
"json": func(obj interface{}) (template.JS, error) {
data, err := json.Marshal(obj)
if err != nil {
return "", err
}
return template.JS(data), nil
},
}
}

50
src/app/spreed-webrtc-server/template_test.go

@ -0,0 +1,50 @@ @@ -0,0 +1,50 @@
/*
* Spreed WebRTC.
* Copyright (C) 2013-2016 struktur AG
*
* This file is part of Spreed WebRTC.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package main
import (
"bytes"
"html/template"
"testing"
)
const (
templateString = `<script type="application/json">{{json .}}</script>`
expectedString = `<script type="application/json">{"name":"Peter"}</script>`
)
type testPerson struct {
Name string `json:"name"`
}
func TestHTMLTemplateWithJSON(t *testing.T) {
tmpl := template.New("").Funcs(templateFuncMap())
if _, err := tmpl.Parse(templateString); err != nil {
t.Fatalf("Could not parse template '%s': %s", templateString, err.Error())
}
buf := bytes.NewBuffer(nil)
tmpl.Execute(buf, testPerson{Name: "Peter"})
out := buf.String()
if out != expectedString {
t.Fatalf("Strings do not match: got '%s', want '%s'", out, expectedString)
}
}

24
src/audio/_build.py

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
#!/usr/bin/python
import audioop
import wave
import os
import glob
@ -9,6 +10,11 @@ import math @@ -9,6 +10,11 @@ import math
silenceDuration = 0.05 # Seconds of silence between merged files
outfile = "sprite1.wav" # Output file. Will be saved in the path below.
# Map containing volume adjustments for some of the files.
AUDIO_FACTORS = {
'end1.wav': 0.5,
}
def main(folder="./files"):
currentTime = 0
@ -16,10 +22,10 @@ def main(folder="./files"): @@ -16,10 +22,10 @@ def main(folder="./files"):
# Open output file
output = wave.open(outfile, 'wb')
# Loop through files in folder and append to outfile
for i, infile in enumerate(glob.glob(os.path.join(folder, '*.wav'))):
for i, infile in enumerate(sorted(glob.glob(os.path.join(folder, '*.wav')))):
# Open file and get info
w = wave.open(infile, 'rb')
soundDuration = w.getnframes() / float(w.getframerate())
@ -32,7 +38,11 @@ def main(folder="./files"): @@ -32,7 +38,11 @@ def main(folder="./files"):
silenceFrames = "".join(wave.struct.pack('h', item) for item in silenceData)
# Output sound + silence to file
output.writeframes(w.readframes(w.getnframes()))
samples = w.readframes(w.getnframes())
factor = AUDIO_FACTORS.get(os.path.basename(infile), None)
if factor is not None:
samples = audioop.mul(samples, w.getsampwidth(), factor)
output.writeframes(samples)
output.writeframes(silenceFrames)
w.close()
@ -47,7 +57,9 @@ def main(folder="./files"): @@ -47,7 +57,9 @@ def main(folder="./files"):
output.close()
# Output howler sprite data
print json.dumps(sprite, sort_keys=True, indent=4, separators=(',', ': '))
sprites = json.dumps(sprite, sort_keys=True, indent=4, separators=(',', ': '))
sprites = '\n'.join([x.rstrip() for x in sprites.split('\n')]) + '\n'
file('sprite1.json', 'wb').write(sprites)
if __name__ == "__main__":
main()

2
src/audio/_encode.sh

@ -7,4 +7,4 @@ avconv -y -i .tmp-sprite1.wav -aq 2 -acodec libvorbis sprite1.ogg @@ -7,4 +7,4 @@ avconv -y -i .tmp-sprite1.wav -aq 2 -acodec libvorbis sprite1.ogg
rm -f .tmp-sprite1.wav
cp -fv sprite1.{mp3,ogg} ../../static/sounds
cp -fv sprite1.{json,mp3,ogg} ../../static/sounds

34
src/audio/sprite1.json

@ -0,0 +1,34 @@ @@ -0,0 +1,34 @@
{
"connect1": [
0,
5179
],
"end1": [
5228,
6199
],
"entry1": [
11476,
3000
],
"leaving1": [
14526,
2126
],
"message1": [
16701,
816
],
"question1": [
17567,
3313
],
"ringtone1": [
20929,
935
],
"whistle1": [
21913,
1405
]
}

BIN
src/audio/sprite1.mp3

Binary file not shown.

BIN
src/audio/sprite1.ogg

Binary file not shown.

BIN
src/audio/sprite1.wav

Binary file not shown.

4
src/i18n/helpers/languages.py

@ -5,12 +5,14 @@ @@ -5,12 +5,14 @@
LANGUAGES = {
"en": "English",
"de": "Deutsch",
"fr": "Français",
"zh-cn": "中文(简体)",
"zh-tw": "繁體中文",
"ko": "한국어",
"ja": "日本語",
"ru": "Русский",
"fr": "Français",
"es": "Español",
"it": "Italiano",
}
import json

19
src/i18n/messages-de.po

@ -8,8 +8,8 @@ msgid "" @@ -8,8 +8,8 @@ msgid ""
msgstr ""
"Project-Id-Version: Spreed WebRTC 1.0\n"
"Report-Msgid-Bugs-To: simon@struktur.de\n"
"POT-Creation-Date: 2016-02-17 14:15+0100\n"
"PO-Revision-Date: 2016-02-17 14:16+0100\n"
"POT-Creation-Date: 2016-08-18 18:21+0200\n"
"PO-Revision-Date: 2016-08-18 18:24+0100\n"
"Last-Translator: Simon Eisenmann <simon@struktur.de>\n"
"Language-Team: struktur AG <opensource@struktur.de>\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
@ -307,9 +307,6 @@ msgstr "" @@ -307,9 +307,6 @@ msgstr ""
msgid "Your ID"
msgstr "Ihre ID"
msgid "Register"
msgstr "Registrieren"
msgid ""
"Authenticated by certificate. To log out you have to remove your "
"certificate from the browser."
@ -457,6 +454,9 @@ msgstr "" @@ -457,6 +454,9 @@ msgstr ""
"Ihre ID bleibt dennoch gespeichert. Klicken Sie Ausloggen weiter oben um "
"die ID zu löschen."
msgid "Room PIN"
msgstr "Raum-PIN"
msgid "Room link"
msgstr "Raum-Link"
@ -759,6 +759,12 @@ msgstr "Browsereinstellung" @@ -759,6 +759,12 @@ msgstr "Browsereinstellung"
msgid "Meet with me here:"
msgstr "Meeting:"
msgid "Please enter a new Room PIN to lock the room"
msgstr "Bitte geben Sie eine neue Raum-PIN ein um diesen Raum abzuschliessen"
msgid "Do you want to unlock the room?"
msgstr "Möchten Sie diesen Raum aufschliessen?"
msgid "Room name"
msgstr "Raum-Name"
@ -877,3 +883,6 @@ msgstr "Unbekannt" @@ -877,3 +883,6 @@ msgstr "Unbekannt"
msgid "Me"
msgstr "Ich"
#~ msgid "Register"
#~ msgstr "Registrieren"

892
src/i18n/messages-es.po

@ -0,0 +1,892 @@ @@ -0,0 +1,892 @@
# Spanish translations for Spreed WebRTC.
# Copyright (C) 2016 struktur AG
# This file is distributed under the same license as the Spreed WebRTC
# project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2016.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: Spreed WebRTC 1.0\n"
"Report-Msgid-Bugs-To: simon@struktur.de\n"
"POT-Creation-Date: 2016-08-18 18:21+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Jhon Felipe Urrego Mejia "
"<ingenierofelipeurrego@gmail.com>\n"
"Language-Team: es <LL@li.org>\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 1.3\n"
msgid "Standard view"
msgstr "Vista estándar"
msgid "Large view"
msgstr "Vista grande"
msgid "Kiosk view"
msgstr "Vista de Quiosco"
msgid "Auditorium"
msgstr "Auditorio"
msgid "Start chat"
msgstr "Iniciar chat"
msgid "Start video call"
msgstr "Iniciar videollamada"
msgid "Start audio conference"
msgstr "Iniciar conferencia de audio"
msgid "No one else here"
msgstr "No hay nadie aquí"
msgid "Take"
msgstr "Tomar"
msgid "Retake"
msgstr "Repetir"
msgid "Cancel"
msgstr "Cancelar"
msgid "Set as Profile Picture"
msgstr "Establecer como imagen de perfil"
msgid "Take picture"
msgstr "Tomar foto"
msgid "Upload picture"
msgstr "Subir imagen"
msgid "Waiting for camera"
msgstr "Esperando cámara"
msgid "Picture"
msgstr "Imagen"
msgid "The file couldn't be read."
msgstr "El archivo no pudo ser leído."
msgid "The file is not an image."
msgstr "El archivo no es una imagen."
#, python-format
msgid "The file is too large. Max. %d MB."
msgstr "El archivo es demasiado grande. Max. %d MB."
msgid "Select file"
msgstr "Seleccionar archivo"
msgid "Chat sessions"
msgstr "Sesiones de chat"
msgid "Room chat"
msgstr "Sala de chat"
msgid "Peer to peer"
msgstr "Extremo a extremo"
msgid "Close chat"
msgstr "Cerrar chat"
msgid "Upload files"
msgstr "Subir archivos"
msgid "Share my location"
msgstr "Compartir mi ubicación"
msgid "Clear chat"
msgstr "Borrar chat"
msgid "is typing..."
msgstr "está escribiendo..."
msgid "has stopped typing..."
msgstr "ha dejado de escribir..."
msgid "Type here to chat..."
msgstr "Escriba aquí para charlar..."
msgid "Send"
msgstr "Enviar"
msgid "Accept"
msgstr "Aceptar"
msgid "Reject"
msgstr "Rechazar"
msgid "You have no contacts."
msgstr "No dispone de contactos."
msgid ""
"To add new contacts, join a room and create a contact add request by "
"clicking on the star icon next to a user entry."
msgstr ""
"Para agregar nuevos contactos, unirse a una sala y crear una solicitud de"
" contacto al hacer click en el icono de la estrella junto al nombre del "
"usuario."
msgid "Edit contact"
msgstr "Editar contacto"
msgid "Edit"
msgstr "Editar"
msgid "Name"
msgstr "Nombre"
msgid "Remove"
msgstr "Remover"
msgid "Refresh"
msgstr "Refrescar"
msgid "Save"
msgstr "Guardar"
msgid "Close"
msgstr "Cerrar"
msgid "File sharing"
msgstr "Compartir archivos"
msgid "File is no longer available"
msgstr "El archivo ya no está disponible"
msgid "Download"
msgstr "Descargar"
msgid "Open"
msgstr "Abierto"
msgid "Unshare"
msgstr "No compartir"
msgid "Retry"
msgstr "Reintentar"
msgid "Download failed."
msgstr "Error de descarga."
msgid "Share a YouTube video"
msgstr "Compartir un vídeo de YouTube"
msgid "Share a file as presentation"
msgstr "Compartir un archivo como presentación"
msgid "Share your screen"
msgstr "Compartir la pantalla"
msgid "Chat"
msgstr "Chat"
msgid "Contacts"
msgstr "Contactos"
msgid "Mute microphone"
msgstr "Silenciar micrófono"
msgid "Turn camera off"
msgstr "Apagar cámara"
msgid "Settings"
msgstr "Ajustes"
msgid "Loading presentation ..."
msgstr "Cargando la presentación ..."
msgid "Please upload a document"
msgstr "Subir un documento"
msgid ""
"Documents are shared with everyone in this call. The supported file types"
" are PDF and OpenDocument files."
msgstr ""
"Los documentos son compartidos con todos los participantes en esta "
"llamada. Los tipos de archivo admitidos son PDF y archivos OpenDocument."
msgid "Upload"
msgstr "Subir"
msgid "You can drag files here too."
msgstr "Puede arrastrar archivos aquí también."
msgid "Presentation controls"
msgstr "Controles de presentación"
msgid "Prev"
msgstr "Ant"
msgid "Next"
msgstr "Sig"
msgid "Change room"
msgstr "Cambiar de sala"
msgid "Room"
msgstr "Sala"
msgid "Leave room"
msgstr "Abandonar sala"
msgid "Main"
msgstr "Principal"
msgid "Current room"
msgstr "Sala actual"
msgid "Screen sharing options"
msgstr "Opciones para compartir pantalla"
msgid "Fit screen."
msgstr "Ajustar pantalla."
msgid "Share screen"
msgstr "Compartir pantalla"
msgid "Please select what to share."
msgstr "Seleccione qué compartir."
msgid "Screen"
msgstr "Pantalla"
msgid "Window"
msgstr "Ventana"
msgid "Application"
msgstr "Aplicación"
msgid "Share the whole screen. Click share to select the screen."
msgstr ""
"Compartir toda la pantalla. Haga clic en Compartir para seleccionar la "
"pantalla."
msgid "Share a single window. Click share to select the window."
msgstr ""
"Compartir una sola ventana. Haga clic en Compartir para seleccionar la "
"ventana."
msgid ""
"Share all windows of a application. This can leak content behind windows "
"when windows get moved. Click share to select the application."
msgstr ""
"Compartir todas las ventanas de una aplicación. Esto puede gotear el "
"contenido detrás de Windows Cuando Windows se mueven. Haga clic en "
"Compartir para seleccionar la aplicación."
msgid "Share"
msgstr "Compartir"
msgid "OK"
msgstr "OK"
msgid "Profile"
msgstr "Perfil"
msgid "Your name"
msgstr "Su nombre"
msgid "Your picture"
msgstr "Su imagen"
msgid "Status message"
msgstr "Mensaje de estado"
msgid "What's on your mind?"
msgstr "¿Qué está pensando?"
msgid ""
"Your picture, name and status message identify yourself in calls, chats "
"and rooms."
msgstr ""
"Su imagen, nombre y mensaje de estado te identificará en llamadas, chat y"
" en las salas."
msgid "Your ID"
msgstr "Su ID"
msgid ""
"Authenticated by certificate. To log out you have to remove your "
"certificate from the browser."
msgstr ""
"Autentificado por certificado. Para cerrar su sesión tendrá que quitar el"
" certificado de su navegador."
msgid "Sign in"
msgstr "Iniciar sesión"
msgid "Create an account"
msgstr "Crear una cuenta"
msgid "Sign out"
msgstr "Cerrar sesión"
msgid "Manage account"
msgstr "Administrar cuenta"
msgid "Media"
msgstr "Medios"
msgid "Microphone"
msgstr "Micrófono"
msgid "Camera"
msgstr "Cámara"
msgid "Video quality"
msgstr "Calidad de vídeo"
msgid "Low"
msgstr "Baja"
msgid "High"
msgstr "Alta"
msgid "HD"
msgstr "HD"
msgid "Full HD"
msgstr "Full HD"
msgid "General"
msgstr "General"
msgid "Language"
msgstr "Idioma"
msgid "Language changes become active on reload."
msgstr "Cambios de lenguaje se vuelven activos al recargar."
msgid "Default room"
msgstr "Sala predeterminada"
msgid "Set alternative room to join at start."
msgstr "Establecer otra sala para unirse al inicio."
msgid "Notifications"
msgstr "Notificaciones"
msgid "Desktop notification"
msgstr "Notificación de escritorio"
msgid "Enable"
msgstr "Activar"
msgid "Denied - check your browser settings"
msgstr "Denegado - compruebe la configuración de su navegador"
msgid "Allowed"
msgstr "Permitido"
msgid "Sounds for incoming messages"
msgstr "Sonidos para los mensajes entrantes"
msgid "Ring on incoming calls"
msgstr "Sonar con llamadas entrantes"
msgid "Sounds for users in current room"
msgstr "Sonidos para usuarios en sala actual"
msgid "Advanced settings"
msgstr "Configuración avanzada"
msgid "Play audio on same device as selected microphone"
msgstr "Reproducir audio en el mismo dispositivo seleccionado como micrófono"
msgid "Experimental AEC"
msgstr "Cancelación de Eco Acústico Experimental"
msgid "Experimental AGC"
msgstr "Control de Ganancia Automática Experimental"
msgid "Experimental noise suppression"
msgstr "Supresión de ruido experimental"
msgid "Max video frame rate"
msgstr "Máx. velocidad de fotogramas de vídeo"
msgid "auto"
msgstr "auto"
msgid "Send stereo audio"
msgstr "Enviar audio estéreo"
msgid ""
"Sending stereo audio disables echo cancellation. Enable only if you have "
"stereo input."
msgstr ""
"Envío de audio estéreo desactiva la cancelación del eco. Activar sólo si "
"tienes La entrada estéreo."
msgid "Detect CPU over use"
msgstr "detectar sobreuso de la CPU"
msgid "Automatically reduces video quality as needed."
msgstr "Reduce automáticamente la calidad de vídeo cuando sea necesario."
msgid "Optimize for high resolution video"
msgstr "Optimizar para vídeo de alta resolución"
msgid "Reduce video noise"
msgstr "Reducir el ruido de vídeo"
msgid "Prefer VP9 video codec"
msgstr "Prefiere VP9 códec de vídeo"
msgid "Enable experiments"
msgstr "Permitir experimentos"
msgid "Show advanced settings"
msgstr "Mostrar opciones avanzadas"
msgid "Hide advanced settings"
msgstr "Ocultar opciones avanzadas"
msgid "Remember settings"
msgstr "Recordar la configuración"
msgid ""
"Your ID will still be kept - press the log out button above to delete the"
" ID."
msgstr ""
"Su ID será conservada - Pulse el botón Cerrar sesión anterior para "
"eliminar el ID."
#, fuzzy
msgid "Room PIN"
msgstr "Link de Sala"
msgid "Room link"
msgstr "Link de Sala"
msgid "Invite by Email"
msgstr "Invitar por correo electrónico"
msgid "Invite with Facebook"
msgstr "Invitar con Facebook"
msgid "Invite with Twitter"
msgstr "Invitar con Twitter"
msgid "Invite with Google Plus"
msgstr "Invitar con Google Plus"
msgid "Invite with XING"
msgstr "Invitar con XING"
msgid "Initializing"
msgstr "Inicializando"
msgid "Online"
msgstr "En línea"
msgid "Calling"
msgstr "LLamando"
msgid "Hangup"
msgstr "Colgar"
msgid "In call with"
msgstr "En llamada con"
msgid "Conference with"
msgstr "Conferencia con"
msgid "Your are offline"
msgstr "está desconectado"
msgid "Go online"
msgstr "Ir Online"
msgid "Connection interrupted"
msgstr "Conexión interrumpida"
msgid "An error occured"
msgstr "Un error ha ocurrido"
msgid "Incoming call"
msgstr "Llamada entrante"
msgid "from"
msgstr "desde"
msgid "Accept call"
msgstr "Aceptar llamada"
msgid "Waiting for camera/microphone access"
msgstr "Esperando acceso cámara/micrófono"
msgid "Your audio level"
msgstr "Tu nivel de audio"
msgid "Checking camera and microphone access."
msgstr "Control de acceso a la cámara y el micrófono."
msgid "Please allow access to your camera and microphone."
msgstr "Por favor, permitir el acceso a la cámara y al micrófono."
msgid "Camera / microphone access required."
msgstr "Cámara / micrófono requiere acceso."
msgid ""
"Please check your browser settings and allow camera and microphone access"
" for this site."
msgstr ""
"Compruebe la configuración de su navegador y permita el acceso a la "
"cámara y el micrófono para este sitio."
msgid "Skip check"
msgstr "Saltar la comprobación"
msgid "Click here for help (Google Chrome)."
msgstr "Haga clic aquí para obtener ayuda (Google Chrome)."
msgid "Please set your user details and settings."
msgstr "Configure su información de usuario y de configuración."
msgid "Enter a room name"
msgstr "Introduzca un nombre de sala"
msgid "Random room name"
msgstr "Nombre de la sala aleatorio"
msgid "Enter room"
msgstr "Ingresar a sala"
msgid ""
"Enter the name of an existing room. You can create new rooms when you are"
" signed in."
msgstr ""
"Introduzca el nombre de la sala existente. Puede crear nuevas salas "
"cuando estás adentro de la aplicación."
msgid "Room history"
msgstr "Historial de Salas"
msgid "Please sign in."
msgstr "Identifícate."
msgid "Videos play simultaneously for everyone in this call."
msgstr "Reproducir videos simultáneamente para todos en esta llamada."
msgid "YouTube URL"
msgstr "URL de YouTube"
msgid ""
"Could not load YouTube player API, please check your network / firewall "
"settings."
msgstr ""
"No se pudo cargar API del reproductor de YouTube, compruebe la red / "
"firewall Ajustes."
msgid "Currently playing"
msgstr "Actualmente reproduciendo"
msgid "YouTube controls"
msgstr "Controles en YouTube"
msgid "YouTube video to share"
msgstr "Compartir video YouTube"
msgid "Peer to peer chat active."
msgstr "Chat P2P activo."
msgid "Peer to peer chat is now off."
msgstr "Chat P2P está ahora desactivado."
msgid " is now offline."
msgstr " está ahora desconectado."
msgid " is now online."
msgstr " está ahora en línea."
msgid "You share file:"
msgstr "Compartir archivo:"
msgid "Incoming file:"
msgstr "Archivo entrante:"
msgid "You shared your location:"
msgstr "Ha compartido su ubicación:"
msgid "Location received:"
msgstr "Ubicación recibida:"
msgid "You accepted the contact request."
msgstr "Ha aceptado la solicitud de contacto."
msgid "You rejected the contact request."
msgstr "Ha rechazado la solicitud de contacto."
msgid "You sent a contact request."
msgstr "Ha enviado una solicitud de contacto."
msgid "Your contact request was accepted."
msgstr "Su solicitud de contacto fue aceptada."
msgid "Incoming contact request."
msgstr "Solicitud de contacto entrante."
msgid "Your contact request was rejected."
msgstr "Fue rechazada su solicitud de contacto."
msgid "Edit Contact"
msgstr "Editar contacto"
msgid "Your browser does not support WebRTC. No calls possible."
msgstr "Su explorador no soporta WebRTC. No hay posibilidad de realizar llamadas."
msgid "Close this window and disconnect?"
msgstr "Cerrar esta ventana y desconectar?"
msgid "Contacts Manager"
msgstr "Administrador de contactos"
msgid "Restart required to apply updates. Click ok to restart now."
msgstr ""
"Es necesario reiniciar para aplicar las actualizaciones. Haga clic en "
"Aceptar para reiniciar el sistema ahora."
msgid "Failed to access camera/microphone."
msgstr "Error al acceder a la cámara/micrófono."
msgid "Failed to establish peer connection."
msgstr "Error al establecer conexión P2P."
msgid "We are sorry but something went wrong. Boo boo."
msgstr "Lo sentimos, pero algo salió mal. AHHHHHHH."
msgid "Oops"
msgstr "Ooops"
msgid "Peer connection failed. Check your settings."
msgstr "Conexión P2P fallida. Compruebe su configuración."
msgid "User hung up because of error."
msgstr "Usuario colgó debido a un error."
msgid " is busy. Try again later."
msgstr " está ocupado. Vuelva a intentarlo más tarde."
msgid " rejected your call."
msgstr " rechazó su llamada."
msgid " does not pick up."
msgstr " no contesta."
msgid " tried to call you"
msgstr " intentó llamarte"
msgid " called you"
msgstr " le llamó"
msgid "Your browser is not supported. Please upgrade to a current version."
msgstr "Su navegador no es compatible. Actualice a una versión actual."
msgid "Chat with"
msgstr "Chatear con"
msgid "Message from "
msgstr "Mensaje de "
#, python-format
msgid "You are now in room %s ..."
msgstr "Ahora estás en la sala %s ..."
msgid "Your browser does not support file transfer."
msgstr "Tu navegador no soporta transferencia de archivos."
msgid "Could not load PDF: Please make sure to select a PDF document."
msgstr "No se pudo cargar el PDF: asegúrese de seleccionar un documento PDF."
msgid "Could not load PDF: Missing PDF file."
msgstr "No se pudo cargar el PDF: Falta el archivo PDF."
#, python-format
msgid "An error occurred while loading the PDF (%s)."
msgstr "Se ha producido un error al cargar el archivo PDF (%s)."
msgid "An unknown error occurred while loading the PDF."
msgstr "Se ha producido un error desconocido al cargar el PDF."
#, python-format
msgid "An error occurred while loading the PDF page (%s)."
msgstr "Se ha producido un error al cargar la página PDF (%s)."
msgid "An unknown error occurred while loading the PDF page."
msgstr "Se ha producido un error desconocido mientras se carga la página PDF."
#, python-format
msgid "An error occurred while rendering the PDF page (%s)."
msgstr "Se ha producido un error al procesar la página PDF (%s)."
msgid "An unknown error occurred while rendering the PDF page."
msgstr "Se ha producido un error desconocido al procesar la página PDF."
msgid "Only PDF documents and OpenDocument files can be shared at this time."
msgstr ""
"Sólo documentos PDF y archivos OpenDocument puede ser compartido en este "
"momento."
#, python-format
msgid "Failed to start screen sharing (%s)."
msgstr "No se pudo iniciar el uso compartido de la pantalla (%s)."
msgid ""
"Permission to start screen sharing was denied. Make sure to have enabled "
"screen sharing access for your browser. Copy chrome://flags/#enable-"
"usermedia-screen-capture and open it with your browser and enable the "
"flag on top. Then restart the browser and you are ready to go."
msgstr ""
"Permiso para comenzar a compartir la pantalla fue denegada. Asegúrese de "
"haber activado compartir el acceso a la pantalla para su navegador. "
"Copiar chrome://flags/#enable-usermedia-screen-capture y ábralo con su "
"navegador y habilitar la bandera en la parte superior. A continuación, "
"reinicie el navegador y listo."
msgid "Permission to start screen sharing was denied."
msgstr "Permiso para comenzar a compartir la pantalla fue denegado."
msgid "Use browser language"
msgstr "Utilizar el idioma del navegador"
msgid "Meet with me here:"
msgstr "Reunirse conmigo aquí:"
msgid "Please enter a new Room PIN to lock the room"
msgstr ""
msgid "Do you want to unlock the room?"
msgstr ""
msgid "Room name"
msgstr "Nombre de sala"
msgid ""
"The request contains an invalid parameter value. Please check the URL of "
"the video you want to share and try again."
msgstr ""
"La solicitud contiene un valor de parámetro no válido. Compruebe la "
"dirección URL de el vídeo que desea compartir e inténtelo de nuevo."
msgid ""
"The requested content cannot be played in an HTML5 player or another "
"error related to the HTML5 player has occurred. Please try again later."
msgstr ""
"El contenido solicitado no se puede reproducir en un reproductor HTML5 u "
"otro error relacionado con el reproductor HTML5 ha ocurrido. Por favor, "
"inténtelo de nuevo más tarde."
msgid ""
"The video requested was not found. Please check the URL of the video you "
"want to share and try again."
msgstr ""
"El video solicitado no fue encontrado. Compruebe la URL del video que "
"quieres compartir e inténtelo de nuevo."
msgid ""
"The owner of the requested video does not allow it to be played in "
"embedded players."
msgstr ""
"El propietario del vídeo solicitado no le permite ser reproducido en "
"reproductores incrustados."
#, python-format
msgid ""
"An unknown error occurred while playing back the video (%s). Please try "
"again later."
msgstr ""
"Se ha producido un error desconocido mientras se reproduce el vídeo (%s)."
" Por favor intente más tarde."
msgid ""
"An unknown error occurred while playing back the video. Please try again "
"later."
msgstr ""
"Se ha producido un error desconocido mientras se reproduce el vídeo. Por "
"favor, inténtelo de nuevo más adelante."
msgid "Unknown URL format. Please make sure to enter a valid YouTube URL."
msgstr ""
"Formato URL desconocido. Por favor, asegúrese de escribir una dirección "
"URL válida de YouTube."
msgid "Error"
msgstr "Error"
msgid "Hint"
msgstr "Sugerencia"
msgid "Please confirm"
msgstr "Confirme"
msgid "More information required"
msgstr "Se necesita más información"
msgid "Ok"
msgstr "Ok"
msgid ""
"Screen sharing requires a browser extension. Please add the Spreed WebRTC"
" screen sharing extension to Chrome and try again."
msgstr ""
"Se requiere una extensión del navegador para Compartir Pantalla. Por "
"favor, añada la extensión Spreed WebRTC screen sharing a Chrome e "
"inténtelo de nuevo."
msgid "Access code required"
msgstr "Código de acceso requerido"
msgid "Access denied"
msgstr "Acceso denegado"
msgid "Please provide a valid access code."
msgstr "Proporcione un código de acceso válido."
msgid ""
"Failed to verify access code. Check your Internet connection and try "
"again."
msgstr ""
"No se ha podido verificar el código de acceso. Compruebe su conexión a "
"Internet e intente una vez más."
#, python-format
msgid "PIN for room %s is now '%s'."
msgstr "PIN de sala %s es ahora '%s'."
#, python-format
msgid "PIN lock has been removed from room %s."
msgstr "bloqueo de PIN se ha eliminado desde la sala %s."
#, python-format
msgid "Enter the PIN for room %s"
msgstr "Introducir el PIN para sala %s"
msgid "Please sign in to create rooms."
msgstr "Inicie sesión para crear nuevas salas."
#, python-format
msgid "and %s"
msgstr "y %s"
#, python-format
msgid "and %d others"
msgstr "y %d otros"
msgid "User"
msgstr "Usuario"
msgid "Someone"
msgstr "Alguien"
msgid "Me"
msgstr "Yo"
#~ msgid "Register"
#~ msgstr "Registrar"

321
src/i18n/messages-fr.po

@ -2,14 +2,14 @@ @@ -2,14 +2,14 @@
# Copyright (C) 2016 struktur AG
# This file is distributed under the same license as the Spreed WebRTC
# project.
# Florent BEAUCHAMP <fbeauchamp@sdis71.fr>, 2013.
# Mathieu Brunot <mb.mathieu.brunot@gmail.com> , 2018.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: Spreed WebRTC 1.0\n"
"Report-Msgid-Bugs-To: simon@struktur.de\n"
"POT-Creation-Date: 2016-02-17 14:15+0100\n"
"POT-Creation-Date: 2016-08-18 18:21+0200\n"
"PO-Revision-Date: 2018-01-28 20:38+0200\n"
"Last-Translator: Mathieu Brunot <mb.mathieu.brunot@gmail.com>\n"
"Language-Team: fr <>\n"
@ -20,65 +20,65 @@ msgstr "" @@ -20,65 +20,65 @@ msgstr ""
"Generated-By: Babel 1.3\n"
msgid "Standard view"
msgstr "Vue standard"
msgstr "Mode standard"
msgid "Large view"
msgstr "Vue large"
msgstr "Mode large"
msgid "Kiosk view"
msgstr "Kiosque"
msgstr "Mode kiosque"
msgid "Auditorium"
msgstr "Salle"
msgstr "Mode auditorium"
msgid "Start chat"
msgstr "Commencer une discussion"
msgstr "Démarrer la discussion"
msgid "Start video call"
msgstr "Commencer un appel vidéo"
msgstr "Appel vidéo"
msgid "Start audio conference"
msgstr "Commencer une conférence audio"
msgstr "Appel audio"
msgid "No one else here"
msgstr "Personne d'autre ici"
msgstr "Personne n'est connecté"
msgid "Take"
msgstr "Prendre"
msgstr "Répondre"
msgid "Retake"
msgstr "Reprendre"
msgstr "Refaire"
msgid "Cancel"
msgstr "Annuler"
msgid "Set as Profile Picture"
msgstr "Définir comme photo de profil"
msgstr "Définir comme avatar"
msgid "Take picture"
msgstr "Prendre une photo"
msgid "Upload picture"
msgstr "Charger une photo"
msgstr "Envoyer une photo"
msgid "Waiting for camera"
msgstr "En attente de la caméra"
msgid "Picture"
msgstr "Photo"
msgstr "Avatar"
msgid "The file couldn't be read."
msgstr "Le fichier n'a pas pu être lu."
msgstr "Le fichier ne peux pas être lu."
msgid "The file is not an image."
msgstr "Le fichier n'est pas une image."
#, python-format
msgid "The file is too large. Max. %d MB."
msgstr "Le fichier est trop volumineux. Max. %d Mo."
msgstr "Le fichier est trop gros. Max. %d Mo."
msgid "Select file"
msgstr "Sélectionner un fichier"
msgstr "Choisir un fichier"
msgid "Chat sessions"
msgstr "Discussions"
@ -90,16 +90,16 @@ msgid "Peer to peer" @@ -90,16 +90,16 @@ msgid "Peer to peer"
msgstr "Peer-to-peer"
msgid "Close chat"
msgstr "Fermer discussion"
msgstr "Fermer la discussion"
msgid "Upload files"
msgstr "Charger des fichiers"
msgstr "Envoyer les fichiers"
msgid "Share my location"
msgstr "Partager ma position"
msgid "Clear chat"
msgstr "Vider la discussion"
msgstr "Effacer la discussion"
msgid "is typing..."
msgstr "est en train d'écrire..."
@ -117,21 +117,21 @@ msgid "Accept" @@ -117,21 +117,21 @@ msgid "Accept"
msgstr "Accepter"
msgid "Reject"
msgstr "Rejeter"
msgstr "Refuser"
msgid "You have no contacts."
msgstr "Vous n'avez aucun contact."
msgstr "Vous n'avez pas de contact."
msgid ""
"To add new contacts, join a room and create a contact add request by "
"clicking on the star icon next to a user entry."
msgstr ""
"Pour ajouter de nouveaux contacts, rejoignez un salon et créez une "
"demande de contact en cliquant sur l'icône en forme d'étoile à côté "
"d'un utilisateur."
"Pour ajouter des contacts, rejoignez un salon et faites une requête "
"d'ajout de contact en cliquant sur l'étoile à côté de la fiche d'un "
"utilisateur."
msgid "Edit contact"
msgstr "Éditer contact"
msgstr "Éditer le contact"
msgid "Edit"
msgstr "Éditer"
@ -143,7 +143,7 @@ msgid "Remove" @@ -143,7 +143,7 @@ msgid "Remove"
msgstr "Supprimer"
msgid "Refresh"
msgstr "Rafraîchir"
msgstr "Actualiser"
msgid "Save"
msgstr "Sauvegarder"
@ -155,7 +155,7 @@ msgid "File sharing" @@ -155,7 +155,7 @@ msgid "File sharing"
msgstr "Partage de fichier"
msgid "File is no longer available"
msgstr "Le fichier n'est plus disponible"
msgstr "Ce fichier n'est plus disponible"
msgid "Download"
msgstr "Télécharger"
@ -164,19 +164,19 @@ msgid "Open" @@ -164,19 +164,19 @@ msgid "Open"
msgstr "Ouvrir"
msgid "Unshare"
msgstr "Retirer du partage"
msgstr "Ne plus partager"
msgid "Retry"
msgstr "Réessayer"
msgid "Download failed."
msgstr "Téléchargement échoué."
msgstr "Echec du téléchargement."
msgid "Share a YouTube video"
msgstr "Partager une vidéo YouTube"
msgstr "Partager une vidéo Youtube"
msgid "Share a file as presentation"
msgstr "Partager un fichier présentation"
msgstr "Partager une présentation"
msgid "Share your screen"
msgstr "Partager votre écran"
@ -188,29 +188,29 @@ msgid "Contacts" @@ -188,29 +188,29 @@ msgid "Contacts"
msgstr "Contacts"
msgid "Mute microphone"
msgstr "Désactiver le microphone"
msgstr "Couper le microphone"
msgid "Turn camera off"
msgstr "Désactiver la caméra"
msgstr "Couper la caméra"
msgid "Settings"
msgstr "Paramètres"
msgid "Loading presentation ..."
msgstr "Chargement de la présentation ..."
msgstr "Chargement de la présentation..."
msgid "Please upload a document"
msgstr "Veuillez charger un document"
msgstr "Veuillez d'envoyer un document"
msgid ""
"Documents are shared with everyone in this call. The supported file types"
" are PDF and OpenDocument files."
msgstr ""
Les documents sont partagés avec tous les membres de cet appel. Les types "
"de fichiers pris en charge sont les fichiers PDF et OpenDocument."
"Les documents sont partagés avec tous les participants de cet appel. Les "
"documents PDF et OpenDocument sont supportés."
msgid "Upload"
msgstr "Charger"
msgstr "Sélectionner un fichier"
msgid "You can drag files here too."
msgstr "Vous pouvez également faire glisser des fichiers ici."
@ -219,7 +219,7 @@ msgid "Presentation controls" @@ -219,7 +219,7 @@ msgid "Presentation controls"
msgstr "Contrôles de la présentation"
msgid "Prev"
msgstr "Prec."
msgstr "Préc."
msgid "Next"
msgstr "Suiv."
@ -231,7 +231,7 @@ msgid "Room" @@ -231,7 +231,7 @@ msgid "Room"
msgstr "Salon"
msgid "Leave room"
msgstr "Quitter salon"
msgstr "Quitter le salon"
msgid "Main"
msgstr "Principal"
@ -243,13 +243,13 @@ msgid "Screen sharing options" @@ -243,13 +243,13 @@ msgid "Screen sharing options"
msgstr "Options de partage d'écran"
msgid "Fit screen."
msgstr "Ajuster à l'écran."
msgstr "Adapter à l'écran."
msgid "Share screen"
msgstr "Partager l'écran"
msgid "Please select what to share."
msgstr "Veuillez sélectionner quoi partager."
msgstr "Veuillez sélectionner l'écran à partager."
msgid "Screen"
msgstr "Écran"
@ -261,18 +261,18 @@ msgid "Application" @@ -261,18 +261,18 @@ msgid "Application"
msgstr "Application"
msgid "Share the whole screen. Click share to select the screen."
msgstr "Partager l'intégralité de l'écran. Cliquez sur partager pour sélectionner un écran."
msgstr "Partager l'intégralité de l'écran. Cliquez pour choisir un écran."
msgid "Share a single window. Click share to select the window."
msgstr "Partager une fenêtre. Cliquez sur partager pour sélectionner une fenêtre."
msgstr "Partager une fenêtre. Cliquez pour choisir une fenêtre."
msgid ""
"Share all windows of a application. This can leak content behind windows "
"when windows get moved. Click share to select the application."
msgstr ""
"Partager toutes les fenêtres d'une application. Cela peut entraîner une "
"fuite d'informations lorsque les fenêtres sont déplacées. "
"Cliquez sur partager pour sélectionner l'application."
"Partager toutes les fenêtres d'une application. Ceci peux amener à "
"montrer du contenu situé derrière une fenêtre lorsqu'elle est déplacée. "
"Cliquez pour sélectionner l'application."
msgid "Share"
msgstr "Partager"
@ -287,10 +287,10 @@ msgid "Your name" @@ -287,10 +287,10 @@ msgid "Your name"
msgstr "Votre nom"
msgid "Your picture"
msgstr "Votre photo"
msgstr "Votre avatar"
msgid "Status message"
msgstr "Message de status"
msgstr "Message de statut"
msgid "What's on your mind?"
msgstr "A quoi pensez-vous?"
@ -299,7 +299,7 @@ msgid "" @@ -299,7 +299,7 @@ msgid ""
"Your picture, name and status message identify yourself in calls, chats "
"and rooms."
msgstr ""
"Votre photo, votre nom et votre message de status vous identifient dans "
"Votre photo, votre nom et votre message de statut vous identifient dans "
"les appels, les discussions et les salons."
msgid "Your ID"
@ -312,23 +312,23 @@ msgid "" @@ -312,23 +312,23 @@ msgid ""
"Authenticated by certificate. To log out you have to remove your "
"certificate from the browser."
msgstr ""
"Authentifié par certificat. Pour vous déconnecter, vous devez retirer "
"votre certificat du navigateur."
"Authentifié par certificat. Vous devrez effacer le certificat de votre "
"navigateur pour vous déconnecter."
msgid "Sign in"
msgstr "Se connecter"
msgstr "Connnexion"
msgid "Create an account"
msgstr "Créer un compte"
msgid "Sign out"
msgstr "Se déconnecter"
msgstr "Déconnexion"
msgid "Manage account"
msgstr "Gérer votre compte"
msgstr "Gestion du compte"
msgid "Media"
msgstr "Media"
msgstr "Webcam / Micro"
msgid "Microphone"
msgstr "Microphone"
@ -340,7 +340,7 @@ msgid "Video quality" @@ -340,7 +340,7 @@ msgid "Video quality"
msgstr "Qualité vidéo"
msgid "Low"
msgstr "Faible"
msgstr "Basse"
msgid "High"
msgstr "Haute"
@ -358,16 +358,16 @@ msgid "Language" @@ -358,16 +358,16 @@ msgid "Language"
msgstr "Langue"
msgid "Language changes become active on reload."
msgstr "Les changements de langue prennent effet au redémarrage."
msgstr "Rechargez la page pour prendre en compte le changement de langue."
msgid "Default room"
msgstr "Salon par défaut"
msgid "Set alternative room to join at start."
msgstr "éfinissez un autre salon à rejoindre au début."
msgstr "Nom du salon à rejoindre à la connexion."
msgid "Notifications"
msgstr "Уведомления"
msgstr "Notifications"
msgid "Desktop notification"
msgstr "Notification de bureau"
@ -376,37 +376,37 @@ msgid "Enable" @@ -376,37 +376,37 @@ msgid "Enable"
msgstr "Activer"
msgid "Denied - check your browser settings"
msgstr "Non autorisé - vérifiez les paramètres de votre navigateur"
msgstr "Interdit - vérifier les paramètres de votre navigateur"
msgid "Allowed"
msgstr "Autorisé"
msgid "Sounds for incoming messages"
msgstr "Sons pour les messages entrants"
msgstr "Son pour les messages entrants"
msgid "Ring on incoming calls"
msgstr "Sonnerie pour les appels entrants"
msgid "Sounds for users in current room"
msgstr "Sons pour les utilisateurs dans le salon courant"
msgstr "Son pour les participants de le salon courant"
msgid "Advanced settings"
msgstr "Paramètres avancés"
msgstr "Réglages avancés"
msgid "Play audio on same device as selected microphone"
msgstr "Jouer l'audio sur le même appareil que le microphone sélectionné"
msgid "Experimental AEC"
msgstr "AEC expérimental"
msgstr "AEC (expérimental)"
msgid "Experimental AGC"
msgstr "AGC expérimental"
msgstr "AGC (expérimental)"
msgid "Experimental noise suppression"
msgstr "Suppression de bruit expérimentale"
msgstr "Suppression de bruit (expérimental)"
msgid "Max video frame rate"
msgstr "Taux de trame maximal de la vidéo"
msgstr "Image par seconde max"
msgid "auto"
msgstr "auto"
@ -418,42 +418,46 @@ msgid "" @@ -418,42 +418,46 @@ msgid ""
"Sending stereo audio disables echo cancellation. Enable only if you have "
"stereo input."
msgstr ""
"L'utilisation du son stéréo désactive l'annulation d'écho. Activer "
"uniquement si vous avez une entrée stéréo."
"Utiliser du son stéréo désactive la suppression de l'écho. Activez "
"uniquement si vous avez un microphone stéréo."
msgid "Detect CPU over use"
msgstr "Détection de surcharge du processeur"
msgstr "Détecter un usage processeur excessif"
msgid "Automatically reduces video quality as needed."
msgstr "Réduit automatiquement la qualité de la vidéo si nécessaire."
msgstr "Réduire automatiquement la qualité vidéo si nécessaire."
msgid "Optimize for high resolution video"
msgstr "Optimiser pour la vidéo haute définition"
msgstr "Optimiser la vidéo pour la haute résolution"
msgid "Reduce video noise"
msgstr "Réduire le bruit vidéo"
msgid "Prefer VP9 video codec"
msgstr "Préférer le codec vidéo VP9"
msgstr "Utiliser le codec VP9 en priorité"
msgid "Enable experiments"
msgstr "Autoriser les expériences"
msgstr "Activer les réglages expérimentaux"
msgid "Show advanced settings"
msgstr "Afficher les paramètres avancés"
msgstr "Afficher les réglages avancés"
msgid "Hide advanced settings"
msgstr "Masquer les paramètres avancées"
msgstr "Masquer les réglages avancées"
msgid "Remember settings"
msgstr "Se souvenir des paramètres"
msgstr "Se souvenir des réglages"
msgid ""
"Your ID will still be kept - press the log out button above to delete the"
" ID."
msgstr ""
"Votre identifiant sera stocké - cliquez sur le bouton de déconnexion"
" ci-dessus pour supprimer l'identifiant."
"Votre identifiant sera conservé - appuyez sur le bouton de déconnexion"
" ci-dessus pour l'effacer."
#, fuzzy
msgid "Room PIN"
msgstr "Code PIN du salon"
msgid "Room link"
msgstr "Lien du salon"
@ -474,19 +478,19 @@ msgid "Invite with XING" @@ -474,19 +478,19 @@ msgid "Invite with XING"
msgstr "Inviter via XING"
msgid "Initializing"
msgstr "Initialisation"
msgstr "Initialisation en cours"
msgid "Online"
msgstr "En ligne"
msgid "Calling"
msgstr "Appel"
msgstr "Appel en cours"
msgid "Hangup"
msgstr "Raccrocher"
msgid "In call with"
msgstr "En conversation avec"
msgstr "En communication avec"
msgid "Conference with"
msgstr "En conférence avec"
@ -513,7 +517,7 @@ msgid "Accept call" @@ -513,7 +517,7 @@ msgid "Accept call"
msgstr "Accepter l'appel"
msgid "Waiting for camera/microphone access"
msgstr "En attente d'accés à caméra / microphone"
msgstr "En attente d'accés à caméra et au microphone"
msgid "Your audio level"
msgstr "Votre niveau sonore"
@ -525,7 +529,7 @@ msgid "Please allow access to your camera and microphone." @@ -525,7 +529,7 @@ msgid "Please allow access to your camera and microphone."
msgstr "Veuillez autoriser l'accès à votre caméra et à votre microphone."
msgid "Camera / microphone access required."
msgstr "Accès caméra / microphone requis."
msgstr "Accès caméra et microphone requis."
msgid ""
"Please check your browser settings and allow camera and microphone access"
@ -541,32 +545,34 @@ msgid "Click here for help (Google Chrome)." @@ -541,32 +545,34 @@ msgid "Click here for help (Google Chrome)."
msgstr "Cliquez ici pour de l'aide (Google Chrome)."
msgid "Please set your user details and settings."
msgstr "Veuillez définir vos détails et paramètres utilisateur."
msgstr "Veuillez mettre à jour votre profil et vos paramètres."
msgid "Enter a room name"
msgstr "Entrez un nom de salon"
msgid "Random room name"
msgstr "Nom de salon aléatoire"
msgstr "Nom aléatoire"
msgid "Enter room"
msgstr "Entrez dans le salon"
msgstr "Rejoindre le salon"
msgid ""
"Enter the name of an existing room. You can create new rooms when you are"
" signed in."
msgstr ""
"Entrez le nom du salon existant. Vous pouvez créer de nouveaux salons"
"Entrez le nom d'un salon existant. Vous pouvez créer de nouveaux salons"
" lorsque vous êtes connecté."
msgid "Room history"
msgstr "Historique de salon"
msgstr "Historique du salon"
msgid "Please sign in."
msgstr "Veuillez vous connecter."
msgid "Videos play simultaneously for everyone in this call."
msgstr "Les vidéos sont jouées simultanément pour tout le monde dans cet appel."
msgstr ""
"Les vidéos sont jouées simultanément entre tous les participants de"
" cet appel."
msgid "YouTube URL"
msgstr "URL YouTube"
@ -575,23 +581,23 @@ msgid "" @@ -575,23 +581,23 @@ msgid ""
"Could not load YouTube player API, please check your network / firewall "
"settings."
msgstr ""
"Impossible de charger l'API du lecteur YouTube. Veuillez vérifier les "
"paramètres de votre réseau / pare-feu."
"Impossible de charger le lecteur de video Youtube. Veuillez vérifier vos "
"paramètres de réseau et pare-feu."
msgid "Currently playing"
msgstr "En cours de lecture"
msgstr "Lecture en cours"
msgid "YouTube controls"
msgstr "Contrôles YouTube"
msgid "YouTube video to share"
msgstr "Vidéos YouTube à partager"
msgstr "Vidéo YouTube à partager"
msgid "Peer to peer chat active."
msgstr "Chat peer-to-peer actif."
msgstr "Les discussions de pair a pair sont actives."
msgid "Peer to peer chat is now off."
msgstr "Le chat peer-to-peer est maintenant désactivé."
msgstr "Les discussions de pair a pair sont maintenant désactivées."
msgid " is now offline."
msgstr " est maintenant hors ligne."
@ -606,28 +612,28 @@ msgid "Incoming file:" @@ -606,28 +612,28 @@ msgid "Incoming file:"
msgstr "Fichier entrant:"
msgid "You shared your location:"
msgstr "Vous avez partagé votre position:"
msgstr "Vous partagez votre position :"
msgid "Location received:"
msgstr "Position reçue:"
msgstr "Position reçue :"
msgid "You accepted the contact request."
msgstr "Vous avez accepté la demande de contact."
msgstr "Vous avez accepté la demande de mise en relation."
msgid "You rejected the contact request."
msgstr "Vous avez refusé la demande de contact."
msgstr "Vous avez refusé la demande de mise en relation."
msgid "You sent a contact request."
msgstr "Vous avez envoyé une demande de contact."
msgstr "Vous avez envoyé une demande de mise en relation."
msgid "Your contact request was accepted."
msgstr "Votre demande de contact a été acceptée."
msgstr "Votre requête de mise en relation a été acceptée."
msgid "Incoming contact request."
msgstr "Demande de contact entrante."
msgstr "Arrivée d'une demande de mise en relation."
msgid "Your contact request was rejected."
msgstr "Votre demande de contact a été refusée."
msgstr "Votre requête de mise en relation a été refusée."
msgid "Edit Contact"
msgstr "Modifier le contact"
@ -638,53 +644,53 @@ msgstr "" @@ -638,53 +644,53 @@ msgstr ""
"sont pas possibles."
msgid "Close this window and disconnect?"
msgstr "Fermez cette fenêtre et vous déconnecter?"
msgstr "Voulez-vous fermer cette fenêtre et vous déconnecter ?"
msgid "Contacts Manager"
msgstr "Gestionnaire de contacts"
msgid "Restart required to apply updates. Click ok to restart now."
msgstr ""
"Redémarrez nécessaire pour appliquer les mises à jour. Cliquez sur "
"OK pour redémarrer maintenant."
"Un redémarrage est requis pour appliquer les mises à jour. Cliquez sur OK"
" pour redémarrer maintenant."
msgid "Failed to access camera/microphone."
msgstr "Impossible d'accéder à la caméra / au microphone."
msgstr "Impossible d'accéder à la caméra et au microphone."
msgid "Failed to establish peer connection."
msgstr "Impossible d'établir une connexion peer-to-peer."
msgstr "Impossible d'établir la connexion de pair a pair."
msgid "We are sorry but something went wrong. Boo boo."
msgstr "Désolé, mais quelque chose s'est mal passé. Aïe Aïe."
msgstr "Nous sommes désolé, mais quelque chose c'est mal passé. Aïe Aïe."
msgid "Oops"
msgstr "Oups"
msgid "Peer connection failed. Check your settings."
msgstr "La connexion peer-to-peer a échoué. Vérifier les paramètres."
msgstr "La connection avec le pair à échoué. Vérifiez vos réglages."
msgid "User hung up because of error."
msgstr "L'utilisateur a raccroché à cause d'une erreur."
msgstr "Le participant à raccroché suite à une erreur."
msgid " is busy. Try again later."
msgstr " est occupé. Réessayez plus tard."
msgid " rejected your call."
msgstr " a rejeté l'appel."
msgstr " à refuser votre appel."
msgid " does not pick up."
msgstr " ne réponds pas."
msgstr " n'a pas répondu."
msgid " tried to call you"
msgstr " a essayé de vous appeler"
msgstr " a essayé de vous appeler."
msgid " called you"
msgstr " vous a appelé"
msgid "Your browser is not supported. Please upgrade to a current version."
msgstr ""
"Votre navigateur n'est pas supporté. Veuillez mettre à jour vers la "
"version actuelle."
"Votre navigateur n'est pas supporté. Veuillez le mettre à jour vers une "
"version plus récente."
msgid "Chat with"
msgstr "Discuter avec"
@ -694,39 +700,39 @@ msgstr "Message de " @@ -694,39 +700,39 @@ msgstr "Message de "
#, python-format
msgid "You are now in room %s ..."
msgstr "Vous êtes maintenant dans le salon %s ..."
msgstr "Vous êtes maintenant dans le salon %s..."
msgid "Your browser does not support file transfer."
msgstr "Votre navigateur ne supporte pas le transfert de fichiers."
msgid "Could not load PDF: Please make sure to select a PDF document."
msgstr ""
"Impossible de charger le PDF: assurez-vous de sélectionner un "
"document PDF."
"Impossible de charger le PDF: assurez-vous que vous avez sélectionné "
"un document PDF."
msgid "Could not load PDF: Missing PDF file."
msgstr "Impossible de charger le fichier PDF: fichier PDF manquant."
#, python-format
msgid "An error occurred while loading the PDF (%s)."
msgstr "Une erreur s'est produite lors du chargement du PDF (%s)."
msgstr "Une erreur est survenue lors du chargement du PDF (%s)."
msgid "An unknown error occurred while loading the PDF."
msgstr "Une erreur inconnue s'est produite lors du chargement du PDF."
msgstr "Une erreur inconnue est survenue lors du chargement du PDF."
#, python-format
msgid "An error occurred while loading the PDF page (%s)."
msgstr "Une erreur est survenue lors du chargement de la page PDF (%s)."
msgid "An unknown error occurred while loading the PDF page."
msgstr "Une erreur inconnue s'est produite lors du chargement de la page PDF."
msgstr "Une erreur inconnue est survenue lors du chargement de la page PDF."
#, python-format
msgid "An error occurred while rendering the PDF page (%s)."
msgstr "Une erreur s'est produite lors du traitement de la page PDF (%s)."
msgstr "Une erreur est survenue lors du traitement de la page PDF (%s)."
msgid "An unknown error occurred while rendering the PDF page."
msgstr "Une erreur inconnue s'est produite lors du traitement de la page PDF."
msgstr "Une erreur inconnue est survenue lors du traitement du PDF."
msgid "Only PDF documents and OpenDocument files can be shared at this time."
msgstr ""
@ -745,8 +751,7 @@ msgstr "" @@ -745,8 +751,7 @@ msgstr ""
"Le partage d'écran a été refusé. Assurez-vous d'avoir activé l'accès"
" au partage d'écran pour votre navigateur. Copiez le lien "
"chrome://flags/#enable-usermedia-screen-capture et ouvrez-le dans votre "
"navigateur et activez le drapeau en haut. Redémarrez ensuite le navigateur "
"et vous êtes prêt à partir."
"navigateur et activez le drapeau en haut. Redémarrez ensuite le navigateur."
msgid "Permission to start screen sharing was denied."
msgstr "Le partage d'écran a été refusé."
@ -755,7 +760,13 @@ msgid "Use browser language" @@ -755,7 +760,13 @@ msgid "Use browser language"
msgstr "Utiliser la langue du navigateur"
msgid "Meet with me here:"
msgstr "Rencontre-moi ici:"
msgstr "Rejoins moi là :"
msgid "Please enter a new Room PIN to lock the room"
msgstr "Veuillez entrer un nouveau code PIN pour verrouiller le salon."
msgid "Do you want to unlock the room?"
msgstr "Voulez-vous déverrouiller le salon ?"
msgid "Room name"
msgstr "Nom du salon"
@ -764,55 +775,56 @@ msgid "" @@ -764,55 +775,56 @@ msgid ""
"The request contains an invalid parameter value. Please check the URL of "
"the video you want to share and try again."
msgstr ""
"La requête contient une valeur de paramètre non valide. Veuillez vérifier "
"l'URL de la vidéo que vous souhaitez partager et réessayer."
"La requête contient des paramètres invalides. Veuillez vérifier l'URL de "
"la vidéo que vous souhaitez partager et réessayer."
msgid ""
"The requested content cannot be played in an HTML5 player or another "
"error related to the HTML5 player has occurred. Please try again later."
msgstr ""
"Le contenu demandé ne peut pas être lu dans le lecteur HTML5 ou une "
"erreur s'est produite avec le lecteur HTML5. Veuillez réessayer plus tard."
"Le contenu requis ne peux pas être pas lu par le lecteur HTML5, ou une "
"autre erreur en relation avec le lecteur HTML5 est survenu. Veuillez "
"réessayer plus tard."
msgid ""
"The video requested was not found. Please check the URL of the video you "
"want to share and try again."
msgstr ""
"La vidéo demandée n'a pas été trouvée. Veuillez vérifier l'URL de la vidéo "
"que vous souhaitez partager et réessayer."
"Cette vidéo n'a pas été trouvée. Veuillez vérifier l'URL de la vidéo que "
"vous souhaitez partager et réessayez."
msgid ""
"The owner of the requested video does not allow it to be played in "
"embedded players."
msgstr ""
"Le propriétaire de la vidéo demandée ne vous permet pas de le lire dans le "
"lecteur intégré."
"Le propriétaire de la vidéo ne vous permet pas de la lire dans un lecteur"
" vidéo embarqué."
#, python-format
msgid ""
"An unknown error occurred while playing back the video (%s). Please try "
"again later."
msgstr ""
"Une erreur inconnue s'est produite lors de la lecture de la vidéo (%s). "
"Une erreur inconnue est survenue lors de la lecture de la vidéo (%s). "
"Veuillez réessayer plus tard."
msgid ""
"An unknown error occurred while playing back the video. Please try again "
"later."
msgstr ""
"Une erreur inconnue s'est produite lors de la lecture de la vidéo. Veuillez"
"Une erreur inconnue est survenue lors de la lecture de la vidéo. Veuillez"
" réessayer plus tard."
msgid "Unknown URL format. Please make sure to enter a valid YouTube URL."
msgstr ""
"Format d'URL inconnu. Veuillez vous assurer que vous avez effectivement entré"
" une URL YouTube."
"Format d'URL inconnu. Veuillez vérifier qu'il s'agit d'une URL Youtube "
"valide."
msgid "Error"
msgstr "Erreur"
msgid "Hint"
msgstr "Indice"
msgstr "Information"
msgid "Please confirm"
msgstr "Veuillez confirmer"
@ -821,14 +833,14 @@ msgid "More information required" @@ -821,14 +833,14 @@ msgid "More information required"
msgstr "Besoin de plus d'informations"
msgid "Ok"
msgstr "Ok"
msgstr "OK"
msgid ""
msgid
"Screen sharing requires a browser extension. Please add the Spreed WebRTC"
" screen sharing extension to Chrome and try again."
msgstr ""
"Le partage de l'écran nécessite une extension pour le navigateur. Veuillez "
"ajouter l'extension Spreed WebRTC à Chrome et réessayer."
"Le partage d'écran nécessite une extension pour le navigateur. Veuillez "
"ajouter l'extension \"Spreed WebRTC screen sharing\" et réessayer."
msgid "Access code required"
msgstr "Code d'accès requis"
@ -843,16 +855,16 @@ msgid "" @@ -843,16 +855,16 @@ msgid ""
"Failed to verify access code. Check your Internet connection and try "
"again."
msgstr ""
"Le code d'accès n'a pas pu être vérifié. Vérifiez votre connexion Internet "
"et réessayez."
"Impossible de vérifier votre code d'accès. Vérifiez votre connexion "
"Internet et réessayez."
#, python-format
msgid "PIN for room %s is now '%s'."
msgstr "PIN pour le salon %s est maintenant '%s'."
msgstr "Le code PIN pour le salon %s est maintenant '%s'."
#, python-format
msgid "PIN lock has been removed from room %s."
msgstr "Le verrouillage par code PIN a été retiré du salon %s."
msgstr "Le salon %s n'est plus protéger par un code PIN."
#, python-format
msgid "Enter the PIN for room %s"
@ -870,7 +882,7 @@ msgid "and %d others" @@ -870,7 +882,7 @@ msgid "and %d others"
msgstr "et %d autres"
msgid "User"
msgstr "Utilisateur"
msgstr "Participant"
msgid "Someone"
msgstr "Quelqu'un"
@ -878,3 +890,6 @@ msgstr "Quelqu'un" @@ -878,3 +890,6 @@ msgstr "Quelqu'un"
msgid "Me"
msgstr "Moi"
#~ msgid "Register"
#~ msgstr "S'enregistrer"

886
src/i18n/messages-it.po

@ -0,0 +1,886 @@ @@ -0,0 +1,886 @@
# Translations template for Spreed WebRTC.
# Copyright (C) 2016 struktur AG
# This file is distributed under the same license as the Spreed WebRTC
# project.
# Riccardo Olivo <olivo@arkitech.it>, 2016.
#
msgid ""
msgstr ""
"Project-Id-Version: Spreed WebRTC 1.0\n"
"Report-Msgid-Bugs-To: simon@struktur.de\n"
"POT-Creation-Date: 2016-11-11 02:29+0100\n"
"PO-Revision-Date: 2016-11-11 02:30+0100\n"
"Last-Translator: \n"
"Language-Team: Riccardo Olivo <olivo@arkitech.it>\n"
"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 1.3\n"
"X-Generator: Poedit 1.8.11\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
msgid "Standard view"
msgstr "Visualizzazione standard"
msgid "Large view"
msgstr "Visualizzazione estesa"
msgid "Kiosk view"
msgstr "Visualizzazione chiosco"
msgid "Auditorium"
msgstr "Auditorium"
msgid "Start chat"
msgstr "Inizia chat"
msgid "Start video call"
msgstr "Inizia chiamata video"
msgid "Start audio conference"
msgstr "Inizia chiamata audio"
msgid "No one else here"
msgstr "Nessun altro qui"
msgid "Take"
msgstr "Scatta"
msgid "Retake"
msgstr "Riscatta"
msgid "Cancel"
msgstr "Annulla"
msgid "Set as Profile Picture"
msgstr "Imposta come immagine profilo"
msgid "Take picture"
msgstr "Scatta immagine"
msgid "Upload picture"
msgstr "Carica immagine"
msgid "Waiting for camera"
msgstr "In attesa della fotocamera"
msgid "Picture"
msgstr "Immagine"
msgid "The file couldn't be read."
msgstr "Il file non può essere aperto."
msgid "The file is not an image."
msgstr "Il file non è un immagine."
#, python-format
msgid "The file is too large. Max. %d MB."
msgstr "Questo file è di dimensioni eccessive. Max. %d MB."
msgid "Select file"
msgstr "Seleziona file"
msgid "Chat sessions"
msgstr "Sessioni di chat"
msgid "Room chat"
msgstr "Room chat"
msgid "Peer to peer"
msgstr "Peer-to-peer"
msgid "Close chat"
msgstr "Chiudi la chat"
msgid "Upload files"
msgstr "Carica file"
msgid "Share my location"
msgstr "Condividi la mia posizione"
msgid "Clear chat"
msgstr "Pulisci chat"
msgid "is typing..."
msgstr "sta scrivendo..."
msgid "has stopped typing..."
msgstr "ha smesso di scrivere..."
msgid "Type here to chat..."
msgstr "Scrivi qui per chattare..."
msgid "Send"
msgstr "Invia"
msgid "Accept"
msgstr "Accetta"
msgid "Reject"
msgstr "Rifiuta"
msgid "You have no contacts."
msgstr "Non hai contatti."
msgid ""
"To add new contacts, join a room and create a contact add request by "
"clicking on the star icon next to a user entry."
msgstr ""
"Per aggiungere nuovi contatti unisciti a una room e crea una richiesta di "
"aggiunta contatto cliccando sull'icona a forma di stella vicina a un utente."
msgid "Edit contact"
msgstr "Modifica contatto"
msgid "Edit"
msgstr "Modifica"
msgid "Name"
msgstr "Nome"
msgid "Remove"
msgstr "Rimuovi"
msgid "Refresh"
msgstr "Aggiorna"
msgid "Save"
msgstr "Salva"
msgid "Close"
msgstr "Chiudi"
msgid "File sharing"
msgstr "Condividi file"
msgid "File is no longer available"
msgstr "Il file non è più disponibile"
msgid "Download"
msgstr "Scarica"
msgid "Open"
msgstr "Apri"
msgid "Unshare"
msgstr "Rimuovi condivisione"
msgid "Retry"
msgstr "Riprova"
msgid "Download failed."
msgstr "Scaricamento fallito."
msgid "Share a YouTube video"
msgstr "Condividi un video YouTube"
msgid "Share a file as presentation"
msgstr "Condividi un file come presentazione"
msgid "Share your screen"
msgstr "Condividi il tuo schermo"
msgid "Chat"
msgstr "Chat"
msgid "Contacts"
msgstr "Contatti"
msgid "Mute microphone"
msgstr "Silenzia il microfono"
msgid "Turn camera off"
msgstr "Spegni la videocamera"
msgid "Settings"
msgstr "Impostazioni"
msgid "Loading presentation ..."
msgstr "Caricando la presentazione..."
msgid "Please upload a document"
msgstr "Carica un documento"
msgid ""
"Documents are shared with everyone in this call. The supported file types "
"are PDF and OpenDocument files."
msgstr ""
"I documenti sono condivisi con tutti i partecipanti a questa chiamata. I "
"tipi di file supportati sono PDF e OpenDocument."
msgid "Upload"
msgstr "Carica"
msgid "You can drag files here too."
msgstr "Puoi trascinare i file qui."
msgid "Presentation controls"
msgstr "Controlli presentazione"
msgid "Prev"
msgstr "Precedente"
msgid "Next"
msgstr "Prossimo"
msgid "Change room"
msgstr "Scegli la stanza"
msgid "Room"
msgstr "Stanza"
msgid "Leave room"
msgstr "Lascia la stanza"
msgid "Main"
msgstr "Principale"
msgid "Current room"
msgstr "Stanza corrente"
msgid "Screen sharing options"
msgstr "Opzioni condivisione schermo"
msgid "Fit screen."
msgstr "Adatta allo schermo."
msgid "Share screen"
msgstr "Condividi schermo"
msgid "Please select what to share."
msgstr "Scegli cosa condividere."
msgid "Screen"
msgstr "Schermo"
msgid "Window"
msgstr "Finestra"
msgid "Application"
msgstr "Applicazione"
msgid "Share the whole screen. Click share to select the screen."
msgstr ""
"Condividi l'intero schermo. Fai click su condividi per selezionare lo "
"schermo."
msgid "Share a single window. Click share to select the window."
msgstr ""
"Condividi una singola finestra. Fai click su condividi per selezionare la "
"singola finestra."
msgid ""
"Share all windows of a application. This can leak content behind windows "
"when windows get moved. Click share to select the application."
msgstr ""
"Condividi tutte le finestre di un'applicazione. Questa opzione può mostrare "
"il contenuto dietro alle finestre quando queste vengono spostate. Fai click "
"su condividi per selezionare l'applicazione."
msgid "Share"
msgstr "Condividi"
msgid "OK"
msgstr "OK"
msgid "Profile"
msgstr "Profilo"
msgid "Your name"
msgstr "Il tuo nome"
msgid "Your picture"
msgstr "La tua immagine"
msgid "Status message"
msgstr "Stato"
msgid "What's on your mind?"
msgstr "A cosa pensi?"
msgid ""
"Your picture, name and status message identify yourself in calls, chats and "
"rooms."
msgstr ""
"La tua immagine, il tuo nome ed il tuo stato vengono visualizzati nelle "
"chiamate, nelle chat e nelle stanze."
msgid "Your ID"
msgstr "Il tuo ID"
msgid ""
"Authenticated by certificate. To log out you have to remove your certificate "
"from the browser."
msgstr ""
"Autenticazione eseguita con certificato. Per disconnettersi rimuovere il "
"certificato dal browser."
msgid "Sign in"
msgstr "Accedi"
msgid "Create an account"
msgstr "Crea un account"
msgid "Sign out"
msgstr "Disconnettiti"
msgid "Manage account"
msgstr "Gestisci account"
msgid "Media"
msgstr "Dispositivi"
msgid "Microphone"
msgstr "Microfono"
msgid "Camera"
msgstr "Videocamera"
msgid "Video quality"
msgstr "Qualità video"
msgid "Low"
msgstr "Bassa"
msgid "High"
msgstr "Alta"
msgid "HD"
msgstr "HD"
msgid "Full HD"
msgstr "Full HD"
msgid "General"
msgstr "Generale"
msgid "Language"
msgstr "Lingua"
msgid "Language changes become active on reload."
msgstr ""
"Le modifiche alla lingua diventano effettive con l'aggiornamento della "
"pagina."
msgid "Default room"
msgstr "Stanza predefinita"
msgid "Set alternative room to join at start."
msgstr "Imposta l'ingresso in una stanza alternativa all'avvio."
msgid "Notifications"
msgstr "Notifiche"
msgid "Desktop notification"
msgstr "Notifiche Desktop"
msgid "Enable"
msgstr "Abilita"
msgid "Denied - check your browser settings"
msgstr "Errore - controlla le impostazioni del tuo browser"
msgid "Allowed"
msgstr "Permesso"
msgid "Sounds for incoming messages"
msgstr "Suoni per i messaggi in arrivo"
msgid "Ring on incoming calls"
msgstr "Suoneria per le chiamate in arrivo"
msgid "Sounds for users in current room"
msgstr "Suoni per gli utenti nella stanza corrente"
msgid "Advanced settings"
msgstr "Opzioni avanzate"
msgid "Play audio on same device as selected microphone"
msgstr "Riproduci l'audio sullo stesso dispositivo del microfono selezionato"
msgid "Experimental AEC"
msgstr "AEC Sperimentale"
msgid "Experimental AGC"
msgstr "AGC Sperimentale"
msgid "Experimental noise suppression"
msgstr "Soppressione del rumore sperimentale"
msgid "Max video frame rate"
msgstr "Massimo framerate video"
msgid "auto"
msgstr "automatico"
msgid "Send stereo audio"
msgstr "Invia audio in stereo"
msgid ""
"Sending stereo audio disables echo cancellation. Enable only if you have "
"stereo input."
msgstr ""
"Inviare l'audio in stereo disabilita la cancellazione dell eco. Abilitare "
"solo se si dispone di un input stereo."
msgid "Detect CPU over use"
msgstr "Rileva utilizzo eccessivo della CPU"
msgid "Automatically reduces video quality as needed."
msgstr "Riduce automaticamente la qualità video se necessario."
msgid "Optimize for high resolution video"
msgstr "Ottimizza per i video ad alta risoluzione"
msgid "Reduce video noise"
msgstr "Riduce il rumore digitale nei video"
msgid "Prefer VP9 video codec"
msgstr "Prediligi il codec VP9"
msgid "Enable experiments"
msgstr "Abilita gli esperimenti"
msgid "Show advanced settings"
msgstr "Mostra opzioni avanzate"
msgid "Hide advanced settings"
msgstr "Nascondi opzioni avanzate"
msgid "Remember settings"
msgstr "Ricorda le impostazioni"
msgid ""
"Your ID will still be kept - press the log out button above to delete the ID."
msgstr ""
"Il tuo ID sarà mantenuto - Premi il pulsante disconnetti per cancellare l'ID."
msgid "Room PIN"
msgstr "PIN della stanza"
msgid "Room link"
msgstr "Link stanza"
msgid "Invite by Email"
msgstr "Invita via Email"
msgid "Invite with Facebook"
msgstr "Invita via Facebook"
msgid "Invite with Twitter"
msgstr "Invita via Twitter"
msgid "Invite with Google Plus"
msgstr "Invita via Google Plus"
msgid "Invite with XING"
msgstr "Invita via XING"
msgid "Initializing"
msgstr "Inizializzazione"
msgid "Online"
msgstr "Online"
msgid "Calling"
msgstr "Sto chiamando"
msgid "Hangup"
msgstr "Fine chiamata"
msgid "In call with"
msgstr "In chiamata con"
msgid "Conference with"
msgstr "In conferenza con"
msgid "Your are offline"
msgstr "Sei offline"
msgid "Go online"
msgstr "Connettiti"
msgid "Connection interrupted"
msgstr "Connessione Interrotta"
msgid "An error occured"
msgstr "Errore"
msgid "Incoming call"
msgstr "Chiamata in arrivo"
msgid "from"
msgstr "da"
msgid "Accept call"
msgstr "Rispondi"
msgid "Waiting for camera/microphone access"
msgstr "Attesa accesso videocamera/microfono"
msgid "Your audio level"
msgstr "Volume"
msgid "Checking camera and microphone access."
msgstr "Controllo accesso videocamera e microfono."
msgid "Please allow access to your camera and microphone."
msgstr "Consenti l'accesso alla videocamera ed al microfono."
msgid "Camera / microphone access required."
msgstr "E' richesto l'accesso alla videocamera/microfono."
msgid ""
"Please check your browser settings and allow camera and microphone access "
"for this site."
msgstr ""
"Per favore verifica le impostazioni del tuo browser e permetti l'accesso "
"alla videocamera ed al microfono per questo sito."
msgid "Skip check"
msgstr "Salta controllo"
msgid "Click here for help (Google Chrome)."
msgstr "Clicca qui per aiuto (Google Chrome)."
msgid "Please set your user details and settings."
msgstr "Inserisci i tuoi dati utente e le impostazioni."
msgid "Enter a room name"
msgstr "Inserisci il nome di una stanza"
msgid "Random room name"
msgstr "Nome casuale stanza"
msgid "Enter room"
msgstr "Entra nella stanza"
msgid ""
"Enter the name of an existing room. You can create new rooms when you are "
"signed in."
msgstr ""
"Inserisci il nome di una stanza esistente. Puoi creare nuove stanze quando "
"effetui l'accesso."
msgid "Room history"
msgstr "Cronologia stanza"
msgid "Please sign in."
msgstr "Prego accedi."
msgid "Videos play simultaneously for everyone in this call."
msgstr ""
"I video vengono riprodotti simultaneamente per gli utenti in questa stanza."
msgid "YouTube URL"
msgstr "YouTube URL"
msgid ""
"Could not load YouTube player API, please check your network / firewall "
"settings."
msgstr ""
"Impossibile caricare le API del player di YouTube, per favore controlla le "
"tue impostazioni di rete / firewall."
msgid "Currently playing"
msgstr "In riproduzione"
msgid "YouTube controls"
msgstr "Opzioni YouTube"
msgid "YouTube video to share"
msgstr "Video YouTube da condividere"
msgid "Peer to peer chat active."
msgstr "Chat Peer-to-peer attiva."
msgid "Peer to peer chat is now off."
msgstr "Chat Peer-to-peer inattiva."
msgid " is now offline."
msgstr " è offline."
msgid " is now online."
msgstr " è online."
msgid "You share file:"
msgstr "File condiviso:"
msgid "Incoming file:"
msgstr "File in arrivo:"
msgid "You shared your location:"
msgstr "Hai condiviso la tua posizione:"
msgid "Location received:"
msgstr "Posizione ricevuta:"
msgid "You accepted the contact request."
msgstr "Hai accettato la richiesta contatto."
msgid "You rejected the contact request."
msgstr "Hai rifiutato la richiesta contatto."
msgid "You sent a contact request."
msgstr "Hai inviato una richiesta di contatto."
msgid "Your contact request was accepted."
msgstr "La tua richiesta di contatto è stata accettata."
msgid "Incoming contact request."
msgstr "Richiesta di contatto in arrivo."
msgid "Your contact request was rejected."
msgstr "La tua richiesta di contatto è stata rifiutata."
msgid "Edit Contact"
msgstr "Modifica contatto"
msgid "Your browser does not support WebRTC. No calls possible."
msgstr "Il tuo browser non supporta WebRTC. Non è possibile chiamare."
msgid "Close this window and disconnect?"
msgstr "Chiudi questa finestra e disconnetti?"
msgid "Contacts Manager"
msgstr "Contatti"
msgid "Restart required to apply updates. Click ok to restart now."
msgstr ""
"E' necessario riavviare il programma per applicare le modifiche. Fai click "
"qui per riavviarlo ora."
msgid "Failed to access camera/microphone."
msgstr "Tentativo fallito di accesso alla videocamera/microfono."
msgid "Failed to establish peer connection."
msgstr "Impossibile stabilire una connessione."
msgid "We are sorry but something went wrong. Boo boo."
msgstr "Ci dispiace ma qualcosa è andato storto."
msgid "Oops"
msgstr "Oops"
msgid "Peer connection failed. Check your settings."
msgstr "Connessione fallita. Controlla le tue impostazioni."
msgid "User hung up because of error."
msgstr "L'utente ha riattaccato a causa di un errore."
msgid " is busy. Try again later."
msgstr " è impegnato. Riprova più tardi."
msgid " rejected your call."
msgstr " ha rifiutato la tua chiamata."
msgid " does not pick up."
msgstr " non ha risposto."
msgid " tried to call you"
msgstr " ha provato a chiamarti."
msgid " called you"
msgstr " ti ha chiamato."
msgid "Your browser is not supported. Please upgrade to a current version."
msgstr "Il tuo browser non è supportato. Per favore aggiornalo."
msgid "Chat with"
msgstr "Parla con"
msgid "Message from "
msgstr "Messaggio da "
#, python-format
msgid "You are now in room %s ..."
msgstr "Ora sei nella stanza %s ..."
msgid "Your browser does not support file transfer."
msgstr "Il tuo browser non supporta il trasferimento di file."
msgid "Could not load PDF: Please make sure to select a PDF document."
msgstr ""
"Impossibile caricare il PDF: Per favore verifica di aver selezionato un file "
"PDF."
msgid "Could not load PDF: Missing PDF file."
msgstr "Impossibile caricare il PDF: File mancante."
#, python-format
msgid "An error occurred while loading the PDF (%s)."
msgstr "Errore durante il caricamento del PDF (%s)."
msgid "An unknown error occurred while loading the PDF."
msgstr "Errore sconosciuto durante il caricamento del PDF."
#, python-format
msgid "An error occurred while loading the PDF page (%s)."
msgstr "Errore in fase di caricamento della pagina del PDF (%s)."
msgid "An unknown error occurred while loading the PDF page."
msgstr "Errore sconosciuto in fase di caricamento della pagina PDF (%s)."
#, python-format
msgid "An error occurred while rendering the PDF page (%s)."
msgstr "Errore durante il rendering della pagina PDF (%s)."
msgid "An unknown error occurred while rendering the PDF page."
msgstr "Errore sconosciuto durante il rendering della pagina PDF."
msgid "Only PDF documents and OpenDocument files can be shared at this time."
msgstr "Possono essere condivisi solamente file PDF ed OpenDocument."
#, python-format
msgid "Failed to start screen sharing (%s)."
msgstr "Impossibile avviare condivisione schermo (%s)."
msgid ""
"Permission to start screen sharing was denied. Make sure to have enabled "
"screen sharing access for your browser. Copy chrome://flags/#enable-"
"usermedia-screen-capture and open it with your browser and enable the flag "
"on top. Then restart the browser and you are ready to go."
msgstr ""
"Il permesso per avviare la condivisione dello schermo è stato negato. "
"Verifica di aver abilitato la condivisione dello schermo per il tuo browser. "
"Copia chrome://flags/#enable-usermedia-screen-capture, aprilo nel tuo "
"browser ed abilita la spunta in cima, quindi riavvia il browser."
msgid "Permission to start screen sharing was denied."
msgstr "Il permesso negato per la condivisione dello schermo."
msgid "Use browser language"
msgstr "Usa la lingua del browser"
msgid "Meet with me here:"
msgstr "Incontriamoci qui:"
msgid "Please enter a new Room PIN to lock the room"
msgstr "Inserisci un nuovo PIN per bloccare la stanza"
msgid "Do you want to unlock the room?"
msgstr "Vuoi sbloccare la stanza?"
msgid "Room name"
msgstr "Nome stanza"
msgid ""
"The request contains an invalid parameter value. Please check the URL of the "
"video you want to share and try again."
msgstr ""
"La richiesta contiene un parametro invalido. Per favore controlla l'URL del "
"video che vuoi condividere e riprova."
msgid ""
"The requested content cannot be played in an HTML5 player or another error "
"related to the HTML5 player has occurred. Please try again later."
msgstr ""
"Il contenuto richiesto non può essere riprodotto in un player HTML5 - o si è "
"verificato un altro errore relativo al player HTML5. Per favore riprova più "
"tardi."
msgid ""
"The video requested was not found. Please check the URL of the video you "
"want to share and try again."
msgstr ""
"Il video richiesto non è stato trovato. Per favore controlla la URL del "
"video che vuoi condividere e riprova."
msgid ""
"The owner of the requested video does not allow it to be played in embedded "
"players."
msgstr ""
"Il proprietario del video non permette che venga riprodotto nel player "
"incluso."
#, python-format
msgid ""
"An unknown error occurred while playing back the video (%s). Please try "
"again later."
msgstr ""
"Errore sconosciuto durante la riproduzione del video (%s). Per favore "
"riprova più tardi."
msgid ""
"An unknown error occurred while playing back the video. Please try again "
"later."
msgstr ""
"Errore sconosciuto durante la riproduzione del video. Riprova più tardi."
msgid "Unknown URL format. Please make sure to enter a valid YouTube URL."
msgstr ""
"Formato URL sconosciuto. Per favore verifica di aver inserito un URL Youtube "
"valido."
msgid "Error"
msgstr "Errore"
msgid "Hint"
msgstr "Suggerimento"
msgid "Please confirm"
msgstr "Prego conferma"
msgid "More information required"
msgstr "Sono richieste maggiori informazioni"
msgid "Ok"
msgstr "Ok"
msgid ""
"Screen sharing requires a browser extension. Please add the Spreed WebRTC "
"screen sharing extension to Chrome and try again."
msgstr ""
"La condivisione dello schermo richiede un'estensione del browser. Per favore "
"aggiungi l'estensione WebRTC screen sharing a Chrome e riprova."
msgid "Access code required"
msgstr "Codice d'accesso richiesto"
msgid "Access denied"
msgstr "Accesso negato"
msgid "Please provide a valid access code."
msgstr "Per favore inserisci un codice di accesso valido."
msgid ""
"Failed to verify access code. Check your Internet connection and try again."
msgstr ""
"Impossibile verificare il codice d'accesso. Controlla la tua conessione a "
"Internet e riprova."
#, python-format
msgid "PIN for room %s is now '%s'."
msgstr "Il PIN per la stanza %s è '%s'."
#, python-format
msgid "PIN lock has been removed from room %s."
msgstr "Il blocco del PIN è stato rimosso per la stanza %s."
#, python-format
msgid "Enter the PIN for room %s"
msgstr "Inserisci il PIN per la stanza %s"
msgid "Please sign in to create rooms."
msgstr "Accedi per creare una stanza."
#, python-format
msgid "and %s"
msgstr "e %s"
#, python-format
msgid "and %d others"
msgstr "e %d altri"
msgid "User"
msgstr "Utente"
msgid "Someone"
msgstr "Qualcuno"
msgid "Me"
msgstr "Me"
#~ msgid "Register"
#~ msgstr "Registrati"

17
src/i18n/messages-ja.po

@ -8,7 +8,7 @@ msgid "" @@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Spreed WebRTC 1.0\n"
"Report-Msgid-Bugs-To: simon@struktur.de\n"
"POT-Creation-Date: 2016-02-17 14:15+0100\n"
"POT-Creation-Date: 2016-08-18 18:21+0200\n"
"PO-Revision-Date: 2014-04-23 22:25+0100\n"
"Last-Translator: Curt Frisemo <curt.frisemo@spreed.com>\n"
"Language-Team: Curt Frisemo <curt.frisemo@spreed.com>\n"
@ -301,9 +301,6 @@ msgstr "" @@ -301,9 +301,6 @@ msgstr ""
msgid "Your ID"
msgstr ""
msgid "Register"
msgstr ""
msgid ""
"Authenticated by certificate. To log out you have to remove your "
"certificate from the browser."
@ -448,6 +445,9 @@ msgid "" @@ -448,6 +445,9 @@ msgid ""
" ID."
msgstr ""
msgid "Room PIN"
msgstr ""
msgid "Room link"
msgstr ""
@ -738,6 +738,12 @@ msgstr "ブラウザの言語を使用" @@ -738,6 +738,12 @@ msgstr "ブラウザの言語を使用"
msgid "Meet with me here:"
msgstr "ここで私と会う:"
msgid "Please enter a new Room PIN to lock the room"
msgstr ""
msgid "Do you want to unlock the room?"
msgstr ""
#, fuzzy
msgid "Room name"
msgstr "あなたの名前"
@ -842,3 +848,6 @@ msgstr "誰か" @@ -842,3 +848,6 @@ msgstr "誰か"
msgid "Me"
msgstr "私"
#~ msgid "Register"
#~ msgstr ""

17
src/i18n/messages-ko.po

@ -8,7 +8,7 @@ msgid "" @@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Spreed WebRTC 1.0\n"
"Report-Msgid-Bugs-To: simon@struktur.de\n"
"POT-Creation-Date: 2016-02-17 14:15+0100\n"
"POT-Creation-Date: 2016-08-18 18:21+0200\n"
"PO-Revision-Date: 2014-04-13 20:30+0900\n"
"Last-Translator: Curt Frisemo <curt.frisemo@spreed.com>\n"
"Language-Team: Curt Frisemo <curt.frisemo@spreed.com>\n"
@ -301,9 +301,6 @@ msgstr "" @@ -301,9 +301,6 @@ msgstr ""
msgid "Your ID"
msgstr ""
msgid "Register"
msgstr ""
msgid ""
"Authenticated by certificate. To log out you have to remove your "
"certificate from the browser."
@ -448,6 +445,9 @@ msgid "" @@ -448,6 +445,9 @@ msgid ""
" ID."
msgstr ""
msgid "Room PIN"
msgstr ""
msgid "Room link"
msgstr ""
@ -738,6 +738,12 @@ msgstr "브라우저 언어 사용" @@ -738,6 +738,12 @@ msgstr "브라우저 언어 사용"
msgid "Meet with me here:"
msgstr "나를 여기서 만납니다:"
msgid "Please enter a new Room PIN to lock the room"
msgstr ""
msgid "Do you want to unlock the room?"
msgstr ""
#, fuzzy
msgid "Room name"
msgstr "사용자 이름"
@ -842,3 +848,6 @@ msgstr "어떤 사람" @@ -842,3 +848,6 @@ msgstr "어떤 사람"
msgid "Me"
msgstr "나"
#~ msgid "Register"
#~ msgstr ""

20
src/i18n/messages-ru.po

@ -9,7 +9,7 @@ msgid "" @@ -9,7 +9,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Spreed WebRTC 1.0\n"
"Report-Msgid-Bugs-To: simon@struktur.de\n"
"POT-Creation-Date: 2016-02-17 14:15+0100\n"
"POT-Creation-Date: 2016-08-18 18:21+0200\n"
"PO-Revision-Date: 2016-02-18 16:03+0500\n"
"Last-Translator: Irek Zaripov <iazaripov@gmail.com>\n"
"Language-Team: ru <>\n"
@ -18,7 +18,7 @@ msgstr "" @@ -18,7 +18,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 0.9.6\n"
"Generated-By: Babel 1.3\n"
msgid "Standard view"
msgstr "Стандартный вид"
@ -306,9 +306,6 @@ msgstr "" @@ -306,9 +306,6 @@ msgstr ""
msgid "Your ID"
msgstr "Ваш ID"
msgid "Register"
msgstr "Отметить"
msgid ""
"Authenticated by certificate. To log out you have to remove your "
"certificate from the browser."
@ -456,6 +453,10 @@ msgstr "" @@ -456,6 +453,10 @@ msgstr ""
"Ваш ID-прежнему будут храниться - нажмите кнопку выхода из системы выше, "
"чтобы удалить ID."
#, fuzzy
msgid "Room PIN"
msgstr "Ссылка комнаты"
msgid "Room link"
msgstr "Ссылка комнаты"
@ -755,6 +756,12 @@ msgstr "Использовать язык браузера" @@ -755,6 +756,12 @@ msgstr "Использовать язык браузера"
msgid "Meet with me here:"
msgstr "Встретиться со мной здесь:"
msgid "Please enter a new Room PIN to lock the room"
msgstr ""
msgid "Do you want to unlock the room?"
msgstr ""
msgid "Room name"
msgstr "Имя комнаты"
@ -878,3 +885,6 @@ msgstr "Кто то" @@ -878,3 +885,6 @@ msgstr "Кто то"
msgid "Me"
msgstr "Я"
#~ msgid "Register"
#~ msgstr "Отметить"

17
src/i18n/messages-zh-cn.po

@ -8,7 +8,7 @@ msgid "" @@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Spreed WebRTC 1.0\n"
"Report-Msgid-Bugs-To: simon@struktur.de\n"
"POT-Creation-Date: 2016-02-17 14:15+0100\n"
"POT-Creation-Date: 2016-08-18 18:21+0200\n"
"PO-Revision-Date: 2014-05-21 09:54+0800\n"
"Last-Translator: Michael P.\n"
"Language-Team: Curt Frisemo <curt.frisemo@spreed.com>\n"
@ -301,9 +301,6 @@ msgstr "" @@ -301,9 +301,6 @@ msgstr ""
msgid "Your ID"
msgstr ""
msgid "Register"
msgstr ""
msgid ""
"Authenticated by certificate. To log out you have to remove your "
"certificate from the browser."
@ -448,6 +445,9 @@ msgid "" @@ -448,6 +445,9 @@ msgid ""
" ID."
msgstr ""
msgid "Room PIN"
msgstr ""
msgid "Room link"
msgstr ""
@ -737,6 +737,12 @@ msgstr "使用浏览器语言" @@ -737,6 +737,12 @@ msgstr "使用浏览器语言"
msgid "Meet with me here:"
msgstr "我们这里见:"
msgid "Please enter a new Room PIN to lock the room"
msgstr ""
msgid "Do you want to unlock the room?"
msgstr ""
#, fuzzy
msgid "Room name"
msgstr "您的名字"
@ -841,3 +847,6 @@ msgstr "某人" @@ -841,3 +847,6 @@ msgstr "某人"
msgid "Me"
msgstr "我"
#~ msgid "Register"
#~ msgstr ""

17
src/i18n/messages-zh-tw.po

@ -8,7 +8,7 @@ msgid "" @@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Spreed WebRTC 1.0\n"
"Report-Msgid-Bugs-To: simon@struktur.de\n"
"POT-Creation-Date: 2016-02-17 14:15+0100\n"
"POT-Creation-Date: 2016-08-18 18:21+0200\n"
"PO-Revision-Date: 2014-05-21 09:55+0800\n"
"Last-Translator: Michael P.\n"
"Language-Team: Curt Frisemo <curt.frisemo@spreed.com>\n"
@ -301,9 +301,6 @@ msgstr "" @@ -301,9 +301,6 @@ msgstr ""
msgid "Your ID"
msgstr ""
msgid "Register"
msgstr ""
msgid ""
"Authenticated by certificate. To log out you have to remove your "
"certificate from the browser."
@ -448,6 +445,9 @@ msgid "" @@ -448,6 +445,9 @@ msgid ""
" ID."
msgstr ""
msgid "Room PIN"
msgstr ""
msgid "Room link"
msgstr ""
@ -737,6 +737,12 @@ msgstr "使用瀏覽器語言" @@ -737,6 +737,12 @@ msgstr "使用瀏覽器語言"
msgid "Meet with me here:"
msgstr "我們這裡見:"
msgid "Please enter a new Room PIN to lock the room"
msgstr ""
msgid "Do you want to unlock the room?"
msgstr ""
#, fuzzy
msgid "Room name"
msgstr "您的名字"
@ -841,3 +847,6 @@ msgstr "某人" @@ -841,3 +847,6 @@ msgstr "某人"
msgid "Me"
msgstr "我"
#~ msgid "Register"
#~ msgstr ""

14
src/i18n/messages.pot

@ -9,7 +9,7 @@ msgid "" @@ -9,7 +9,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Spreed WebRTC 1.0\n"
"Report-Msgid-Bugs-To: simon@struktur.de\n"
"POT-Creation-Date: 2016-02-17 14:15+0100\n"
"POT-Creation-Date: 2016-08-18 18:21+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -294,9 +294,6 @@ msgstr "" @@ -294,9 +294,6 @@ msgstr ""
msgid "Your ID"
msgstr ""
msgid "Register"
msgstr ""
msgid ""
"Authenticated by certificate. To log out you have to remove your "
"certificate from the browser."
@ -438,6 +435,9 @@ msgid "" @@ -438,6 +435,9 @@ msgid ""
" ID."
msgstr ""
msgid "Room PIN"
msgstr ""
msgid "Room link"
msgstr ""
@ -720,6 +720,12 @@ msgstr "" @@ -720,6 +720,12 @@ msgstr ""
msgid "Meet with me here:"
msgstr ""
msgid "Please enter a new Room PIN to lock the room"
msgstr ""
msgid "Do you want to unlock the room?"
msgstr ""
msgid "Room name"
msgstr ""

4
src/styles/components/_social.scss

@ -20,6 +20,10 @@ @@ -20,6 +20,10 @@
*/
.#{$fa-css-prefix} {
&.pin {
color: $social-email-color;
}
&.link {
color: $social-email-color;
}

4
static/css/main.min.css vendored

File diff suppressed because one or more lines are too long

1
static/js/app.js

@ -267,6 +267,7 @@ define([ @@ -267,6 +267,7 @@ define([
}());
console.info("Selected language: "+lang);
app.constant("translationLanguage", {lang: lang});
// Set language and load default translations.
launcher.translationData.lang = lang;

3
static/js/controllers/appcontroller.js

@ -57,6 +57,9 @@ define(["jquery", "angular", "underscore"], function($, angular, _) { @@ -57,6 +57,9 @@ define(["jquery", "angular", "underscore"], function($, angular, _) {
videoNoiseReduction: false,
preferVideoSendCodecVP9: false
},
turn: {
selectedRegion: null
},
sound: {
incomingMessages: true,
incomingCall: true,

7
static/js/controllers/chatroomcontroller.js

@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
*/
"use strict";
define(['jquery', 'underscore', 'moment', 'text!partials/fileinfo.html', 'text!partials/contactrequest.html', 'text!partials/geolocation.html', 'text!partials/picturehover.html'], function($, _, moment, templateFileInfo, templateContactRequest, templateGeolocation, templatePictureHover) {
define(['angular', 'jquery', 'underscore', 'moment', 'text!partials/fileinfo.html', 'text!partials/contactrequest.html', 'text!partials/geolocation.html', 'text!partials/picturehover.html'], function(angular, $, _, moment, templateFileInfo, templateContactRequest, templateGeolocation, templatePictureHover) {
// ChatroomController
return ["$scope", "$element", "$window", "safeMessage", "safeDisplayName", "$compile", "$filter", "translation", "mediaStream", function($scope, $element, $window, safeMessage, safeDisplayName, $compile, $filter, translation, mediaStream) {
@ -220,6 +220,9 @@ define(['jquery', 'underscore', 'moment', 'text!partials/fileinfo.html', 'text!p @@ -220,6 +220,9 @@ define(['jquery', 'underscore', 'moment', 'text!partials/fileinfo.html', 'text!p
var element;
var scroll = this.canScroll();
lastMessageContainer = null;
if (angular.isString(s)) {
s = safeMessage(s);
}
if (!extra_css) {
extra_css = "";
@ -547,7 +550,7 @@ define(['jquery', 'underscore', 'moment', 'text!partials/fileinfo.html', 'text!p @@ -547,7 +550,7 @@ define(['jquery', 'underscore', 'moment', 'text!partials/fileinfo.html', 'text!p
if (!noop) {
// Default handling is to use full message with security in place.
if (message === null && nodes === null && data.Message && typeof data.Message == "string") {
message = safeMessage(data.Message);
message = data.Message;
}
// Show the beast.
element = $scope.showmessage(from, timestamp, message, nodes);

59
static/js/controllers/uicontroller.js

@ -20,9 +20,9 @@ @@ -20,9 +20,9 @@
*/
"use strict";
define(['jquery', 'underscore', 'bigscreen', 'moment', 'sjcl', 'modernizr', 'webrtc.adapter'], function($, _, BigScreen, moment, sjcl, Modernizr) {
define(['jquery', 'underscore', 'bigscreen', 'moment', 'sjcl', 'modernizr', 'text!sounds/sprite1.json', 'webrtc.adapter'], function($, _, BigScreen, moment, sjcl, Modernizr, sprite1Definition) {
return ["$scope", "$rootScope", "$element", "$window", "$timeout", "safeDisplayName", "safeApply", "mediaStream", "appData", "playSound", "desktopNotify", "alertify", "toastr", "translation", "fileDownload", "localStorage", "screensharing", "localStatus", "dialogs", "rooms", "constraints", function($scope, $rootScope, $element, $window, $timeout, safeDisplayName, safeApply, mediaStream, appData, playSound, desktopNotify, alertify, toastr, translation, fileDownload, localStorage, screensharing, localStatus, dialogs, rooms, constraints) {
return ["$scope", "$rootScope", "$element", "$window", "$timeout", "safeDisplayName", "safeApply", "mediaStream", "appData", "playSound", "desktopNotify", "alertify", "toastr", "translation", "fileDownload", "localStorage", "screensharing", "localStatus", "dialogs", "rooms", "constraints", "turnData", function($scope, $rootScope, $element, $window, $timeout, safeDisplayName, safeApply, mediaStream, appData, playSound, desktopNotify, alertify, toastr, translation, fileDownload, localStorage, screensharing, localStatus, dialogs, rooms, constraints, turnData) {
alertify.dialog.registerCustom({
baseType: 'notify',
@ -98,35 +98,14 @@ define(['jquery', 'underscore', 'bigscreen', 'moment', 'sjcl', 'modernizr', 'web @@ -98,35 +98,14 @@ define(['jquery', 'underscore', 'bigscreen', 'moment', 'sjcl', 'modernizr', 'web
};
if (typeof(sprite1Definition) === "string") {
sprite1Definition = JSON.parse(sprite1Definition);
}
// Load default sounds.
playSound.initialize({
urls: ['sounds/sprite1.ogg', 'sounds/sprite1.mp3'],
sprite: {
"connect1": [
0,
5179],
"end1": [
12892,
6199],
"entry1": [
8387,
3000],
"leaving1": [
5228,
2126],
"message1": [
19140,
816],
"question1": [
20006,
3313],
"ringtone1": [
7403,
935],
"whistle1": [
11437,
1405]
}
sprite: sprite1Definition
}, null, {
"ring": "whistle1",
"joined": "entry1",
@ -364,7 +343,6 @@ define(['jquery', 'underscore', 'bigscreen', 'moment', 'sjcl', 'modernizr', 'web @@ -364,7 +343,6 @@ define(['jquery', 'underscore', 'bigscreen', 'moment', 'sjcl', 'modernizr', 'web
mediaStream.api.e.on("received.self", function(event, data) {
$timeout.cancel(ttlTimeout);
safeApply($scope, function(scope) {
scope.id = scope.myid = data.Id;
scope.userid = scope.myuserid = data.Userid ? data.Userid : null;
@ -372,8 +350,8 @@ define(['jquery', 'underscore', 'bigscreen', 'moment', 'sjcl', 'modernizr', 'web @@ -372,8 +350,8 @@ define(['jquery', 'underscore', 'bigscreen', 'moment', 'sjcl', 'modernizr', 'web
});
// Set TURN and STUN data and refresh webrtc settings.
constraints.turn(data.Turn);
constraints.stun(data.Stun);
turnData.update(data.Turn);
$scope.refreshWebrtcSettings();
if (data.Version !== mediaStream.version) {
@ -410,14 +388,6 @@ define(['jquery', 'underscore', 'bigscreen', 'moment', 'sjcl', 'modernizr', 'web @@ -410,14 +388,6 @@ define(['jquery', 'underscore', 'bigscreen', 'moment', 'sjcl', 'modernizr', 'web
}
}
// Support to upgrade stuff when ttl was reached.
if (data.Turn.ttl) {
ttlTimeout = $timeout(function() {
console.log("Ttl reached - sending refresh request.");
mediaStream.api.sendSelf();
}, data.Turn.ttl / 100 * 90 * 1000);
}
// Support resurrection shrine.
if (appData.flags.resurrect) {
var resurrection = appData.flags.resurrect;
@ -468,6 +438,12 @@ define(['jquery', 'underscore', 'bigscreen', 'moment', 'sjcl', 'modernizr', 'web @@ -468,6 +438,12 @@ define(['jquery', 'underscore', 'bigscreen', 'moment', 'sjcl', 'modernizr', 'web
});
mediaStream.api.e.on("received.turnUpdate", function(event, data) {
// Set TURN data and refresh webrtc settings.
turnData.update(data.Turn);
$scope.refreshWebrtcSettings();
});
mediaStream.webrtc.e.on("peercall", function(event, peercall) {
// Kill timeout.
@ -775,8 +751,13 @@ define(['jquery', 'underscore', 'bigscreen', 'moment', 'sjcl', 'modernizr', 'web @@ -775,8 +751,13 @@ define(['jquery', 'underscore', 'bigscreen', 'moment', 'sjcl', 'modernizr', 'web
}
});
turnData.e.on("apply", function(event, turnData) {
constraints.turn(turnData);
$scope.refreshWebrtcSettings()
});
$scope.$on("status", function(event, status) {
if (status === "connecting" && dialerEnabled) {
if (status === "connecting" && dialerEnabled && !$scope.isConferenceRoom()) {
dialer.start();
// Start accept timeout.
ringerTimeout = $timeout(function() {

6
static/js/directives/audiovideo.js

@ -146,7 +146,7 @@ define(['jquery', 'underscore', 'text!partials/audiovideo.html', 'text!partials/ @@ -146,7 +146,7 @@ define(['jquery', 'underscore', 'text!partials/audiovideo.html', 'text!partials/
var video = clonedElement.find("video")[0];
$window.attachMediaStream(video, stream);
// Waiter callbacks also count as connected, as browser support (FireFox 25) is not setting state changes properly.
videoWaiter.wait(video, stream, function(withvideo) {
videoWaiter.wait(video, stream, function(withvideo, retriggered) {
if (scope.destroyed) {
console.log("Abort wait for video on destroyed scope.");
return;
@ -163,7 +163,9 @@ define(['jquery', 'underscore', 'text!partials/audiovideo.html', 'text!partials/ @@ -163,7 +163,9 @@ define(['jquery', 'underscore', 'text!partials/audiovideo.html', 'text!partials/
$scope.onlyaudio = true;
});
}
scope.$emit("active", currentcall);
if (!retriggered) {
scope.$emit("active", currentcall);
}
$scope.redraw();
}, function() {
if (scope.destroyed) {

20
static/js/directives/settings.js

@ -55,7 +55,7 @@ define(['jquery', 'underscore', 'text!partials/settings.html'], function($, _, t @@ -55,7 +55,7 @@ define(['jquery', 'underscore', 'text!partials/settings.html'], function($, _, t
return ["$compile", "mediaStream", function($compile, mediaStream) {
var controller = ['$scope', 'desktopNotify', 'mediaSources', 'safeApply', 'availableLanguages', 'translation', 'localStorage', 'userSettingsData', 'constraints', 'appData', '$timeout', function($scope, desktopNotify, mediaSources, safeApply, availableLanguages, translation, localStorage, userSettingsData, constraints, appData, $timeout) {
var controller = ['$scope', 'desktopNotify', 'mediaSources', 'safeApply', 'availableLanguages', 'translation', 'localStorage', 'userSettingsData', 'constraints', 'appData', '$timeout', 'turnData', function($scope, desktopNotify, mediaSources, safeApply, availableLanguages, translation, localStorage, userSettingsData, constraints, appData, $timeout, turnData) {
$scope.layout.settings = false;
$scope.showAdvancedSettings = true;
@ -63,6 +63,7 @@ define(['jquery', 'underscore', 'text!partials/settings.html'], function($, _, t @@ -63,6 +63,7 @@ define(['jquery', 'underscore', 'text!partials/settings.html'], function($, _, t
$scope.rememberSettings = true;
$scope.desktopNotify = desktopNotify;
$scope.mediaSources = mediaSources;
$scope.turnData = turnData;
$scope.availableLanguages = [{
code: "",
name: translation._("Use browser language")
@ -90,6 +91,7 @@ define(['jquery', 'underscore', 'text!partials/settings.html'], function($, _, t @@ -90,6 +91,7 @@ define(['jquery', 'underscore', 'text!partials/settings.html'], function($, _, t
if (form.$valid && form.$dirty) {
var user = $scope.user;
$scope.update(user);
$scope.turnData.refresh();
if ($scope.rememberSettings) {
userSettingsData.save(user);
localStorage.setItem("mediastream-language", user.settings.language || "");
@ -148,6 +150,9 @@ define(['jquery', 'underscore', 'text!partials/settings.html'], function($, _, t @@ -148,6 +150,9 @@ define(['jquery', 'underscore', 'text!partials/settings.html'], function($, _, t
});
$scope.refreshWebrtcSettings();
});
if ($scope.user.settings.turn.selectedRegion === null && $scope.turnData.data.geo_uri) {
$scope.user.settings.turn.selectedRegion = "auto";
}
} else if (!showSettings && oldValue) {
$scope.saveSettings();
}
@ -171,8 +176,19 @@ define(['jquery', 'underscore', 'text!partials/settings.html'], function($, _, t @@ -171,8 +176,19 @@ define(['jquery', 'underscore', 'text!partials/settings.html'], function($, _, t
$timeout($scope.maybeShowSettings);
});
constraints.e.on("refresh", function(event, c) {
turnData.e.on("refresh", function(event, turn) {
var settings = $scope.master.settings;
if (turn && turn.servers) {
var selected = settings.turn.selectedRegion;
if (turn.geo_uri && selected === null) {
selected = "auto";
}
turn.selected = selected;
}
});
constraints.e.on("refresh", function(event, c) {
var settings = $scope.master.settings;
// Assert that selected devices are there.

5
static/js/directives/socialshare.js

@ -31,7 +31,7 @@ define(['text!partials/socialshare.html'], function(template) { @@ -31,7 +31,7 @@ define(['text!partials/socialshare.html'], function(template) {
};
// socialShare
return ["$window", "translation", "rooms", "alertify", function($window, translation, rooms, alertify) {
return ["$window", "translation", "rooms", "roompin", "alertify", function($window, translation, rooms, roompin, alertify) {
var title = $window.encodeURIComponent($window.document.title);
var makeUrl = function(nw, target) {
@ -49,6 +49,7 @@ define(['text!partials/socialshare.html'], function(template) { @@ -49,6 +49,7 @@ define(['text!partials/socialshare.html'], function(template) {
replace: true,
link: function($scope, $element, $attr) {
$scope.$on("room.updated", function(ev, room) {
$scope.isRoomLocked = rooms.isLocked();
$scope.roomlink = rooms.link(room);
});
$scope.$on("room.left", function(ev) {
@ -69,6 +70,8 @@ define(['text!partials/socialshare.html'], function(template) { @@ -69,6 +70,8 @@ define(['text!partials/socialshare.html'], function(template) {
if (nw === "link") {
//$window.alert("Room link: " + $scope.roomlink);
alertify.dialog.notify(translation._("Room link"), '<a href="'+$scope.roomlink+'" rel="external" target="_blank">'+$scope.roomlink+'</a>');
} else if (nw === "pin") {
roompin.toggleCurrentRoomState(rooms);
}
}
});

4
static/js/mediastream/api.js

@ -159,6 +159,10 @@ define(['jquery', 'underscore', 'ua-parser'], function($, _, uaparser) { @@ -159,6 +159,10 @@ define(['jquery', 'underscore', 'ua-parser'], function($, _, uaparser) {
this.sid = data.Sid;
this.e.triggerHandler("received.self", [data]);
break;
case "TurnUpdate":
//console.log("TURN update received", data);
this.e.triggerHandler("received.turnUpdate", [data]);
break;
case "Offer":
//console.log("Offer received", data.To, data.Offer);
this.e.triggerHandler("received.offer", [data.To, data.Offer, data.Type, d.To, d.From]);

5
static/js/mediastream/peercall.js

@ -22,12 +22,13 @@ @@ -22,12 +22,13 @@
"use strict";
define(['jquery', 'underscore', 'mediastream/utils', 'mediastream/peerconnection'], function($, _, utils, PeerConnection) {
var PeerCall = function(webrtc, id, from, to) {
var PeerCall = function(webrtc, id, from, to, outgoing) {
this.webrtc = webrtc;
this.id = id;
this.from = from;
this.to = to;
this.outgoing = !!outgoing;
this.e = $({}) // events
@ -49,7 +50,7 @@ define(['jquery', 'underscore', 'mediastream/utils', 'mediastream/peerconnection @@ -49,7 +50,7 @@ define(['jquery', 'underscore', 'mediastream/utils', 'mediastream/peerconnection
};
PeerCall.prototype.isOutgoing = function() {
return !!this.from;
return this.outgoing;
};
PeerCall.prototype.setInitiate = function(initiate) {

2
static/js/mediastream/peerconnection.js

@ -347,7 +347,7 @@ define(['jquery', 'underscore', 'webrtc.adapter'], function($, _) { @@ -347,7 +347,7 @@ define(['jquery', 'underscore', 'webrtc.adapter'], function($, _) {
if (!this.pc) {
return [];
}
return this.pc.getRemoteStreams.apply(this.pc, arguments);
return this.pc.getLocalStreams.apply(this.pc, arguments);
};

47
static/js/mediastream/usermedia.js

@ -98,7 +98,52 @@ define(['jquery', 'underscore', 'audiocontext', 'mediastream/dummystream', 'webr @@ -98,7 +98,52 @@ define(['jquery', 'underscore', 'audiocontext', 'mediastream/dummystream', 'webr
var c = {audio: convertConstraints(constraints.audio), video: convertConstraints(constraints.video)};
// mediaDevices API returns a promise.
console.log("Constraints for mediaDevices", c);
window.navigator.mediaDevices.getUserMedia(c).then(success).catch(error);
window.navigator.mediaDevices.getUserMedia(c).then(success).catch(function(err) {
if (!navigator.mediaDevices.enumerateDevices) {
// Don't know how to check for available devices.
error(err);
return;
}
// gUM fails if one of audio/video is not available, check which devices are
// available and retry with updated constraints.
console.log("getUserMedia with audio/video contraints failed", err);
navigator.mediaDevices.enumerateDevices().then(function(devices) {
var has_audio = false;
var has_video = false;
console.log("Available devices", devices);
_.each(devices, function(device) {
switch (device.kind) {
case "audioinput":
has_audio = true;
break;
case "videoinput":
has_video = true;
break;
default:
break;
}
});
if (!has_audio && !has_video) {
// No audio or video device found, no need to retry gUM.
error(err);
return;
}
if (!has_audio) {
delete c.audio;
}
if (!has_video) {
delete c.video;
}
console.log("Retry getUserMedia with updated constraints", c);
window.navigator.mediaDevices.getUserMedia(c).then(success).catch(error);
}).catch(function(devicesError) {
console.log("Could not enumerate devices", devicesError);
// Fail initial gUM
error(err);
});
});
}
} else {
// Use existing adapter.

6
static/js/mediastream/webrtc.js

@ -461,8 +461,8 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u @@ -461,8 +461,8 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u
};
WebRTC.prototype.createCall = function(id, from, to) {
var call = new PeerCall(this, id, from, to);
WebRTC.prototype.createCall = function(id, from, to, outgoing) {
var call = new PeerCall(this, id, from, to, outgoing);
call.e.on("connectionStateChange", _.bind(function(event, iceConnectionState, currentcall) {
this.onConnectionStateChange(iceConnectionState, currentcall);
}, this));
@ -577,7 +577,7 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u @@ -577,7 +577,7 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u
};
WebRTC.prototype.doCall = function(id, autocall) {
var call = this.createCall(id, null, id);
var call = this.createCall(id, null, id, true);
call.setInitiate(true);
var count = this.conference.getCallsCount();
if (!this.conference.addOutgoing(id, call)) {

2
static/js/services/constraints.js

@ -212,4 +212,4 @@ @@ -212,4 +212,4 @@
}];
});
});

79
static/js/services/roompin.js

@ -21,11 +21,16 @@ @@ -21,11 +21,16 @@
"use strict";
define([
], function() {
"moment"
], function(moment) {
return ["$window", "$q", "alertify", "translation", function($window, $q, alertify, translation) {
return ["$window", "$q", "globalContext", "alertify", "toastr", "translation", "safeMessage", "randomGen", "localStorage", function($window, $q, context, alertify, toastr, translation, safeMessage, randomGen, localStorage) {
var pinCache = {};
var getLocalStoragePINIDForRoom = function(roomName) {
return "room-pin-" + roomName;
};
var lockedRoomsJoinable = !!context.Cfg.LockedRoomJoinableWithPIN;
var roompin = {
get: function(roomName) {
var cachedPIN = pinCache[roomName];
@ -33,30 +38,84 @@ define([ @@ -33,30 +38,84 @@ define([
},
clear: function(roomName) {
delete pinCache[roomName];
localStorage.removeItem(getLocalStoragePINIDForRoom(roomName));
console.log("Cleared PIN for", roomName);
},
update: function(roomName, pin) {
update: function(roomName, pin, noAlert) {
if (pin) {
pinCache[roomName] = pin;
alertify.dialog.alert(translation._("PIN for room %s is now '%s'.", roomName, pin));
localStorage.setItem(getLocalStoragePINIDForRoom(roomName), pin);
if (!noAlert && lockedRoomsJoinable) {
alertify.dialog.alert(translation._("PIN for room %s is now '%s'.", safeMessage(roomName), safeMessage(pin)));
}
} else {
roompin.clear(roomName);
alertify.dialog.alert(translation._("PIN lock has been removed from room %s.", roomName));
if (!noAlert && lockedRoomsJoinable) {
toastr.info(moment().format("lll"), translation._("PIN lock has been removed from room '%s'", safeMessage(roomName)));
}
}
},
requestInteractively: function(roomName) {
var deferred = $q.defer();
alertify.dialog.prompt(translation._("Enter the PIN for room %s", roomName), function(pin) {
var tryJoinWithStoredPIN = function() {
var pin = localStorage.getItem(getLocalStoragePINIDForRoom(roomName));
if (pin) {
pinCache[roomName] = pin;
roompin.update(roomName, pin, true);
deferred.resolve();
} else {
return true;
}
return false;
};
if (lockedRoomsJoinable) {
if (!tryJoinWithStoredPIN()) {
alertify.dialog.prompt(translation._("Enter the PIN for room %s", safeMessage(roomName)), function(pin) {
if (pin) {
roompin.update(roomName, pin);
deferred.resolve();
} else {
deferred.reject();
}
}, function() {
deferred.reject();
});
}
} else {
if (!tryJoinWithStoredPIN()) {
alertify.dialog.error(
translation._("Can't join locked room '%s'.", safeMessage(roomName)),
translation._("Room '%s' is locked. This server is configured to not let anyone join locked rooms.", safeMessage(roomName))
);
deferred.reject();
}
}
return deferred.promise;
},
// Passing in "rooms" is a bit of a hack to prevent circular dependencies
toggleCurrentRoomState: function(rooms) {
if (!rooms.isLocked()) {
// Lock
if (lockedRoomsJoinable) {
alertify.dialog.prompt(translation._("Please enter a new Room PIN to lock the room"), function(pin) {
rooms.setPIN(pin);
}, function() {
// Do nothing
});
} else {
alertify.dialog.confirm(translation._("Do you want to lock the room?"), function() {
var pin = randomGen.random({hex: true});
rooms.setPIN(pin);
}, function() {
// Do nothing
});
}
return;
}
// Unlock
alertify.dialog.confirm(translation._("Do you want to unlock the room?"), function() {
rooms.setPIN("");
}, function() {
deferred.reject();
// Do nothing
});
return deferred.promise;
}
};

9
static/js/services/rooms.js

@ -260,12 +260,13 @@ define([ @@ -260,12 +260,13 @@ define([
console.log("Failed to set room PIN", error);
return $q.reject(error);
});
},
isLocked: function(room) {
room = room || currentRoom.Name
return roompin.get(room) !== null;
}
};
// NOTE(lcooper): For debugging only, do not use this on production.
$window.setRoomPIN = rooms.setPIN;
return rooms;
}];
}];
});

9
static/js/services/services.js

@ -71,7 +71,8 @@ define([ @@ -71,7 +71,8 @@ define([
'services/sandbox',
'services/dummystream',
'services/usermedia',
'services/playpromise'], function(_,
'services/playpromise',
'services/turndata'], function(_,
desktopNotify,
playSound,
safeApply,
@ -120,7 +121,8 @@ mediaDevices, @@ -120,7 +121,8 @@ mediaDevices,
sandbox,
dummyStream,
userMedia,
playPromise) {
playPromise,
turnData) {
var services = {
desktopNotify: desktopNotify,
@ -171,7 +173,8 @@ playPromise) { @@ -171,7 +173,8 @@ playPromise) {
sandbox: sandbox,
dummyStream: dummyStream,
userMedia: userMedia,
playPromise: playPromise
playPromise: playPromise,
turnData: turnData
};
var initialize = function(angModule) {

213
static/js/services/turndata.js

@ -0,0 +1,213 @@ @@ -0,0 +1,213 @@
/*
* Spreed WebRTC.
* Copyright (C) 2013-2016 struktur AG
*
* This file is part of Spreed WebRTC.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
"use strict";
define(["jquery"], function($) {
var geoRequestTimeout = 30000; // Timeout for geo requests in milliseconds.
var geoFastRetryTimeout = 45000; // Refresh timer in milliseconds, after which GEO requests should be retried if failed before.
var refreshPercentile = 90; // Percent of the TTL when TURN credentials should be refreshed.
var refreshMinimumInterval = 30000; // Minimal TURN refresh interval in milliseconds.
// turnData
return ["$timeout", "$http", "api", "randomGen", "appData", "translationLanguage", function($timeout, $http, api, randomGen, appData, translationLanguage) {
var ttlTimeout = null;
var geoRefresh = null;
var geoPreferred = null;
var service = this;
service.e = $({});
service.data = {};
service.apply = function() {
var turn = service.data;
var turnData = {
"username": turn.username,
"password": turn.password,
"ttl": turn.ttl
};
if (turn && turn.servers) {
// Multiple options, need to sort and use settings.
var i;
if (!turn.serverMap) {
var servers = {};
var serversSelectable = [];
// Sort for prio.
turn.servers.sort(function(a, b) {
servers[a.id] = a;
servers[b.id] = b;
return (a.prio > b.prio) ? 1 : ((a.prio < b.prio) ? -1 : 0);
});
turn.first = turn.servers[0];
// Create selectable servers.
var lang = translationLanguage.lang;
for (i=0; i<turn.servers.length; i++) {
var label = turn.servers[i].label;
if (turn.servers[i].i18n && turn.servers[i].i18n[lang]) {
label = turn.servers[i].label = turn.servers[i].i18n[lang];
}
if (label === "hidden") {
continue;
}
if (!label) {
// Use id as label.
label = turn.servers[i].label = turn.servers[i].id;
}
if (!label) {
// Use index as label.
label = turn.servers[i].label = ''+i;
}
serversSelectable.push(turn.servers[i]);
}
// Add auto zone if geo URI and available zones.
if (turn.geo_uri && turn.servers.length > 0) {
serversSelectable.unshift({
"id": "auto",
"label": "auto"
})
}
// Make created data available.
turn.serverMap = servers;
turn.serversSelectable = serversSelectable;
}
var urls;
if (turn.preferred) {
for (i=0; i<turn.preferred.length; i++) {
if (turn.serverMap.hasOwnProperty(turn.preferred[i])) {
urls = turn.serverMap[turn.preferred[i]].urns;
break;
}
}
}
if (!urls && turn.first) {
urls = turn.first.urns;
}
turnData.urls = urls;
} else if (turn && turn.urls) {
// Simple case, single region.
turnData.urls = turn.urls
} else {
// Unknown data.
turnData.urls = [];
}
console.log("TURN servers selected: ", turnData.urls, turnData.ttl, turn.preferred || null);
service.e.triggerHandler("apply", [turnData]);
return turnData;
};
service.refresh = function(withGeo) {
$timeout.cancel(geoRefresh);
var turn = service.data;
service.e.triggerHandler("refresh", [turn]);
if (turn.selected === "auto" && turn.geo_uri) {
if (geoPreferred !== null) {
// Use existing data.
turn.preferred = geoPreferred;
} else {
if (!withGeo) {
// Avoid triggering spurious GEO request for fast updates.
geoRefresh = $timeout(function() {
service.refresh(true);
}, 1000);
return;
}
// Run Geo request.
var nonce = randomGen.random({hex: true});
$http({
method: "POST",
url: turn.geo_uri,
headers: {"Content-Type": "application/x-www-form-urlencoded"},
data: "nonce="+encodeURIComponent(nonce)+"&username="+encodeURIComponent(turn.username)+"&password="+encodeURIComponent(turn.password),
timeout: geoRequestTimeout
}).then(function(response) {
// success
if (turn !== service.data) {
// No longer our data.
return;
}
if (response.status === 200) {
var data = response.data;
if (data.success && data.nonce === nonce) {
geoPreferred = turn.preferred = data.geo.prefer;
console.log("TURN GEO auto selected: ", turn.preferred);
service.apply();
}
}
}, function(response) {
// failed
if (turn !== service.data) {
// No longer our data.
return;
}
console.warn("TURN GEO failed:", response.status, response);
$timeout.cancel(ttlTimeout);
ttlTimeout = $timeout(function() {
// Fast retry.
console.warn("TURN GEO failed - refreshing early.")
api.sendSelf();
}, geoFastRetryTimeout)
})
}
} else {
// Set directly.
turn.preferred = [];
if (turn.selected) {
turn.preferred.push(turn.selected);
}
}
service.apply();
};
service.update = function(turn) {
$timeout.cancel(ttlTimeout);
if (service.data && service.data.preferred) {
// Keep preferred list if there is one.
turn.preferred = service.data.preferred;
}
service.data = turn;
service.refresh()
// Support to refresh TURN data when ttl was reached.
if (turn.ttl) {
var timer = turn.ttl * 0.01 * refreshPercentile * 1000;
if (timer < refreshMinimumInterval) {
timer = refreshMinimumInterval;
}
ttlTimeout = $timeout(function() {
console.log("TURN TTL reached - sending refresh request.");
api.sendSelf();
}, timer);
}
};
service.cancel = function() {
$timeout.cancel(ttlTimeout);
}
appData.e.on("userSettingsLoaded", service.refresh);
return service;
}]
})

59
static/js/services/videowaiter.js

@ -24,39 +24,61 @@ define(["underscore"], function(_) { @@ -24,39 +24,61 @@ define(["underscore"], function(_) {
return ["$window", function($window) {
var Waiter = function() {
var Waiter = function(video, stream, cb, err_cb) {
this.stop = false;
this.triggered = false;
this.count = 0;
this.retries = 100;
this.video = video;
this.stream = stream;
this.cb = cb;
this.err_cb = err_cb;
};
Waiter.prototype.trigger = function() {
var oldTriggered = this.triggered;
this.triggered = true;
return oldTriggered;
};
Waiter.prototype.error = function() {
var triggered = this.trigger();
if (this.err_cb) {
this.err_cb(this.video, this.stream, triggered);
}
};
Waiter.prototype.start = function(video, stream, cb, err_cb) {
Waiter.prototype.found = function(withvideo) {
var triggered = this.trigger();
this.cb(withvideo, triggered);
};
Waiter.prototype.start = function() {
if (this.stop) {
if (err_cb) {
err_cb(video, stream);
}
this.error();
return;
}
var videoTracks = stream && stream.getVideoTracks() || [];
var recheck = _.bind(this.start, this);
var videoTracks = this.stream && this.stream.getVideoTracks() || [];
//console.log("wait for video", videoTracks.length, video.currentTime, video.videoHeight, video);
if (videoTracks.length === 0 && this.count >= 10) {
cb(false, video, stream);
} else if (video.currentTime > 0 && video.videoHeight > 0) {
cb(true, video, stream);
this.found(false);
} else if (this.video.currentTime > 0 && this.video.videoHeight > 0) {
this.found(true);
} else {
if (videoTracks.length > 0 && this.count >= 10) {
var videoTrack = videoTracks[0];
if (videoTrack.enabled === true && videoTrack.muted === true) {
cb(false, video, stream);
videoTrack.onunmute = function() {
videoTrack.onunmute = undefined;
_.defer(recheck);
};
this.found(false);
return;
}
}
this.count++;
if (this.count < this.retries) {
$window.setTimeout(_.bind(this.start, this, video, stream, cb, err_cb), 100);
$window.setTimeout(recheck, 100);
} else {
if (err_cb) {
err_cb(video, stream);
}
this.error();
}
}
};
@ -67,15 +89,16 @@ define(["underscore"], function(_) { @@ -67,15 +89,16 @@ define(["underscore"], function(_) {
// videoWaiter wait
return {
wait: function(video, stream, cb, err_cb) {
var waiter = new Waiter();
var waiter = new Waiter(video, stream, cb, err_cb);
_.defer(function() {
waiter.start(video, stream, cb, err_cb);
waiter.start();
});
return waiter;
return {
stop: waiter.stop,
};
}
}
}]
});

9
static/partials/settings.html

@ -239,6 +239,13 @@ @@ -239,6 +239,13 @@
</div>
</div>
<div class="form-group" ng-show="turnData.data.serversSelectable">
<label class="col-xs-4 control-label">{{_('TURN')}}</label>
<div class="col-xs-8">
<select class="form-control" ng-model="user.settings.turn.selectedRegion" ng-options="region.id as region.label for region in turnData.data.serversSelectable"></select>
</div>
</div>
<div ng-show="user.settings.experimental.enabled">
<div class="form-group" ng-show="supported.constraints.chrome">
@ -318,4 +325,4 @@ @@ -318,4 +325,4 @@
</fieldset>
</div>
</div>
</div>
</div>

1
static/partials/socialshare.html

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
<div class="socialshare btn-group" ng-show="roomlink">
<a ng-show="withModule('roomlocking')" title="{{_('Room PIN')}}" class="btn btn-link btn-sm" data-nw="pin"><i class="fa fa-lg pin" ng-class="isRoomLocked ? 'fa-lock' : 'fa-unlock'"></i></a>
<a title="{{_('Room link')}}" target="_blank" rel="external" href="{{roomlink}}" class="btn btn-link btn-sm" data-nw="link"><i class="fa fa-link fa-lg link"></i></a>
<a title="{{_('Invite by Email')}}" class="btn btn-link btn-sm" data-nw="email"><i class="fa fa-envelope fa-lg email"></i></a>
<a title="{{_('Invite with Facebook')}}" class="btn btn-link btn-sm" data-nw="facebook"><i class="fa fa-facebook fa-lg facebook"></i></a>

34
static/sounds/sprite1.json

@ -0,0 +1,34 @@ @@ -0,0 +1,34 @@
{
"connect1": [
0,
5179
],
"end1": [
5228,
6199
],
"entry1": [
11476,
3000
],
"leaving1": [
14526,
2126
],
"message1": [
16701,
816
],
"question1": [
17567,
3313
],
"ringtone1": [
20929,
935
],
"whistle1": [
21913,
1405
]
}

BIN
static/sounds/sprite1.mp3

Binary file not shown.

BIN
static/sounds/sprite1.ogg

Binary file not shown.

2
static/translation/languages.js

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
// This file is auto generated, do not modify.
"use strict";
define([], function() {
return {"zh-cn": "\u4e2d\u6587\uff08\u7b80\u4f53\uff09", "fr": "Fran\u00e7ais", "en": "English", "ru": "\u0420\u0443\u0441\u0441\u043a\u0438\u0439", "zh-tw": "\u7e41\u9ad4\u4e2d\u6587", "de": "Deutsch", "ko": "\ud55c\uad6d\uc5b4", "ja": "\u65e5\u672c\u8a9e"};
return {"zh-cn": "\u4e2d\u6587\uff08\u7b80\u4f53\uff09", "fr": "Fran\u00e7ais", "en": "English", "ru": "\u0420\u0443\u0441\u0441\u043a\u0438\u0439", "zh-tw": "\u7e41\u9ad4\u4e2d\u6587", "de": "Deutsch", "ko": "\ud55c\uad6d\uc5b4", "ja": "\u65e5\u672c\u8a9e", "es": "Espa\u00f1ol", "it": "Italiano"};
});

2
static/translation/messages-de.json

File diff suppressed because one or more lines are too long

1
static/translation/messages-es.json

File diff suppressed because one or more lines are too long

2
static/translation/messages-fr.json

File diff suppressed because one or more lines are too long

1
static/translation/messages-it.json

File diff suppressed because one or more lines are too long

2
static/translation/messages-ja.json

File diff suppressed because one or more lines are too long

2
static/translation/messages-ko.json

File diff suppressed because one or more lines are too long

2
static/translation/messages-ru.json

File diff suppressed because one or more lines are too long

2
static/translation/messages-zh-cn.json

File diff suppressed because one or more lines are too long

2
static/translation/messages-zh-tw.json

File diff suppressed because one or more lines are too long
Loading…
Cancel
Save