stalk-js-webrtc
Version:
S-Talk web-rtc javascript client implementation.
292 lines (291 loc) • 9.82 kB
JavaScript
var util = require('util');
var hark = require('hark');
var getScreenMedia = require('getscreenmedia');
var WildEmitter = require('wildemitter');
var mockconsole = require('mockconsole');
import MicGainController from './mediastream-gain';
function isAllTracksEnded(stream) {
return stream.getTracks().every(function (track) { return track.readyState === 'ended'; });
}
function shouldWorkAroundFirefoxStopStream() {
if (typeof window === 'undefined') {
return false;
}
if (!window.navigator.mozGetUserMedia) {
return false;
}
var match = window.navigator.userAgent.match(/Firefox\/(\d+)\./);
var version = match && match.length >= 1 && parseInt(match[1], 10);
return version < 50;
}
function LocalMedia(opts) {
WildEmitter.call(this);
var config = this.config = {
detectSpeakingEvents: false,
audioFallback: false,
media: {
audio: true,
video: true
},
harkOptions: null,
logger: mockconsole
};
var item;
for (item in opts) {
if (opts.hasOwnProperty(item)) {
this.config[item] = opts[item];
}
}
this.logger = config.logger;
this._log = this.logger.log.bind(this.logger, 'LocalMedia:');
this._logerror = this.logger.error.bind(this.logger, 'LocalMedia:');
this.localStreams = [];
this.localScreens = [];
this.unControllMic = [];
// this.gainController = null;
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
this._logerror('Your browser does not support local media capture.');
}
this._audioMonitors = [];
this.on('localStreamStopped', this._stopAudioMonitor.bind(this));
}
util.inherits(LocalMedia, WildEmitter);
LocalMedia.prototype.start = function (mediaConstraints, cb) {
var self = this;
var constraints = mediaConstraints || this.config.media;
this.emit('localStreamRequested', constraints);
navigator.mediaDevices.getUserMedia(constraints).then(function (stream) {
var controllableMic = new MicGainController(stream);
stream.addTrack(controllableMic.outputMic);
self.unControllMic.push(stream.getAudioTracks()[0]);
stream.removeTrack(stream.getAudioTracks()[0]);
self.localStreams.push(stream);
if (constraints.audio && self.config.detectSpeakingEvents) {
self._setupAudioMonitor(stream, self.config.harkOptions);
}
self.on('changeLocalVolume', function (vol) {
if (!!controllableMic) {
if (vol == 0) {
self.mute();
}
else {
self.unmute();
}
controllableMic.setGain(vol);
}
});
stream.getTracks().forEach(function (track) {
track.addEventListener('ended', function () {
if (isAllTracksEnded(stream)) {
self._removeStream(stream);
}
});
});
self.emit('localStream', stream);
if (cb) {
return cb(null, stream);
}
}).catch(function (err) {
// Fallback for users without a camera
if (self.config.audioFallback && err.name === 'DevicesNotFoundError' && constraints.video !== false) {
constraints.video = false;
self.start(constraints, cb);
return;
}
self.emit('localStreamRequestFailed', constraints);
if (cb) {
return cb(err, null);
}
});
};
LocalMedia.prototype.stop = function (stream) {
this.stopStream(stream);
this.stopScreenShare(stream);
};
LocalMedia.prototype.stopStream = function (stream) {
var self = this;
if (stream) {
var idx = this.localStreams.indexOf(stream);
if (idx > -1) {
stream.getTracks().forEach(function (track) { track.stop(); });
//Half-working fix for Firefox, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1208373
if (shouldWorkAroundFirefoxStopStream()) {
this._removeStream(stream);
}
}
}
else {
this.localStreams.forEach(function (stream) {
stream.getTracks().forEach(function (track) { track.stop(); });
//Half-working fix for Firefox, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1208373
if (shouldWorkAroundFirefoxStopStream()) {
self._removeStream(stream);
}
});
}
};
LocalMedia.prototype.startScreenShare = function (constraints, cb) {
var self = this;
this.emit('localScreenRequested');
if (typeof constraints === 'function' && !cb) {
cb = constraints;
constraints = null;
}
getScreenMedia(constraints, function (err, stream) {
if (!err) {
self.localScreens.push(stream);
stream.getTracks().forEach(function (track) {
track.addEventListener('ended', function () {
var isAllTracksEnded = true;
stream.getTracks().forEach(function (t) {
isAllTracksEnded = t.readyState === 'ended' && isAllTracksEnded;
});
if (isAllTracksEnded) {
self._removeStream(stream);
}
});
});
self.emit('localScreen', stream);
}
else {
self.emit('localScreenRequestFailed');
}
// enable the callback
if (cb) {
return cb(err, stream);
}
});
};
LocalMedia.prototype.stopScreenShare = function (stream) {
var self = this;
if (stream) {
var idx = this.localScreens.indexOf(stream);
if (idx > -1) {
stream.getTracks().forEach(function (track) { track.stop(); });
//Half-working fix for Firefox, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1208373
if (shouldWorkAroundFirefoxStopStream()) {
this._removeStream(stream);
}
}
}
else {
this.localScreens.forEach(function (stream) {
stream.getTracks().forEach(function (track) { track.stop(); });
//Half-working fix for Firefox, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1208373
if (shouldWorkAroundFirefoxStopStream()) {
self._removeStream(stream);
}
});
}
};
// Audio controls
LocalMedia.prototype.mute = function () {
this._audioEnabled(false);
this.emit('audioOff');
};
LocalMedia.prototype.unmute = function () {
this._audioEnabled(true);
this.emit('audioOn');
};
// Video controls
LocalMedia.prototype.pauseVideo = function () {
this._videoEnabled(false);
this.emit('videoOff');
};
LocalMedia.prototype.resumeVideo = function () {
this._videoEnabled(true);
this.emit('videoOn');
};
// Combined controls
LocalMedia.prototype.pause = function () {
this.mute();
this.pauseVideo();
};
LocalMedia.prototype.resume = function () {
this.unmute();
this.resumeVideo();
};
// Internal methods for enabling/disabling audio/video
LocalMedia.prototype._audioEnabled = function (bool) {
this.localStreams.forEach(function (stream) {
stream.getAudioTracks().forEach(function (track) {
track.enabled = !!bool;
});
});
};
LocalMedia.prototype._videoEnabled = function (bool) {
this.localStreams.forEach(function (stream) {
stream.getVideoTracks().forEach(function (track) {
track.enabled = !!bool;
});
});
};
// check if all audio streams are enabled
LocalMedia.prototype.isAudioEnabled = function () {
var enabled = true;
this.localStreams.forEach(function (stream) {
stream.getAudioTracks().forEach(function (track) {
enabled = enabled && track.enabled;
});
});
return enabled;
};
// check if all video streams are enabled
LocalMedia.prototype.isVideoEnabled = function () {
var enabled = true;
this.localStreams.forEach(function (stream) {
stream.getVideoTracks().forEach(function (track) {
enabled = enabled && track.enabled;
});
});
return enabled;
};
LocalMedia.prototype._removeStream = function (stream) {
var idx = this.localStreams.indexOf(stream);
if (idx > -1) {
this.localStreams.splice(idx, 1);
this.emit('localStreamStopped', stream);
}
else {
idx = this.localScreens.indexOf(stream);
if (idx > -1) {
this.localScreens.splice(idx, 1);
this.emit('localScreenStopped', stream);
}
}
this.unControllMic.map(function (each) { return each.stop(); });
};
LocalMedia.prototype._setupAudioMonitor = function (stream, harkOptions) {
this._log('Setup audio');
var audio = hark(stream, harkOptions);
var self = this;
var timeout;
audio.on('speaking', function () {
self.emit('speaking');
});
audio.on('stopped_speaking', function () {
if (timeout) {
clearTimeout(timeout);
}
timeout = setTimeout(function () {
self.emit('stoppedSpeaking');
}, 1000);
});
audio.on('volume_change', function (volume, threshold) {
self.emit('volumeChange', volume, threshold);
});
this._audioMonitors.push({ audio: audio, stream: stream });
};
LocalMedia.prototype._stopAudioMonitor = function (stream) {
var idx = -1;
this._audioMonitors.forEach(function (monitors, i) {
if (monitors.stream === stream) {
idx = i;
}
});
if (idx > -1) {
this._audioMonitors[idx].audio.stop();
this._audioMonitors.splice(idx, 1);
}
};
export default LocalMedia;