diff --git a/dependencies.tsv b/dependencies.tsv index 9b46c45c..50801999 100644 --- a/dependencies.tsv +++ b/dependencies.tsv @@ -10,3 +10,4 @@ github.com/strukturag/goacceptlanguageparser git 68066e68c2940059aadc6e19661610c 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/sloth git 74a8bcf67368de59baafe5d3e17aee9875564cfc 2015-04-22T08:59:42Z +github.com/strukturag/spreed-turnservicecli git 51f45889f0c6a4a7d406c29b9cc345b07a1a94ab 2016-08-26T13:54:01Z diff --git a/doc/CHANNELING-API.txt b/doc/CHANNELING-API.txt index 8b64f148..4848b9ba 100644 --- a/doc/CHANNELING-API.txt +++ b/doc/CHANNELING-API.txt @@ -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 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 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 For questions, contact mailto:opensource@struktur.de. -(c)2014 struktur AG +(c)2016 struktur AG diff --git a/go/channelling/api/api.go b/go/channelling/api/api.go index 854dcb90..e75644d7 100644 --- a/go/channelling/api/api.go +++ b/go/channelling/api/api.go @@ -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 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 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) diff --git a/go/channelling/api/api_test.go b/go/channelling/api/api_test.go index 90787787..6e1745a8 100644 --- a/go/channelling/api/api_test.go +++ b/go/channelling/api/api_test.go @@ -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 diff --git a/go/channelling/api/handle_authentication.go b/go/channelling/api/handle_authentication.go index 5487d05b..6a84d903 100644 --- a/go/channelling/api/handle_authentication.go +++ b/go/channelling/api/handle_authentication.go @@ -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() } diff --git a/go/channelling/api/handle_self.go b/go/channelling/api/handle_self.go index 1c5e118d..10a87575 100644 --- a/go/channelling/api/handle_self.go +++ b/go/channelling/api/handle_self.go @@ -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 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) diff --git a/go/channelling/client.go b/go/channelling/client.go index c9b604a5..168b1489 100644 --- a/go/channelling/client.go +++ b/go/channelling/client.go @@ -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 { 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{}) { } } +func (client *Client) Outgoing(m interface{}) { + client.reply("", m) +} + func (client *Client) Session() *Session { return client.session } diff --git a/go/channelling/data.go b/go/channelling/data.go index 960231b7..65a4aba8 100644 --- a/go/channelling/data.go +++ b/go/channelling/data.go @@ -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 { 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 { diff --git a/go/channelling/hub.go b/go/channelling/hub.go index 057fcb60..913c01bd 100644 --- a/go/channelling/hub.go +++ b/go/channelling/hub.go @@ -91,7 +91,7 @@ func (h *hub) ClientInfo(details bool) (clientCount int, sessions map[string]*Da return } -func (h *hub) CreateTurnData(session *Session) *DataTurn { +func (h *hub) CreateTurnData(sender Sender, 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 @@ -109,7 +109,12 @@ func (h *hub) CreateTurnData(session *Session) *DataTurn { foo.Write([]byte(user)) password := base64.StdEncoding.EncodeToString(foo.Sum(nil)) - return &DataTurn{user, password, turnTTL, h.config.TurnURIs} + return &DataTurn{ + Username: user, + Password: password, + Ttl: turnTTL, + Urls: h.config.TurnURIs, + } } func (h *hub) GetSession(id string) (session *Session, ok bool) { diff --git a/go/channelling/pipeline.go b/go/channelling/pipeline.go index 6f57303c..edca9580 100644 --- a/go/channelling/pipeline.go +++ b/go/channelling/pipeline.go @@ -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 } diff --git a/go/channelling/turndata.go b/go/channelling/turndata.go index 71002494..c6991ee9 100644 --- a/go/channelling/turndata.go +++ b/go/channelling/turndata.go @@ -22,5 +22,5 @@ package channelling type TurnDataCreator interface { - CreateTurnData(*Session) *DataTurn + CreateTurnData(Sender, *Session) *DataTurn } diff --git a/go/channelling/turnservice_manager.go b/go/channelling/turnservice_manager.go new file mode 100644 index 00000000..66e20614 --- /dev/null +++ b/go/channelling/turnservice_manager.go @@ -0,0 +1,125 @@ +/* + * 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 . + * + */ + +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 + } + } + + 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() +} diff --git a/server.conf.in b/server.conf.in index 08365caf..369f3e3c 100644 --- a/server.conf.in +++ b/server.conf.in @@ -227,3 +227,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 = diff --git a/src/app/spreed-webrtc-server/main.go b/src/app/spreed-webrtc-server/main.go index b1ebbc4e..44b83dcd 100644 --- a/src/app/spreed-webrtc-server/main.go +++ b/src/app/spreed-webrtc-server/main.go @@ -296,8 +296,18 @@ func runner(runtime phoenix.Runtime) error { 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. diff --git a/static/js/controllers/appcontroller.js b/static/js/controllers/appcontroller.js index 65e10948..d1b07b74 100644 --- a/static/js/controllers/appcontroller.js +++ b/static/js/controllers/appcontroller.js @@ -57,6 +57,9 @@ define(["jquery", "angular", "underscore"], function($, angular, _) { videoNoiseReduction: false, preferVideoSendCodecVP9: false }, + turn: { + selectedRegion: null + }, sound: { incomingMessages: true, incomingCall: true, diff --git a/static/js/controllers/uicontroller.js b/static/js/controllers/uicontroller.js index 89a04dee..595dbd2c 100644 --- a/static/js/controllers/uicontroller.js +++ b/static/js/controllers/uicontroller.js @@ -22,7 +22,7 @@ "use strict"; define(['jquery', 'underscore', 'bigscreen', 'moment', 'sjcl', 'modernizr', 'webrtc.adapter'], function($, _, BigScreen, moment, sjcl, Modernizr) { - 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', @@ -364,7 +364,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 +371,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 +409,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 +459,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,6 +772,11 @@ 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) { dialer.start(); diff --git a/static/js/directives/settings.js b/static/js/directives/settings.js index 43c9f9a2..d1d30a9d 100644 --- a/static/js/directives/settings.js +++ b/static/js/directives/settings.js @@ -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 $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 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 }); $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 $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. diff --git a/static/js/mediastream/api.js b/static/js/mediastream/api.js index 058b523c..ee5e576d 100644 --- a/static/js/mediastream/api.js +++ b/static/js/mediastream/api.js @@ -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]); diff --git a/static/js/services/constraints.js b/static/js/services/constraints.js index 99dbc27d..80061204 100644 --- a/static/js/services/constraints.js +++ b/static/js/services/constraints.js @@ -212,4 +212,4 @@ }]; - }); \ No newline at end of file + }); diff --git a/static/js/services/services.js b/static/js/services/services.js index a42445f9..b32f196a 100644 --- a/static/js/services/services.js +++ b/static/js/services/services.js @@ -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, sandbox, dummyStream, userMedia, -playPromise) { +playPromise, +turnData) { var services = { desktopNotify: desktopNotify, @@ -171,7 +173,8 @@ playPromise) { sandbox: sandbox, dummyStream: dummyStream, userMedia: userMedia, - playPromise: playPromise + playPromise: playPromise, + turnData: turnData }; var initialize = function(angModule) { diff --git a/static/js/services/turndata.js b/static/js/services/turndata.js new file mode 100644 index 00000000..22f11a54 --- /dev/null +++ b/static/js/services/turndata.js @@ -0,0 +1,180 @@ +/* + * 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 . + * + */ + +"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. + + // turnData + return ["$timeout", "$http", "api", "randomGen", "appData", function($timeout, $http, api, randomGen, appData) { + 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. + if (!turn.serverMap) { + var servers = {}; + 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]; + if (turn.geo_uri) { + turn.servers.unshift({ + "id": "auto" + }) + } + turn.serverMap = servers; + } + var urls; + if (turn.preferred) { + for (var i=0; i +
+ +
+ + {{_('Geographic region for TURN service.')}} +
+
+
@@ -318,4 +326,4 @@
- \ No newline at end of file +