UNPKG

8.88 kBJavaScriptView Raw
1var util = require('util');
2var hark = require('hark');
3var webrtcSupport = require('webrtcsupport');
4var getUserMedia = require('getusermedia');
5var getScreenMedia = require('getscreenmedia');
6var WildEmitter = require('wildemitter');
7var GainController = require('mediastream-gain');
8var mockconsole = require('mockconsole');
9
10
11function LocalMedia(opts) {
12 WildEmitter.call(this);
13
14 var config = this.config = {
15 autoAdjustMic: false,
16 detectSpeakingEvents: false,
17 audioFallback: false,
18 media: {
19 audio: true,
20 video: true
21 },
22 logger: mockconsole
23 };
24
25 var item;
26 for (item in opts) {
27 this.config[item] = opts[item];
28 }
29
30 this.logger = config.logger;
31 this._log = this.logger.log.bind(this.logger, 'LocalMedia:');
32 this._logerror = this.logger.error.bind(this.logger, 'LocalMedia:');
33
34 this.screenSharingSupport = webrtcSupport.screenSharing;
35
36 this.localStreams = [];
37 this.localScreens = [];
38
39 if (!webrtcSupport.supportGetUserMedia) {
40 this._logerror('Your browser does not support local media capture.');
41 }
42}
43
44util.inherits(LocalMedia, WildEmitter);
45
46
47LocalMedia.prototype.start = function (mediaConstraints, cb) {
48 var self = this;
49 var constraints = mediaConstraints || this.config.media;
50
51 getUserMedia(constraints, function (err, stream) {
52
53 if (!err) {
54 if (constraints.audio && self.config.detectSpeakingEvents) {
55 self.setupAudioMonitor(stream, self.config.harkOptions);
56 }
57 self.localStreams.push(stream);
58
59 if (self.config.autoAdjustMic) {
60 self.gainController = new GainController(stream);
61 // start out somewhat muted if we can track audio
62 self.setMicIfEnabled(0.5);
63 }
64
65 // TODO: might need to migrate to the video tracks onended
66 // FIXME: firefox does not seem to trigger this...
67 stream.onended = function () {
68 /*
69 var idx = self.localStreams.indexOf(stream);
70 if (idx > -1) {
71 self.localScreens.splice(idx, 1);
72 }
73 self.emit('localStreamStopped', stream);
74 */
75 };
76
77 self.emit('localStream', stream);
78 } else {
79 // Fallback for users without a camera
80 if (self.config.audioFallback && err.name === 'DevicesNotFoundError' && constraints.video !== false) {
81 constraints.video = false;
82 self.start(constraints, cb);
83 return;
84 }
85 }
86 if (cb) {
87 return cb(err, stream);
88 }
89 });
90};
91
92LocalMedia.prototype.stop = function (stream) {
93 var self = this;
94 // FIXME: duplicates cleanup code until fixed in FF
95 if (stream) {
96 stream.getTracks().forEach(function (track) { track.stop(); });
97 var idx = self.localStreams.indexOf(stream);
98 if (idx > -1) {
99 self.emit('localStreamStopped', stream);
100 self.localStreams = self.localStreams.splice(idx, 1);
101 } else {
102 idx = self.localScreens.indexOf(stream);
103 if (idx > -1) {
104 self.emit('localScreenStopped', stream);
105 self.localScreens = self.localScreens.splice(idx, 1);
106 }
107 }
108 } else {
109 this.stopStreams();
110 this.stopScreenShare();
111 }
112};
113
114LocalMedia.prototype.stopStreams = function () {
115 var self = this;
116 if (this.audioMonitor) {
117 this.audioMonitor.stop();
118 delete this.audioMonitor;
119 }
120 this.localStreams.forEach(function (stream) {
121 stream.getTracks().forEach(function (track) { track.stop(); });
122 self.emit('localStreamStopped', stream);
123 });
124 this.localStreams = [];
125};
126
127LocalMedia.prototype.startScreenShare = function (cb) {
128 var self = this;
129 getScreenMedia(function (err, stream) {
130 if (!err) {
131 self.localScreens.push(stream);
132
133 // TODO: might need to migrate to the video tracks onended
134 // Firefox does not support .onended but it does not support
135 // screensharing either
136 stream.onended = function () {
137 var idx = self.localScreens.indexOf(stream);
138 if (idx > -1) {
139 self.localScreens.splice(idx, 1);
140 }
141 self.emit('localScreenStopped', stream);
142 };
143 self.emit('localScreen', stream);
144 }
145
146 // enable the callback
147 if (cb) {
148 return cb(err, stream);
149 }
150 });
151};
152
153LocalMedia.prototype.stopScreenShare = function (stream) {
154 var self = this;
155 if (stream) {
156 stream.getTracks().forEach(function (track) { track.stop(); });
157 this.emit('localScreenStopped', stream);
158 } else {
159 this.localScreens.forEach(function (stream) {
160 stream.getTracks().forEach(function (track) { track.stop(); });
161 self.emit('localScreenStopped', stream);
162 });
163 this.localScreens = [];
164 }
165};
166
167// Audio controls
168LocalMedia.prototype.mute = function () {
169 this._audioEnabled(false);
170 this.hardMuted = true;
171 this.emit('audioOff');
172};
173
174LocalMedia.prototype.unmute = function () {
175 this._audioEnabled(true);
176 this.hardMuted = false;
177 this.emit('audioOn');
178};
179
180LocalMedia.prototype.setupAudioMonitor = function (stream, harkOptions) {
181 this._log('Setup audio');
182 var audio = this.audioMonitor = hark(stream, harkOptions);
183 var self = this;
184 var timeout;
185
186 audio.on('speaking', function () {
187 self.emit('speaking');
188 if (self.hardMuted) {
189 return;
190 }
191 self.setMicIfEnabled(1);
192 });
193
194 audio.on('stopped_speaking', function () {
195 if (timeout) {
196 clearTimeout(timeout);
197 }
198
199 timeout = setTimeout(function () {
200 self.emit('stoppedSpeaking');
201 if (self.hardMuted) {
202 return;
203 }
204 self.setMicIfEnabled(0.5);
205 }, 1000);
206 });
207 audio.on('volume_change', function (volume, treshold) {
208 self.emit('volumeChange', volume, treshold);
209 });
210};
211
212// We do this as a seperate method in order to
213// still leave the "setMicVolume" as a working
214// method.
215LocalMedia.prototype.setMicIfEnabled = function (volume) {
216 if (!this.config.autoAdjustMic) {
217 return;
218 }
219 this.gainController.setGain(volume);
220};
221
222// Video controls
223LocalMedia.prototype.pauseVideo = function () {
224 this._videoEnabled(false);
225 this.emit('videoOff');
226};
227LocalMedia.prototype.resumeVideo = function () {
228 this._videoEnabled(true);
229 this.emit('videoOn');
230};
231
232// Combined controls
233LocalMedia.prototype.pause = function () {
234 this.mute();
235 this.pauseVideo();
236};
237LocalMedia.prototype.resume = function () {
238 this.unmute();
239 this.resumeVideo();
240};
241
242// Internal methods for enabling/disabling audio/video
243LocalMedia.prototype._audioEnabled = function (bool) {
244 // work around for chrome 27 bug where disabling tracks
245 // doesn't seem to work (works in canary, remove when working)
246 this.setMicIfEnabled(bool ? 1 : 0);
247 this.localStreams.forEach(function (stream) {
248 stream.getAudioTracks().forEach(function (track) {
249 track.enabled = !!bool;
250 });
251 });
252};
253LocalMedia.prototype._videoEnabled = function (bool) {
254 this.localStreams.forEach(function (stream) {
255 stream.getVideoTracks().forEach(function (track) {
256 track.enabled = !!bool;
257 });
258 });
259};
260
261// check if all audio streams are enabled
262LocalMedia.prototype.isAudioEnabled = function () {
263 var enabled = true;
264 this.localStreams.forEach(function (stream) {
265 stream.getAudioTracks().forEach(function (track) {
266 enabled = enabled && track.enabled;
267 });
268 });
269 return enabled;
270};
271
272// check if all video streams are enabled
273LocalMedia.prototype.isVideoEnabled = function () {
274 var enabled = true;
275 this.localStreams.forEach(function (stream) {
276 stream.getVideoTracks().forEach(function (track) {
277 enabled = enabled && track.enabled;
278 });
279 });
280 return enabled;
281};
282
283// Backwards Compat
284LocalMedia.prototype.startLocalMedia = LocalMedia.prototype.start;
285LocalMedia.prototype.stopLocalMedia = LocalMedia.prototype.stop;
286
287// fallback for old .localStream behaviour
288Object.defineProperty(LocalMedia.prototype, 'localStream', {
289 get: function () {
290 return this.localStreams.length > 0 ? this.localStreams[0] : null;
291 }
292});
293// fallback for old .localScreen behaviour
294Object.defineProperty(LocalMedia.prototype, 'localScreen', {
295 get: function () {
296 return this.localScreens.length > 0 ? this.localScreens[0] : null;
297 }
298});
299
300module.exports = LocalMedia;