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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/Changes.md +5 -0
  4. data/Guardfile +44 -0
  5. data/README.md +59 -24
  6. data/app/assets/images/talking_stick/line-spinner.svg +1 -0
  7. data/app/assets/images/talking_stick/loading.gif +0 -0
  8. data/app/assets/javascripts/talking_stick/talking_stick.js.erb +272 -0
  9. data/app/assets/javascripts/talking_stick/talking_stick/partner.js +89 -28
  10. data/app/assets/javascripts/talking_stick/talking_stick/rails_signaling.js +24 -6
  11. data/app/assets/stylesheets/talking_stick/application.css +55 -0
  12. data/app/controllers/talking_stick/application_controller.rb +6 -0
  13. data/app/controllers/talking_stick/participants_controller.rb +1 -1
  14. data/app/controllers/talking_stick/rooms_controller.rb +4 -1
  15. data/app/models/talking_stick/participant.rb +2 -1
  16. data/app/models/talking_stick/room.rb +17 -0
  17. data/app/models/talking_stick/signal.rb +2 -0
  18. data/app/views/talking_stick/rooms/_form.html.erb +12 -9
  19. data/app/views/talking_stick/rooms/edit.html.erb +5 -4
  20. data/app/views/talking_stick/rooms/index.html.erb +29 -22
  21. data/app/views/talking_stick/rooms/new.html.erb +5 -3
  22. data/app/views/talking_stick/rooms/show.html.erb +92 -19
  23. data/config/locales/en.yml +15 -0
  24. data/config/locales/it.yml +14 -0
  25. data/db/migrate/20150722200822_add_slug_to_talking_stick_rooms.rb +11 -0
  26. data/lib/generators/talking_stick/views/USAGE +18 -0
  27. data/lib/generators/talking_stick/views/views_generator.rb +18 -0
  28. data/lib/talking_stick/version.rb +1 -1
  29. data/spec/dummy/app/assets/javascripts/application.js +1 -0
  30. data/spec/dummy/app/assets/stylesheets/application.css +1 -0
  31. data/spec/models/talking_stick/participant_spec.rb +1 -1
  32. data/spec/models/talking_stick/room_spec.rb +32 -2
  33. data/talking_stick.gemspec +2 -0
  34. metadata +42 -5
  35. data/app/assets/javascripts/talking_stick/talking_stick.js +0 -141
  36. 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: 9d0ec4a547d57cdd27a4fdaf84c9d903d49e10a4
4
- data.tar.gz: 6eeecae1fc08976b80259b980e86106b2ae75779
3
+ metadata.gz: 81f6e10b86c1f1a61757f55d1938f636fe8b03d4
4
+ data.tar.gz: 26896201414612c81686e2f28640f085f4850fb9
5
5
  SHA512:
6
- metadata.gz: 173d28bc43ac5404146d61977c44ad2cec441d37d487dcc2b184d881a6887ee42aea88197160e240113ab5ea528cc737b06c96f03cb9c0eb24b9a4febb5facc9
7
- data.tar.gz: 4985639dd6ed35028f8c2356af4fd2ac460370833260b0a86deb3c894364dfe87237b87b12cd9dfb9c0bb57f430027ce79f1bc73e365e85981695e364eb3474d
6
+ metadata.gz: ac6e81d17687651c2edfb3fe21d2637eda9c838f78061a41a67c2317b8c97608291cfa9ba2da3d6b8be4adb2c1ea023f3fb44532c912acc85c282d91b8878064
7
+ data.tar.gz: a0b240e87cf62a9d5df4289391ee1cc0bd09d7a0903711f6c84f352c9768391765329f2fb9f19f3e953dbcb32af27c392ea5d572365d8ec3ab8539a139613710
data/.gitignore CHANGED
@@ -13,3 +13,5 @@ spec/dummy/.sass-cache
13
13
  # Editor files
14
14
  .*.sw*
15
15
  *~
16
+
17
+ .DS_Store
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 basic [WebRTC](https://webrtc.org) communication in any Rails app.
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
- Now run `bundle install` to download the gem.
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
- 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
- ```
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 group is assigned a unique ID
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
- ## Providing your own signaling mechanism
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 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.
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 in to instances of `TalkingStick.Partner`
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 [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/).
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>
@@ -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 || {}));