chrome-video-capturestream-fix
Version:
Fixes possible crashes caused by calling captureStream on a HTMLVideoElement with its src being a MediaSource in chrome.
97 lines (74 loc) • 3.19 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(factory((global.VideoCaptureStreamFix = {})));
}(this, (function (exports) { 'use strict';
const DEFAULT_FRAMERATE = 24;
function captureStream(videoEl, streamOptions) {
const cvResult = createVideoCanvas(videoEl, streamOptions);
const framerate = streamOptions.framerate || DEFAULT_FRAMERATE;
const v_stream = cvResult.canvas.captureStream(framerate);
const a_stream = getAudioStream(videoEl);
const a_track = a_stream.getAudioTracks()[0];
if (a_track) {
v_stream.addTrack(a_track);
}
// Optimization methods but also provides feature to pause the video frame.
// It's necessary to call stopVideo when finished to clean up or it will
// otherwise continue rendering (to canvas) in the bg and prevent gc.
v_stream.startVideo = cvResult.startUpdating;
v_stream.stopVideo = cvResult.stopUpdating;
return v_stream;
}
function createVideoCanvas(videoEl, options = {}) {
const width = options.width || videoEl.videoWidth;
const height = options.height || videoEl.videoHeight;
const canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext("2d");
const framerate = options.framerate || DEFAULT_FRAMERATE;
function update() {
ctx.drawImage(videoEl, 0, 0, width, height);
}
let stopId;
function stopUpdating() {
clearInterval(stopId);
}
function startUpdating() {
stopId = setInterval(update, 1000 / framerate);
}
// Start automatically
startUpdating();
return { videoEl, canvas, stopUpdating, startUpdating };
}
// Audio streams need to be cached because createMediaElementSource() can only be called once per videoEl
const audioStreamRefs = new WeakMap();
// Issue/downside that volume depends on video element
function getAudioStream(videoEl) {
let stream = audioStreamRefs.get(videoEl);
if (stream) return stream.clone();
const audioCtx = new AudioContext();
const source = audioCtx.createMediaElementSource(videoEl);
const destination = audioCtx.createMediaStreamDestination();
// Pipe video sounds to stream
source.connect(destination);
// Reconnect to audio output (so client hears sound)
source.connect(audioCtx.destination);
stream = destination.stream;
audioStreamRefs.set(videoEl, stream);
return stream.clone();
}
function polyfill() {
HTMLVideoElement.prototype.captureStream = function(streamOptions = {}) {
return captureStream(this, streamOptions);
};
}
exports.polyfill = polyfill;
exports.captureStream = captureStream;
exports.DEFAULT_FRAMERATE = DEFAULT_FRAMERATE;
exports.createVideoCanvas = createVideoCanvas;
exports.getAudioStream = getAudioStream;
Object.defineProperty(exports, '__esModule', { value: true });
})));
//# sourceMappingURL=videoCaptureStreamFix.umd.js.map