talking_stick 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.rspec +1 -0
  4. data/Gemfile +15 -0
  5. data/LICENSE.md +50 -0
  6. data/README.md +111 -0
  7. data/Rakefile +32 -0
  8. data/app/assets/images/talking_stick/.keep +0 -0
  9. data/app/assets/javascripts/talking_stick/application.js +15 -0
  10. data/app/assets/javascripts/talking_stick/talking_stick.js +141 -0
  11. data/app/assets/javascripts/talking_stick/talking_stick/partner.js +100 -0
  12. data/app/assets/javascripts/talking_stick/talking_stick/rails_signaling.js +117 -0
  13. data/app/assets/stylesheets/talking_stick/application.css +15 -0
  14. data/app/controllers/talking_stick/application_controller.rb +4 -0
  15. data/app/controllers/talking_stick/participants_controller.rb +72 -0
  16. data/app/controllers/talking_stick/rooms_controller.rb +122 -0
  17. data/app/helpers/talking_stick/application_helper.rb +4 -0
  18. data/app/models/talking_stick/participant.rb +18 -0
  19. data/app/models/talking_stick/room.rb +6 -0
  20. data/app/models/talking_stick/signal.rb +17 -0
  21. data/app/views/layouts/talking_stick/application.html.erb +14 -0
  22. data/app/views/talking_stick/participants/_form.html.erb +25 -0
  23. data/app/views/talking_stick/participants/edit.html.erb +6 -0
  24. data/app/views/talking_stick/participants/index.html.erb +29 -0
  25. data/app/views/talking_stick/participants/new.html.erb +5 -0
  26. data/app/views/talking_stick/participants/show.html.erb +14 -0
  27. data/app/views/talking_stick/rooms/_form.html.erb +25 -0
  28. data/app/views/talking_stick/rooms/edit.html.erb +6 -0
  29. data/app/views/talking_stick/rooms/index.html.erb +29 -0
  30. data/app/views/talking_stick/rooms/new.html.erb +5 -0
  31. data/app/views/talking_stick/rooms/show.html.erb +40 -0
  32. data/bin/rails +12 -0
  33. data/config/routes.rb +7 -0
  34. data/db/migrate/20150510181337_create_talking_stick_rooms.rb +10 -0
  35. data/db/migrate/20150510182258_create_talking_stick_participants.rb +14 -0
  36. data/db/migrate/20150511005922_create_talking_stick_signals.rb +12 -0
  37. data/lib/talking_stick.rb +4 -0
  38. data/lib/talking_stick/engine.rb +12 -0
  39. data/lib/talking_stick/version.rb +3 -0
  40. data/lib/tasks/talking_stick_tasks.rake +4 -0
  41. data/spec/dummy/README.rdoc +28 -0
  42. data/spec/dummy/Rakefile +6 -0
  43. data/spec/dummy/app/assets/images/.keep +0 -0
  44. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  45. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  46. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  47. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  48. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  49. data/spec/dummy/app/mailers/.keep +0 -0
  50. data/spec/dummy/app/models/.keep +0 -0
  51. data/spec/dummy/app/models/concerns/.keep +0 -0
  52. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  53. data/spec/dummy/bin/bundle +3 -0
  54. data/spec/dummy/bin/rails +4 -0
  55. data/spec/dummy/bin/rake +4 -0
  56. data/spec/dummy/bin/setup +29 -0
  57. data/spec/dummy/config.ru +4 -0
  58. data/spec/dummy/config/application.rb +32 -0
  59. data/spec/dummy/config/boot.rb +5 -0
  60. data/spec/dummy/config/database.yml +25 -0
  61. data/spec/dummy/config/environment.rb +5 -0
  62. data/spec/dummy/config/environments/development.rb +41 -0
  63. data/spec/dummy/config/environments/production.rb +79 -0
  64. data/spec/dummy/config/environments/test.rb +42 -0
  65. data/spec/dummy/config/initializers/assets.rb +11 -0
  66. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  67. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  68. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  69. data/spec/dummy/config/initializers/inflections.rb +16 -0
  70. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  71. data/spec/dummy/config/initializers/session_store.rb +3 -0
  72. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  73. data/spec/dummy/config/locales/en.yml +23 -0
  74. data/spec/dummy/config/routes.rb +4 -0
  75. data/spec/dummy/config/secrets.yml +22 -0
  76. data/spec/dummy/lib/assets/.keep +0 -0
  77. data/spec/dummy/log/.keep +0 -0
  78. data/spec/dummy/public/404.html +67 -0
  79. data/spec/dummy/public/422.html +67 -0
  80. data/spec/dummy/public/500.html +66 -0
  81. data/spec/dummy/public/favicon.ico +0 -0
  82. data/spec/models/talking_stick/participant_spec.rb +7 -0
  83. data/spec/models/talking_stick/room_spec.rb +7 -0
  84. data/spec/spec_helper.rb +13 -0
  85. data/talking_stick.gemspec +27 -0
  86. data/vendor/assets/javascripts/talking_stick/adapter.js +243 -0
  87. 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
+