From 015492e8da099c4d594122d4772702e7173a2684 Mon Sep 17 00:00:00 2001 From: isymchych Date: Thu, 12 Nov 2015 19:18:23 +0200 Subject: [PATCH] added functions to retrieve devices list and check if that's possible --- JitsiMeetJS.js | 12 ++- modules/RTC/RTC.js | 12 +++ modules/RTC/RTCUtils.js | 160 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 177 insertions(+), 7 deletions(-) diff --git a/JitsiMeetJS.js b/JitsiMeetJS.js index 016eeed52..cca2e9e34 100644 --- a/JitsiMeetJS.js +++ b/JitsiMeetJS.js @@ -3,6 +3,7 @@ var JitsiConferenceEvents = require("./JitsiConferenceEvents"); var JitsiConnectionEvents = require("./JitsiConnectionEvents"); var JitsiConnectionErrors = require("./JitsiConnectionErrors"); var JitsiConferenceErrors = require("./JitsiConferenceErrors"); +var RTC = require("./modules/RTC/RTC"); /** * Namespace for the interface of Jitsi Meet Library. @@ -19,10 +20,15 @@ var LibJitsiMeet = { connection: JitsiConnectionErrors }, init: function (options) { - require("./modules/RTC/RTC").init(options || {}); + RTC.init(options || {}); + }, + isDeviceListAvailable: function () { + return RTC.isDeviceListAvailable(); + }, + enumerateDevices: function (callback) { + RTC.enumerateDevices(callback); } - -} +}; //Setups the promise object. window.Promise = window.Promise || require("es6-promise").polyfill(); diff --git a/modules/RTC/RTC.js b/modules/RTC/RTC.js index 2de2b88be..de6cf91eb 100644 --- a/modules/RTC/RTC.js +++ b/modules/RTC/RTC.js @@ -211,6 +211,18 @@ RTC.getVideoSrc = function (element) { return RTCUtils.getVideoSrc(element); }; +RTC.isDeviceListAvailable = function () { + return RTCUtils.isDeviceListAvailable(); +}; + +/** + * Allows to receive list of available cameras/microphones. + * @param {function} callback would receive array of devices as an argument + */ +RTC.enumerateDevices = function (callback) { + RTCUtils.enumerateDevices(callback); +}; + RTC.setVideoSrc = function (element, src) { RTCUtils.setVideoSrc(element, src); }; diff --git a/modules/RTC/RTCUtils.js b/modules/RTC/RTCUtils.js index 785f6665c..887e251bd 100644 --- a/modules/RTC/RTCUtils.js +++ b/modules/RTC/RTCUtils.js @@ -1,4 +1,5 @@ -/* global config, require, attachMediaStream, getUserMedia */ +/* global config, require, attachMediaStream, getUserMedia, + MediaStreamTrack, mozRTCPeerConnection */ var RTCBrowserType = require("./RTCBrowserType"); var Resolutions = require("../../service/RTC/Resolutions"); var AdapterJS = require("./adapter.screenshare"); @@ -141,6 +142,131 @@ function getConstraints(um, resolution, bandwidth, fps, desktopStream) { return constraints; } +/** + * Apply function with arguments if function exists. + * Do nothing if function not provided. + * @param {function} [fn] function to apply + * @param {Array} [args=[]] arguments for function + */ +function maybeApply(fn, args) { + if (fn) { + fn.apply(null, args || []); + } +} + +var getUserMediaStatus = { + initialized: false, + callbacks: [] +}; + +/** + * Wrap `getUserMedia` to allow others to know if it was executed at least + * once or not. Wrapper function uses `getUserMediaStatus` object. + * @param {Function} getUserMedia native function + * @returns {Function} wrapped function + */ +function wrapGetUserMedia(getUserMedia) { + return function (constraints, successCallback, errorCallback) { + getUserMedia(constraints, function (stream) { + maybeApply(successCallback, [stream]); + if (!getUserMediaStatus.initialized) { + getUserMediaStatus.initialized = true; + getUserMediaStatus.callbacks.forEach(function (callback) { + callback(); + }); + getUserMediaStatus.callbacks.length = 0; + } + }, function (error) { + maybeApply(errorCallback, [error]); + }); + }; +} + +/** + * Create stub device which equals to auto selected device. + * @param {string} kind if that should be `audio` or `video` device + * @returns {Object} stub device description in `enumerateDevices` format + */ +function createAutoDeviceInfo(kind) { + return { + facing: null, + label: 'Auto', + kind: kind, + deviceId: '', + groupId: null + }; +} + + +/** + * Execute function after getUserMedia was executed at least once. + * @param {Function} callback function to execute after getUserMedia + */ +function afterUserMediaInitialized(callback) { + if (getUserMediaStatus.initialized) { + callback(); + } else { + getUserMediaStatus.callbacks.push(callback); + } +} + +/** + * Wrapper function which makes enumerateDevices to wait + * until someone executes getUserMedia first time. + * @param {Function} enumerateDevices native function + * @returns {Funtion} wrapped function + */ +function wrapEnumerateDevices(enumerateDevices) { + return function (callback) { + // enumerate devices only after initial getUserMedia + afterUserMediaInitialized(function () { + + enumerateDevices().then(function (devices) { + //add auto devices + devices.unshift( + createAutoDeviceInfo('audioinput'), + createAutoDeviceInfo('videoinput') + ); + + callback(devices); + }, function (err) { + console.error('cannot enumerate devices: ', err); + + // return only auto devices + callback([createAutoDeviceInfo('audioInput'), + createAutoDeviceInfo('videoinput')]); + }); + }); + }; +} + +/** + * Use old MediaStreamTrack to get devices list and + * convert it to enumerateDevices format. + * @param {Function} callback function to call when received devices list. + */ +function enumerateDevicesThroughMediaStreamTrack (callback) { + MediaStreamTrack.getSources(function (sources) { + var devices = sources.map(function (source) { + var kind = (source.kind || '').toLowerCase(); + return { + facing: source.facing || null, + label: source.label, + kind: kind ? kind + 'input': null, + deviceId: source.id, + groupId: source.groupId || null + }; + }); + + //add auto devices + devices.unshift( + createAutoDeviceInfo('audioinput'), + createAutoDeviceInfo('videoinput') + ); + callback(devices); + }); +} + //Options parameter is to pass config options. Currently uses only "useIPv6". var RTCUtils = { eventEmitter: new EventEmitter(), @@ -150,7 +276,10 @@ var RTCUtils = { var FFversion = RTCBrowserType.getFirefoxVersion(); if (FFversion >= 40) { this.peerconnection = mozRTCPeerConnection; - this.getUserMedia = navigator.mozGetUserMedia.bind(navigator); + this.getUserMedia = wrapGetUserMedia(navigator.mozGetUserMedia.bind(navigator)); + this.enumerateDevices = wrapEnumerateDevices( + navigator.mediaDevices.enumerateDevices.bind(navigator.mediaDevices) + ); this.pc_constraints = {}; this.attachMediaStream = function (element, stream) { // srcObject is being standardized and FF will eventually @@ -196,7 +325,16 @@ var RTCUtils = { } else if (RTCBrowserType.isChrome() || RTCBrowserType.isOpera()) { this.peerconnection = webkitRTCPeerConnection; - this.getUserMedia = navigator.webkitGetUserMedia.bind(navigator); + var getUserMedia = navigator.webkitGetUserMedia.bind(navigator); + if (navigator.mediaDevices) { + this.getUserMedia = wrapGetUserMedia(getUserMedia); + this.enumerateDevices = wrapEnumerateDevices( + navigator.mediaDevices.enumerateDevices.bind(navigator.mediaDevices) + ); + } else { + this.getUserMedia = getUserMedia; + this.enumerateDevices = enumerateDevicesThroughMediaStreamTrack; + } this.attachMediaStream = function (element, stream) { element.attr('src', webkitURL.createObjectURL(stream)); }; @@ -246,6 +384,7 @@ var RTCUtils = { self.peerconnection = RTCPeerConnection; self.getUserMedia = getUserMedia; + self.enumerateDevices = enumerateDevicesThroughMediaStreamTrack; self.attachMediaStream = function (elSel, stream) { if (stream.id === "dummyAudio" || stream.id === "dummyVideo") { @@ -585,7 +724,20 @@ var RTCUtils = { } return newStream; + }, + + /** + * Checks if its possible to enumerate available cameras/micropones. + * @returns {boolean} true if available, false otherwise. + */ + isDeviceListAvailable: function () { + var isEnumerateDevicesAvailable = navigator.mediaDevices && navigator.mediaDevices.enumerateDevices; + if (isEnumerateDevicesAvailable) { + return true; + } + + return MediaStreamTrack && MediaStreamTrack.getSources; } -} +}; module.exports = RTCUtils;