palava-client 2.2.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/README.md +1 -1
- package/dist/browser.js +122 -0
- package/dist/data_channel.js +66 -0
- package/dist/distributor.js +57 -0
- package/dist/gum.js +59 -0
- package/dist/identity.js +27 -0
- package/dist/index.js +53 -0
- package/dist/local_peer.js +220 -0
- package/dist/peer.js +150 -0
- package/dist/remote_peer.js +484 -0
- package/dist/room.js +213 -0
- package/dist/session.js +319 -0
- package/dist/web_socket_channel.js +132 -0
- package/package.json +12 -4
- package/Gemfile +0 -8
- package/Gemfile.lock +0 -32
- package/Rakefile +0 -47
- package/bower.json +0 -25
- package/palava.bundle.js +0 -3550
- package/palava.js +0 -1608
- package/palava.min.js +0 -24
- package/uglifier_options.json +0 -1
package/dist/peer.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
// Generated by CoffeeScript 2.7.0
|
|
2
|
+
var boundMethodCheck = function(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new Error('Bound instance method accessed before binding'); } };
|
|
3
|
+
|
|
4
|
+
import EventEmitter from 'wolfy87-eventemitter';
|
|
5
|
+
|
|
6
|
+
import * as browser from './browser.js';
|
|
7
|
+
|
|
8
|
+
// Class representing a participant in a room
|
|
9
|
+
|
|
10
|
+
export var Peer = class Peer extends EventEmitter {
|
|
11
|
+
// @param id [String] ID of the participant
|
|
12
|
+
// @param status [Object] An object conataining state which is exchanged through the palava machine
|
|
13
|
+
// @option staus name [String] The chosen name of the participant
|
|
14
|
+
|
|
15
|
+
constructor(id, status) {
|
|
16
|
+
var base;
|
|
17
|
+
super();
|
|
18
|
+
// Checks whether the participant is sending audio
|
|
19
|
+
|
|
20
|
+
// @return [Boolean] `true` if participant is sending audio
|
|
21
|
+
|
|
22
|
+
this.transmitsAudio = this.transmitsAudio.bind(this);
|
|
23
|
+
// Checks whether the participant is could send audio (but maybe has it muted)
|
|
24
|
+
|
|
25
|
+
// @return [Boolean] `true` if participant has audio tracks
|
|
26
|
+
|
|
27
|
+
this.hasAudio = this.hasAudio.bind(this);
|
|
28
|
+
// Checks whether the participant is sending audio
|
|
29
|
+
|
|
30
|
+
// @return [Boolean] `true` if participant is sending audio
|
|
31
|
+
|
|
32
|
+
this.transmitsVideo = this.transmitsVideo.bind(this);
|
|
33
|
+
// Checks whether the participant is could send video (but maybe put in on hold)
|
|
34
|
+
|
|
35
|
+
// @return [Boolean] `true` if participant has audio tracks
|
|
36
|
+
|
|
37
|
+
this.hasVideo = this.hasVideo.bind(this);
|
|
38
|
+
// Checks whether the peer connection is somewhat erroneous
|
|
39
|
+
|
|
40
|
+
// @return [Boolean] `true` if participant connection has an error
|
|
41
|
+
|
|
42
|
+
this.hasError = this.hasError.bind(this);
|
|
43
|
+
// Returns the error message of the peer
|
|
44
|
+
|
|
45
|
+
// @return [String] error message
|
|
46
|
+
|
|
47
|
+
this.getError = this.getError.bind(this);
|
|
48
|
+
// Checks whether the participant is muted
|
|
49
|
+
|
|
50
|
+
// @return [Boolean] `true` if participant is muted
|
|
51
|
+
|
|
52
|
+
this.isMuted = this.isMuted.bind(this);
|
|
53
|
+
// Checks whether the peer is ready
|
|
54
|
+
|
|
55
|
+
// @return [Boolean] `true` if participant is ready, that they have a stream
|
|
56
|
+
|
|
57
|
+
this.isReady = this.isReady.bind(this);
|
|
58
|
+
// Checks whether the participant is local
|
|
59
|
+
|
|
60
|
+
// @return [Boolean] `true` if participant is the local peer
|
|
61
|
+
|
|
62
|
+
this.isLocal = this.isLocal.bind(this);
|
|
63
|
+
// Checks whether the participant is remote
|
|
64
|
+
|
|
65
|
+
// @return [Boolean] `true` if participant is the remote peer
|
|
66
|
+
|
|
67
|
+
this.isRemote = this.isRemote.bind(this);
|
|
68
|
+
this.id = id;
|
|
69
|
+
this.status = status || {};
|
|
70
|
+
(base = this.status).user_agent || (base.user_agent = browser.getUserAgent());
|
|
71
|
+
this.joinTime = (new Date()).getTime();
|
|
72
|
+
this.ready = false;
|
|
73
|
+
this.error = null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
transmitsAudio() {
|
|
77
|
+
var ref, ref1, ref2;
|
|
78
|
+
boundMethodCheck(this, Peer);
|
|
79
|
+
return !!((ref = this.getStream()) != null ? (ref1 = ref.getAudioTracks()) != null ? (ref2 = ref1[0]) != null ? ref2.enabled : void 0 : void 0 : void 0);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
hasAudio() {
|
|
83
|
+
var ref, ref1;
|
|
84
|
+
boundMethodCheck(this, Peer);
|
|
85
|
+
return !!((ref = this.getStream()) != null ? (ref1 = ref.getAudioTracks()) != null ? ref1[0] : void 0 : void 0);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
transmitsVideo() {
|
|
89
|
+
var ref, ref1, ref2;
|
|
90
|
+
boundMethodCheck(this, Peer);
|
|
91
|
+
return !!((ref = this.getStream()) != null ? (ref1 = ref.getVideoTracks()) != null ? (ref2 = ref1[0]) != null ? ref2.enabled : void 0 : void 0 : void 0);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
hasVideo() {
|
|
95
|
+
var ref, ref1;
|
|
96
|
+
boundMethodCheck(this, Peer);
|
|
97
|
+
return !!((ref = this.getStream()) != null ? (ref1 = ref.getVideoTracks()) != null ? ref1[0] : void 0 : void 0);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
hasError() {
|
|
101
|
+
boundMethodCheck(this, Peer);
|
|
102
|
+
if (this.error) {
|
|
103
|
+
return true;
|
|
104
|
+
} else {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
getError() {
|
|
110
|
+
boundMethodCheck(this, Peer);
|
|
111
|
+
return this.error;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
isMuted() {
|
|
115
|
+
boundMethodCheck(this, Peer);
|
|
116
|
+
if (this.muted) {
|
|
117
|
+
return true;
|
|
118
|
+
} else {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
isReady() {
|
|
124
|
+
boundMethodCheck(this, Peer);
|
|
125
|
+
if (this.ready) {
|
|
126
|
+
return true;
|
|
127
|
+
} else {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
isLocal() {
|
|
133
|
+
boundMethodCheck(this, Peer);
|
|
134
|
+
if (this.local) {
|
|
135
|
+
return true;
|
|
136
|
+
} else {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
isRemote() {
|
|
142
|
+
boundMethodCheck(this, Peer);
|
|
143
|
+
if (this.local) {
|
|
144
|
+
return false;
|
|
145
|
+
} else {
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
};
|
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
// Generated by CoffeeScript 2.7.0
|
|
2
|
+
var boundMethodCheck = function(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new Error('Bound instance method accessed before binding'); } };
|
|
3
|
+
|
|
4
|
+
import * as browser from './browser.js';
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
Peer
|
|
8
|
+
} from './peer.js';
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
Distributor
|
|
12
|
+
} from './distributor.js';
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
DataChannel
|
|
16
|
+
} from './data_channel.js';
|
|
17
|
+
|
|
18
|
+
// A remote participant in a room
|
|
19
|
+
|
|
20
|
+
export var RemotePeer = class RemotePeer extends Peer {
|
|
21
|
+
// @param id [String] ID of the participant
|
|
22
|
+
// @param status [Object] Status object of the participant
|
|
23
|
+
// @param room [Room] Room the participant is in
|
|
24
|
+
// @param hasOfferPriority [Boolean] If true, we send the initial offer and win offer collisions (impolite)
|
|
25
|
+
// @param turnCredentials [Object] username and password for the turn server (optional)
|
|
26
|
+
|
|
27
|
+
constructor(id, status, room, hasOfferPriority, turnCredentials) {
|
|
28
|
+
super(id, status);
|
|
29
|
+
// Get the stream
|
|
30
|
+
|
|
31
|
+
// @return [MediaStream] Remote stream as defined by WebRTC
|
|
32
|
+
|
|
33
|
+
this.getStream = this.getStream.bind(this);
|
|
34
|
+
// Toggle the mute state of the peer
|
|
35
|
+
|
|
36
|
+
this.toggleMute = this.toggleMute.bind(this);
|
|
37
|
+
// Generates the STUN and TURN options for a peer connection
|
|
38
|
+
|
|
39
|
+
// @return [Object] ICE options for the peer connections
|
|
40
|
+
|
|
41
|
+
this.generateIceOptions = this.generateIceOptions.bind(this);
|
|
42
|
+
// Sets up the peer connection and its events
|
|
43
|
+
|
|
44
|
+
// @nodoc
|
|
45
|
+
|
|
46
|
+
this.setupPeerConnection = this.setupPeerConnection.bind(this);
|
|
47
|
+
// Queues a negotiation task to ensure sequential execution
|
|
48
|
+
// Each task is a function that returns a Promise
|
|
49
|
+
|
|
50
|
+
// @param task [Function] Function returning a Promise
|
|
51
|
+
|
|
52
|
+
this.queueNegotiation = this.queueNegotiation.bind(this);
|
|
53
|
+
// Creates and sends an offer
|
|
54
|
+
// This is queued via queueNegotiation to prevent race conditions
|
|
55
|
+
|
|
56
|
+
// @nodoc
|
|
57
|
+
|
|
58
|
+
this.createAndSendOffer = this.createAndSendOffer.bind(this);
|
|
59
|
+
// Handles an incoming offer
|
|
60
|
+
// This is queued via queueNegotiation to prevent race conditions
|
|
61
|
+
|
|
62
|
+
// @param sdp [RTCSessionDescription] The remote offer
|
|
63
|
+
// @nodoc
|
|
64
|
+
|
|
65
|
+
this.handleOffer = this.handleOffer.bind(this);
|
|
66
|
+
// Handles an incoming answer
|
|
67
|
+
// This is queued via queueNegotiation to prevent race conditions
|
|
68
|
+
|
|
69
|
+
// @param sdp [RTCSessionDescription] The remote answer
|
|
70
|
+
// @nodoc
|
|
71
|
+
|
|
72
|
+
this.handleAnswer = this.handleAnswer.bind(this);
|
|
73
|
+
// Adds a new track to this peer connection
|
|
74
|
+
// Used when the local user enables video/audio after initially joining without it
|
|
75
|
+
// Triggers renegotiation via onnegotiationneeded
|
|
76
|
+
|
|
77
|
+
// @param track [MediaStreamTrack] The track to add
|
|
78
|
+
// @param stream [MediaStream] The stream the track belongs to
|
|
79
|
+
|
|
80
|
+
this.addTrack = this.addTrack.bind(this);
|
|
81
|
+
// Removes a track from this peer connection
|
|
82
|
+
// Used when the local user disables video/audio
|
|
83
|
+
// Triggers renegotiation via onnegotiationneeded
|
|
84
|
+
|
|
85
|
+
// @param track [MediaStreamTrack] The track to remove
|
|
86
|
+
|
|
87
|
+
this.removeTrack = this.removeTrack.bind(this);
|
|
88
|
+
// Sets up the distributor connecting to the participant
|
|
89
|
+
|
|
90
|
+
// @nodoc
|
|
91
|
+
|
|
92
|
+
this.setupDistributor = this.setupDistributor.bind(this);
|
|
93
|
+
// Forward events to the room and listen for local peer events
|
|
94
|
+
|
|
95
|
+
// @nodoc
|
|
96
|
+
|
|
97
|
+
this.setupRoom = this.setupRoom.bind(this);
|
|
98
|
+
// Listen for video_added and audio_added events from the local peer
|
|
99
|
+
// and add those tracks to this peer connection
|
|
100
|
+
|
|
101
|
+
// @nodoc
|
|
102
|
+
|
|
103
|
+
this.setupLocalPeerListeners = this.setupLocalPeerListeners.bind(this);
|
|
104
|
+
this.sendMessage = this.sendMessage.bind(this);
|
|
105
|
+
// End peer connection
|
|
106
|
+
|
|
107
|
+
this.closePeerConnection = this.closePeerConnection.bind(this);
|
|
108
|
+
this.muted = false;
|
|
109
|
+
this.local = false;
|
|
110
|
+
this.room = room;
|
|
111
|
+
this.remoteStream = null;
|
|
112
|
+
this.turnCredentials = turnCredentials;
|
|
113
|
+
this.hasOfferPriority = hasOfferPriority;
|
|
114
|
+
// Queue to ensure negotiation operations happen sequentially
|
|
115
|
+
this.negotiationQueue = Promise.resolve();
|
|
116
|
+
this.dataChannels = {};
|
|
117
|
+
this.setupRoom();
|
|
118
|
+
this.setupPeerConnection();
|
|
119
|
+
this.setupDistributor();
|
|
120
|
+
if (this.hasOfferPriority) {
|
|
121
|
+
this.queueNegotiation(this.createAndSendOffer);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
getStream() {
|
|
126
|
+
boundMethodCheck(this, RemotePeer);
|
|
127
|
+
return this.remoteStream;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
toggleMute() {
|
|
131
|
+
boundMethodCheck(this, RemotePeer);
|
|
132
|
+
return this.muted = !this.muted;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
generateIceOptions() {
|
|
136
|
+
var options;
|
|
137
|
+
boundMethodCheck(this, RemotePeer);
|
|
138
|
+
options = [];
|
|
139
|
+
if (this.room.options.stun) {
|
|
140
|
+
options.push({
|
|
141
|
+
urls: [this.room.options.stun]
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
if (this.room.options.turnUrls && this.turnCredentials) {
|
|
145
|
+
options.push({
|
|
146
|
+
urls: this.room.options.turnUrls,
|
|
147
|
+
username: this.turnCredentials.user,
|
|
148
|
+
credential: this.turnCredentials.password
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
iceServers: options
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
setupPeerConnection() {
|
|
157
|
+
var channel, i, label, len, localStream, options, ref, ref1, registerChannel, track;
|
|
158
|
+
boundMethodCheck(this, RemotePeer);
|
|
159
|
+
this.peerConnection = new RTCPeerConnection(this.generateIceOptions(), browser.getPeerConnectionOptions());
|
|
160
|
+
this.peerConnection.onicecandidate = (event) => {
|
|
161
|
+
if (event.candidate) {
|
|
162
|
+
return this.distributor.send({
|
|
163
|
+
event: 'ice_candidate',
|
|
164
|
+
sdpmlineindex: event.candidate.sdpMLineIndex,
|
|
165
|
+
sdpmid: event.candidate.sdpMid,
|
|
166
|
+
candidate: event.candidate.candidate
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
this.peerConnection.ontrack = (event) => {
|
|
171
|
+
var stream, track;
|
|
172
|
+
stream = event.streams[0];
|
|
173
|
+
track = event.track;
|
|
174
|
+
if (!this.remoteStream) {
|
|
175
|
+
this.remoteStream = stream;
|
|
176
|
+
this.ready = true;
|
|
177
|
+
this.emit('stream_ready');
|
|
178
|
+
// Listen for tracks being added/removed from the remote stream
|
|
179
|
+
this.remoteStream.onaddtrack = (e) => {
|
|
180
|
+
if (e.track.kind === 'video') {
|
|
181
|
+
return this.emit('video_added', e.track, this.remoteStream);
|
|
182
|
+
} else if (e.track.kind === 'audio') {
|
|
183
|
+
return this.emit('audio_added', e.track, this.remoteStream);
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
return this.remoteStream.onremovetrack = (e) => {
|
|
187
|
+
if (e.track.kind === 'video') {
|
|
188
|
+
return this.emit('video_removed', e.track, this.remoteStream);
|
|
189
|
+
} else if (e.track.kind === 'audio') {
|
|
190
|
+
return this.emit('audio_removed', e.track, this.remoteStream);
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
} else {
|
|
194
|
+
// Additional track added to existing stream
|
|
195
|
+
if (track.kind === 'video') {
|
|
196
|
+
return this.emit('video_added', track, this.remoteStream);
|
|
197
|
+
} else if (track.kind === 'audio') {
|
|
198
|
+
return this.emit('audio_added', track, this.remoteStream);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
this.peerConnection.onremovestream = (event) => {
|
|
203
|
+
this.remoteStream = null;
|
|
204
|
+
this.ready = false;
|
|
205
|
+
return this.emit('stream_removed');
|
|
206
|
+
};
|
|
207
|
+
this.peerConnection.oniceconnectionstatechange = (event) => {
|
|
208
|
+
var connectionState;
|
|
209
|
+
connectionState = event.target.iceConnectionState;
|
|
210
|
+
switch (connectionState) {
|
|
211
|
+
case 'connecting':
|
|
212
|
+
this.error = null;
|
|
213
|
+
return this.emit('connection_pending');
|
|
214
|
+
case 'connected':
|
|
215
|
+
this.error = null;
|
|
216
|
+
return this.emit('connection_established');
|
|
217
|
+
case 'failed':
|
|
218
|
+
this.error = "connection_failed";
|
|
219
|
+
return this.emit('connection_failed');
|
|
220
|
+
case 'disconnected':
|
|
221
|
+
this.error = "connection_disconnected";
|
|
222
|
+
return this.emit('connection_disconnected');
|
|
223
|
+
case 'closed':
|
|
224
|
+
this.error = "connection_closed";
|
|
225
|
+
return this.emit('connection_closed');
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
// Handle negotiationneeded event - queue an offer
|
|
229
|
+
this.peerConnection.onnegotiationneeded = () => {
|
|
230
|
+
return this.queueNegotiation(this.createAndSendOffer);
|
|
231
|
+
};
|
|
232
|
+
// Add local tracks if we have a stream
|
|
233
|
+
localStream = this.room.localPeer.getStream();
|
|
234
|
+
if (localStream) {
|
|
235
|
+
ref = localStream.getTracks();
|
|
236
|
+
for (i = 0, len = ref.length; i < len; i++) {
|
|
237
|
+
track = ref[i];
|
|
238
|
+
this.peerConnection.addTrack(track, localStream);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
// data channel setup
|
|
242
|
+
if (this.room.options.dataChannels != null) {
|
|
243
|
+
registerChannel = (channel) => {
|
|
244
|
+
var name, wrapper;
|
|
245
|
+
name = channel.label;
|
|
246
|
+
wrapper = new DataChannel(channel);
|
|
247
|
+
this.dataChannels[name] = wrapper;
|
|
248
|
+
return this.emit('channel_ready', name, wrapper);
|
|
249
|
+
};
|
|
250
|
+
if (this.hasOfferPriority) {
|
|
251
|
+
ref1 = this.room.options.dataChannels;
|
|
252
|
+
for (label in ref1) {
|
|
253
|
+
options = ref1[label];
|
|
254
|
+
channel = this.peerConnection.createDataChannel(label, options);
|
|
255
|
+
channel.onopen = function() {
|
|
256
|
+
return registerChannel(this);
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
} else {
|
|
260
|
+
this.peerConnection.ondatachannel = (event) => {
|
|
261
|
+
return registerChannel(event.channel);
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return this.peerConnection;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
queueNegotiation(task) {
|
|
269
|
+
boundMethodCheck(this, RemotePeer);
|
|
270
|
+
return this.negotiationQueue = this.negotiationQueue.then(task).catch((error) => {
|
|
271
|
+
return this.emit('oaerror', error);
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
createAndSendOffer() {
|
|
276
|
+
boundMethodCheck(this, RemotePeer);
|
|
277
|
+
return this.peerConnection.createOffer(browser.getConstraints()).then((offer) => {
|
|
278
|
+
return this.peerConnection.setLocalDescription(offer);
|
|
279
|
+
}).then(() => {
|
|
280
|
+
this.distributor.send({
|
|
281
|
+
event: 'offer',
|
|
282
|
+
sdp: this.peerConnection.localDescription
|
|
283
|
+
});
|
|
284
|
+
return this.emit('offer');
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
handleOffer(sdp) {
|
|
289
|
+
boundMethodCheck(this, RemotePeer);
|
|
290
|
+
// Check for offer collision: both peers sent offers at the same time
|
|
291
|
+
// If we're impolite (hasOfferPriority) and we have a pending local offer, ignore incoming offer
|
|
292
|
+
if (this.hasOfferPriority && this.peerConnection.signalingState === 'have-local-offer') {
|
|
293
|
+
return Promise.resolve();
|
|
294
|
+
}
|
|
295
|
+
// If we're polite and have a pending local offer, implicit rollback will happen
|
|
296
|
+
// when we call setRemoteDescription with the incoming offer
|
|
297
|
+
return this.peerConnection.setRemoteDescription(sdp).then(() => {
|
|
298
|
+
return this.peerConnection.createAnswer(browser.getConstraints());
|
|
299
|
+
}).then((answer) => {
|
|
300
|
+
return this.peerConnection.setLocalDescription(answer);
|
|
301
|
+
}).then(() => {
|
|
302
|
+
this.distributor.send({
|
|
303
|
+
event: 'answer',
|
|
304
|
+
sdp: this.peerConnection.localDescription
|
|
305
|
+
});
|
|
306
|
+
return this.emit('answer');
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
handleAnswer(sdp) {
|
|
311
|
+
boundMethodCheck(this, RemotePeer);
|
|
312
|
+
// Only process answer if we're expecting one
|
|
313
|
+
if (this.peerConnection.signalingState !== 'have-local-offer') {
|
|
314
|
+
return Promise.resolve();
|
|
315
|
+
}
|
|
316
|
+
return this.peerConnection.setRemoteDescription(sdp);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
addTrack(track, stream) {
|
|
320
|
+
boundMethodCheck(this, RemotePeer);
|
|
321
|
+
if (!this.peerConnection) {
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
return this.peerConnection.addTrack(track, stream);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
removeTrack(track) {
|
|
328
|
+
var sender;
|
|
329
|
+
boundMethodCheck(this, RemotePeer);
|
|
330
|
+
if (!this.peerConnection) {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
// Find the sender for this track and remove it
|
|
334
|
+
sender = this.peerConnection.getSenders().find(function(s) {
|
|
335
|
+
return s.track === track;
|
|
336
|
+
});
|
|
337
|
+
if (sender) {
|
|
338
|
+
return this.peerConnection.removeTrack(sender);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
setupDistributor() {
|
|
343
|
+
boundMethodCheck(this, RemotePeer);
|
|
344
|
+
this.distributor = new Distributor(this.room.channel, this.id);
|
|
345
|
+
this.distributor.on('peer_left', (msg) => {
|
|
346
|
+
if (this.ready) {
|
|
347
|
+
this.remoteStream = null;
|
|
348
|
+
this.emit('stream_removed');
|
|
349
|
+
this.ready = false;
|
|
350
|
+
}
|
|
351
|
+
this.peerConnection.close();
|
|
352
|
+
return this.emit('left');
|
|
353
|
+
});
|
|
354
|
+
this.distributor.on('ice_candidate', (msg) => {
|
|
355
|
+
var candidate;
|
|
356
|
+
// empty msg.candidate causes error messages in firefox, so let RTCPeerConnection deal with it and return here
|
|
357
|
+
if (msg.candidate === "") {
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
candidate = new RTCIceCandidate({
|
|
361
|
+
candidate: msg.candidate,
|
|
362
|
+
sdpMLineIndex: msg.sdpmlineindex,
|
|
363
|
+
sdpMid: msg.sdpmid
|
|
364
|
+
});
|
|
365
|
+
if (!this.room.options.filterIceCandidateTypes.includes(candidate.type)) {
|
|
366
|
+
return this.peerConnection.addIceCandidate(candidate);
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
this.distributor.on('offer', (msg) => {
|
|
370
|
+
var sdp;
|
|
371
|
+
sdp = new RTCSessionDescription(msg.sdp);
|
|
372
|
+
return this.queueNegotiation(() => {
|
|
373
|
+
return this.handleOffer(sdp);
|
|
374
|
+
});
|
|
375
|
+
});
|
|
376
|
+
this.distributor.on('answer', (msg) => {
|
|
377
|
+
var sdp;
|
|
378
|
+
sdp = new RTCSessionDescription(msg.sdp);
|
|
379
|
+
return this.queueNegotiation(() => {
|
|
380
|
+
return this.handleAnswer(sdp);
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
this.distributor.on('peer_updated_status', (msg) => {
|
|
384
|
+
this.status = msg.status;
|
|
385
|
+
return this.emit('update');
|
|
386
|
+
});
|
|
387
|
+
this.distributor.on('message', (msg) => {
|
|
388
|
+
return this.emit('message', msg.data);
|
|
389
|
+
});
|
|
390
|
+
return this.distributor;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
setupRoom() {
|
|
394
|
+
boundMethodCheck(this, RemotePeer);
|
|
395
|
+
this.room.peers[this.id] = this;
|
|
396
|
+
this.on('left', () => {
|
|
397
|
+
delete this.room.peers[this.id];
|
|
398
|
+
return this.room.emit('peer_left', this);
|
|
399
|
+
});
|
|
400
|
+
this.on('offer', () => {
|
|
401
|
+
return this.room.emit('peer_offer', this);
|
|
402
|
+
});
|
|
403
|
+
this.on('answer', () => {
|
|
404
|
+
return this.room.emit('peer_answer', this);
|
|
405
|
+
});
|
|
406
|
+
this.on('update', () => {
|
|
407
|
+
return this.room.emit('peer_update', this);
|
|
408
|
+
});
|
|
409
|
+
this.on('stream_ready', () => {
|
|
410
|
+
return this.room.emit('peer_stream_ready', this);
|
|
411
|
+
});
|
|
412
|
+
this.on('stream_removed', () => {
|
|
413
|
+
return this.room.emit('peer_stream_removed', this);
|
|
414
|
+
});
|
|
415
|
+
this.on('connection_pending', () => {
|
|
416
|
+
return this.room.emit('peer_connection_pending', this);
|
|
417
|
+
});
|
|
418
|
+
this.on('connection_established', () => {
|
|
419
|
+
return this.room.emit('peer_connection_established', this);
|
|
420
|
+
});
|
|
421
|
+
this.on('connection_failed', () => {
|
|
422
|
+
return this.room.emit('peer_connection_failed', this);
|
|
423
|
+
});
|
|
424
|
+
this.on('connection_disconnected', () => {
|
|
425
|
+
return this.room.emit('peer_connection_disconnected', this);
|
|
426
|
+
});
|
|
427
|
+
this.on('connection_closed', () => {
|
|
428
|
+
return this.room.emit('peer_connection_closed', this);
|
|
429
|
+
});
|
|
430
|
+
this.on('oaerror', (e) => {
|
|
431
|
+
return this.room.emit('peer_oaerror', this, e);
|
|
432
|
+
});
|
|
433
|
+
this.on('channel_ready', (n, c) => {
|
|
434
|
+
return this.room.emit('peer_channel_ready', this, n, c);
|
|
435
|
+
});
|
|
436
|
+
// Listen for local peer adding new tracks (when user enables video/audio after joining)
|
|
437
|
+
return this.setupLocalPeerListeners();
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
setupLocalPeerListeners() {
|
|
441
|
+
boundMethodCheck(this, RemotePeer);
|
|
442
|
+
this.localPeerVideoAddedHandler = (track, stream) => {
|
|
443
|
+
return this.addTrack(track, stream);
|
|
444
|
+
};
|
|
445
|
+
this.localPeerAudioAddedHandler = (track, stream) => {
|
|
446
|
+
return this.addTrack(track, stream);
|
|
447
|
+
};
|
|
448
|
+
this.localPeerVideoRemovedHandler = (track, stream) => {
|
|
449
|
+
return this.removeTrack(track);
|
|
450
|
+
};
|
|
451
|
+
this.localPeerAudioRemovedHandler = (track, stream) => {
|
|
452
|
+
return this.removeTrack(track);
|
|
453
|
+
};
|
|
454
|
+
this.room.localPeer.on('video_added', this.localPeerVideoAddedHandler);
|
|
455
|
+
this.room.localPeer.on('audio_added', this.localPeerAudioAddedHandler);
|
|
456
|
+
this.room.localPeer.on('video_removed', this.localPeerVideoRemovedHandler);
|
|
457
|
+
this.room.localPeer.on('audio_removed', this.localPeerAudioRemovedHandler);
|
|
458
|
+
// Clean up listeners when this peer leaves
|
|
459
|
+
return this.on('left', () => {
|
|
460
|
+
this.room.localPeer.off('video_added', this.localPeerVideoAddedHandler);
|
|
461
|
+
this.room.localPeer.off('audio_added', this.localPeerAudioAddedHandler);
|
|
462
|
+
this.room.localPeer.off('video_removed', this.localPeerVideoRemovedHandler);
|
|
463
|
+
return this.room.localPeer.off('audio_removed', this.localPeerAudioRemovedHandler);
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
sendMessage(data) {
|
|
468
|
+
boundMethodCheck(this, RemotePeer);
|
|
469
|
+
return this.distributor.send({
|
|
470
|
+
event: 'message',
|
|
471
|
+
data: data
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
closePeerConnection() {
|
|
476
|
+
var ref;
|
|
477
|
+
boundMethodCheck(this, RemotePeer);
|
|
478
|
+
if ((ref = this.peerConnection) != null) {
|
|
479
|
+
ref.close();
|
|
480
|
+
}
|
|
481
|
+
return this.peerConnection = null;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
};
|