123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161 |
- // @flow
-
- import {
- CLEAR_INTERVAL,
- INTERVAL_TIMEOUT,
- SET_INTERVAL,
- timerWorkerScript
- } from './TimeWorker';
-
- /**
- * Represents a modified MediaStream that adds video as pip on a desktop stream.
- * <tt>JitsiStreamPresenterEffect</tt> does the processing of the original
- * desktop stream.
- */
- export default class JitsiStreamPresenterEffect {
- _canvas: HTMLCanvasElement;
- _ctx: CanvasRenderingContext2D;
- _desktopElement: HTMLVideoElement;
- _desktopStream: MediaStream;
- _frameRate: number;
- _onVideoFrameTimer: Function;
- _onVideoFrameTimerWorker: Function;
- _renderVideo: Function;
- _videoFrameTimerWorker: Worker;
- _videoElement: HTMLVideoElement;
- isEnabled: Function;
- startEffect: Function;
- stopEffect: Function;
-
- /**
- * Represents a modified MediaStream that adds a camera track at the
- * bottom right corner of the desktop track using a HTML canvas.
- * <tt>JitsiStreamPresenterEffect</tt> does the processing of the original
- * video stream.
- *
- * @param {MediaStream} videoStream - The video stream which is user for
- * creating the canvas.
- */
- constructor(videoStream: MediaStream) {
- const videoDiv = document.createElement('div');
- const firstVideoTrack = videoStream.getVideoTracks()[0];
- const { height, width, frameRate } = firstVideoTrack.getSettings() ?? firstVideoTrack.getConstraints();
-
- this._canvas = document.createElement('canvas');
- this._ctx = this._canvas.getContext('2d');
-
- if (document.body !== null) {
- document.body.appendChild(this._canvas);
- }
- this._desktopElement = document.createElement('video');
- this._videoElement = document.createElement('video');
- videoDiv.appendChild(this._videoElement);
- videoDiv.appendChild(this._desktopElement);
- if (document.body !== null) {
- document.body.appendChild(videoDiv);
- }
-
- // Set the video element properties
- this._frameRate = parseInt(frameRate, 10);
- this._videoElement.width = parseInt(width, 10);
- this._videoElement.height = parseInt(height, 10);
- this._videoElement.autoplay = true;
- this._videoElement.srcObject = videoStream;
-
- // set the style attribute of the div to make it invisible
- videoDiv.style.display = 'none';
-
- // Bind event handler so it is only bound once for every instance.
- this._onVideoFrameTimer = this._onVideoFrameTimer.bind(this);
- this._videoFrameTimerWorker = new Worker(timerWorkerScript);
- this._videoFrameTimerWorker.onmessage = this._onVideoFrameTimer;
- }
-
- /**
- * EventHandler onmessage for the videoFrameTimerWorker WebWorker.
- *
- * @private
- * @param {EventHandler} response - The onmessage EventHandler parameter.
- * @returns {void}
- */
- _onVideoFrameTimer(response) {
- if (response.data.id === INTERVAL_TIMEOUT) {
- this._renderVideo();
- }
- }
-
- /**
- * Loop function to render the video frame input and draw presenter effect.
- *
- * @private
- * @returns {void}
- */
- _renderVideo() {
- // adjust the canvas width/height on every frame incase the window has been resized.
- const [ track ] = this._desktopStream.getVideoTracks();
- const { height, width } = track.getSettings() ?? track.getConstraints();
-
- this._canvas.width = parseInt(width, 10);
- this._canvas.height = parseInt(height, 10);
- this._ctx.drawImage(this._desktopElement, 0, 0, this._canvas.width, this._canvas.height);
- this._ctx.drawImage(this._videoElement, this._canvas.width - this._videoElement.width, this._canvas.height
- - this._videoElement.height, this._videoElement.width, this._videoElement.height);
-
- // draw a border around the video element.
- this._ctx.beginPath();
- this._ctx.lineWidth = 2;
- this._ctx.strokeStyle = '#A9A9A9'; // dark grey
- this._ctx.rect(this._canvas.width - this._videoElement.width, this._canvas.height - this._videoElement.height,
- this._videoElement.width, this._videoElement.height);
- this._ctx.stroke();
- }
-
- /**
- * Checks if the local track supports this effect.
- *
- * @param {JitsiLocalTrack} jitsiLocalTrack - Track to apply effect.
- * @returns {boolean} - Returns true if this effect can run on the
- * specified track, false otherwise.
- */
- isEnabled(jitsiLocalTrack: Object) {
- return jitsiLocalTrack.isVideoTrack() && jitsiLocalTrack.videoType === 'desktop';
- }
-
- /**
- * Starts loop to capture video frame and render presenter effect.
- *
- * @param {MediaStream} desktopStream - Stream to be used for processing.
- * @returns {MediaStream} - The stream with the applied effect.
- */
- startEffect(desktopStream: MediaStream) {
- const firstVideoTrack = desktopStream.getVideoTracks()[0];
- const { height, width } = firstVideoTrack.getSettings() ?? firstVideoTrack.getConstraints();
-
- // set the desktop element properties.
- this._desktopStream = desktopStream;
- this._desktopElement.width = parseInt(width, 10);
- this._desktopElement.height = parseInt(height, 10);
- this._desktopElement.autoplay = true;
- this._desktopElement.srcObject = desktopStream;
- this._canvas.width = parseInt(width, 10);
- this._canvas.height = parseInt(height, 10);
- this._videoFrameTimerWorker.postMessage({
- id: SET_INTERVAL,
- timeMs: 1000 / this._frameRate
- });
-
- return this._canvas.captureStream(this._frameRate);
- }
-
- /**
- * Stops the capture and render loop.
- *
- * @returns {void}
- */
- stopEffect() {
- this._videoFrameTimerWorker.postMessage({
- id: CLEAR_INTERVAL
- });
- }
-
- }
|