1 | var util = require('util');
|
2 | var hark = require('hark');
|
3 | var webrtcSupport = require('webrtcsupport');
|
4 | var getUserMedia = require('getusermedia');
|
5 | var getScreenMedia = require('getscreenmedia');
|
6 | var WildEmitter = require('wildemitter');
|
7 | var GainController = require('mediastream-gain');
|
8 | var mockconsole = require('mockconsole');
|
9 |
|
10 |
|
11 | function 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 |
|
44 | util.inherits(LocalMedia, WildEmitter);
|
45 |
|
46 |
|
47 | LocalMedia.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 |
|
62 | self.setMicIfEnabled(0.5);
|
63 | }
|
64 |
|
65 |
|
66 |
|
67 | stream.onended = function () {
|
68 | |
69 |
|
70 |
|
71 |
|
72 |
|
73 |
|
74 |
|
75 | };
|
76 |
|
77 | self.emit('localStream', stream);
|
78 | } else {
|
79 |
|
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 |
|
92 | LocalMedia.prototype.stop = function (stream) {
|
93 | var self = this;
|
94 |
|
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 |
|
114 | LocalMedia.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 |
|
127 | LocalMedia.prototype.startScreenShare = function (cb) {
|
128 | var self = this;
|
129 | getScreenMedia(function (err, stream) {
|
130 | if (!err) {
|
131 | self.localScreens.push(stream);
|
132 |
|
133 |
|
134 |
|
135 |
|
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 |
|
147 | if (cb) {
|
148 | return cb(err, stream);
|
149 | }
|
150 | });
|
151 | };
|
152 |
|
153 | LocalMedia.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 |
|
168 | LocalMedia.prototype.mute = function () {
|
169 | this._audioEnabled(false);
|
170 | this.hardMuted = true;
|
171 | this.emit('audioOff');
|
172 | };
|
173 |
|
174 | LocalMedia.prototype.unmute = function () {
|
175 | this._audioEnabled(true);
|
176 | this.hardMuted = false;
|
177 | this.emit('audioOn');
|
178 | };
|
179 |
|
180 | LocalMedia.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 |
|
213 |
|
214 |
|
215 | LocalMedia.prototype.setMicIfEnabled = function (volume) {
|
216 | if (!this.config.autoAdjustMic) {
|
217 | return;
|
218 | }
|
219 | this.gainController.setGain(volume);
|
220 | };
|
221 |
|
222 |
|
223 | LocalMedia.prototype.pauseVideo = function () {
|
224 | this._videoEnabled(false);
|
225 | this.emit('videoOff');
|
226 | };
|
227 | LocalMedia.prototype.resumeVideo = function () {
|
228 | this._videoEnabled(true);
|
229 | this.emit('videoOn');
|
230 | };
|
231 |
|
232 |
|
233 | LocalMedia.prototype.pause = function () {
|
234 | this.mute();
|
235 | this.pauseVideo();
|
236 | };
|
237 | LocalMedia.prototype.resume = function () {
|
238 | this.unmute();
|
239 | this.resumeVideo();
|
240 | };
|
241 |
|
242 |
|
243 | LocalMedia.prototype._audioEnabled = function (bool) {
|
244 |
|
245 |
|
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 | };
|
253 | LocalMedia.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 |
|
262 | LocalMedia.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 |
|
273 | LocalMedia.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 |
|
284 | LocalMedia.prototype.startLocalMedia = LocalMedia.prototype.start;
|
285 | LocalMedia.prototype.stopLocalMedia = LocalMedia.prototype.stop;
|
286 |
|
287 |
|
288 | Object.defineProperty(LocalMedia.prototype, 'localStream', {
|
289 | get: function () {
|
290 | return this.localStreams.length > 0 ? this.localStreams[0] : null;
|
291 | }
|
292 | });
|
293 |
|
294 | Object.defineProperty(LocalMedia.prototype, 'localScreen', {
|
295 | get: function () {
|
296 | return this.localScreens.length > 0 ? this.localScreens[0] : null;
|
297 | }
|
298 | });
|
299 |
|
300 | module.exports = LocalMedia;
|