You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

JitsiStreamBlurEffect.js 5.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. import { drawBokehEffect } from '@tensorflow-models/body-pix';
  2. import {
  3. CLEAR_INTERVAL,
  4. INTERVAL_TIMEOUT,
  5. SET_INTERVAL,
  6. timerWorkerScript
  7. } from './TimerWorker';
  8. /**
  9. * Represents a modified MediaStream that adds blur to video background.
  10. * <tt>JitsiStreamBlurEffect</tt> does the processing of the original
  11. * video stream.
  12. */
  13. export default class JitsiStreamBlurEffect {
  14. /**
  15. * Represents a modified video MediaStream track.
  16. *
  17. * @class
  18. * @param {BodyPix} bpModel - BodyPix model.
  19. */
  20. constructor(bpModel) {
  21. this._bpModel = bpModel;
  22. // Bind event handler so it is only bound once for every instance.
  23. this._onMaskFrameTimer = this._onMaskFrameTimer.bind(this);
  24. this._onVideoFrameTimer = this._onVideoFrameTimer.bind(this);
  25. this._outputCanvasElement = document.createElement('canvas');
  26. this._maskCanvasElement = document.createElement('canvas');
  27. this._inputVideoElement = document.createElement('video');
  28. this._videoFrameTimerWorker = new Worker(timerWorkerScript);
  29. this._maskFrameTimerWorker = new Worker(timerWorkerScript);
  30. this._videoFrameTimerWorker.onmessage = this._onVideoFrameTimer;
  31. this._maskFrameTimerWorker.onmessage = this._onMaskFrameTimer;
  32. }
  33. /**
  34. * EventHandler onmessage for the videoFrameTimerWorker WebWorker.
  35. *
  36. * @private
  37. * @param {EventHandler} response - The onmessage EventHandler parameter.
  38. * @returns {void}
  39. */
  40. _onVideoFrameTimer(response) {
  41. if (response.data.id === INTERVAL_TIMEOUT) {
  42. this._renderVideo();
  43. }
  44. }
  45. /**
  46. * EventHandler onmessage for the maskFrameTimerWorker WebWorker.
  47. *
  48. * @private
  49. * @param {EventHandler} response - The onmessage EventHandler parameter.
  50. * @returns {void}
  51. */
  52. _onMaskFrameTimer(response) {
  53. if (response.data.id === INTERVAL_TIMEOUT) {
  54. this._renderMask();
  55. }
  56. }
  57. /**
  58. * Starts loop to capture video frame and render the segmentation mask.
  59. *
  60. * @param {MediaStream} stream - Stream to be used for processing.
  61. * @returns {MediaStream} - The stream with the applied effect.
  62. */
  63. startEffect(stream) {
  64. const firstVideoTrack = stream.getVideoTracks()[0];
  65. const { height, frameRate, width }
  66. = firstVideoTrack.getSettings ? firstVideoTrack.getSettings() : firstVideoTrack.getConstraints();
  67. this._frameRate = frameRate;
  68. this._height = height;
  69. this._width = width;
  70. this._outputCanvasElement.width = width;
  71. this._outputCanvasElement.height = height;
  72. this._maskCanvasElement.width = width;
  73. this._maskCanvasElement.height = height;
  74. this._maskCanvasContext = this._maskCanvasElement.getContext('2d');
  75. this._inputVideoElement.width = width;
  76. this._inputVideoElement.height = height;
  77. this._inputVideoElement.autoplay = true;
  78. this._inputVideoElement.srcObject = stream;
  79. this._videoFrameTimerWorker.postMessage({
  80. id: SET_INTERVAL,
  81. timeMs: 1000 / this._frameRate
  82. });
  83. this._maskFrameTimerWorker.postMessage({
  84. id: SET_INTERVAL,
  85. timeMs: 200
  86. });
  87. return this._outputCanvasElement.captureStream(this._frameRate);
  88. }
  89. /**
  90. * Stops the capture and render loop.
  91. *
  92. * @returns {void}
  93. */
  94. stopEffect() {
  95. this._videoFrameTimerWorker.postMessage({
  96. id: CLEAR_INTERVAL
  97. });
  98. this._maskFrameTimerWorker.postMessage({
  99. id: CLEAR_INTERVAL
  100. });
  101. }
  102. /**
  103. * Loop function to render the video frame input and draw blur effect.
  104. *
  105. * @private
  106. * @returns {void}
  107. */
  108. _renderVideo() {
  109. this._maskCanvasContext.drawImage(this._inputVideoElement, 0, 0, this._width, this._height);
  110. if (this._segmentationData) {
  111. drawBokehEffect(
  112. this._outputCanvasElement,
  113. this._inputVideoElement,
  114. this._segmentationData,
  115. 7, // Constant for background blur, integer values between 0-20
  116. 7 // Constant for edge blur, integer values between 0-20
  117. );
  118. }
  119. }
  120. /**
  121. * Loop function to render the background mask.
  122. *
  123. * @private
  124. * @returns {void}
  125. */
  126. _renderMask() {
  127. this._bpModel.estimatePersonSegmentation(
  128. this._maskCanvasElement,
  129. 32, // Chose 32 for better performance
  130. 0.75 // Represents probability that a pixel belongs to a person
  131. )
  132. .then(value => {
  133. this._segmentationData = value;
  134. });
  135. }
  136. /**
  137. * Checks if the local track supports this effect.
  138. *
  139. * @param {JitsiLocalTrack} jitsiLocalTrack - Track to apply effect.
  140. * @returns {boolean} - Returns true if this effect can run on the specified track
  141. * false otherwise.
  142. */
  143. isEnabled(jitsiLocalTrack) {
  144. return jitsiLocalTrack.isVideoTrack();
  145. }
  146. }