UNPKG

mediastream-video-resizer

Version:
221 lines 8.39 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.VideoResizer = void 0; // Video resizer class class VideoResizer { constructor() { this.resizers = {}; this.resizerTemplate = { video: null, canvas: null, context: null, inputStream: null, outputStream: null, interval: null, }; } /** Resize the media stream * * @param {MediaStream} inputStream - original media stream * @param {string} key - ID or name of the resize process, used only for stopping the existing resizer * @param {number} width - output width * @param {number} height - output height * @param {number} fps - output fps * * @returns {MediaSteam} */ start(inputStream, key, width, height, fps) { const self = this; // Check if any video track exists if (!inputStream || !inputStream.getVideoTracks) { console.warn('Incorrect mediaStream is received', inputStream); return inputStream; } const videoTrack = inputStream.getVideoTracks().find((track) => track.enabled); if (!videoTrack || !videoTrack.enabled) { console.warn('There is no video tracks in the input MediaStream'); return inputStream; } // Get the Video Resizer Item let resizer = self.resizers[key]; if (!resizer) { self.resizers[key] = resizer = Object.assign({}, self.resizerTemplate); } // Create canvas, if missing if (!resizer.canvas) { resizer.canvas = document.createElement('canvas'); resizer.canvas.style.position = 'fixed'; resizer.canvas.style.top = '1px'; resizer.canvas.style.left = '1px'; resizer.canvas.style.width = '1px'; resizer.canvas.style.height = '1px'; document.body.appendChild(resizer.canvas); } // Create rendering context if (!resizer.context) { resizer.context = null; } resizer.context = resizer.canvas.getContext('2d', { alpha: false, desynchronized: true, antialias: false, powerPreference: 'low-power', preserveDrawingBuffer: true, }); // Get video dimensions - calculate output size and offsets const dimensions = self.privateGetResizeDimensions(inputStream, width, height); // No height? Calculate it automatically if (!height) { resizer.canvas.height = dimensions.displayHeight; } else { resizer.canvas.height = height; } resizer.canvas.width = width; // No video element? Create it if (!resizer.video) { resizer.video = document.createElement('video'); resizer.video.autoplay = true; resizer.video.playsinline = true; resizer.video.muted = true; resizer.video.style.position = 'fixed'; resizer.video.style.top = '1px'; resizer.video.style.left = '1px'; resizer.video.style.width = '1px'; resizer.video.style.height = '1px'; document.body.appendChild(resizer.video); } resizer.video.srcObject = inputStream; // Draw the first frame resizer.context.drawImage(resizer.video, dimensions.left, dimensions.top, dimensions.displayWidth, dimensions.displayHeight); // Start the drawing process self.privateResizeSetInterval(resizer, dimensions, fps); // Stop the output stream, if present if (resizer.outputStream) { self.privateStopMediaStream(resizer.outputStream); } resizer.inputStream = inputStream; // Generate the output stream resizer.outputStream = resizer.canvas.captureStream(); inputStream.getAudioTracks().forEach((track) => { resizer.outputStream.addTrack(track); }); return resizer.outputStream; } /** * Stop the resizer * * @param {string} key - ID of the resize process */ stop(key) { const self = this; const resizer = self.resizers[key]; if (!resizer) { return; } // Stop drawing process if (resizer.interval) { clearInterval(resizer.interval); } // Terminate the output stream if (resizer.outputStream) { self.privateStopMediaStream(resizer.outputStream); } // Remove html elements try { if (resizer.video) { document.body.removeChild(resizer.video); } if (resizer.canvas) { document.body.removeChild(resizer.canvas); } } catch (e) { } delete self.resizers[key]; } /** * Resizer interval configuration * * @param {VideoResizerItem} resizer * @param {VideoDimensions} dimensions * @param {number} fps - output video fps */ privateResizeSetInterval(resizer, dimensions, fps) { const self = this; if (resizer.interval) { clearInterval(resizer.interval); } resizer.interval = setInterval(() => { if (!resizer.context) { if (resizer.interval) { clearInterval(resizer.interval); } return; } resizer.context.drawImage(resizer.video, 0, 0, dimensions.originWidth, dimensions.originHeight, dimensions.left, dimensions.top, dimensions.displayWidth, dimensions.displayHeight); // Each second re-calculate the dimensions - it's inpotrant as the input video // track constrains can be changed (for example, user turned his smartphone) without any notification if (Math.round(Math.random() * fps) === 1) { dimensions = self.privateGetResizeDimensions(resizer.inputStream, dimensions.requiredWidth, dimensions.requiredHeight); } }, Math.round(950 / fps)); } /** * Get video dimensions * * @param {MediaStream} inputStream - input media stream * @param {number} requiredWidth - output video width * @param {number} requiredHeight - output video height * * @returns {VideoDimensions} */ privateGetResizeDimensions(inputStream, requiredWidth, requiredHeight) { // Get video tracks const tracks = inputStream.getVideoTracks(); // Actually we process only first video track const trackSettings = tracks[0].getSettings(); // Get the track dimensions const trackWidth = trackSettings.width; const trackHeight = trackSettings.height; // Calculate output dimensions and offsets let displayWidth, displayHeight, deltaX, deltaY; if (requiredHeight && trackHeight < trackWidth) { displayWidth = (trackWidth * requiredHeight) / trackHeight; displayHeight = requiredHeight; deltaX = (requiredWidth - displayWidth) / 2; deltaY = (requiredHeight - displayHeight) / 2; } else { displayWidth = requiredWidth; displayHeight = (trackHeight * requiredWidth) / trackWidth; deltaX = (requiredWidth - displayWidth) / 2; deltaY = requiredHeight ? (requiredHeight - displayHeight) / 2 : 0; } return { requiredWidth, requiredHeight, originWidth: Math.round(trackWidth), originHeight: Math.round(trackHeight), displayWidth: Math.round(displayWidth), displayHeight: Math.round(displayHeight), top: Math.round(deltaY), left: Math.round(deltaX), }; } /** * Stop the media stream correctly * * @param {MediaStream} stream */ privateStopMediaStream(stream) { if (!stream) { return; } stream.getTracks().forEach((track) => { track.stop(); stream.removeTrack(track); }); } } exports.VideoResizer = VideoResizer; //# sourceMappingURL=videoresize.js.map