talking_stick 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.rspec +1 -0
- data/Gemfile +15 -0
- data/LICENSE.md +50 -0
- data/README.md +111 -0
- data/Rakefile +32 -0
- data/app/assets/images/talking_stick/.keep +0 -0
- data/app/assets/javascripts/talking_stick/application.js +15 -0
- data/app/assets/javascripts/talking_stick/talking_stick.js +141 -0
- data/app/assets/javascripts/talking_stick/talking_stick/partner.js +100 -0
- data/app/assets/javascripts/talking_stick/talking_stick/rails_signaling.js +117 -0
- data/app/assets/stylesheets/talking_stick/application.css +15 -0
- data/app/controllers/talking_stick/application_controller.rb +4 -0
- data/app/controllers/talking_stick/participants_controller.rb +72 -0
- data/app/controllers/talking_stick/rooms_controller.rb +122 -0
- data/app/helpers/talking_stick/application_helper.rb +4 -0
- data/app/models/talking_stick/participant.rb +18 -0
- data/app/models/talking_stick/room.rb +6 -0
- data/app/models/talking_stick/signal.rb +17 -0
- data/app/views/layouts/talking_stick/application.html.erb +14 -0
- data/app/views/talking_stick/participants/_form.html.erb +25 -0
- data/app/views/talking_stick/participants/edit.html.erb +6 -0
- data/app/views/talking_stick/participants/index.html.erb +29 -0
- data/app/views/talking_stick/participants/new.html.erb +5 -0
- data/app/views/talking_stick/participants/show.html.erb +14 -0
- data/app/views/talking_stick/rooms/_form.html.erb +25 -0
- data/app/views/talking_stick/rooms/edit.html.erb +6 -0
- data/app/views/talking_stick/rooms/index.html.erb +29 -0
- data/app/views/talking_stick/rooms/new.html.erb +5 -0
- data/app/views/talking_stick/rooms/show.html.erb +40 -0
- data/bin/rails +12 -0
- data/config/routes.rb +7 -0
- data/db/migrate/20150510181337_create_talking_stick_rooms.rb +10 -0
- data/db/migrate/20150510182258_create_talking_stick_participants.rb +14 -0
- data/db/migrate/20150511005922_create_talking_stick_signals.rb +12 -0
- data/lib/talking_stick.rb +4 -0
- data/lib/talking_stick/engine.rb +12 -0
- data/lib/talking_stick/version.rb +3 -0
- data/lib/tasks/talking_stick_tasks.rake +4 -0
- data/spec/dummy/README.rdoc +28 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/images/.keep +0 -0
- data/spec/dummy/app/assets/javascripts/application.js +13 -0
- data/spec/dummy/app/assets/stylesheets/application.css +15 -0
- data/spec/dummy/app/controllers/application_controller.rb +5 -0
- data/spec/dummy/app/controllers/concerns/.keep +0 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/mailers/.keep +0 -0
- data/spec/dummy/app/models/.keep +0 -0
- data/spec/dummy/app/models/concerns/.keep +0 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/bin/setup +29 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +32 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +41 -0
- data/spec/dummy/config/environments/production.rb +79 -0
- data/spec/dummy/config/environments/test.rb +42 -0
- data/spec/dummy/config/initializers/assets.rb +11 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +4 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/routes.rb +4 -0
- data/spec/dummy/config/secrets.yml +22 -0
- data/spec/dummy/lib/assets/.keep +0 -0
- data/spec/dummy/log/.keep +0 -0
- data/spec/dummy/public/404.html +67 -0
- data/spec/dummy/public/422.html +67 -0
- data/spec/dummy/public/500.html +66 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/models/talking_stick/participant_spec.rb +7 -0
- data/spec/models/talking_stick/room_spec.rb +7 -0
- data/spec/spec_helper.rb +13 -0
- data/talking_stick.gemspec +27 -0
- data/vendor/assets/javascripts/talking_stick/adapter.js +243 -0
- metadata +258 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 9d0ec4a547d57cdd27a4fdaf84c9d903d49e10a4
|
4
|
+
data.tar.gz: 6eeecae1fc08976b80259b980e86106b2ae75779
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 173d28bc43ac5404146d61977c44ad2cec441d37d487dcc2b184d881a6887ee42aea88197160e240113ab5ea528cc737b06c96f03cb9c0eb24b9a4febb5facc9
|
7
|
+
data.tar.gz: 4985639dd6ed35028f8c2356af4fd2ac460370833260b0a86deb3c894364dfe87237b87b12cd9dfb9c0bb57f430027ce79f1bc73e365e85981695e364eb3474d
|
data/.gitignore
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
.bundle/
|
2
|
+
log/*.log
|
3
|
+
pkg/
|
4
|
+
spec/dummy/db/*.sqlite3
|
5
|
+
spec/dummy/db/*.sqlite3-journal
|
6
|
+
spec/dummy/log/*.log
|
7
|
+
spec/dummy/tmp/
|
8
|
+
spec/dummy/.sass-cache
|
9
|
+
|
10
|
+
# Gemfile.lock is ignored in libraries
|
11
|
+
/Gemfile.lock
|
12
|
+
|
13
|
+
# Editor files
|
14
|
+
.*.sw*
|
15
|
+
*~
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Declare your gem's dependencies in talking_stick.gemspec.
|
4
|
+
# Bundler will treat runtime dependencies like base dependencies, and
|
5
|
+
# development dependencies will be added by default to the :development group.
|
6
|
+
gemspec
|
7
|
+
|
8
|
+
# Declare any dependencies that are still in development here instead of in
|
9
|
+
# your gemspec. These might include edge Rails or gems from your path or
|
10
|
+
# Git. Remember to move these dependencies to your gemspec before releasing
|
11
|
+
# your gem to rubygems.org.
|
12
|
+
|
13
|
+
# To use a debugger
|
14
|
+
# gem 'byebug', group: [:development, :test]
|
15
|
+
|
data/LICENSE.md
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
TalkingStick Copyright (C) 2015 Mojo Lingo LLC
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
22
|
+
adapter.js Copyright (c) 2014, The WebRTC project authors. All rights reserved.
|
23
|
+
|
24
|
+
Redistribution and use in source and binary forms, with or without
|
25
|
+
modification, are permitted provided that the following conditions are
|
26
|
+
met:
|
27
|
+
|
28
|
+
* Redistributions of source code must retain the above copyright
|
29
|
+
notice, this list of conditions and the following disclaimer.
|
30
|
+
|
31
|
+
* Redistributions in binary form must reproduce the above copyright
|
32
|
+
notice, this list of conditions and the following disclaimer in
|
33
|
+
the documentation and/or other materials provided with the
|
34
|
+
distribution.
|
35
|
+
|
36
|
+
* Neither the name of Google nor the names of its contributors may
|
37
|
+
be used to endorse or promote products derived from this software
|
38
|
+
without specific prior written permission.
|
39
|
+
|
40
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
41
|
+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
42
|
+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
43
|
+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
44
|
+
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
45
|
+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
46
|
+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
47
|
+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
48
|
+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
49
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
50
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
# TalkingStick
|
2
|
+
|
3
|
+
This gem provides basic [WebRTC](https://webrtc.org) communication in any Rails app.
|
4
|
+
|
5
|
+
|
6
|
+
## Goals
|
7
|
+
|
8
|
+
* Provide easy group-based (2 or more participants) audio/video communication
|
9
|
+
* Allow selecting audio or audio+video
|
10
|
+
* Pluggable signaling delivery technology
|
11
|
+
* Plug & Play functionality - works out of the box
|
12
|
+
* Focus on simplicity at the API level, while maximizing the possibilites of integrating communication into existing Rails apps
|
13
|
+
|
14
|
+
## Installation
|
15
|
+
|
16
|
+
Note that TalkingStick requires jQuery, and installs [jquery-rails gem](https://github.com/rails/jquery-rails) as a dependency.
|
17
|
+
|
18
|
+
TalkingStick is built as a Rails Engine, and so follows those conventions.
|
19
|
+
|
20
|
+
1. Add to Gemfile and update bundle
|
21
|
+
```Ruby
|
22
|
+
gem 'talking_stick'
|
23
|
+
```
|
24
|
+
|
25
|
+
Now run `bundle install` to download the gem.
|
26
|
+
|
27
|
+
2. Mount the engine
|
28
|
+
Add the following to your application's `config/routes.rb`:
|
29
|
+
```Ruby
|
30
|
+
mount TalkingStick::Engine, at: '/talking_stick'
|
31
|
+
```
|
32
|
+
|
33
|
+
3. Install and Run migrations
|
34
|
+
To add the required models to your application, the migrations must be copied and run:
|
35
|
+
```
|
36
|
+
$ rake railties:install:migrations db:migrate
|
37
|
+
```
|
38
|
+
|
39
|
+
## How it works
|
40
|
+
|
41
|
+
* Each group is assigned a unique ID
|
42
|
+
* Rails app keeps track of participants in each room
|
43
|
+
* Rails app notifies all room participants when one joins or leaves
|
44
|
+
* Rails app proxies call setup between participants in each room
|
45
|
+
|
46
|
+
## Providing your own signaling mechanism
|
47
|
+
|
48
|
+
One of the critical decisions made by the standards bodies behind WebRTC was a decision to leave the signaling of WebRTC up to the application. This means that an application may use any means it desires to give the endpoints the data necessary to communicate directly.
|
49
|
+
|
50
|
+
This plugin tries to be "batteries included", which means that we included a signaling mechanism that will work on any Rails server. As such, we could not rely on any particular push technology, such as Websockets or long polling. Thus, the default behavior is that the browser polls the Rails API for updates on connected peers. This works fine for small user bases, but obviously will not scale well.
|
51
|
+
|
52
|
+
### Want help designing and deploying an efficient, scalable signaling mechanism for your application?
|
53
|
+
[Contact Mojo Lingo](https://mojolingo.com/connect) today! As authors of this Gem, Mojo Lingo can help you get the most value out of WebRTC by integrating communications into your application and business processes.
|
54
|
+
|
55
|
+
|
56
|
+
Alternatively, you can build your own by passing a "Signaling Engine" to the TalkingStick class.
|
57
|
+
|
58
|
+
The [reference Signaling Engine](https://github.com/mojolingo/talking_stick/blob/master/app/assets/javascripts/talking_stick/talking_stick/rails_signaling.js) periodically polls the Rails API for signals, but can be replaced with any API-compatible implementation, including Websockets, XMPP, or anything you like.
|
59
|
+
|
60
|
+
The Signaling Engine must implement the following methods. For each method where `guid` is specified, it represents the ID of the remote participant to which the information must be sent.
|
61
|
+
|
62
|
+
* `connected`: Takes no parameters. Is called after the user's media is set up and the participant is registered to the room.
|
63
|
+
* `sendICECandidate(guid, candidate)`: The second parameter is the ICE candidate. Called each time a new local ICE candidate is discovered by the browser. This must be sent to the remote participant.
|
64
|
+
* `iceCandidateGatheringComplete(guid, candidates)`: Called when all ICE candidates have been gathered. This can be used in place of `sendICECandidate` to send all candidates at once, rather than trickling them in one-by-one (though trickling is generally a better user experience).
|
65
|
+
* `sendOffer(guid, offer)`: Called when the local browser wants to initiate an offer to a remote party.
|
66
|
+
* `sendAnswer(guid, answer)`: Called when the local browser wants to respond to a received offer.
|
67
|
+
|
68
|
+
The Signaling Engine will also need to use the following methods to add new Partners to the conversation:
|
69
|
+
|
70
|
+
* `TalkingStick.addPartner(partner)`
|
71
|
+
|
72
|
+
```javascript
|
73
|
+
var partner = {
|
74
|
+
guid: 'GloballyUniqueIdentifierForThisParter-Session',
|
75
|
+
joined_at: Date
|
76
|
+
}
|
77
|
+
```
|
78
|
+
|
79
|
+
The `guid` should be unique not only for the remote participant, but also his connection into this session. If a single participant were to join two different rooms on the same server (for example), he would need two separate GUIDs, one for each room.
|
80
|
+
|
81
|
+
The `joined_at` field signifies the date & time that the remote Participant joined the room. It is important that the `joined_at` Date object be provided by the remote party to avoid race conditions. The `joined_at` field is used internally by TalkingStick to manage the sequence of offers & answers.
|
82
|
+
|
83
|
+
This function returns an instance of `TalkingStick.Partner`.
|
84
|
+
|
85
|
+
* `TalkingStick.Partner.handleOffer(offer)` Pass in offer received from a Partner
|
86
|
+
* `TalkingStick.Partner.handleAnswer(answer)` Pass in an answer received from a Partner
|
87
|
+
* `TalkingStick.Partner.handleRemoteICECandidate(RTCIceCandidate)` Pass ICE candidates received from remote Partners in to instances of `TalkingStick.Partner`
|
88
|
+
|
89
|
+
|
90
|
+
|
91
|
+
### Additional Methods
|
92
|
+
|
93
|
+
* `TalkingStick.parters` The collection of `TalkingStick.Partners` to whom the local session is currently connected. Treat it as read-only.
|
94
|
+
* `TalkingStick.log(level, obj[, obj ...])` Allows messages to be conditionally logged to the console. Valid levels are strings, and may be one of `trace`, `debug`, `notice`, `warning`, or `error`.
|
95
|
+
|
96
|
+
## TODO
|
97
|
+
|
98
|
+
See the [Pivotal Tracker story board](https://www.pivotaltracker.com/n/projects/1343190)
|
99
|
+
|
100
|
+
## The Name
|
101
|
+
|
102
|
+
The [Talking Stick] is a tradition among Native American (and other) cultures which specifies a simple yet effective way for facilitating group conversations. For more, see the [Wikipedia Article](https://en.wikipedia.org/wiki/Talking_stick), or this insightful blog post by [Rich Goidel](http://www.dangerouskitchen.com/shut-up-and-listen/).
|
103
|
+
|
104
|
+
## Credits & Copyright
|
105
|
+
|
106
|
+
* Original author: [Ben Klang](https://twitter.com/bklang), [Mojo Lingo LLC](https://mojolingo.com)
|
107
|
+
|
108
|
+
This project is licensed under the MIT License. This project incorporates [Adapter.js from the WebRTC.org team](https://github.com/webrtc/adapter), which is licensed under the Apache license.
|
109
|
+
|
110
|
+
Copyright 2015 Mojo Lingo LLC
|
111
|
+
Portions copyright 2014 The WebRTC project authors
|
data/Rakefile
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'TalkingStick'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.rdoc')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
|
18
|
+
load 'rails/tasks/engine.rake'
|
19
|
+
|
20
|
+
|
21
|
+
load 'rails/tasks/statistics.rake'
|
22
|
+
|
23
|
+
Dir[File.join(File.dirname(__FILE__), 'tasks/**/*.rake')].each {|f| load f }
|
24
|
+
|
25
|
+
require 'rspec/core'
|
26
|
+
require 'rspec/core/rake_task'
|
27
|
+
desc "Run all specs in spec directory (excluding plugin specs)"
|
28
|
+
RSpec::Core::RakeTask.new(:spec => 'app:db:test:prepare')
|
29
|
+
task :default => :spec
|
30
|
+
|
31
|
+
Bundler::GemHelper.install_tasks
|
32
|
+
|
File without changes
|
@@ -0,0 +1,15 @@
|
|
1
|
+
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
2
|
+
// listed below.
|
3
|
+
//
|
4
|
+
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
5
|
+
// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
|
6
|
+
//
|
7
|
+
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
8
|
+
// compiled file.
|
9
|
+
//
|
10
|
+
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
|
11
|
+
// about supported directives.
|
12
|
+
//
|
13
|
+
//= require jquery
|
14
|
+
//= require_tree .
|
15
|
+
//= require talking_stick/adapter
|
@@ -0,0 +1,141 @@
|
|
1
|
+
var TalkingStick = (function(self) {
|
2
|
+
self.guid = undefined;
|
3
|
+
self.myStream = undefined;
|
4
|
+
self.joinedAt = undefined;
|
5
|
+
self._options = {
|
6
|
+
media: { audio: true, video: true },
|
7
|
+
localVideo: undefined, // Set this to the DOM element where video should be rendered
|
8
|
+
logLevel: 'error',
|
9
|
+
};
|
10
|
+
|
11
|
+
self.logLevels = [
|
12
|
+
'trace',
|
13
|
+
'debug',
|
14
|
+
'notice',
|
15
|
+
'warning',
|
16
|
+
'error',
|
17
|
+
];
|
18
|
+
|
19
|
+
self.init = function(options) {
|
20
|
+
$.extend(self._options, options);
|
21
|
+
|
22
|
+
self.logLevelIdx = self.logLevels.indexOf(self._options.logLevel);
|
23
|
+
self.partners = {};
|
24
|
+
self.guid = self._options.guid || self.generateGUID();
|
25
|
+
self.signalingEngine = self._options.signalingEngine;
|
26
|
+
self.setupLocalVideo();
|
27
|
+
self.log('notice', 'TalkingStick initialized.');
|
28
|
+
};
|
29
|
+
|
30
|
+
self.log = function() {
|
31
|
+
var level = arguments[0];
|
32
|
+
var levelIdx = self.logLevels.indexOf(level);
|
33
|
+
if (levelIdx >= self.logLevelIdx) {
|
34
|
+
var args = Array.prototype.slice.call(arguments, 1);
|
35
|
+
args.unshift('[' + level + ']');
|
36
|
+
switch(levelIdx) {
|
37
|
+
case 4:
|
38
|
+
console.error.apply(console, args);
|
39
|
+
break;
|
40
|
+
case 3:
|
41
|
+
console.warn.apply(console, args);
|
42
|
+
break;
|
43
|
+
default:
|
44
|
+
console.log.apply(console, args);
|
45
|
+
break;
|
46
|
+
}
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
50
|
+
self.setupLocalVideo = function() {
|
51
|
+
var localVideo = $(self._options.localVideo);
|
52
|
+
|
53
|
+
self.prepareVideoElement(localVideo);
|
54
|
+
|
55
|
+
self.log('debug', 'Requesting user consent to access to input devices');
|
56
|
+
navigator.getUserMedia(self._options.media, function(stream) {
|
57
|
+
self.log('debug', 'User has granted access to input devices');
|
58
|
+
self.myStream = stream;
|
59
|
+
|
60
|
+
// The JS API requires the raw DOM element, not a jQuery wrapper
|
61
|
+
attachMediaStream(localVideo[0], stream);
|
62
|
+
}, self.errorCallback);
|
63
|
+
};
|
64
|
+
|
65
|
+
self.connect = function() {
|
66
|
+
// Tell the server we're ready to start contacting the other participants
|
67
|
+
var data = {
|
68
|
+
participant: {
|
69
|
+
guid: self.guid,
|
70
|
+
}
|
71
|
+
}
|
72
|
+
$.post(self._options.roomUrl + '/participants.json', data)
|
73
|
+
.success(function(data) {
|
74
|
+
self.log('notice', 'TalkingStick connected to the room.');
|
75
|
+
self.joinedAt = new Date(data.joined_at);
|
76
|
+
self.signalingEngine.connected();
|
77
|
+
})
|
78
|
+
.fail(function() { self.ajaxErrorLog('Error joining the room', arguments) });
|
79
|
+
};
|
80
|
+
|
81
|
+
|
82
|
+
self.errorCallback = function(error) {
|
83
|
+
self.log('error', error);
|
84
|
+
};
|
85
|
+
|
86
|
+
/*
|
87
|
+
* participant.guid
|
88
|
+
* participant.joined_at
|
89
|
+
*/
|
90
|
+
self.addPartner = function(participant) {
|
91
|
+
self.log('debug', 'Adding new partner to this conversation', participant);
|
92
|
+
var partnerVideo = document.createElement('video');
|
93
|
+
self.prepareVideoElement(partnerVideo);
|
94
|
+
$('#partnerVideos').append(partnerVideo);
|
95
|
+
var options = {
|
96
|
+
videoElement: partnerVideo,
|
97
|
+
signalingEngine: self.signalingEngine,
|
98
|
+
}
|
99
|
+
|
100
|
+
partner = new TalkingStick.Partner(participant, options);
|
101
|
+
self.partners[participant.guid] = partner;
|
102
|
+
|
103
|
+
partner.connect(self.myStream);
|
104
|
+
|
105
|
+
if (partner.joinedAt < TalkingStick.joinedAt) {
|
106
|
+
self.log('trace', 'Sending Offer to', partner.guid);
|
107
|
+
// Send Offers to partners who joined before us.
|
108
|
+
// Expect partners who join after us to send us Offers.
|
109
|
+
partner.sendOffer();
|
110
|
+
}
|
111
|
+
|
112
|
+
return partner;
|
113
|
+
};
|
114
|
+
|
115
|
+
self.prepareVideoElement = function(el) {
|
116
|
+
el = $(el);
|
117
|
+
|
118
|
+
// Ensure video streams play as soon as they are attached
|
119
|
+
el.attr('autoplay', true);
|
120
|
+
|
121
|
+
// Prevent local audio feedback
|
122
|
+
el.attr('muted', true);
|
123
|
+
};
|
124
|
+
|
125
|
+
self.generateGUID = function () {
|
126
|
+
function s4() {
|
127
|
+
return Math.floor((1 + Math.random()) * 0x10000)
|
128
|
+
.toString(16)
|
129
|
+
.substring(1);
|
130
|
+
}
|
131
|
+
return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
|
132
|
+
s4() + '-' + s4() + s4() + s4();
|
133
|
+
};
|
134
|
+
|
135
|
+
self.ajaxErrorLog = function(message, ajaxFailArgs) {
|
136
|
+
// ajaxFailArgs: 0: jqXHR; 1: textStatus; 2: errorThrown
|
137
|
+
self.log('error', message + ':', ajaxFailArgs[2]);
|
138
|
+
}
|
139
|
+
|
140
|
+
return self;
|
141
|
+
}(TalkingStick || {}));
|
@@ -0,0 +1,100 @@
|
|
1
|
+
TalkingStick.Partner = function(participant, options) {
|
2
|
+
this.gatheringCandidates = false;
|
3
|
+
this.guid = participant.guid;
|
4
|
+
this.joinedAt = new Date(participant.joined_at);
|
5
|
+
this.localICECandidates = [];
|
6
|
+
this._options = {
|
7
|
+
videoElement: undefined, // Set this to the DOM element where video should be rendered
|
8
|
+
};
|
9
|
+
$.extend(this._options, options);
|
10
|
+
this.signalingEngine = this._options.signalingEngine;
|
11
|
+
this.videoElement = this._options.videoElement;
|
12
|
+
}
|
13
|
+
|
14
|
+
TalkingStick.Partner.prototype.log = function() {
|
15
|
+
var level = arguments[0];
|
16
|
+
var args = Array.prototype.slice.call(arguments, 1);
|
17
|
+
args.unshift('[Partner ' + this.guid + ']');
|
18
|
+
args.unshift(level);
|
19
|
+
TalkingStick.log.apply(this, args);
|
20
|
+
}
|
21
|
+
|
22
|
+
TalkingStick.Partner.prototype.errorCallback = function() {
|
23
|
+
// Convert arguments to a real array
|
24
|
+
var args = Array.prototype.slice.call(arguments);
|
25
|
+
args.unshift('error');
|
26
|
+
this.log(args);
|
27
|
+
}
|
28
|
+
|
29
|
+
TalkingStick.Partner.prototype.setDescription = function(answer) {
|
30
|
+
this.log('trace', 'Setting remote description to', answer);
|
31
|
+
this.peerConnection.setRemoteDescription(new RTCSessionDescription(answer));
|
32
|
+
};
|
33
|
+
|
34
|
+
TalkingStick.Partner.prototype.connect = function(stream) {
|
35
|
+
this.log('trace', 'Creating new peer connection');
|
36
|
+
this.peerConnection = new RTCPeerConnection();
|
37
|
+
|
38
|
+
var partner = this;
|
39
|
+
this.peerConnection.onicecandidate = function() {
|
40
|
+
partner.handleLocalICECandidate.apply(partner, arguments);
|
41
|
+
}
|
42
|
+
|
43
|
+
this.peerConnection.addStream(stream);
|
44
|
+
};
|
45
|
+
|
46
|
+
TalkingStick.Partner.prototype.sendOffer = function() {
|
47
|
+
// Fix scope for "this" inside createOffer()
|
48
|
+
var partner = this;
|
49
|
+
this.peerConnection.createOffer(function(offer) {
|
50
|
+
partner.log('trace', 'Created PeerConnection Offer; ICE candidate collection starting', offer);
|
51
|
+
partner.gatheringCandidates = true;
|
52
|
+
partner.peerConnection.setLocalDescription(offer);
|
53
|
+
partner.signalingEngine.sendOffer(partner.guid, offer);
|
54
|
+
}, this.errorCallback);
|
55
|
+
};
|
56
|
+
|
57
|
+
TalkingStick.Partner.prototype.handleOffer = function(offer) {
|
58
|
+
this.log('debug', 'Processing Offer received from', this.guid);
|
59
|
+
this.peerConnection.setRemoteDescription(new RTCSessionDescription(offer));
|
60
|
+
var partner = this;
|
61
|
+
|
62
|
+
this.peerConnection.onaddstream = function(event) {
|
63
|
+
attachMediaStream($(partner.videoElement)[0], event.stream);
|
64
|
+
};
|
65
|
+
this.peerConnection.createAnswer(function(answer) {
|
66
|
+
partner.peerConnection.setLocalDescription(new RTCSessionDescription(answer));
|
67
|
+
partner.log('debug', 'Sending Answer to', partner.guid);
|
68
|
+
partner.signalingEngine.sendAnswer(partner.guid, answer);
|
69
|
+
});
|
70
|
+
};
|
71
|
+
|
72
|
+
TalkingStick.Partner.prototype.handleAnswer = function(answer) {
|
73
|
+
this.log('debug', 'Processing Answer received from', this.guid);
|
74
|
+
var partner = this;
|
75
|
+
this.peerConnection.onaddstream = function(event) {
|
76
|
+
attachMediaStream($(partner.videoElement)[0], event.stream);
|
77
|
+
};
|
78
|
+
this.peerConnection.setRemoteDescription(new RTCSessionDescription(answer));
|
79
|
+
};
|
80
|
+
|
81
|
+
TalkingStick.Partner.prototype.handleRemoteICECandidate = function(candidate) {
|
82
|
+
this.log('trace', 'Adding remote ICE candidate', candidate);
|
83
|
+
this.peerConnection.addIceCandidate(new RTCIceCandidate(candidate));
|
84
|
+
};
|
85
|
+
|
86
|
+
TalkingStick.Partner.prototype.handleLocalICECandidate = function(event) {
|
87
|
+
var candidate = event.candidate;
|
88
|
+
if (candidate) {
|
89
|
+
this.log('trace', 'Discovered local ICE candidate', candidate);
|
90
|
+
// Store and transmit new ICE candidate
|
91
|
+
this.localICECandidates.push(event.candidate);
|
92
|
+
this.signalingEngine.sendICECandidate(this.guid, event.candidate);
|
93
|
+
|
94
|
+
} else {
|
95
|
+
this.gatheringCandidates = false;
|
96
|
+
this.log('debug', 'ICE candidate collection complete');
|
97
|
+
this.signalingEngine.iceCandidateGatheringComplete(this.guid, this.localICECandidates);
|
98
|
+
}
|
99
|
+
};
|
100
|
+
|