talking_stick 0.0.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/Changes.md +5 -0
- data/Guardfile +44 -0
- data/README.md +59 -24
- data/app/assets/images/talking_stick/line-spinner.svg +1 -0
- data/app/assets/images/talking_stick/loading.gif +0 -0
- data/app/assets/javascripts/talking_stick/talking_stick.js.erb +272 -0
- data/app/assets/javascripts/talking_stick/talking_stick/partner.js +89 -28
- data/app/assets/javascripts/talking_stick/talking_stick/rails_signaling.js +24 -6
- data/app/assets/stylesheets/talking_stick/application.css +55 -0
- data/app/controllers/talking_stick/application_controller.rb +6 -0
- data/app/controllers/talking_stick/participants_controller.rb +1 -1
- data/app/controllers/talking_stick/rooms_controller.rb +4 -1
- data/app/models/talking_stick/participant.rb +2 -1
- data/app/models/talking_stick/room.rb +17 -0
- data/app/models/talking_stick/signal.rb +2 -0
- data/app/views/talking_stick/rooms/_form.html.erb +12 -9
- data/app/views/talking_stick/rooms/edit.html.erb +5 -4
- data/app/views/talking_stick/rooms/index.html.erb +29 -22
- data/app/views/talking_stick/rooms/new.html.erb +5 -3
- data/app/views/talking_stick/rooms/show.html.erb +92 -19
- data/config/locales/en.yml +15 -0
- data/config/locales/it.yml +14 -0
- data/db/migrate/20150722200822_add_slug_to_talking_stick_rooms.rb +11 -0
- data/lib/generators/talking_stick/views/USAGE +18 -0
- data/lib/generators/talking_stick/views/views_generator.rb +18 -0
- data/lib/talking_stick/version.rb +1 -1
- data/spec/dummy/app/assets/javascripts/application.js +1 -0
- data/spec/dummy/app/assets/stylesheets/application.css +1 -0
- data/spec/models/talking_stick/participant_spec.rb +1 -1
- data/spec/models/talking_stick/room_spec.rb +32 -2
- data/talking_stick.gemspec +2 -0
- metadata +42 -5
- data/app/assets/javascripts/talking_stick/talking_stick.js +0 -141
- data/app/views/layouts/talking_stick/application.html.erb +0 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 81f6e10b86c1f1a61757f55d1938f636fe8b03d4
|
4
|
+
data.tar.gz: 26896201414612c81686e2f28640f085f4850fb9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ac6e81d17687651c2edfb3fe21d2637eda9c838f78061a41a67c2317b8c97608291cfa9ba2da3d6b8be4adb2c1ea023f3fb44532c912acc85c282d91b8878064
|
7
|
+
data.tar.gz: a0b240e87cf62a9d5df4289391ee1cc0bd09d7a0903711f6c84f352c9768391765329f2fb9f19f3e953dbcb32af27c392ea5d572365d8ec3ab8539a139613710
|
data/.gitignore
CHANGED
data/Changes.md
ADDED
@@ -0,0 +1,5 @@
|
|
1
|
+
# [1.0.0](https://github.com/mojolingo/talking_stick/compare/v0.0.1...v1.0.0) - [2016-01-02](https://rubygems.org/gems/talking_stick/versions/1.0.0)
|
2
|
+
* First stable version of Talking Stick - see [README](https://github.com/mojolingo/talking_stick/blob/v1.0.0/README.md) for details
|
3
|
+
|
4
|
+
# [0.0.1](https://github.com/mojolingo/talking_stick/releases/tag/v0.0.1) - [2015-05-17](https://rubygems.org/gems/talking_stick/versions/0.0.1)
|
5
|
+
* Initial layout of the Rails engine...not much function yet
|
data/Guardfile
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
guard :rspec, cmd: "bundle exec rspec" do
|
2
|
+
require "guard/rspec/dsl"
|
3
|
+
dsl = Guard::RSpec::Dsl.new(self)
|
4
|
+
|
5
|
+
# Feel free to open issues for suggestions and improvements
|
6
|
+
|
7
|
+
# RSpec files
|
8
|
+
rspec = dsl.rspec
|
9
|
+
watch(rspec.spec_helper) { rspec.spec_dir }
|
10
|
+
watch(rspec.spec_support) { rspec.spec_dir }
|
11
|
+
watch(rspec.spec_files)
|
12
|
+
|
13
|
+
# Ruby files
|
14
|
+
ruby = dsl.ruby
|
15
|
+
dsl.watch_spec_files_for(ruby.lib_files)
|
16
|
+
|
17
|
+
# Rails files
|
18
|
+
rails = dsl.rails(view_extensions: %w(erb haml slim))
|
19
|
+
dsl.watch_spec_files_for(rails.app_files)
|
20
|
+
dsl.watch_spec_files_for(rails.views)
|
21
|
+
|
22
|
+
watch(rails.controllers) do |m|
|
23
|
+
[
|
24
|
+
rspec.spec.("routing/#{m[1]}_routing"),
|
25
|
+
rspec.spec.("controllers/#{m[1]}_controller"),
|
26
|
+
rspec.spec.("acceptance/#{m[1]}")
|
27
|
+
]
|
28
|
+
end
|
29
|
+
|
30
|
+
# Rails config changes
|
31
|
+
watch(rails.spec_helper) { rspec.spec_dir }
|
32
|
+
watch(rails.routes) { "#{rspec.spec_dir}/routing" }
|
33
|
+
watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" }
|
34
|
+
|
35
|
+
# Capybara features specs
|
36
|
+
watch(rails.view_dirs) { |m| rspec.spec.("features/#{m[1]}") }
|
37
|
+
watch(rails.layouts) { |m| rspec.spec.("features/#{m[1]}") }
|
38
|
+
|
39
|
+
# Turnip features and steps
|
40
|
+
watch(%r{^spec/acceptance/(.+)\.feature$})
|
41
|
+
watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m|
|
42
|
+
Dir[File.join("**/#{m[1]}.feature")][0] || "spec/acceptance"
|
43
|
+
end
|
44
|
+
end
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# TalkingStick
|
2
2
|
|
3
|
-
This gem provides
|
3
|
+
This gem provides easy [WebRTC](https://webrtc.org) communication in any Rails app.
|
4
4
|
|
5
5
|
|
6
6
|
## Goals
|
@@ -17,45 +17,73 @@ Note that TalkingStick requires jQuery, and installs [jquery-rails gem](https://
|
|
17
17
|
|
18
18
|
TalkingStick is built as a Rails Engine, and so follows those conventions.
|
19
19
|
|
20
|
-
1. Add to Gemfile and update bundle
|
21
|
-
```Ruby
|
22
|
-
gem 'talking_stick'
|
23
|
-
```
|
20
|
+
1. Add to Gemfile and update the bundle
|
24
21
|
|
25
|
-
|
22
|
+
```Ruby
|
23
|
+
gem 'talking_stick'
|
24
|
+
```
|
26
25
|
|
26
|
+
Now run `bundle install` to download and install the gem.
|
27
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
|
-
```
|
28
|
+
Add the following to your application's `config/routes.rb`:
|
32
29
|
|
33
|
-
|
34
|
-
|
35
|
-
```
|
36
|
-
|
37
|
-
|
30
|
+
```Ruby
|
31
|
+
mount TalkingStick::Engine, at: '/talking_stick'
|
32
|
+
```
|
33
|
+
3. Install and Run migration
|
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
|
+
4. Import Assets
|
40
|
+
Add the following to your application's `assets/javascripts/application.js`:
|
41
|
+
|
42
|
+
```javascript
|
43
|
+
//= require talking_stick/application
|
44
|
+
```
|
45
|
+
|
46
|
+
And the following to your application's `assets/stylesheets/application.scss`:
|
47
|
+
|
48
|
+
```css
|
49
|
+
*= require talking_stick/application
|
50
|
+
```
|
51
|
+
|
52
|
+
5. **[optional]** Generate the default `participant` and `room` views which are then available for customization. From your app's root directory:
|
53
|
+
|
54
|
+
```
|
55
|
+
$ rails generate talking_stick:views
|
56
|
+
```
|
57
|
+
|
58
|
+
6. Videconference!
|
59
|
+
After booting Rails, point your browser to something like [http://localhost:3000/talking_stick/rooms](http://localhost:3000/talking_stick/rooms). From there you will be able to create and join rooms.
|
60
|
+
|
61
|
+
For a better experience, we recommend also using [Bootstrap](http://getbootstrap.com). Easy installation of Bootstrap into Rails is available via [twitter-bootstrap-rails](https://github.com/seyhunak/twitter-bootstrap-rails).
|
38
62
|
|
39
63
|
## How it works
|
40
64
|
|
41
|
-
* Each
|
65
|
+
* Each room is assigned a unique ID
|
42
66
|
* Rails app keeps track of participants in each room
|
43
67
|
* Rails app notifies all room participants when one joins or leaves
|
44
68
|
* Rails app proxies call setup between participants in each room
|
45
69
|
|
46
|
-
##
|
70
|
+
## Extending TalkingStick
|
71
|
+
|
72
|
+
### Providing your own signaling mechanism
|
47
73
|
|
48
74
|
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
75
|
|
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
|
76
|
+
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 Server Sent Events. 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
77
|
|
52
78
|
### 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.
|
79
|
+
[Contact Mojo Lingo](https://mojolingo.com/connect) today! As authors of this Gem, Mojo Lingo are experts in Real-Time Communications Applications. We can help you get the most value out of WebRTC by integrating communications into your application and business processes, and scale it to very large groups. [Learn more.](https://mojolingo.com)
|
54
80
|
|
55
81
|
|
82
|
+
### Building Your Own Signaling Engine
|
83
|
+
|
56
84
|
Alternatively, you can build your own by passing a "Signaling Engine" to the TalkingStick class.
|
57
85
|
|
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.
|
86
|
+
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 else you like.
|
59
87
|
|
60
88
|
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
89
|
|
@@ -84,8 +112,7 @@ This function returns an instance of `TalkingStick.Partner`.
|
|
84
112
|
|
85
113
|
* `TalkingStick.Partner.handleOffer(offer)` Pass in offer received from a Partner
|
86
114
|
* `TalkingStick.Partner.handleAnswer(answer)` Pass in an answer received from a Partner
|
87
|
-
* `TalkingStick.Partner.handleRemoteICECandidate(RTCIceCandidate)` Pass ICE candidates received from remote Partners
|
88
|
-
|
115
|
+
* `TalkingStick.Partner.handleRemoteICECandidate(RTCIceCandidate)` Pass in ICE candidates received from remote Partners
|
89
116
|
|
90
117
|
|
91
118
|
### Additional Methods
|
@@ -93,13 +120,18 @@ This function returns an instance of `TalkingStick.Partner`.
|
|
93
120
|
* `TalkingStick.parters` The collection of `TalkingStick.Partners` to whom the local session is currently connected. Treat it as read-only.
|
94
121
|
* `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
122
|
|
123
|
+
### Events
|
124
|
+
|
125
|
+
* `talking_stick.local_media_setup`: (no arguments) Triggered after local media has been initialized and drawn to the configured element
|
126
|
+
* TODO: Document the rest of the events
|
127
|
+
|
96
128
|
## TODO
|
97
129
|
|
98
|
-
See the [Pivotal Tracker story board](https://www.pivotaltracker.com/n/projects/1343190)
|
130
|
+
See the [Pivotal Tracker story board](https://www.pivotaltracker.com/n/projects/1343190). Contributions welcome!
|
99
131
|
|
100
132
|
## The Name
|
101
133
|
|
102
|
-
The
|
134
|
+
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
135
|
|
104
136
|
## Credits & Copyright
|
105
137
|
|
@@ -108,4 +140,7 @@ The [Talking Stick] is a tradition among Native American (and other) cultures wh
|
|
108
140
|
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
141
|
|
110
142
|
Copyright 2015 Mojo Lingo LLC
|
143
|
+
|
111
144
|
Portions copyright 2014 The WebRTC project authors
|
145
|
+
|
146
|
+
[Flikr2005](https://www.flickr.com/photos/flikr/371850310/) loading image courtesy Flickr user [Flikr](https://www.flickr.com/photos/flikr/)
|
@@ -0,0 +1 @@
|
|
1
|
+
<svg width='120px' height='90px' xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" class="uil-default"><rect x="0" y="0" width="100" height="100" fill="none" class="bk"></rect><rect x='46.5' y='40' width='7' height='20' rx='5' ry='5' fill='#aea59e' transform='rotate(0 50 50) translate(0 -30)'> <animate attributeName='opacity' from='1' to='0' dur='1s' begin='0s' repeatCount='indefinite'/></rect><rect x='46.5' y='40' width='7' height='20' rx='5' ry='5' fill='#aea59e' transform='rotate(30 50 50) translate(0 -30)'> <animate attributeName='opacity' from='1' to='0' dur='1s' begin='0.08333333333333333s' repeatCount='indefinite'/></rect><rect x='46.5' y='40' width='7' height='20' rx='5' ry='5' fill='#aea59e' transform='rotate(60 50 50) translate(0 -30)'> <animate attributeName='opacity' from='1' to='0' dur='1s' begin='0.16666666666666666s' repeatCount='indefinite'/></rect><rect x='46.5' y='40' width='7' height='20' rx='5' ry='5' fill='#aea59e' transform='rotate(90 50 50) translate(0 -30)'> <animate attributeName='opacity' from='1' to='0' dur='1s' begin='0.25s' repeatCount='indefinite'/></rect><rect x='46.5' y='40' width='7' height='20' rx='5' ry='5' fill='#aea59e' transform='rotate(120 50 50) translate(0 -30)'> <animate attributeName='opacity' from='1' to='0' dur='1s' begin='0.3333333333333333s' repeatCount='indefinite'/></rect><rect x='46.5' y='40' width='7' height='20' rx='5' ry='5' fill='#aea59e' transform='rotate(150 50 50) translate(0 -30)'> <animate attributeName='opacity' from='1' to='0' dur='1s' begin='0.4166666666666667s' repeatCount='indefinite'/></rect><rect x='46.5' y='40' width='7' height='20' rx='5' ry='5' fill='#aea59e' transform='rotate(180 50 50) translate(0 -30)'> <animate attributeName='opacity' from='1' to='0' dur='1s' begin='0.5s' repeatCount='indefinite'/></rect><rect x='46.5' y='40' width='7' height='20' rx='5' ry='5' fill='#aea59e' transform='rotate(210 50 50) translate(0 -30)'> <animate attributeName='opacity' from='1' to='0' dur='1s' begin='0.5833333333333334s' repeatCount='indefinite'/></rect><rect x='46.5' y='40' width='7' height='20' rx='5' ry='5' fill='#aea59e' transform='rotate(240 50 50) translate(0 -30)'> <animate attributeName='opacity' from='1' to='0' dur='1s' begin='0.6666666666666666s' repeatCount='indefinite'/></rect><rect x='46.5' y='40' width='7' height='20' rx='5' ry='5' fill='#aea59e' transform='rotate(270 50 50) translate(0 -30)'> <animate attributeName='opacity' from='1' to='0' dur='1s' begin='0.75s' repeatCount='indefinite'/></rect><rect x='46.5' y='40' width='7' height='20' rx='5' ry='5' fill='#aea59e' transform='rotate(300 50 50) translate(0 -30)'> <animate attributeName='opacity' from='1' to='0' dur='1s' begin='0.8333333333333334s' repeatCount='indefinite'/></rect><rect x='46.5' y='40' width='7' height='20' rx='5' ry='5' fill='#aea59e' transform='rotate(330 50 50) translate(0 -30)'> <animate attributeName='opacity' from='1' to='0' dur='1s' begin='0.9166666666666666s' repeatCount='indefinite'/></rect></svg>
|
Binary file
|
@@ -0,0 +1,272 @@
|
|
1
|
+
var TalkingStick = (function(self) {
|
2
|
+
self.guid = undefined;
|
3
|
+
self.myStream = undefined;
|
4
|
+
self.joinedAt = undefined;
|
5
|
+
self.audioMuted = false;
|
6
|
+
self.videoMuted = false;
|
7
|
+
self.partners = {};
|
8
|
+
self._options = {
|
9
|
+
media: { audio: true, video: true },
|
10
|
+
localVideo: '#localvideo',
|
11
|
+
partnerVideos: '#partnervideos',
|
12
|
+
connectionTimeout: 60000,
|
13
|
+
logLevel: 'error',
|
14
|
+
iceServers: [],
|
15
|
+
};
|
16
|
+
|
17
|
+
self.logLevels = [
|
18
|
+
'trace',
|
19
|
+
'debug',
|
20
|
+
'notice',
|
21
|
+
'warning',
|
22
|
+
'error',
|
23
|
+
];
|
24
|
+
|
25
|
+
self.init = function(options) {
|
26
|
+
$.extend(self._options, options);
|
27
|
+
|
28
|
+
self.logLevelIdx = self.logLevels.indexOf(self._options.logLevel);
|
29
|
+
self.guid = self._options.guid || self.generateGUID();
|
30
|
+
self.localVideo = $(self._options.localVideo);
|
31
|
+
self.partnerVideos = $(self._options.partnerVideos);
|
32
|
+
self.signalingEngine = self._options.signalingEngine;
|
33
|
+
self.detectDevices();
|
34
|
+
self.setupLocalVideo();
|
35
|
+
self.log('notice', 'TalkingStick initialized.');
|
36
|
+
};
|
37
|
+
|
38
|
+
self.log = function() {
|
39
|
+
var level = arguments[0];
|
40
|
+
var levelIdx = self.logLevels.indexOf(level);
|
41
|
+
if (levelIdx >= self.logLevelIdx) {
|
42
|
+
var args = Array.prototype.slice.call(arguments, 1);
|
43
|
+
args.unshift('[' + level + ']');
|
44
|
+
switch(levelIdx) {
|
45
|
+
case 4:
|
46
|
+
console.error.apply(console, args);
|
47
|
+
break;
|
48
|
+
case 3:
|
49
|
+
console.warn.apply(console, args);
|
50
|
+
break;
|
51
|
+
default:
|
52
|
+
console.log.apply(console, args);
|
53
|
+
break;
|
54
|
+
}
|
55
|
+
}
|
56
|
+
};
|
57
|
+
|
58
|
+
self.trigger = function(name) {
|
59
|
+
name = 'talking_stick.' + name;
|
60
|
+
args = Array.prototype.slice.call(arguments, 1);
|
61
|
+
// Syntactic sugar: make it easy to pass a list of args as the only argument
|
62
|
+
// This is the "right way" per
|
63
|
+
// http://stackoverflow.com/questions/4775722/check-if-object-is-array
|
64
|
+
if (args.length == 1 && Object.prototype.toString.call(args[0]) === '[object Array]') {
|
65
|
+
args = args[0];
|
66
|
+
}
|
67
|
+
self.localVideo.trigger(name, args);
|
68
|
+
};
|
69
|
+
|
70
|
+
self.detectDevices = function() {
|
71
|
+
if (typeof MediaStreamTrack === 'undefined' || typeof MediaStreamTrack.getSources === 'undefined') {
|
72
|
+
self.log('warning', 'MediaStreamTrack is not defined. We will not be able to offer device selection.');
|
73
|
+
} else {
|
74
|
+
MediaStreamTrack.getSources(self.handleDevices);
|
75
|
+
}
|
76
|
+
};
|
77
|
+
|
78
|
+
self.handleDevices = function(devices) {
|
79
|
+
var microphones = 1;
|
80
|
+
var cameras = 1;
|
81
|
+
devices = devices.map(function(device) {
|
82
|
+
// Devices (SourceInfo) objects are read-only
|
83
|
+
// Make a copy of the data here so we can apply default labels
|
84
|
+
var tsDevice = {
|
85
|
+
id: "" + device.id,
|
86
|
+
label: "" + device.label,
|
87
|
+
facing: "" + device.facing,
|
88
|
+
kind: "" + device.kind,
|
89
|
+
}
|
90
|
+
if (tsDevice.kind === 'audio' && tsDevice.label == '') {
|
91
|
+
tsDevice.label = "<%= I18n.t('microphone') %> " + microphones++;
|
92
|
+
} else if (tsDevice.kind === 'video' && tsDevice.label == '') {
|
93
|
+
tsDevice.label = "<%= I18n.t('camera') %> " + cameras++;
|
94
|
+
}
|
95
|
+
return tsDevice;
|
96
|
+
});
|
97
|
+
self.log('trace', 'Device enumeration complete', devices);
|
98
|
+
self.devices = devices;
|
99
|
+
self.trigger('devices_ready', devices);
|
100
|
+
};
|
101
|
+
|
102
|
+
self.applyDeviceSelections = function(sourceIds) {
|
103
|
+
self.log('info', "Changing devices at user's request.", sourceIds)
|
104
|
+
if (!!self.myStream) {
|
105
|
+
self.localVideo.src = null;
|
106
|
+
self.myStream.stop();
|
107
|
+
}
|
108
|
+
|
109
|
+
var newConstraints = {}
|
110
|
+
if (sourceIds.audio) {
|
111
|
+
newConstraints.audio = {
|
112
|
+
optional: [{
|
113
|
+
sourceId: sourceIds.audio
|
114
|
+
}]
|
115
|
+
}
|
116
|
+
}
|
117
|
+
|
118
|
+
if (sourceIds.video) {
|
119
|
+
newConstraints.video = {
|
120
|
+
optional: [{
|
121
|
+
sourceId: sourceIds.video
|
122
|
+
}]
|
123
|
+
}
|
124
|
+
}
|
125
|
+
|
126
|
+
$.extend(self._options.media, newConstraints);
|
127
|
+
self.setupLocalVideo();
|
128
|
+
}
|
129
|
+
|
130
|
+
self.setupLocalVideo = function() {
|
131
|
+
self.prepareVideoElement(self.localVideo);
|
132
|
+
|
133
|
+
// Prevent local audio feedback
|
134
|
+
// Chrome allows setting the attribute via jQuery; Firefox does not.
|
135
|
+
self.localVideo[0].muted = true;
|
136
|
+
|
137
|
+
self.log('debug', 'Requesting user consent to access to input devices');
|
138
|
+
getUserMedia(self._options.media, self._handleUserMedia, self._handleUserMediaFailure);
|
139
|
+
};
|
140
|
+
|
141
|
+
self.muteAudio = function() {
|
142
|
+
self.log('debug', 'User has requested audio be muted');
|
143
|
+
self.myStream.getAudioTracks()[0].enabled = false;
|
144
|
+
}
|
145
|
+
|
146
|
+
self.muteVideo = function() {
|
147
|
+
self.log('debug', 'User has requested video be muted');
|
148
|
+
self.myStream.getVideoTracks()[0].enabled = false;
|
149
|
+
}
|
150
|
+
|
151
|
+
self.unmuteAudio = function() {
|
152
|
+
self.log('debug', 'User has requested audio be unmuted');
|
153
|
+
self.myStream.getAudioTracks()[0].enabled = true;
|
154
|
+
}
|
155
|
+
|
156
|
+
self.unmuteVideo = function() {
|
157
|
+
self.log('debug', 'User has requested video be unmuted');
|
158
|
+
self.myStream.getVideoTracks()[0].enabled = true;
|
159
|
+
}
|
160
|
+
|
161
|
+
self.audioMuted = function() {
|
162
|
+
return !self.myStream.getAudioTracks()[0].enabled;
|
163
|
+
}
|
164
|
+
|
165
|
+
self.videoMuted = function() {
|
166
|
+
return !self.myStream.getVideoTracks()[0].enabled;
|
167
|
+
}
|
168
|
+
|
169
|
+
self._handleUserMedia = function(stream) {
|
170
|
+
self.log('debug', 'User has granted access to input devices');
|
171
|
+
self.myStream = stream;
|
172
|
+
|
173
|
+
// The JS API requires the raw DOM element, not a jQuery wrapper
|
174
|
+
attachMediaStream(self.localVideo[0], stream);
|
175
|
+
|
176
|
+
self.trigger('local_media_setup');
|
177
|
+
};
|
178
|
+
|
179
|
+
self._handleUserMediaFailure = function(error) {
|
180
|
+
switch(error.name) {
|
181
|
+
case 'PermissionDeniedError':
|
182
|
+
self.log('error', 'User declined access to camera & microphone');
|
183
|
+
break;
|
184
|
+
default:
|
185
|
+
self.log('error', error);
|
186
|
+
}
|
187
|
+
};
|
188
|
+
|
189
|
+
self.connect = function(name) {
|
190
|
+
// Tell the server we're ready to start contacting the other participants
|
191
|
+
var data = {
|
192
|
+
participant: {
|
193
|
+
guid: self.guid,
|
194
|
+
name: name
|
195
|
+
}
|
196
|
+
}
|
197
|
+
$.post(self._options.roomUrl + '/participants.json', data)
|
198
|
+
.success(function(data) {
|
199
|
+
self.log('notice', 'TalkingStick connected to the room.');
|
200
|
+
self.joinedAt = new Date(data.joined_at);
|
201
|
+
self.trigger('connected');
|
202
|
+
})
|
203
|
+
.fail(function() { self.ajaxErrorLog('Error joining the room', arguments) });
|
204
|
+
};
|
205
|
+
|
206
|
+
self.disconnect = function() {
|
207
|
+
$.each(self.partners, function(i, partner) {
|
208
|
+
partner.cleanup();
|
209
|
+
});
|
210
|
+
self.trigger('disconnected');
|
211
|
+
};
|
212
|
+
|
213
|
+
self.errorCallback = function(error) {
|
214
|
+
self.log('error', error);
|
215
|
+
};
|
216
|
+
|
217
|
+
/*
|
218
|
+
* participant.guid
|
219
|
+
* participant.joined_at
|
220
|
+
*/
|
221
|
+
self.addPartner = function(participant) {
|
222
|
+
self.log('debug', 'Adding new partner to this conversation', participant);
|
223
|
+
var partnerVideo = $(document.createElement('video')); // <video>
|
224
|
+
partnerVideo.attr('data-partner-guid', participant.guid); // <video data-partner-guid="abcd1234">
|
225
|
+
partnerVideo.attr('poster', "<%= image_path 'talking_stick/line-spinner.svg' %>"); // <video poster="talking_stick/line-spinner.svg"
|
226
|
+
self.prepareVideoElement(partnerVideo);
|
227
|
+
var options = {
|
228
|
+
videoElement: partnerVideo,
|
229
|
+
signalingEngine: self.signalingEngine,
|
230
|
+
iceServers: self._options.iceServers,
|
231
|
+
}
|
232
|
+
|
233
|
+
partner = new TalkingStick.Partner(participant, options);
|
234
|
+
self.partners[participant.guid] = partner;
|
235
|
+
|
236
|
+
partner.connect(self.myStream);
|
237
|
+
|
238
|
+
if (partner.joinedAt < TalkingStick.joinedAt) {
|
239
|
+
self.log('trace', 'Sending Offer to', partner.guid);
|
240
|
+
// Send Offers to partners who joined before us.
|
241
|
+
// Expect partners who join after us to send us Offers.
|
242
|
+
partner.sendOffer({connectionTimeout: self.connectionTimeout});
|
243
|
+
}
|
244
|
+
|
245
|
+
self.trigger('new_partner', partner);
|
246
|
+
return partner;
|
247
|
+
};
|
248
|
+
|
249
|
+
self.prepareVideoElement = function(el) {
|
250
|
+
el = $(el);
|
251
|
+
|
252
|
+
// Ensure video streams play as soon as they are attached
|
253
|
+
el.attr('autoplay', true);
|
254
|
+
};
|
255
|
+
|
256
|
+
self.generateGUID = function () {
|
257
|
+
function s4() {
|
258
|
+
return Math.floor((1 + Math.random()) * 0x10000)
|
259
|
+
.toString(16)
|
260
|
+
.substring(1);
|
261
|
+
}
|
262
|
+
return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
|
263
|
+
s4() + '-' + s4() + s4() + s4();
|
264
|
+
};
|
265
|
+
|
266
|
+
self.ajaxErrorLog = function(message, ajaxFailArgs) {
|
267
|
+
// ajaxFailArgs: 0: jqXHR; 1: textStatus; 2: errorThrown
|
268
|
+
self.log('error', message + ':', ajaxFailArgs[2]);
|
269
|
+
}
|
270
|
+
|
271
|
+
return self;
|
272
|
+
}(TalkingStick || {}));
|