Home Reference Source

lib/media.js

import { id, qs, qsa } from './dom';
import { uuidv4 } from './general';

/**
 * Calls the play method of the element returned by the querySelector
 * @param {string} selector - The query selector to select a media element
 * @param {Document|Element} [cntx] - Optional element/Document to use
 * to perform the querySelector call from
 * @return {Promise<boolean>} - Returns false if an exception occurred or true if playing was successful
 */
export function selectAndPlay(selector, cntx) {
  const elem = qs(selector, cntx);
  return noExceptPlayMediaElement(elem);
}

/**
 * Calls the play method of the element returned by calling document.getElementById
 * @param {string} eid - The id of the media element
 * @param {Document} [cntx] - Optional document object to use
 * @return {Promise<boolean>} - Returns false if an exception occurred or true if playing was successful
 */
export function selectIdAndPlay(eid, cntx) {
  const elem = id(eid, cntx);
  return noExceptPlayMediaElement(elem);
}

/**
 * Calls the play function of the supplied element catching the exception thrown
 * if any was thrown. Returns true indicating call to play was successful otherwise false
 * @param {HTMLMediaElement} mediaElement
 * @param {boolean} [playThrough]
 * @return {Promise<boolean>} - Returns false if an exception occurred or true if playing was successful
 */
export async function noExceptPlayMediaElement(mediaElement, playThrough) {
  if (mediaElement == null || typeof mediaElement.play !== 'function') {
    return false;
  }
  try {
    if (playThrough) {
      const plp = uaThinksMediaElementCanPlayAllTheWay(mediaElement);
      await mediaElement.play();
      await plp;
    } else {
      await mediaElement.play();
    }
  } catch (e) {
    return false;
  }
  return true;
}

/**
 * Returns a promise that resolves once the canplaythrough or error event
 * is fired from the supplied media element
 * @param {HTMLMediaElement} mediaElement
 * @return {Promise<void>}
 */
export function uaThinksMediaElementCanPlayAllTheWay(mediaElement) {
  return new Promise(resolve => {
    let to;
    const listener = () => {
      if (to != null) {
        clearTimeout(to);
        to = null;
      }
      mediaElement.removeEventListener('canplaythrough', listener);
      mediaElement.removeEventListener('error', listener);
      resolve();
    };
    mediaElement.addEventListener('canplaythrough', listener);
    mediaElement.addEventListener('error', listener);
    to = setTimeout(listener, 60000);
  });
}

/**
 * @type {string}
 * @private
 */
const __MediaElementSelector__ = 'audio, video';
/** @ignore  */
let __Played__;

/**
 * Calls the play function on all audio and video elements found in the document.
 * If context is supplied, the selection of audio and video elements is done from
 * the supplied context.
 * @param {Document|Element} [cntx] - Optional element to use rather
 * than the current JS context's document object
 * @return {Promise<boolean>}
 */
export async function findAllMediaElementsAndPlay(cntx) {
  if (__Played__ == null) __Played__ = Symbol(uuidv4());
  const mediaElems = qsa(__MediaElementSelector__, cntx);
  if (mediaElems.length === 0) return false;
  const proms = [];
  let shouldWait = false;
  for (var i = 0; i < mediaElems.length; i++) {
    if (!mediaElems[i][__Played__]) {
      proms.push(noExceptPlayMediaElement(mediaElems[i]));
      Object.defineProperty(mediaElems[i], __Played__, {
        value: true,
        enumerable: false,
      });
      mediaElems[i][__Played__] = true;
      shouldWait = true;
    }
  }
  await Promise.all(proms);
  return shouldWait;
}