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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # Change Log
2
2
 
3
+ ## 3.0.0
4
+
5
+ * Use modern coffee/js modules
6
+ * Add support for WebRTC renegotiation
7
+ * Add support for request local audio/video after the connection is established
8
+
3
9
  ## 2.2.1
4
10
 
5
11
  * Only catch parsing-related errors with "invalid_format" error
package/README.md CHANGED
@@ -39,7 +39,7 @@ Use the [yarn link](https://classic.yarnpkg.com/en/docs/cli/link/) feature:
39
39
  ### Compile latest source
40
40
 
41
41
  - Make sure you have Ruby and Bundler installed (and have run `$ bundle install` once)
42
- - `$ rake build:bundle`
42
+ - `$ rake bundle`
43
43
  - Rebuild palava-web, for example, by restarting the yarn dev server
44
44
 
45
45
  ## Credits
@@ -0,0 +1,122 @@
1
+ // Generated by CoffeeScript 2.7.0
2
+ import adapter from 'webrtc-adapter';
3
+
4
+ // Checks whether the browser is a Firefox
5
+
6
+ // @return [Boolean] `true` if Firefox
7
+
8
+ export var isMozilla = function() {
9
+ return adapter.browserDetails.browser === 'firefox';
10
+ };
11
+
12
+ // Checks whether the browser is a Chrome/Chromium
13
+
14
+ // @return [Boolean] `true` if Chrome
15
+
16
+ export var isChrome = function() {
17
+ return adapter.browserDetails.browser === 'chrome';
18
+ };
19
+
20
+ // Checks which browser is used
21
+
22
+ // @return [String] A well defined id of the browser (firefox, chrome, safari, or unknown)
23
+
24
+ export var getUserAgent = function() {
25
+ return adapter.browserDetails.browser;
26
+ };
27
+
28
+ // Checks which browser is used
29
+
30
+ // @return [Integer] The user agent version
31
+
32
+ export var getUserAgentVersion = function() {
33
+ return adapter.browserDetails.version;
34
+ };
35
+
36
+ // Checks whether the WebRTC support of the browser should be compatible with palava
37
+
38
+ // Please note: The test requires network connectivity
39
+
40
+ // @return [Boolean] `true` if the browser is supported by palava
41
+
42
+ export var checkForWebrtcError = function() {
43
+ var e;
44
+ try {
45
+ new window.RTCPeerConnection({
46
+ iceServers: []
47
+ });
48
+ } catch (error) {
49
+ e = error;
50
+ return e;
51
+ }
52
+ return !(window.RTCPeerConnection && window.RTCIceCandidate && window.RTCSessionDescription && navigator.mediaDevices && navigator.mediaDevices.getUserMedia);
53
+ };
54
+
55
+ // Get WebRTC constraints argument
56
+
57
+ // @return [Object] Appropriate constraints for WebRTC
58
+
59
+ export var getConstraints = function() {
60
+ return {
61
+ optional: [],
62
+ mandatory: {
63
+ OfferToReceiveAudio: true,
64
+ OfferToReceiveVideo: true
65
+ }
66
+ };
67
+ };
68
+
69
+ // Get WebRTC PeerConnection options
70
+
71
+ // @return [Object] Appropriate options for the PeerConnection
72
+
73
+ export var getPeerConnectionOptions = function() {
74
+ if (isChrome()) {
75
+ return {
76
+ optional: [
77
+ {
78
+ DtlsSrtpKeyAgreement: true
79
+ }
80
+ ]
81
+ };
82
+ } else {
83
+ return {};
84
+ }
85
+ };
86
+
87
+ // Attaches a media stream to a DOM element
88
+
89
+ // @param element [DOM Element] The element to attach the stream to
90
+ // @param stream [MediaStream] The stream to attach
91
+
92
+ export var attachMediaStream = function(element, stream) {
93
+ if (stream) {
94
+ return element.srcObject = stream;
95
+ } else {
96
+ element.pause();
97
+ return element.srcObject = null;
98
+ }
99
+ };
100
+
101
+ // Attaches a peer's stream to a DOM element
102
+
103
+ // @param element [DOM Element] The element to attach the stream to
104
+ // @param peer [Peer] The peer whose stream to attach
105
+
106
+ export var attachPeer = function(element, peer) {
107
+ var attach;
108
+ attach = function() {
109
+ attachMediaStream(element, peer.getStream());
110
+ if (peer.isLocal()) {
111
+ element.setAttribute('muted', true);
112
+ }
113
+ return element.play();
114
+ };
115
+ if (peer.getStream()) {
116
+ return attach();
117
+ } else {
118
+ return peer.on('stream_ready', function() {
119
+ return attach();
120
+ });
121
+ }
122
+ };
@@ -0,0 +1,66 @@
1
+ // Generated by CoffeeScript 2.7.0
2
+ import EventEmitter from 'wolfy87-eventemitter';
3
+
4
+ export var DataChannel = (function() {
5
+ class DataChannel extends EventEmitter {
6
+ constructor(channel) {
7
+ super();
8
+ this.channel = channel;
9
+ this.channel.onmessage = (event) => {
10
+ return this.emit('message', event.data);
11
+ };
12
+ this.channel.onclose = () => {
13
+ return this.emit('close');
14
+ };
15
+ this.channel.onerror = (e) => {
16
+ return this.emit('error', e);
17
+ };
18
+ this.sendBuffer = [];
19
+ }
20
+
21
+ send(data, cb) {
22
+ this.sendBuffer.push([data, cb]);
23
+ if (this.sendBuffer.length === 1) {
24
+ return this.actualSend();
25
+ }
26
+ }
27
+
28
+ actualSend() {
29
+ var cb, data, e;
30
+ if (this.channel.readyState !== 'open') {
31
+ console.log("Not sending when not open!");
32
+ return;
33
+ }
34
+ while (this.sendBuffer.length) {
35
+ if (this.channel.bufferedAmount > this.MAX_BUFFER) {
36
+ setTimeout(this.actualSend.bind(this), 1);
37
+ return;
38
+ }
39
+ [data, cb] = this.sendBuffer[0];
40
+ try {
41
+ this.channel.send(data);
42
+ } catch (error) {
43
+ e = error;
44
+ setTimeout(this.actualSend.bind(this), 1);
45
+ return;
46
+ }
47
+ try {
48
+ if (typeof cb === "function") {
49
+ cb();
50
+ }
51
+ } catch (error) {
52
+ e = error;
53
+ // TODO: find a better way to tell the user ...
54
+ console.log('Exception in write callback:', e);
55
+ }
56
+ this.sendBuffer.shift();
57
+ }
58
+ }
59
+
60
+ };
61
+
62
+ DataChannel.prototype.MAX_BUFFER = 1024 * 1024;
63
+
64
+ return DataChannel;
65
+
66
+ }).call(this);
@@ -0,0 +1,57 @@
1
+ // Generated by CoffeeScript 2.7.0
2
+ // Distributor supports exchanging direct messages with peers through a channel.
3
+ // The incoming messages are filtered and outgoing messages are are sent within
4
+ // appropriate `send_to_peer` messages.
5
+
6
+ export var Distributor = class Distributor {
7
+ // @param channel [palava.Channel] The channel to connect through
8
+ // @param peerId [String] The id of the peer to connect to or `null` for global messages
9
+
10
+ constructor(channel, peerId = null) {
11
+ // Adds a handler to the Distributor
12
+
13
+ // @example
14
+ // distributor.on 'peer_left', (msg) => console.log "peer left!"
15
+
16
+ // @param event [String] Event id on which the handler is called
17
+ // @param handler [function] This function is called when the event is received
18
+
19
+ this.on = this.on.bind(this);
20
+ // Sends a message through the Distributor
21
+
22
+ // @param msg [Object] The message to send through the distributor
23
+
24
+ this.send = this.send.bind(this);
25
+ this.channel = channel;
26
+ this.peerId = peerId;
27
+ }
28
+
29
+ on(event, handler) {
30
+ return this.channel.on('message', (msg) => {
31
+ if (this.peerId) {
32
+ if (msg.sender_id === this.peerId && event === msg.event) {
33
+ return handler(msg);
34
+ }
35
+ } else {
36
+ if (!msg.sender_id && event === msg.event) {
37
+ return handler(msg);
38
+ }
39
+ }
40
+ });
41
+ }
42
+
43
+ send(msg) {
44
+ var payload;
45
+ if (this.peerId) {
46
+ payload = {
47
+ event: 'send_to_peer',
48
+ peer_id: this.peerId,
49
+ data: msg
50
+ };
51
+ } else {
52
+ payload = msg;
53
+ }
54
+ return this.channel.send(payload);
55
+ }
56
+
57
+ };
package/dist/gum.js ADDED
@@ -0,0 +1,59 @@
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
+ export var Gum = class Gum extends EventEmitter {
7
+ constructor(config) {
8
+ super();
9
+ this.changeConfig = this.changeConfig.bind(this);
10
+ this.requestStream = this.requestStream.bind(this);
11
+ this.getStream = this.getStream.bind(this);
12
+ this.releaseStream = this.releaseStream.bind(this);
13
+ this.config = config || {
14
+ video: true,
15
+ audio: true
16
+ };
17
+ this.stream = null;
18
+ }
19
+
20
+ changeConfig(config) {
21
+ boundMethodCheck(this, Gum);
22
+ this.config = config;
23
+ this.releaseStream();
24
+ return this.requestStream();
25
+ }
26
+
27
+ requestStream() {
28
+ boundMethodCheck(this, Gum);
29
+ return navigator.mediaDevices.getUserMedia(this.config).then((stream) => {
30
+ this.stream = stream;
31
+ return this.emit('stream_ready', stream);
32
+ }).catch((error) => {
33
+ return this.emit('stream_error', error);
34
+ });
35
+ }
36
+
37
+ getStream() {
38
+ boundMethodCheck(this, Gum);
39
+ return this.stream;
40
+ }
41
+
42
+ releaseStream() {
43
+ boundMethodCheck(this, Gum);
44
+ if (this.stream) {
45
+ this.stream.getAudioTracks().forEach((track) => {
46
+ return track.stop();
47
+ });
48
+ this.stream.getVideoTracks().forEach((track) => {
49
+ return track.stop();
50
+ });
51
+ this.stream = null;
52
+ this.emit('stream_released', this);
53
+ return true;
54
+ } else {
55
+ return false;
56
+ }
57
+ }
58
+
59
+ };
@@ -0,0 +1,27 @@
1
+ // Generated by CoffeeScript 2.7.0
2
+ import {
3
+ Gum
4
+ } from './gum.js';
5
+
6
+ export var Identity = class Identity {
7
+ constructor(o) {
8
+ this.getName = this.getName.bind(this);
9
+ this.getStatus = this.getStatus.bind(this);
10
+ this.userMediaConfig = o.userMediaConfig;
11
+ this.status = o.status || {};
12
+ this.status.name = o.name;
13
+ }
14
+
15
+ newUserMedia() {
16
+ return new Gum(this.userMediaConfig);
17
+ }
18
+
19
+ getName() {
20
+ return this.name;
21
+ }
22
+
23
+ getStatus() {
24
+ return this.status;
25
+ }
26
+
27
+ };
package/dist/index.js ADDED
@@ -0,0 +1,53 @@
1
+ // Generated by CoffeeScript 2.7.0
2
+ import * as browser from './browser.js';
3
+
4
+ export {
5
+ browser
6
+ };
7
+
8
+ export {
9
+ Gum
10
+ } from './gum.js';
11
+
12
+ export {
13
+ Identity
14
+ } from './identity.js';
15
+
16
+ export {
17
+ Peer
18
+ } from './peer.js';
19
+
20
+ export {
21
+ LocalPeer
22
+ } from './local_peer.js';
23
+
24
+ export {
25
+ RemotePeer
26
+ } from './remote_peer.js';
27
+
28
+ export {
29
+ Room
30
+ } from './room.js';
31
+
32
+ export {
33
+ Session
34
+ } from './session.js';
35
+
36
+ export {
37
+ WebSocketChannel
38
+ } from './web_socket_channel.js';
39
+
40
+ export {
41
+ Distributor
42
+ } from './distributor.js';
43
+
44
+ export {
45
+ DataChannel
46
+ } from './data_channel.js';
47
+
48
+ // Version info
49
+ export var PROTOCOL_NAME = 'palava';
50
+
51
+ export var PROTOCOL_VERSION = '1.0.0';
52
+
53
+ export var LIB_VERSION = '3.0.0';
@@ -0,0 +1,220 @@
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
+ // A specialized peer representing the local user in the conference
11
+ export var LocalPeer = class LocalPeer extends Peer {
12
+ // @param id [String] Unique ID of the local peer in the conference
13
+ // @param status [Object] An object containing state which is exchanged through the palava machine (see `palava.Peer` for more informations)
14
+ // @param room [palava.Room] The room in which the peer is present
15
+ constructor(id, status, room) {
16
+ super(id, status);
17
+ // Initializes the events based on the userMedia
18
+
19
+ // @nodoc
20
+
21
+ this.setupUserMedia = this.setupUserMedia.bind(this);
22
+ // Initializes the events based on the room
23
+
24
+ // @nodoc
25
+
26
+ this.setupRoom = this.setupRoom.bind(this);
27
+ // Returns the local stream
28
+
29
+ // @return [MediaStream] The local stream as defined by the WebRTC API
30
+
31
+ this.getStream = this.getStream.bind(this);
32
+ // Updates the status of the local peer. The status is extended or updated with the given items.
33
+
34
+ // @param status [Object] Object containing the new items
35
+
36
+ this.updateStatus = this.updateStatus.bind(this);
37
+ // Remove video track and emit event for renegotiation
38
+
39
+ this.disableVideo = this.disableVideo.bind(this);
40
+ // Remove audio track and emit event for renegotiation
41
+
42
+ this.disableAudio = this.disableAudio.bind(this);
43
+ // Request video from getUserMedia and add it to the local stream
44
+ // Emits 'video_added' event which remote peers listen to for adding the track to their connections
45
+
46
+ // @param constraints [Object] Video constraints for getUserMedia (optional, defaults to true)
47
+ // @return [Promise] Resolves with the video track when added, rejects on error
48
+
49
+ this.requestVideo = this.requestVideo.bind(this);
50
+ // Request audio from getUserMedia and add it to the local stream
51
+ // Emits 'audio_added' event which remote peers listen to for adding the track to their connections
52
+
53
+ // @param constraints [Object] Audio constraints for getUserMedia (optional, defaults to true)
54
+ // @return [Promise] Resolves with the audio track when added, rejects on error
55
+
56
+ this.requestAudio = this.requestAudio.bind(this);
57
+ // Leave the room
58
+ this.leave = this.leave.bind(this);
59
+ this.muted = true;
60
+ this.local = true;
61
+ this.room = room;
62
+ this.userMedia = room.userMedia;
63
+ this.setupRoom();
64
+ this.setupUserMedia();
65
+ }
66
+
67
+ setupUserMedia() {
68
+ boundMethodCheck(this, LocalPeer);
69
+ this.userMedia.on('stream_released', () => {
70
+ this.ready = false;
71
+ return this.emit('stream_removed');
72
+ });
73
+ this.userMedia.on('stream_ready', (e) => {
74
+ this.ready = true;
75
+ return this.emit('stream_ready', e);
76
+ });
77
+ this.userMedia.on('stream_error', (e) => {
78
+ return this.emit('stream_error', e);
79
+ });
80
+ if (this.getStream()) {
81
+ this.ready = true;
82
+ return this.emit('stream_ready');
83
+ }
84
+ }
85
+
86
+ setupRoom() {
87
+ boundMethodCheck(this, LocalPeer);
88
+ this.room.peers[this.id] = this.room.localPeer = this;
89
+ this.on('update', () => {
90
+ return this.room.emit('peer_update', this);
91
+ });
92
+ this.on('stream_ready', () => {
93
+ return this.room.emit('peer_stream_ready', this);
94
+ });
95
+ return this.on('stream_removed', () => {
96
+ return this.room.emit('peer_stream_removed', this);
97
+ });
98
+ }
99
+
100
+ getStream() {
101
+ boundMethodCheck(this, LocalPeer);
102
+ return this.userMedia.getStream();
103
+ }
104
+
105
+ updateStatus(status) {
106
+ var base, key;
107
+ boundMethodCheck(this, LocalPeer);
108
+ if (!status || !(status instanceof Object) || Object.keys(status).length === 0) {
109
+ return status;
110
+ }
111
+ for (key in status) {
112
+ this.status[key] = status[key];
113
+ }
114
+ (base = this.status).user_agent || (base.user_agent = browser.getUserAgent());
115
+ this.room.channel.send({
116
+ event: 'update_status',
117
+ status: this.status
118
+ });
119
+ return this.status;
120
+ }
121
+
122
+ disableVideo() {
123
+ var i, len, ref, results, stream, track;
124
+ boundMethodCheck(this, LocalPeer);
125
+ stream = this.getStream();
126
+ if (!stream) {
127
+ return;
128
+ }
129
+ ref = stream.getVideoTracks();
130
+ results = [];
131
+ for (i = 0, len = ref.length; i < len; i++) {
132
+ track = ref[i];
133
+ track.stop();
134
+ stream.removeTrack(track);
135
+ results.push(this.emit('video_removed', track, stream));
136
+ }
137
+ return results;
138
+ }
139
+
140
+ disableAudio() {
141
+ var i, len, ref, results, stream, track;
142
+ boundMethodCheck(this, LocalPeer);
143
+ stream = this.getStream();
144
+ if (!stream) {
145
+ return;
146
+ }
147
+ ref = stream.getAudioTracks();
148
+ results = [];
149
+ for (i = 0, len = ref.length; i < len; i++) {
150
+ track = ref[i];
151
+ track.stop();
152
+ stream.removeTrack(track);
153
+ results.push(this.emit('audio_removed', track, stream));
154
+ }
155
+ return results;
156
+ }
157
+
158
+ requestVideo(constraints = true) {
159
+ boundMethodCheck(this, LocalPeer);
160
+ if (!this.getStream()) {
161
+ return Promise.reject(new Error('No stream available'));
162
+ }
163
+ if (this.hasVideo()) {
164
+ return Promise.resolve(this.getStream().getVideoTracks()[0]);
165
+ }
166
+ return navigator.mediaDevices.getUserMedia({
167
+ video: constraints,
168
+ audio: false
169
+ }).then((stream) => {
170
+ var videoTrack;
171
+ videoTrack = stream.getVideoTracks()[0];
172
+ if (!videoTrack) {
173
+ return Promise.reject(new Error('No video track received'));
174
+ }
175
+ // Add track to our local stream
176
+ this.getStream().addTrack(videoTrack);
177
+ // Emit event - remote peers will listen and add the track to their connections
178
+ this.emit('video_added', videoTrack, this.getStream());
179
+ return videoTrack;
180
+ }).catch((error) => {
181
+ this.emit('video_error', error);
182
+ return Promise.reject(error);
183
+ });
184
+ }
185
+
186
+ requestAudio(constraints = true) {
187
+ boundMethodCheck(this, LocalPeer);
188
+ if (!this.getStream()) {
189
+ return Promise.reject(new Error('No stream available'));
190
+ }
191
+ if (this.hasAudio()) {
192
+ return Promise.resolve(this.getStream().getAudioTracks()[0]);
193
+ }
194
+ return navigator.mediaDevices.getUserMedia({
195
+ video: false,
196
+ audio: constraints
197
+ }).then((stream) => {
198
+ var audioTrack;
199
+ audioTrack = stream.getAudioTracks()[0];
200
+ if (!audioTrack) {
201
+ return Promise.reject(new Error('No audio track received'));
202
+ }
203
+ // Add track to our local stream
204
+ this.getStream().addTrack(audioTrack);
205
+ // Emit event - remote peers will listen and add the track to their connections
206
+ this.emit('audio_added', audioTrack, this.getStream());
207
+ return audioTrack;
208
+ }).catch((error) => {
209
+ this.emit('audio_error', error);
210
+ return Promise.reject(error);
211
+ });
212
+ }
213
+
214
+ leave() {
215
+ boundMethodCheck(this, LocalPeer);
216
+ this.ready = false;
217
+ return this.emit('left');
218
+ }
219
+
220
+ };