Home Manual Reference Source

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'));
  }
}