src/core/util/Media.js
/* eslint max-params: [2, 5], max-len: [0, 120] */
import * as Log from './Log';
import Reach from '../../Reach';
/**
* Video resolution presets
* @access private
* @type {{UHD: {w: number, h: number, min: string}, FHD: {w: number, h: number, min: string, max: string}, HD: {w: number, h: number, min: string, max: string}, SVGA: {w: number, h: number, min: string, max: string}, SD: {w: number, h: number, min: string, max: string}, VGA: {w: number, h: number, max: string}}}
*/
const presets = {
UHD: { w: 3840, h: 2160, min: 'HD' },
FHD: {
w: 1920, h: 1080, min: 'HD', max: 'UHD'
},
HD: {
w: 1280, h: 720, min: 'SD', max: 'FHD'
},
SVGA: {
w: 800, h: 600, min: 'VGA', max: 'HD'
},
SD: {
w: 720, h: 576, min: 'VGA', max: 'HD'
},
VGA: { w: 640, h: 480, max: 'SVGA' }
};
/**
* Assign deviceId to constraint
* @param constraint
* @param deviceId
* @returns {*}
*/
const _assignDevice = (constraint, deviceId) => {
if (constraint && deviceId) {
return Object.assign(
/^((user)|(environment))$/i.test(deviceId) ? { facingMode: deviceId } : { deviceId },
constraint === true ? {} : constraint
);
}
return constraint;
};
/**
* Helpers for MediaDevices and MediaStreamConstraints.
*/
export default class Media {
/**
* facingMode values to use with constraints
* @returns {{USER: string, ENVIRONMENT: string}}
*/
static get facingMode() {
return {
USER: 'user',
ENVIRONMENT: 'environment'
};
}
/**
* Helpers to create a MediaStreamConstraints configuration object
* @param {boolean|MediaTrackConstraints|string} [videoConstraints='HD'] a boolean, a video constraints object or a preset id (UHD, FHD, HD, SVGA, SD, VGA)
* @param {boolean|MediaTrackConstraints} [audioConstraints=true] a boolean or an audio constraints object
* @param {string} [type=ideal] type of constraints for video when using a preset (exact,min,max or ideal)
* @param {string|object} [videoDeviceId] video input device id or facingMode
* @param {string|object} [audioDeviceId] audio input device id
* @returns {object}
* @throws {Error}
*
* @example <caption>HD AudioVideo with default devices</caption>
* let myConstraints = Reach.media.constraints();
* console.log(myConstraints);
*
* @example <caption>Full HD Video without audio using default devices</caption>
* let myConstraints = Reach.media.constraints('FHD', false, 'exact');
* console.log(myConstraints);
*/
static constraints(videoConstraints = 'HD', audioConstraints = true, type = 'ideal', videoDeviceId, audioDeviceId) {
let video = videoConstraints;
if (typeof videoConstraints === 'string') {
if (presets[videoConstraints.toUpperCase()]) {
const
preset = presets[videoConstraints.toUpperCase()];
const dimConstraint = (dim) => {
if (/^(min|max|exact)$/.test(type)) {
const r = {};
r[type] = preset[dim];
return r;
}
return {
min: preset.min ? presets[preset.min][dim] : preset[dim],
ideal: preset[dim],
max: preset.max ? presets[preset.max][dim] : preset[dim]
};
};
video = { width: dimConstraint('w'), height: dimConstraint('h') };
} else {
throw new Error('Unknown Video Resolution preset (UHD, FHD, HD, SVGA, SD, VGA)');
}
}
video = _assignDevice(video, videoDeviceId);
const audio = _assignDevice(audioConstraints, audioDeviceId);
Log.d('Media#constraints', { video, audio });
return { video, audio };
}
/**
* Init stream display node depending on stream type
* @param {MediaStream} mediaStream The MediaStream to display
* @param {Element} container Container node for streams
* @param {Element} previous Previous node for the stream
* @param {number} [volume=.7] the default volume
* @return {Element}
*/
static attachStream(mediaStream, container, previous, volume = 0.7) {
let tagName = '';
if (mediaStream.getVideoTracks().length > 0) {
tagName = 'video';
} else if (mediaStream.getAudioTracks().length > 0) {
tagName = 'audio';
}
Log.d('Media#attachStream', mediaStream, tagName);
if (tagName.length > 0) {
let _node = previous;
if (!_node || _node.tagName.toLowerCase() !== tagName) {
_node = document.createElement(tagName);
_node.autoplay = true;
// set these attributes in order to launch the video on IOS
if (Reach.browser.browser === 'safari') {
_node.setAttribute('playsinline', true);
_node.setAttribute('muted', true);
} else {
// _node.setAttribute('type','video/mp4');
}
_node.style.borderRadius = '1px';
}
if (container) {
if (previous && previous !== _node) {
container.replaceChild(_node, previous);
} else if (!previous) {
container.appendChild(_node);
}
}
_node.srcObject = mediaStream;
/* _node.addEventListener('play', (event) => {
Log.d(`video.onplay = ${event.type}`);
_node.srcObject.onaddtrack = (track) => {
Log.d(`[Local] listener: video.onaddtrack = ${track.label}`); // eslint-disable-line
};
_node.srcObject.onremovetrack = (track) => {
Log.d(`[Local] listener: video.onremovetrack = ${track.label}`); // eslint-disable-line
};
_node.srcObject.oninactive = () => {
Log.d(`[Local] listener: video.oninactive`); // eslint-disable-line
};
_node.srcObject.onplaying = (event) => {
console.debug(`[Local] listener: video.onplaying = ${event.type}`); // eslint-disable-line
};
_node.srcObject.onstalled = (event) => {
console.debug(`[Local] listener: video.onstalled = ${event.type}`); // eslint-disable-line
};
_node.srcObject.onsuspend = (event) => {
console.debug(`[Local] listener: video.onsuspend = ${event.type}`); // eslint-disable-line
console.debug(event); // eslint-disable-line
console.debug('on passe là');
};
}); */
/* _node.onsuspend = (event) => {
console.debug(`[Local] listener: video.onsuspend = ${event}`); // eslint-disable-line
console.debug(event); // eslint-disable-line
/!* console.debug('on est ici');
const tagmuted = _node.muted;
_node.setAttribute('muted',true);
let autoPlayAllowed = true;
const promise = _node.play();
if (promise instanceof Promise) {
promise.then(function(status) {
console.dir(promise);
});
promise.catch(function(error) {
console.error(error.message);
if (error.name === 'NotAllowedError') {
autoPlayAllowed = false;
} else {
// Don't throw the error so that we get to the then
// or throw it but set the autoPlayAllowed to true in here
}
}).then(function() {
if (autoPlayAllowed) {
console.log('autoplay allowed')
} else {
console.log('autoplay NOT allowed')
}
});
} else {
console.log('autoplay unknown')
} *!/
}; */
// _node.addEventListener('loadeddata', () => Log.d('on a chargé les données'));
// _node.addEventListener('error', error => Log.d(`on a une erreur ${error}`));
// _node.setAttribute('controls',true);
// disabled doesn't seem to be needed
// _node.disabled = false;
_node.volume = volume;
return _node;
}
return previous;
}
/**
* List available input devices
* @return {Promise<{audioinput: MediaDeviceInfo[], videoinput: MediaDeviceInfo[]}>}
*
* @example
* Reach.media.devices().then(devices => {
* // Video cameras
* console.log(devices.videoinput);
* // Audio mics
* console.log(devices.audioinput);
* });
*/
static devices() {
return navigator.mediaDevices.enumerateDevices()
.then((devices) => {
const r = {};
devices.forEach((device) => {
if (!r[device.kind]) {
r[device.kind] = [];
}
r[device.kind].push(device);
});
Log.d('Media#devices', r);
return r;
})
.catch(Log.r('Media#devices'));
}
}