Browse Source

Merge 99c5b5023b into 92a885ebc7

pull/456/merge
Tunc Kayikcioglu 8 years ago committed by GitHub
parent
commit
dcf526fe6b
  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. 3
      src/i18n/helpers/languages.py
  39. 19
      src/i18n/messages-de.po
  40. 892
      src/i18n/messages-es.po
  41. 888
      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. 1
      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. 853
      static/translation/messages-tr.po
  83. 2
      static/translation/messages-zh-cn.json
  84. 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.

3
src/i18n/helpers/languages.py

@ -5,11 +5,14 @@ @@ -5,11 +5,14 @@
LANGUAGES = {
"en": "English",
"de": "Deutsch",
"fr": "Français",
"zh-cn": "中文(简体)",
"zh-tw": "繁體中文",
"ko": "한국어",
"ja": "日本語",
"ru": "Русский",
"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"

888
src/i18n/messages-fr.po

@ -0,0 +1,888 @@ @@ -0,0 +1,888 @@
# French translations for Spreed WebRTC.
# Copyright (C) 2014 struktur AG
# This file is distributed under the same license as Spreed WebRTC
# project.
# Florent BEAUCHAMP <fbeauchamp@sdis71.fr>, 2013.
#
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: 2016-02-17 14:16+0100\n"
"Last-Translator: Florent BEAUCHAMP <fbeauchamp@sdis71.fr>\n"
"Language-Team: Florent BEAUCHAMP <fbeauchamp@sdis71.fr>\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 "Mode standard"
msgid "Large view"
msgstr "Mode large"
msgid "Kiosk view"
msgstr "Mode kiosque"
msgid "Auditorium"
msgstr "Mode auditorium"
msgid "Start chat"
msgstr "Démarrer le chat"
msgid "Start video call"
msgstr "Appel vidéo"
msgid "Start audio conference"
msgstr "Appel audio"
msgid "No one else here"
msgstr "Personne n'est connecté"
msgid "Take"
msgstr "Répondre"
msgid "Retake"
msgstr "Refaire"
msgid "Cancel"
msgstr "Annuler"
msgid "Set as Profile Picture"
msgstr "Définir comme avatar"
msgid "Take picture"
msgstr "Prendre une photo"
msgid "Upload picture"
msgstr "Envoyer une photo"
msgid "Waiting for camera"
msgstr "En attente de la camera"
msgid "Picture"
msgstr "Avatar"
msgid "The file couldn't be read."
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 ficheir est trop gros. Max. %d MB."
msgid "Select file"
msgstr "Choisir un fichier"
msgid "Chat sessions"
msgstr "Session de chat"
msgid "Room chat"
msgstr "Chat de cette salle"
msgid "Peer to peer"
msgstr "Peer-to-peer"
msgid "Close chat"
msgstr "Fermer le chat"
msgid "Upload files"
msgstr "Envoyer les fichiers"
msgid "Share my location"
msgstr "Partager mon emplacement"
msgid "Clear chat"
msgstr "Effacer le chat"
msgid "is typing..."
msgstr "est en train de taper..."
msgid "has stopped typing..."
msgstr "a fini de taper..."
msgid "Type here to chat..."
msgstr "Taper ici pour chatter..."
msgid "Send"
msgstr "Envoyer"
msgid "Accept"
msgstr "Accepter"
msgid "Reject"
msgstr "Refuser"
msgid "You have no contacts."
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 des contacts, rejoignez une salle 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 "Modifier le contact"
msgid "Edit"
msgstr "Modifier"
msgid "Name"
msgstr "Nom"
msgid "Remove"
msgstr "Enlever"
msgid "Refresh"
msgstr "Actualiser"
msgid "Save"
msgstr "Enregistrer"
msgid "Close"
msgstr "Fermer"
msgid "File sharing"
msgstr "Partage de fichier"
msgid "File is no longer available"
msgstr "Ce fichier n'est plus disponible"
msgid "Download"
msgstr "Télécharger"
msgid "Open"
msgstr "Ouvrir"
msgid "Unshare"
msgstr "Ne plus partager"
msgid "Retry"
msgstr "Réessayer"
msgid "Download failed."
msgstr "Echec du téléchargement."
msgid "Share a YouTube video"
msgstr "Partager une vidéo Youtube"
msgid "Share a file as presentation"
msgstr "Partager une présentation"
msgid "Share your screen"
msgstr "Partager votre écran"
msgid "Chat"
msgstr "Chat"
msgid "Contacts"
msgstr "Contact"
msgid "Mute microphone"
msgstr "Couper le micro"
msgid "Turn camera off"
msgstr "Couper la caméra"
msgid "Settings"
msgstr "Paramètres"
msgid "Loading presentation ..."
msgstr "Chargement de la présentation..."
msgid "Please upload a document"
msgstr "Merci 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 participants à cet appel. Les "
"documents PDF et opendocument sont supportés."
msgid "Upload"
msgstr "Sélectionner un fichier"
msgid "You can drag files here too."
msgstr "Vous pouvez aussi déposer les fichiers ici."
msgid "Presentation controls"
msgstr "Controles de présentation"
msgid "Prev"
msgstr "Préc."
msgid "Next"
msgstr "Suiv."
msgid "Change room"
msgstr "Changer de salle"
msgid "Room"
msgstr "Salle"
msgid "Leave room"
msgstr "Quitter la salle"
msgid "Main"
msgstr "Principale"
msgid "Current room"
msgstr "Salle actuelle"
msgid "Screen sharing options"
msgstr "Options de partage d'écran"
msgid "Fit screen."
msgstr "Adapter à l'écran."
msgid "Share screen"
msgstr "Partager mon écran"
msgid "Please select what to share."
msgstr "Merci de choisir l'écran à partager."
msgid "Screen"
msgstr "Moniteur"
msgid "Window"
msgstr "Fenêtre"
msgid "Application"
msgstr "Application"
msgid "Share the whole screen. Click share to select the screen."
msgstr "Partager tout le moniteur. Cliquez pour choisir le moniteur."
msgid "Share a single window. Click share to select the window."
msgstr "Partager une seule fenêtre. Cliquez pour choisir la 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. 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"
msgid "OK"
msgstr "OK"
msgid "Profile"
msgstr "Profil"
msgid "Your name"
msgstr "Votre nom"
msgid "Your picture"
msgstr "Votre avatar"
msgid "Status message"
msgstr "Message de statut"
msgid "What's on your mind?"
msgstr "Quel est votre état d'esprit?"
msgid ""
"Your picture, name and status message identify yourself in calls, chats "
"and rooms."
msgstr ""
"Votre avatar, nom et état d'esprit servent à vous identifier dans les "
"appels, chat et salle."
msgid "Your ID"
msgstr "Votre ID"
msgid ""
"Authenticated by certificate. To log out you have to remove your "
"certificate from the browser."
msgstr ""
"Authentifié par certificat. Vous devrez effacer le certificat de votre "
"navigateur pour vous déconnecter."
msgid "Sign in"
msgstr "Connnexion"
msgid "Create an account"
msgstr "Créer un compte"
msgid "Sign out"
msgstr "Deconnexion"
msgid "Manage account"
msgstr "Gestion du compte"
msgid "Media"
msgstr "Webcam / Micro"
msgid "Microphone"
msgstr "Micro"
msgid "Camera"
msgstr "Webcam"
msgid "Video quality"
msgstr "Qualité vidéo"
msgid "Low"
msgstr "Basse"
msgid "High"
msgstr "Haute"
msgid "HD"
msgstr "HD"
msgid "Full HD"
msgstr "Full HD"
msgid "General"
msgstr "Général"
msgid "Language"
msgstr "Langue"
msgid "Language changes become active on reload."
msgstr "Rechargez la page pour prendre en compte le changement de langue."
msgid "Default room"
msgstr "Salle par défaut"
msgid "Set alternative room to join at start."
msgstr "Nom de la salle à rejoindre à la connexion."
msgid "Notifications"
msgstr "Notifications"
msgid "Desktop notification"
msgstr "Notifications sur le bureau"
msgid "Enable"
msgstr "Activer"
msgid "Denied - check your browser settings"
msgstr "Interdit - vérifier les paramètres de votre navigateur"
msgid "Allowed"
msgstr "Autorisé"
msgid "Sounds for incoming messages"
msgstr "Son pour les messages entrant"
msgid "Ring on incoming calls"
msgstr "Sonnerie pour les appels entrant"
msgid "Sounds for users in current room"
msgstr "Son pour les participants de la salle courante"
msgid "Advanced settings"
msgstr "Réglages avancés"
msgid "Play audio on same device as selected microphone"
msgstr "Ecouter l'audio sur le même materiel que le micro"
msgid "Experimental AEC"
msgstr "AEC (experimental)"
msgid "Experimental AGC"
msgstr "AGC (experimental)"
msgid "Experimental noise suppression"
msgstr "Suppresion de bruit (experimental)"
msgid "Max video frame rate"
msgstr "Image par seconde max"
msgid "auto"
msgstr "auto"
msgid "Send stereo audio"
msgstr "Envoyer de l'audio stéréo"
msgid ""
"Sending stereo audio disables echo cancellation. Enable only if you have "
"stereo input."
msgstr ""
"Envoyer de l'audio en stéréo désactive la suppression de l'écho. Utilisez"
" le uniquement si vous avez un micro stéréo."
msgid "Detect CPU over use"
msgstr "Détecter un usage processeur excessif"
msgid "Automatically reduces video quality as needed."
msgstr "Réduire automatiquement la qualité vidéo si besoin."
msgid "Optimize for high resolution video"
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 "Utiliser le codec VP9 en priorité"
msgid "Enable experiments"
msgstr "Activer les réglages experimentaux"
msgid "Show advanced settings"
msgstr "Montrer les réglages avancés"
msgid "Hide advanced settings"
msgstr "Masquer les réglage avancés"
msgid "Remember settings"
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 ID sera conservé - appuyer sur le bouton de déconnection pour "
"l'effacer."
#, fuzzy
msgid "Room PIN"
msgstr "Lien vers la salle"
msgid "Room link"
msgstr "Lien vers la salle"
msgid "Invite by Email"
msgstr "Inviter par email"
msgid "Invite with Facebook"
msgstr "Inviter par Facebook"
msgid "Invite with Twitter"
msgstr "Inviter par Twitter"
msgid "Invite with Google Plus"
msgstr "Inviter par Google Plus"
msgid "Invite with XING"
msgstr "Inviter par XING"
msgid "Initializing"
msgstr "En cours d'initialisation"
msgid "Online"
msgstr "En ligne"
msgid "Calling"
msgstr "Appel en cours"
msgid "Hangup"
msgstr "Raccrocher"
msgid "In call with"
msgstr "En communication avec"
msgid "Conference with"
msgstr "En conférence avec"
msgid "Your are offline"
msgstr "Vous êtes hors ligne"
msgid "Go online"
msgstr "Passer en ligne"
msgid "Connection interrupted"
msgstr "Connection interrompue"
msgid "An error occured"
msgstr "Une erreur est survenue"
msgid "Incoming call"
msgstr "Appel entrant"
msgid "from"
msgstr "de"
msgid "Accept call"
msgstr "Accepter l'appel"
msgid "Waiting for camera/microphone access"
msgstr "En attente de l'accès à la webcam et au micro"
msgid "Your audio level"
msgstr "Niveau de votre audio"
msgid "Checking camera and microphone access."
msgstr "Vérification de l'accès à la webcam et au micro."
msgid "Please allow access to your camera and microphone."
msgstr "Merci d'autoriser l'accès à la webcam et au micro."
msgid "Camera / microphone access required."
msgstr "Accès requis à la webcam et au micro."
msgid ""
"Please check your browser settings and allow camera and microphone access"
" for this site."
msgstr ""
"Merci de verifier les reglages de votre navigateur et pour autoriser "
"l'accès à votre micro et a votre webcam pour ce site."
msgid "Skip check"
msgstr "Passer la vérification"
msgid "Click here for help (Google Chrome)."
msgstr "Cliquez ici pour l'aide (Google Chrome)."
msgid "Please set your user details and settings."
msgstr "Merci de mettre à jours votre profil et vos paramètres."
msgid "Enter a room name"
msgstr "Entrer un nom de salle"
msgid "Random room name"
msgstr "Nom aléatoire"
msgid "Enter room"
msgstr "Rejoindre la salle"
msgid ""
"Enter the name of an existing room. You can create new rooms when you are"
" signed in."
msgstr ""
"Entrez le nom d'une salle existante. Vous pouvez créer de nouvelles "
"salles lorsque vous êtes connecté."
msgid "Room history"
msgstr "Historique de la salle"
msgid "Please sign in."
msgstr "Merci de vous connecter."
msgid "Videos play simultaneously for everyone in this call."
msgstr "Les vidéos sont lues de manière synchronisée entre tous les participants."
msgid "YouTube URL"
msgstr "URL YouTube"
msgid ""
"Could not load YouTube player API, please check your network / firewall "
"settings."
msgstr ""
"Impossible de charger le lecteur de video Youtube. Merci de vérifier vos "
"paramètres de réseau et pare-feu."
msgid "Currently playing"
msgstr "Lecture en cours"
msgid "YouTube controls"
msgstr "Contrôles youtube"
msgid "YouTube video to share"
msgstr "Vidéo YouTube à partager"
msgid "Peer to peer chat active."
msgstr "Chat privé actif."
msgid "Peer to peer chat is now off."
msgstr "Chat privé désactivé."
msgid " is now offline."
msgstr " est passé hors ligne."
msgid " is now online."
msgstr " est passé en ligne."
msgid "You share file:"
msgstr "Vous partagez le fichier:"
msgid "Incoming file:"
msgstr "Fichier entrant:"
msgid "You shared your location:"
msgstr "Vous partagez votre emplacement:"
msgid "Location received:"
msgstr "Emplacement reçu:"
msgid "You accepted the contact request."
msgstr "Vous avez accepté la demande de mise en relation."
msgid "You rejected the contact request."
msgstr "Vous avez refusé la demande de mise en relation."
msgid "You sent a contact request."
msgstr "Vous avez envoyé une demande de mise en relation."
msgid "Your contact request was accepted."
msgstr "Votre requête de mise en relation a été acceptée."
msgid "Incoming contact request."
msgstr "Arrivée d'une demande de mise en relation."
msgid "Your contact request was rejected."
msgstr "Votre requête de mise en relation a été refusée."
msgid "Edit Contact"
msgstr "Modifier le contact"
msgid "Your browser does not support WebRTC. No calls possible."
msgstr "Votre navigateur ne supporte pas WebRTC. Appels impossible."
msgid "Close this window and disconnect?"
msgstr "Voulez 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 ""
"Un redémarrage est requis pour appliquer les mises à jour. Cliquez sur OK"
" pour redémarrer."
msgid "Failed to access camera/microphone."
msgstr "Impossible d'accéder à la webcam et au micro."
msgid "Failed to establish peer connection."
msgstr "Impossible d'établir la connexion de pair a pair."
msgid "We are sorry but something went wrong. Boo boo."
msgstr "Nous sommes désolé, mais quelque chose à planté. Ouin."
msgid "Oops"
msgstr "Oups"
msgid "Peer connection failed. Check your settings."
msgstr "La connection avec le pair à échoué. Vérifiez vos réglages."
msgid "User hung up because of error."
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 " à refuser votre appel."
msgid " does not pick up."
msgstr " n'a pas répondu."
msgid " tried to call you"
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é. Merci de le mettre à jout vers une "
"version plus récente."
msgid "Chat with"
msgstr "Chat avec"
msgid "Message from "
msgstr "Message de "
#, python-format
msgid "You are now in room %s ..."
msgstr "Vous avez rejoint la salle %s ..."
msgid "Your browser does not support file transfer."
msgstr "Votre navigateur ne supporte pas les transferts de fichier."
msgid "Could not load PDF: Please make sure to select a PDF document."
msgstr ""
"Impossible de charger le PDF: merci de vérifier que vous avez sélectionné"
" un PDF."
msgid "Could not load PDF: Missing PDF file."
msgstr "Impossible de charger le PDF: le fichier PDF est manquant."
#, python-format
msgid "An error occurred while loading the 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 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 du PDF (%s)."
msgid "An unknown error occurred while loading the PDF page."
msgstr "Une erreur inconnue est survenue lors du chargement du PDF (%s)."
#, python-format
msgid "An error occurred while rendering the PDF page (%s)."
msgstr "Une erreur est survenue lors du rendu du PDF (%s)."
msgid "An unknown error occurred while rendering the PDF page."
msgstr "Une erreur inconnue est survenue lors du rendu du PDF."
msgid "Only PDF documents and OpenDocument files can be shared at this time."
msgstr ""
"Pour l'instant, seuls les documents au format PDF et open documents "
"peuvent être partagés."
#, python-format
msgid "Failed to start screen sharing (%s)."
msgstr "Impossible de démarrer le partage d'écran (%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 ""
"La permission de démarrer le partage d'écran a été refusée. Soyez sûr que"
" vous avez activez le partage pour votre navigateur. Copiez "
"chrome://flags/#enable-usermedia-screen-capture dans votre barre "
"d'adresse et activez le, puis redémarrez votre navigateur."
msgid "Permission to start screen sharing was denied."
msgstr "La permission de partager l'écran a été refusé."
msgid "Use browser language"
msgstr "Utiliser la langue du navigateur"
msgid "Meet with me here:"
msgstr "Rejoins moi là:"
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 "Nom de la salle"
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 des paramètres invalides. Merci de vérifier l'URL de "
"la vidéo que vous voulez partager."
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 requis ne peux pas être pas lu par le lecteur HTML5, ou une "
"autre erreur en relation avec le lecteur HTML5 est survenu. Merci de "
"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 ""
"Cette vidéo n'a pas été trouvée. Merci de 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 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 est survenue lors de la lecture de la vidéo (%s). "
"Merci de réessayer plus tard."
msgid ""
"An unknown error occurred while playing back the video. Please try again "
"later."
msgstr ""
"Une erreur inconnue est survenue lors de la lecture de la vidéo. Merci de"
" réessayer plus tard."
msgid "Unknown URL format. Please make sure to enter a valid YouTube URL."
msgstr ""
"Format d'URL inconnu. Merci de vérifier qu'il s'agit d'une URL Youtube "
"valide."
msgid "Error"
msgstr "Erreur"
msgid "Hint"
msgstr "Information"
msgid "Please confirm"
msgstr "Merci de confirmer"
msgid "More information required"
msgstr "Il faut plus d'information"
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 ""
"Le partage d'écran nécessite une extension pour le navigateur. Merci "
"d'ajouter l'extension \"Spreed WebRTC screen sharing\"."
msgid "Access code required"
msgstr "Code d'accès requis"
msgid "Access denied"
msgstr "Accès refusé"
msgid "Please provide a valid access code."
msgstr "Merci de donner un code d'accès valide."
msgid ""
"Failed to verify access code. Check your Internet connection and try "
"again."
msgstr ""
"Impossible de vérifier votre code d'accès. Vérifiez votre connection et "
"réssayez."
#, python-format
msgid "PIN for room %s is now '%s'."
msgstr "Le code PIN pour la salle %s est maintenant '%s'."
#, python-format
msgid "PIN lock has been removed from room %s."
msgstr "La salle %s n'est plus proteger par un code PIN."
#, python-format
msgid "Enter the PIN for room %s"
msgstr "Entrez le code PIN pour la salle %s"
msgid "Please sign in to create rooms."
msgstr "Merci de vous connecter pour créer des salles."
#, python-format
msgid "and %s"
msgstr "et %s"
#, python-format
msgid "and %d others"
msgstr "et %d autres"
msgid "User"
msgstr "Participa,t"
msgid "Someone"
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 {"ru": "\u0420\u0443\u0441\u0441\u043a\u0438\u0439", "en": "English", "zh-tw": "\u7e41\u9ad4\u4e2d\u6587", "de": "Deutsch", "ko": "\ud55c\uad6d\uc5b4", "zh-cn": "\u4e2d\u6587\uff08\u7b80\u4f53\uff09", "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

1
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

853
static/translation/messages-tr.po

@ -0,0 +1,853 @@ @@ -0,0 +1,853 @@
# Turkish translations for Spreed WebRTC.
# Copyright (C) 2017
# This file is distributed under the same license as the Spreed WebRTC
# project.
#
#, 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: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\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 "Standart görünüm"
msgid "Large view"
msgstr "Büyük görüntüler"
msgid "Kiosk view"
msgstr "Kiosk görünümü"
msgid "Auditorium"
msgstr "Toplantı görünümü"
msgid "Start chat"
msgstr "Chate başla"
msgid "Start video call"
msgstr "Görüntülü arama"
msgid "Start audio conference"
msgstr "Sesli arama"
msgid "No one else here"
msgstr "Burada başka kimse yok"
msgid "Take"
msgstr "Cevapla"
msgid "Retake"
msgstr "Tekrar"
msgid "Cancel"
msgstr "İptal"
msgid "Set as Profile Picture"
msgstr "Profil görüntüsü yap"
msgid "Take picture"
msgstr "Resim çek"
msgid "Upload picture"
msgstr "Resim yükle"
msgid "Waiting for camera"
msgstr "Kamerayı bekliyor"
msgid "Picture"
msgstr "Resim"
msgid "The file couldn't be read."
msgstr "Dosya okunamadı."
msgid "The file is not an image."
msgstr "Bu bir resim dosyası değil."
#, python-format
msgid "The file is too large. Max. %d MB."
msgstr "Dosya çok büyükç Maksç %d MB"
msgid "Select file"
msgstr "Dosya seç"
msgid "Chat sessions"
msgstr "Chat oturumları"
msgid "Room chat"
msgstr "Chat odası"
msgid "Peer to peer"
msgstr "peer-to-peer"
msgid "Close chat"
msgstr "Chati sonlandır"
msgid "Upload files"
msgstr "Dosya yükle"
msgid "Share my location"
msgstr "Konumumu paylaş"
msgid "Clear chat"
msgstr "Geçmiş yazıları sil"
msgid "is typing..."
msgstr "yazıyor..."
msgid "has stopped typing..."
msgstr "yazmayı bitirdi..."
msgid "Type here to chat..."
msgstr "Chatleşmek için buraya yazın..."
msgid "Send"
msgstr "Gönder"
msgid "Accept"
msgstr "Kabul et"
msgid "Reject"
msgstr "Reddet"
msgid "You have no contacts."
msgstr "Hiç tanıdığınız yok"
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 "Yeni tanıdıklar eklemek için bir odaya girin ve kullanıcının girdisinin yanındaki yıldıza basarak tanışma istemi gönderin. "
msgid "Edit contact"
msgstr "Tanıdığı değiştir"
msgid "Edit"
msgstr "Değiştir"
msgid "Name"
msgstr "Ad"
msgid "Remove"
msgstr "Sil"
msgid "Refresh"
msgstr "Güncelle"
msgid "Save"
msgstr "Kaydet"
msgid "Close"
msgstr "Kapat"
msgid "File sharing"
msgstr "Dosya paylaşımı"
msgid "File is no longer available"
msgstr "Bu dosya artık kullanılabilir değil"
msgid "Download"
msgstr "İndir"
msgid "Open"
msgstr "Aç"
msgid "Unshare"
msgstr "Paylaşmayı bırak"
msgid "Retry"
msgstr "Tekrar dene"
msgid "Download failed."
msgstr "İndirme başarısız."
msgid "Share a YouTube video"
msgstr "YouTube videosu paylaş"
msgid "Share a file as presentation"
msgstr "Bir dosyadan sunum yap"
msgid "Share your screen"
msgstr "Ekranı paylaş"
msgid "Chat"
msgstr "Chat"
msgid "Contacts"
msgstr "Tanıdıklar"
msgid "Mute microphone"
msgstr "Mikrofonu kapat"
msgid "Turn camera off"
msgstr "Kamerayı kapat"
msgid "Settings"
msgstr "Ayarlar"
msgid "Loading presentation ..."
msgstr "Sunum yükleniyor..."
msgid "Please upload a document"
msgstr "Lütfen bir dosya yükleyin"
msgid ""
"Documents are shared with everyone in this call. The supported file types"
" are PDF and OpenDocument files."
msgstr "Dosyalar bu oturumdaki herkesle paylaşılacak. Desteklenen dosya türleri: PDF ve OpenDocument."
msgid "Upload"
msgstr "Yükle"
msgid "You can drag files here too."
msgstr "Dosyaları buraya sürükleyebilirisiniz de."
msgid "Presentation controls"
msgstr "Sunum kontrolleri"
msgid "Prev"
msgstr "Önceki"
msgid "Next"
msgstr "Sonraki"
msgid "Change room"
msgstr "Odayı değiştir"
msgid "Room"
msgstr "Oda"
msgid "Leave room"
msgstr "Odadan çık"
msgid "Main"
msgstr "Ana oda"
msgid "Current room"
msgstr "Şu anki oda"
msgid "Screen sharing options"
msgstr "Ekran paylaşım seçenekleri"
msgid "Fit screen."
msgstr "Ekrana sığdır"
msgid "Share screen"
msgstr "Ekranı paylaş"
msgid "Please select what to share."
msgstr "Paylaşmak istediğinizi seçin."
msgid "Screen"
msgstr "Ekran"
msgid "Window"
msgstr "Pencere"
msgid "Application"
msgstr "Uygulama"
msgid "Share the whole screen. Click share to select the screen."
msgstr "Tüm ekranı paylaş. Seçmek için paylaşa basın."
msgid "Share a single window. Click share to select the window."
msgstr "Sadece bir pencereyi paylaş. Seçmek için paylaşa basın."
msgid ""
"Share all windows of a application. This can leak content behind windows "
"when windows get moved. Click share to select the application."
msgstr "Bir uygulamanın tüm pencerelerini paylaş. Bu seçim pencereler "
"oynatıldığında arkalarındaki nesnelerin görünmesine yol açabilir. Seçmek için paylaşa basın."
msgid "Share"
msgstr "Paylaş"
msgid "OK"
msgstr "Tamam"
msgid "Profile"
msgstr "Profil"
msgid "Your name"
msgstr "Adınız"
msgid "Your picture"
msgstr "Resminiz"
msgid "Status message"
msgstr "Durum bildirimi"
msgid "What's on your mind?"
msgstr "Aklınızdan neler geçiyor?"
msgid ""
"Your picture, name and status message identify yourself in calls, chats "
"and rooms."
msgstr "Resminiz, adınız ve durum bildiriminiz arama ve odalarda sizi tanıtır."
msgid "Your ID"
msgstr "Kullanıcı adınız"
msgid ""
"Authenticated by certificate. To log out you have to remove your "
"certificate from the browser."
msgstr "Sertıfıka ile doğrulama yapıldı. Hesabınızdan çıkmak istediğinizde sertifikayı taraıcınızdan kaldırmalısınız."
msgid "Sign in"
msgstr "Gir"
msgid "Create an account"
msgstr "Hesap oluştur"
msgid "Sign out"
msgstr "Güvenli çıkış"
msgid "Manage account"
msgstr "Hesabım"
msgid "Media"
msgstr "Media"
msgid "Microphone"
msgstr "Mikrofon"
msgid "Camera"
msgstr "Kamera"
msgid "Video quality"
msgstr "Görüntü kalitesi"
msgid "Low"
msgstr "Düşük"
msgid "High"
msgstr "Yüksek"
msgid "HD"
msgstr "HD"
msgid "Full HD"
msgstr "Full HD"
msgid "General"
msgstr "Genel"
msgid "Language"
msgstr "Dil"
msgid "Language changes become active on reload."
msgstr "Dil değişiklikleri sayfa yeniden yüklendiğinde yürülüğe girer."
msgid "Default room"
msgstr "Temel oda"
msgid "Set alternative room to join at start."
msgstr "Başlangıçta girilecek başka bir oda seçin."
msgid "Notifications"
msgstr "Bilgilendirmeler"
msgid "Desktop notification"
msgstr "Masaüstü bilgilendirmeleri"
msgid "Enable"
msgstr "Aktifle"
msgid "Denied - check your browser settings"
msgstr "Reddedildi - tarayıcı ayarlarınızı kontrol edin."
msgid "Allowed"
msgstr "Onaylandı"
msgid "Sounds for incoming messages"
msgstr "Gelen mesajlar için ses"
msgid "Ring on incoming calls"
msgstr "Gelen arama olursa sesli uyar"
msgid "Sounds for users in current room"
msgstr "Odadaki mevcut kullanıcılar için sesli uyarılar"
msgid "Advanced settings"
msgstr "Gelişmiş ayarlar"
msgid "Play audio on same device as selected microphone"
msgstr "Mikrofonla aynı cihazdaki hoparlörü kullan"
msgid "Experimental AEC"
msgstr "Deneysel AEC"
msgid "Experimental AGC"
msgstr "Deneysel AGC"
msgid "Experimental noise suppression"
msgstr "Deneysel gürültü filtresi"
msgid "Max video frame rate"
msgstr "Maks. çerçeve hızı"
msgid "auto"
msgstr "Otomatik"
msgid "Send stereo audio"
msgstr "Stereo ses gönder"
msgid ""
"Sending stereo audio disables echo cancellation. Enable only if you have "
"stereo input."
msgstr "Stereo ses gönderimi yankı baskılamayı kaldırır. Sadece stereo ses alıcınız varsa kullanınız."
msgid "Detect CPU over use"
msgstr "İşlemci aşırı kullanımını tespit et"
msgid "Automatically reduces video quality as needed."
msgstr "Gerektiğinde görüntü kalitesini otomatik olarak düşür."
msgid "Optimize for high resolution video"
msgstr "Yüksek çözünürlüklü görüntü için en iyi hale getir"
msgid "Reduce video noise"
msgstr "Görüntüdeki gürültüyü azalt"
msgid "Prefer VP9 video codec"
msgstr "VP9 codec tercih et"
msgid "Enable experiments"
msgstr "Deneysel fonksiyonları kullan"
msgid "Show advanced settings"
msgstr "Gelişmiş seçenekleri göster"
msgid "Hide advanced settings"
msgstr "Gelişmiş seçenekleri sakla"
msgid "Remember settings"
msgstr "Ayarları hatırla"
msgid ""
"Your ID will still be kept - press the log out button above to delete the"
" ID."
msgstr "Kullanıcı adınız saklanacak - silmek için yukarıdaki çıkış tuşuna basın"
msgid "Room PIN"
msgstr "Oda anahtarı"
msgid "Room link"
msgstr "Oda bağlantısı"
msgid "Invite by Email"
msgstr "E-posta ile çağır"
msgid "Invite with Facebook"
msgstr "Facebook ile çağır"
msgid "Invite with Twitter"
msgstr "Twıtter ile çağır"
msgid "Invite with Google Plus"
msgstr "Google Plus ile çağıt"
msgid "Invite with XING"
msgstr "XING ile çağır"
msgid "Initializing"
msgstr "Başlatılıyor"
msgid "Online"
msgstr "Online"
msgid "Calling"
msgstr "Arıyor"
msgid "Hangup"
msgstr "Sonlandır"
msgid "In call with"
msgstr "Konuştuğu kişi"
msgid "Conference with"
msgstr "Görüştüğü kişi"
msgid "Your are offline"
msgstr "Çevrimdışısınız"
msgid "Go online"
msgstr "Bağlan"
msgid "Connection interrupted"
msgstr "Bağlantı koptu"
msgid "An error occured"
msgstr "Bir hata oluştu"
msgid "Incoming call"
msgstr "Gelen arama"
msgid "from"
msgstr "arayan"
msgid "Accept call"
msgstr "Aç"
msgid "Waiting for camera/microphone access"
msgstr "Kamera/mikrofon bekleniyor"
msgid "Your audio level"
msgstr "Ses seviyeniz"
msgid "Checking camera and microphone access."
msgstr "Kamera ve mikrofon bağlantısı deneniyor"
msgid "Please allow access to your camera and microphone."
msgstr "Lütfen kamera ve mikrofon bağlantısına izin verin."
msgid "Camera / microphone access required."
msgstr "Kamera / mikrofon erişimi gerekli."
msgid ""
"Please check your browser settings and allow camera and microphone access"
" for this site."
msgstr "Tarayıcı ayarlarınızı konrol ederek kamera ve mikrofona bu site"
" için erişim izni verin"
msgid "Skip check"
msgstr "Kontrolü atla"
msgid "Click here for help (Google Chrome)."
msgstr "Yardım için buraya tıklayın (Google Chrome)."
msgid "Please set your user details and settings."
msgstr "Kullanıcı bilgi ve ayarlarınızı yapın."
msgid "Enter a room name"
msgstr "Oda adı girin"
msgid "Random room name"
msgstr "Rastgele bir oda adı"
msgid "Enter room"
msgstr "Odaya gir"
msgid ""
"Enter the name of an existing room. You can create new rooms when you are"
" signed in."
msgstr "Mevcut odalardan birinin adını yazın. Yeni bir odayı ancak giriş"
" yaptıktan sonra oluşturabilirsiniz."
msgid "Room history"
msgstr "Oda geçmişi"
msgid "Please sign in."
msgstr "Lütfen giriş yapın."
msgid "Videos play simultaneously for everyone in this call."
msgstr "Videolar bu aramadaki herkese eşzamanlı gösterilir."
msgid "YouTube URL"
msgstr "YouTube URL"
msgid ""
"Could not load YouTube player API, please check your network / firewall "
"settings."
msgstr ""
"YouTube-çalar yükelenemedi, lütfen bağlantı ve firewall ayarlarınızı kontrol edin."
msgid "Currently playing"
msgstr "Şu an çalan"
msgid "YouTube controls"
msgstr "YouTube ayarları"
msgid "YouTube video to share"
msgstr "Paylaşılacak YouTube videosu"
msgid "Peer to peer chat active."
msgstr "Peer to peer chat aktif."
msgid "Peer to peer chat is now off."
msgstr "Peer to peer chat inaktif."
msgid " is now offline."
msgstr " şu anda çevrim dışı."
msgid " is now online."
msgstr " şimdi online."
msgid "You share file:"
msgstr "Şu dosyayı paylaşıyorsunuz:"
msgid "Incoming file:"
msgstr "Gelen dosya:"
msgid "You shared your location:"
msgstr "Konumunuzu paylaştınız:"
msgid "Location received:"
msgstr "Konum alındı:"
msgid "You accepted the contact request."
msgstr "Tanışma isteğini kabul ettiniz."
msgid "You rejected the contact request."
msgstr "Tanışma isteğini reddettiniz."
msgid "You sent a contact request."
msgstr "Tanışma isteği gönderdiniz."
msgid "Your contact request was accepted."
msgstr "Tanışma isteğiniz kabul edildi."
msgid "Incoming contact request."
msgstr "Sizinle tanışmak isteyen biri var."
msgid "Your contact request was rejected."
msgstr "Tanışma isteğiniz reddedildi."
msgid "Edit Contact"
msgstr "Tanıdığı değiştir"
msgid "Your browser does not support WebRTC. No calls possible."
msgstr "Tarayıcınızın WebRTC desteği yok. Hiçbir arama yapılamaz."
msgid "Close this window and disconnect?"
msgstr "Pencereyi kapat ve çık?"
msgid "Contacts Manager"
msgstr "Tanıdık yöneticisi"
msgid "Restart required to apply updates. Click ok to restart now."
msgstr "Güncellemeler için yeniden başlatmanız gerekli. Şimdi yeniden "
"başlatmak için tamama basın."
msgid "Failed to access camera/microphone."
msgstr "Kamera/mikrofon bağlantısı başarısız."
msgid "Failed to establish peer connection."
msgstr "Kişiye bağlantı başarısız."
msgid "We are sorry but something went wrong. Boo boo."
msgstr "Üzgünüz ama bir terslik oldu. Yuuuuuh."
msgid "Oops"
msgstr "Eyvah"
msgid "Peer connection failed. Check your settings."
msgstr "Kişiye bağlantı başarısız. Ayarlarınızı kontrol edin."
msgid "User hung up because of error."
msgstr "Bir hata sebebiyle kullanıcı bağlantıyı sonlandırdı"
msgid " is busy. Try again later."
msgstr " şu an meşgul. Daha sonra tekrar deneyin."
msgid " rejected your call."
msgstr " aramanızı reddetti."
msgid " does not pick up."
msgstr " telefonu açmıyor."
msgid " tried to call you"
msgstr " sizi aramaya çalıştı"
msgid " called you"
msgstr " sizi aradı"
msgid "Your browser is not supported. Please upgrade to a current version."
msgstr "Tarayıcınız uyumsuz. Lütfen güncelleyin. "
msgid "Chat with"
msgstr "Chat arkadaşınız"
msgid "Message from "
msgstr "Mesaj gönderen "
#, python-format
msgid "You are now in room %s ..."
msgstr "Şimdi %s odadasındasınız..."
msgid "Your browser does not support file transfer."
msgstr "Tarayıcınız dosya alışverişine uyumlu değil."
msgid "Could not load PDF: Please make sure to select a PDF document."
msgstr "PDF yüklenemedi: lütfen bir PDF dosyası seçtiğinizden emin olun."
msgid "Could not load PDF: Missing PDF file."
msgstr "PDF yüklenemedi: dosya bulunamadı."
#, python-format
msgid "An error occurred while loading the PDF (%s)."
msgstr "PDFyi yüklerken bir hata oluştu (%s)"
msgid "An unknown error occurred while loading the PDF."
msgstr "PDFyi yüklerken bilinmeyen bir hata oluştu."
#, python-format
msgid "An error occurred while loading the PDF page (%s)."
msgstr "PDF sayfasını yüklerken bir hata oluştu (%s)"
msgid "An unknown error occurred while loading the PDF page."
msgstr "PDF sayfasını yüklerken bilinmeyen bir hata oluştu."
#, python-format
msgid "An error occurred while rendering the PDF page (%s)."
msgstr "PDF sayfasını işlerken bir hata oluştu (%s)"
msgid "An unknown error occurred while rendering the PDF page."
msgstr "PDF sayfasını işlerken bilinmeyen bir hata oluştu."
msgid "Only PDF documents and OpenDocument files can be shared at this time."
msgstr "Şu anda sadece PDF ve OpenDocument dosyaları paylaşılabilmektedir."
#, python-format
msgid "Failed to start screen sharing (%s)."
msgstr "Ekran paylaşımı başarısız (%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 "Ekran paylaşma izni reddedildi. Tarayıcınızın ekran paylaşımına "
"izin verdiğinden emin olun. chrome://flags/#enable-usermedia-screen-capture"
" adresini tarayıcınızda açin ve en üstteki kutucuğu seçin. Tarayıcınızı tekrar"
" başlattıktan sonra her şey hazır."
msgid "Permission to start screen sharing was denied."
msgstr "Ekran paylaşımı denemesi engellendi."
msgid "Use browser language"
msgstr "Tarayıcının dilini kullan"
msgid "Meet with me here:"
msgstr "Şurada buluşalım:"
msgid "Please enter a new Room PIN to lock the room"
msgstr "Odayı kilitlemek için bir şifre verin"
msgid "Do you want to unlock the room?"
msgstr "Odayı herkese açmak istiyor musunuz?"
msgid "Room name"
msgstr "Oda adı"
msgid ""
"The request contains an invalid parameter value. Please check the URL of "
"the video you want to share and try again."
msgstr ""
"İsteminiz geçersiz bir parametre içeriyor. Video URLsini kontrol ettikten "
"sonra tekrar deneyin."
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 ""
"İstediğiniz obje HTML5-çalar ile gösterime uygun değil veya başka bir"
" HTML5-çalar hatası oluştı. Daha sonra tekrar deneyin."
msgid ""
"The video requested was not found. Please check the URL of the video you "
"want to share and try again."
msgstr ""
"İstediğiniz video bulunamadı. Paylaşmak istediğiniz videonun URLsini"
" kontrol edip tekrar deneyin."
msgid ""
"The owner of the requested video does not allow it to be played in "
"embedded players."
msgstr ""
"İstediğiniz videonun sahibi gömülü programlar ile gösterilmesine"
" izin vermiyor."
#, python-format
msgid ""
"An unknown error occurred while playing back the video (%s). Please try "
"again later."
msgstr ""
"Video gösterimi sırasında bilinmeyen bir hata oluştu (%s). Lütfen daha"
" sonra tekrar deneyin."
msgid ""
"An unknown error occurred while playing back the video. Please try again "
"later."
msgstr ""
"Video gösterimi sırasında bilinmeyen bir hata oluştu. Lütfen daha"
" sonra tekrar deneyin."
msgid "Unknown URL format. Please make sure to enter a valid YouTube URL."
msgstr "Bilinmeyen URL formatı. geçerli bir YouTube linki girdiğinizi kontrol edin."
msgid "Error"
msgstr "Hata"
msgid "Hint"
msgstr "İpucu"
msgid "Please confirm"
msgstr "Teyit ediniz"
msgid "More information required"
msgstr "Daha fazla bilgi gerekli"
msgid "Ok"
msgstr "Tamam"
msgid ""
"Screen sharing requires a browser extension. Please add the Spreed WebRTC"
" screen sharing extension to Chrome and try again."
msgstr ""
"Ekran paylaşımı bir tarayıcı eklentisi gerektiriyor. Spreed WebRTC"
" eklentisini Chrome'a ekledikten sonra tekrar deneyin."
msgid "Access code required"
msgstr "Şifre gerekli"
msgid "Access denied"
msgstr "Erişim reddedildi"
msgid "Please provide a valid access code."
msgstr "Lütfen geçerli bir şifre girin."
msgid ""
"Failed to verify access code. Check your Internet connection and try "
"again."
msgstr ""
"Şifre doğrulanamadı. İnternet bağlantınızı kontrol edip tekrar deneyin."
#, python-format
msgid "PIN for room %s is now '%s'."
msgstr "%s odasının şifresi artık '%s'."
#, python-format
msgid "PIN lock has been removed from room %s."
msgstr "%s odasının şifre koruması kaldırıldı."
#, python-format
msgid "Enter the PIN for room %s"
msgstr "%s odasının şifresini girin"
msgid "Please sign in to create rooms."
msgstr "Yeni bir oda oluşturabilmek için giriş yapın."
#, python-format
msgid "and %s"
msgstr "ve %s"
#, python-format
msgid "and %d others"
msgstr "ve diğer %d kişi"
msgid "User"
msgstr "Kullanıcı"
msgid "Someone"
msgstr "Birisi"
msgid "Me"
msgstr "Ben"

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