Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/spreed.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Calviño Sánchez <danxuliu@gmail.com>2022-07-30 14:15:12 +0300
committerbackportbot-nextcloud[bot] <backportbot-nextcloud[bot]@users.noreply.github.com>2022-08-11 11:42:34 +0300
commitd68799b9cbae8f9ddd4077ede4f8ecb64dd26e16 (patch)
tree3115846df0a2a58cb14d71d876b181c93ac197b2
parent336736c9fb15676ebd94177bf92cd202c6a52d2e (diff)
Enforce zero-information-content video tracks
This ensures that other participants will receive a black video track when no visible video should be sent, even if the browser does not properly update a previously sent track when disabled or stopped. Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
-rw-r--r--src/utils/media/pipeline/BlackVideoEnforcer.js174
-rw-r--r--src/utils/media/pipeline/BlackVideoEnforcer.spec.js1445
-rw-r--r--src/utils/webrtc/simplewebrtc/localmedia.js7
3 files changed, 1625 insertions, 1 deletions
diff --git a/src/utils/media/pipeline/BlackVideoEnforcer.js b/src/utils/media/pipeline/BlackVideoEnforcer.js
new file mode 100644
index 000000000..529fc4420
--- /dev/null
+++ b/src/utils/media/pipeline/BlackVideoEnforcer.js
@@ -0,0 +1,174 @@
+/**
+ *
+ * @copyright Copyright (c) 2022, Daniel Calviño Sánchez (danxuliu@gmail.com)
+ *
+ * @license AGPL-3.0-or-later
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+import TrackSinkSource from './TrackSinkSource.js'
+
+/**
+ * Processor node to enforce zero-information-content on a disabled or ended
+ * video track.
+ *
+ * A single input track slot with the default id is accepted. A single output
+ * track slot with the default id is provided. The input track must be a video
+ * track. The output track will be a video track.
+ *
+ * When the input track is enabled it is just bypassed to the output. However,
+ * if the input track is disabled or stopped a black video track is generated
+ * and set as the output instead; the black video track will be initially
+ * enabled and later automatically disabled, unless anything changes in the
+ * input and causes a different output track, even another black video track, to
+ * be set (a previous black video track is not reused, a new one is always
+ * generated). If the input track is removed the black video will be initially
+ * set as the output too, but then it will be also removed instead of disabled.
+ * If the input track is removed when the output is already a black video track
+ * a new black video track will not be set, the current one will be removed as
+ * soon as it would have been disabled.
+ *
+ * --------------------
+ * | |
+ * ---> | BlackVideoEnforcer | --->
+ * | |
+ * --------------------
+ */
+export default class BlackVideoEnforcer extends TrackSinkSource {
+
+ constructor() {
+ super()
+
+ this._addInputTrackSlot()
+ this._addOutputTrackSlot()
+ }
+
+ _handleInputTrack(trackId, newTrack, oldTrack) {
+ if (!newTrack && !oldTrack) {
+ return
+ }
+
+ if (oldTrack && this._startBlackVideoWhenTrackEndedHandler) {
+ oldTrack.removeEventListener('ended', this._startBlackVideoWhenTrackEndedHandler)
+ this._startBlackVideoWhenTrackEndedHandler = null
+ }
+
+ if (!newTrack && this._disableOrRemoveOutputTrackTimeout) {
+ return
+ }
+
+ if (!newTrack && this._outputStream) {
+ this._stopBlackVideo()
+ this._setOutputTrack('default', null)
+
+ return
+ }
+
+ if (newTrack) {
+ this._disableRemoveTrackWhenEnded(newTrack)
+
+ this._startBlackVideoWhenTrackEndedHandler = () => {
+ this._startBlackVideo(newTrack.getSettings())
+ }
+ newTrack.addEventListener('ended', this._startBlackVideoWhenTrackEndedHandler)
+ }
+
+ this._stopBlackVideo()
+
+ if (newTrack && newTrack.enabled) {
+ this._setOutputTrack('default', this.getInputTrack())
+
+ return
+ }
+
+ const trackSettings = newTrack ? newTrack.getSettings() : oldTrack?.getSettings()
+ this._startBlackVideo(trackSettings)
+ }
+
+ _handleInputTrackEnabled(trackId, enabled) {
+ // Same enabled state as before, nothing to do
+ if ((enabled && !this._outputStream)
+ || (!enabled && this._outputStream)) {
+ return
+ }
+
+ if (enabled) {
+ this._stopBlackVideo()
+
+ this._setOutputTrack('default', this.getInputTrack())
+
+ return
+ }
+
+ if (this._outputStream) {
+ this._setOutputTrackEnabled('default', false)
+
+ return
+ }
+
+ this._startBlackVideo(this.getInputTrack().getSettings())
+ }
+
+ _startBlackVideo(trackSettings) {
+ if (this._outputStream) {
+ return
+ }
+
+ const { width, height } = trackSettings ?? { width: 640, height: 480 }
+
+ const outputCanvasElement = document.createElement('canvas')
+ outputCanvasElement.width = parseInt(width, 10)
+ outputCanvasElement.height = parseInt(height, 10)
+ const outputCanvasContext = outputCanvasElement.getContext('2d')
+
+ this._outputStream = outputCanvasElement.captureStream()
+
+ outputCanvasContext.fillStyle = 'black'
+ outputCanvasContext.fillRect(0, 0, outputCanvasElement.width, outputCanvasElement.height)
+
+ this._setOutputTrack('default', this._outputStream.getVideoTracks()[0])
+
+ this._disableOrRemoveOutputTrackTimeout = setTimeout(() => {
+ clearTimeout(this._disableOrRemoveOutputTrackTimeout)
+ this._disableOrRemoveOutputTrackTimeout = null
+
+ if (this.getInputTrack()) {
+ this._setOutputTrackEnabled('default', false)
+ } else {
+ this._stopBlackVideo()
+ this._setOutputTrack('default', null)
+ }
+ }, 1000)
+ }
+
+ _stopBlackVideo() {
+ if (!this._outputStream) {
+ return
+ }
+
+ clearTimeout(this._disableOrRemoveOutputTrackTimeout)
+ this._disableOrRemoveOutputTrackTimeout = null
+
+ this._outputStream.getTracks().forEach(track => {
+ this._disableRemoveTrackWhenEnded(track)
+
+ track.stop()
+ })
+
+ this._outputStream = null
+ }
+
+}
diff --git a/src/utils/media/pipeline/BlackVideoEnforcer.spec.js b/src/utils/media/pipeline/BlackVideoEnforcer.spec.js
new file mode 100644
index 000000000..1ff891cc0
--- /dev/null
+++ b/src/utils/media/pipeline/BlackVideoEnforcer.spec.js
@@ -0,0 +1,1445 @@
+/**
+ *
+ * @copyright Copyright (c) 2022, Daniel Calviño Sánchez (danxuliu@gmail.com)
+ *
+ * @license AGPL-3.0-or-later
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+import BlackVideoEnforcer from './BlackVideoEnforcer.js'
+
+/**
+ * Helper function to create MediaStreamTrack mocks with just the attributes and
+ * methods used by BlackVideoEnforcer.
+ *
+ * @param {string} id the ID of the track
+ */
+function newMediaStreamTrackMock(id) {
+ /**
+ * MediaStreamTrackMock constructor.
+ */
+ function MediaStreamTrackMock() {
+ this._endedEventHandlers = []
+ this._width = 720
+ this._height = 540
+ this.id = id
+ this.enabled = true
+ this.addEventListener = jest.fn((eventName, eventHandler) => {
+ if (eventName !== 'ended') {
+ return
+ }
+
+ this._endedEventHandlers.push(eventHandler)
+ })
+ this.removeEventListener = jest.fn((eventName, eventHandler) => {
+ if (eventName !== 'ended') {
+ return
+ }
+
+ const index = this._endedEventHandlers.indexOf(eventHandler)
+ if (index !== -1) {
+ this._endedEventHandlers.splice(index, 1)
+ }
+ })
+ this.stop = jest.fn(() => {
+ for (let i = 0; i < this._endedEventHandlers.length; i++) {
+ const handler = this._endedEventHandlers[i]
+ handler.apply(handler)
+ }
+ })
+ this.getSettings = jest.fn(() => {
+ return {
+ width: this._width,
+ height: this._height,
+ }
+ })
+ }
+ return new MediaStreamTrackMock()
+}
+
+describe('BlackVideoEnforcer', () => {
+ let blackVideoEnforcer
+ let outputTrackSetHandler
+ let outputTrackEnabledHandler
+ let expectedTrackEnabledStateInOutputTrackSetEvent
+ let blackVideoTrackCount
+ let blackVideoTracks
+
+ beforeAll(() => {
+ const originalCreateElement = document.createElement
+ jest.spyOn(document, 'createElement').mockImplementation((tagName, options) => {
+ if (tagName !== 'canvas') {
+ return originalCreateElement(tagName, options)
+ }
+
+ return new function() {
+ this.getContext = jest.fn(() => {
+ return {
+ fillRect: jest.fn(),
+ }
+ })
+ this.captureStream = jest.fn(() => {
+ const blackVideoTrackLocal = newMediaStreamTrackMock('blackVideoTrack' + blackVideoTrackCount)
+ blackVideoTracks[blackVideoTrackCount] = blackVideoTrackLocal
+ blackVideoTrackCount++
+
+ blackVideoTrackLocal._width = this.width
+ blackVideoTrackLocal._height = this.height
+
+ return {
+ getVideoTracks: jest.fn(() => {
+ return [blackVideoTrackLocal]
+ }),
+ getTracks: jest.fn(() => {
+ return [blackVideoTrackLocal]
+ }),
+ }
+ })
+ }()
+ })
+ })
+
+ beforeEach(() => {
+ jest.useFakeTimers()
+
+ blackVideoTrackCount = 0
+ blackVideoTracks = []
+
+ blackVideoEnforcer = new BlackVideoEnforcer()
+
+ expectedTrackEnabledStateInOutputTrackSetEvent = undefined
+
+ outputTrackSetHandler = jest.fn((blackVideoEnforcer, trackId, track) => {
+ if (expectedTrackEnabledStateInOutputTrackSetEvent !== undefined) {
+ expect(track.enabled).toBe(expectedTrackEnabledStateInOutputTrackSetEvent)
+ }
+ })
+ outputTrackEnabledHandler = jest.fn()
+
+ blackVideoEnforcer.on('outputTrackSet', outputTrackSetHandler)
+ blackVideoEnforcer.on('outputTrackEnabled', outputTrackEnabledHandler)
+ })
+
+ afterEach(() => {
+ clearTimeout(blackVideoEnforcer._disableOrRemoveOutputTrackTimeout)
+ })
+
+ afterAll(() => {
+ jest.restoreAllMocks()
+ })
+
+ const DISABLE_OR_REMOVE_TIMEOUT = 1000
+
+ const STOPPED = true
+
+ /**
+ * Checks that a black video track has the expected attributes.
+ *
+ * @param {number} index the index of the black video track to check.
+ * @param {number} width the expected width of the black video track.
+ * @param {number} height the expected height of the black video track.
+ * @param {boolean} stopped whether the black video track is expected to
+ * have been stopped already or not.
+ */
+ function assertBlackVideoTrack(index, width, height, stopped = false) {
+ expect(blackVideoTracks[index].getSettings().width).toBe(width)
+ expect(blackVideoTracks[index].getSettings().height).toBe(height)
+ expect(blackVideoTracks[index].stop).toHaveBeenCalledTimes(stopped ? 1 : 0)
+ }
+
+ describe('set input track', () => {
+ test('sets input track as its output track when setting enabled input track', () => {
+ const inputTrack = newMediaStreamTrackMock('input')
+
+ expectedTrackEnabledStateInOutputTrackSetEvent = true
+
+ blackVideoEnforcer._setInputTrack('default', inputTrack)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(1)
+ expect(outputTrackSetHandler).toHaveBeenCalledWith(blackVideoEnforcer, 'default', inputTrack)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(0)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT * 5)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(0)
+ })
+
+ test('sets black video track as its output track when setting disabled input track', () => {
+ const inputTrack = newMediaStreamTrackMock('input')
+
+ expectedTrackEnabledStateInOutputTrackSetEvent = true
+
+ inputTrack.enabled = false
+ blackVideoEnforcer._setInputTrack('default', inputTrack)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(1)
+ expect(outputTrackSetHandler).toHaveBeenCalledWith(blackVideoEnforcer, 'default', blackVideoTracks[0])
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(1)
+ assertBlackVideoTrack(0, 720, 540)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT - 1)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+
+ jest.advanceTimersByTime(1)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(1)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledWith(blackVideoEnforcer, 'default', false)
+ expect(blackVideoTrackCount).toBe(1)
+ assertBlackVideoTrack(0, 720, 540)
+ })
+ })
+
+ describe('enable/disable input track', () => {
+ test('sets black video track as its output track if input track is disabled', () => {
+ const inputTrack = newMediaStreamTrackMock('input')
+
+ blackVideoEnforcer._setInputTrack('default', inputTrack)
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT / 2)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ expectedTrackEnabledStateInOutputTrackSetEvent = true
+
+ inputTrack.enabled = false
+ blackVideoEnforcer._setInputTrackEnabled('default', false)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(1)
+ expect(outputTrackSetHandler).toHaveBeenCalledWith(blackVideoEnforcer, 'default', blackVideoTracks[0])
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(1)
+ assertBlackVideoTrack(0, 720, 540)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT - 1)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+
+ jest.advanceTimersByTime(1)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(1)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledWith(blackVideoEnforcer, 'default', false)
+ expect(blackVideoTrackCount).toBe(1)
+ assertBlackVideoTrack(0, 720, 540)
+ })
+
+ test('sets input track as its output track if input track is enabled', () => {
+ const inputTrack = newMediaStreamTrackMock('input')
+
+ inputTrack.enabled = false
+ blackVideoEnforcer._setInputTrack('default', inputTrack)
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT / 2)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ expectedTrackEnabledStateInOutputTrackSetEvent = true
+
+ inputTrack.enabled = true
+ blackVideoEnforcer._setInputTrackEnabled('default', true)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(1)
+ expect(outputTrackSetHandler).toHaveBeenCalledWith(blackVideoEnforcer, 'default', inputTrack)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(1)
+ assertBlackVideoTrack(0, 720, 540, STOPPED)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT * 5)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(1)
+ assertBlackVideoTrack(0, 720, 540, STOPPED)
+ })
+
+ test('sets input track as its output track if input track is later enabled', () => {
+ const inputTrack = newMediaStreamTrackMock('input')
+
+ inputTrack.enabled = false
+ blackVideoEnforcer._setInputTrack('default', inputTrack)
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT * 2)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ expectedTrackEnabledStateInOutputTrackSetEvent = true
+
+ inputTrack.enabled = true
+ blackVideoEnforcer._setInputTrackEnabled('default', true)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(1)
+ expect(outputTrackSetHandler).toHaveBeenCalledWith(blackVideoEnforcer, 'default', inputTrack)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(1)
+ assertBlackVideoTrack(0, 720, 540, STOPPED)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT * 5)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(1)
+ assertBlackVideoTrack(0, 720, 540, STOPPED)
+ })
+
+ test('does nothing if input track is enabled again', () => {
+ const inputTrack = newMediaStreamTrackMock('input')
+
+ blackVideoEnforcer._setInputTrack('default', inputTrack)
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT / 2)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ blackVideoEnforcer._setInputTrackEnabled('default', true)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(0)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT * 5)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(0)
+ })
+
+ test('does nothing if input track is disabled again', () => {
+ const inputTrack = newMediaStreamTrackMock('input')
+
+ inputTrack.enabled = false
+ blackVideoEnforcer._setInputTrack('default', inputTrack)
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT / 2)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ blackVideoEnforcer._setInputTrackEnabled('default', false)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(1)
+ assertBlackVideoTrack(0, 720, 540)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT / 2 - 1)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+
+ jest.advanceTimersByTime(1)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(1)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledWith(blackVideoEnforcer, 'default', false)
+ expect(blackVideoTrackCount).toBe(1)
+ assertBlackVideoTrack(0, 720, 540)
+ })
+ })
+
+ describe('remove input track', () => {
+ test('sets black video track as its output track and later removes output track when removing enabled input track', () => {
+ const inputTrack = newMediaStreamTrackMock('input')
+
+ blackVideoEnforcer._setInputTrack('default', inputTrack)
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT / 2)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ expectedTrackEnabledStateInOutputTrackSetEvent = true
+
+ blackVideoEnforcer._setInputTrack('default', null)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(1)
+ expect(outputTrackSetHandler).toHaveBeenCalledWith(blackVideoEnforcer, 'default', blackVideoTracks[0])
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(1)
+ assertBlackVideoTrack(0, 720, 540)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT - 1)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTracks[0].stop).toHaveBeenCalledTimes(0)
+
+ expectedTrackEnabledStateInOutputTrackSetEvent = undefined
+
+ jest.advanceTimersByTime(1)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(1)
+ expect(outputTrackSetHandler).toHaveBeenCalledWith(blackVideoEnforcer, 'default', null)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(1)
+ assertBlackVideoTrack(0, 720, 540, STOPPED)
+ })
+
+ test('sets input track as its output track when setting enabled input track after removing enabled input track', () => {
+ const inputTrack = newMediaStreamTrackMock('input')
+ const inputTrack2 = newMediaStreamTrackMock('input2')
+
+ blackVideoEnforcer._setInputTrack('default', inputTrack)
+
+ blackVideoEnforcer._setInputTrack('default', null)
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT / 2)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ expectedTrackEnabledStateInOutputTrackSetEvent = true
+
+ inputTrack2._width = 320
+ inputTrack2._height = 180
+ blackVideoEnforcer._setInputTrack('default', inputTrack2)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(1)
+ expect(outputTrackSetHandler).toHaveBeenCalledWith(blackVideoEnforcer, 'default', inputTrack2)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(1)
+ assertBlackVideoTrack(0, 720, 540, STOPPED)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT * 5)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(1)
+ assertBlackVideoTrack(0, 720, 540, STOPPED)
+ })
+
+ test('sets black video track as its output track when setting disabled input track after removing enabled input track', () => {
+ const inputTrack = newMediaStreamTrackMock('input')
+ const inputTrack2 = newMediaStreamTrackMock('input2')
+
+ blackVideoEnforcer._setInputTrack('default', inputTrack)
+
+ blackVideoEnforcer._setInputTrack('default', null)
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT / 2)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ expectedTrackEnabledStateInOutputTrackSetEvent = true
+
+ inputTrack2.enabled = false
+ inputTrack2._width = 320
+ inputTrack2._height = 180
+ blackVideoEnforcer._setInputTrack('default', inputTrack2)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(1)
+ expect(outputTrackSetHandler).toHaveBeenCalledWith(blackVideoEnforcer, 'default', blackVideoTracks[1])
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(2)
+ assertBlackVideoTrack(0, 720, 540, STOPPED)
+ assertBlackVideoTrack(1, 320, 180)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT - 1)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+
+ jest.advanceTimersByTime(1)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(1)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledWith(blackVideoEnforcer, 'default', false)
+ expect(blackVideoTrackCount).toBe(2)
+ assertBlackVideoTrack(0, 720, 540, STOPPED)
+ assertBlackVideoTrack(1, 320, 180)
+ })
+
+ test('removes output track when removing disabled input track', () => {
+ const inputTrack = newMediaStreamTrackMock('input')
+
+ inputTrack.enabled = false
+ blackVideoEnforcer._setInputTrack('default', inputTrack)
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT / 2)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ blackVideoEnforcer._setInputTrack('default', null)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(1)
+ assertBlackVideoTrack(0, 720, 540)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT / 2 - 1)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTracks[0].stop).toHaveBeenCalledTimes(0)
+
+ expectedTrackEnabledStateInOutputTrackSetEvent = undefined
+
+ jest.advanceTimersByTime(1)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(1)
+ expect(outputTrackSetHandler).toHaveBeenCalledWith(blackVideoEnforcer, 'default', null)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(1)
+ assertBlackVideoTrack(0, 720, 540, STOPPED)
+ })
+
+ test('removes output track when later removing disabled input track', () => {
+ const inputTrack = newMediaStreamTrackMock('input')
+
+ inputTrack.enabled = false
+ blackVideoEnforcer._setInputTrack('default', inputTrack)
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT * 5)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ blackVideoEnforcer._setInputTrack('default', null)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(1)
+ expect(outputTrackSetHandler).toHaveBeenCalledWith(blackVideoEnforcer, 'default', null)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(1)
+ assertBlackVideoTrack(0, 720, 540, STOPPED)
+ })
+
+ test('does nothing when removing null track', () => {
+ blackVideoEnforcer._setInputTrack('default', null)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(0)
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT * 5)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(0)
+ })
+
+ test('does nothing when removing null track again after removing enabled input track', () => {
+ const inputTrack = newMediaStreamTrackMock('input')
+
+ blackVideoEnforcer._setInputTrack('default', inputTrack)
+
+ blackVideoEnforcer._setInputTrack('default', null)
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT / 2)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ expectedTrackEnabledStateInOutputTrackSetEvent = true
+
+ blackVideoEnforcer._setInputTrack('default', null)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(1)
+ assertBlackVideoTrack(0, 720, 540)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT / 2 - 1)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTracks[0].stop).toHaveBeenCalledTimes(0)
+
+ expectedTrackEnabledStateInOutputTrackSetEvent = undefined
+
+ jest.advanceTimersByTime(1)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(1)
+ expect(outputTrackSetHandler).toHaveBeenCalledWith(blackVideoEnforcer, 'default', null)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(1)
+ assertBlackVideoTrack(0, 720, 540, STOPPED)
+ })
+ })
+
+ describe('stop input track', () => {
+ test('sets black video track as its output track when stopping enabled input track', () => {
+ const inputTrack = newMediaStreamTrackMock('input')
+
+ blackVideoEnforcer._setInputTrack('default', inputTrack)
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT / 2)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ expectedTrackEnabledStateInOutputTrackSetEvent = true
+
+ inputTrack.stop()
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(1)
+ expect(outputTrackSetHandler).toHaveBeenCalledWith(blackVideoEnforcer, 'default', blackVideoTracks[0])
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(1)
+ assertBlackVideoTrack(0, 720, 540)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT - 1)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+
+ jest.advanceTimersByTime(1)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(1)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledWith(blackVideoEnforcer, 'default', false)
+ expect(blackVideoTrackCount).toBe(1)
+ assertBlackVideoTrack(0, 720, 540)
+ })
+
+ test('sets black video track as its output track when stopping initially disabled and then enabled input track', () => {
+ const inputTrack = newMediaStreamTrackMock('input')
+
+ inputTrack.enabled = false
+ blackVideoEnforcer._setInputTrack('default', inputTrack)
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT / 4)
+
+ blackVideoEnforcer._setInputTrackEnabled('default', true)
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT / 4)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ expectedTrackEnabledStateInOutputTrackSetEvent = true
+
+ inputTrack.stop()
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(1)
+ expect(outputTrackSetHandler).toHaveBeenCalledWith(blackVideoEnforcer, 'default', blackVideoTracks[1])
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(2)
+ assertBlackVideoTrack(0, 720, 540, STOPPED)
+ assertBlackVideoTrack(1, 720, 540)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT - 1)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+
+ jest.advanceTimersByTime(1)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(1)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledWith(blackVideoEnforcer, 'default', false)
+ expect(blackVideoTrackCount).toBe(2)
+ assertBlackVideoTrack(0, 720, 540, STOPPED)
+ assertBlackVideoTrack(1, 720, 540)
+ })
+
+ test('does nothing when stopping disabled input track', () => {
+ const inputTrack = newMediaStreamTrackMock('input')
+
+ inputTrack.enabled = false
+ blackVideoEnforcer._setInputTrack('default', inputTrack)
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT / 2)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ inputTrack.stop()
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(1)
+ assertBlackVideoTrack(0, 720, 540)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT / 2 - 1)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+
+ jest.advanceTimersByTime(1)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(1)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledWith(blackVideoEnforcer, 'default', false)
+ expect(blackVideoTrackCount).toBe(1)
+ assertBlackVideoTrack(0, 720, 540)
+ })
+
+ test('does nothing when stopping initially enabled and then disabled input track', () => {
+ const inputTrack = newMediaStreamTrackMock('input')
+
+ blackVideoEnforcer._setInputTrack('default', inputTrack)
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT / 4)
+
+ blackVideoEnforcer._setInputTrackEnabled('default', false)
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT / 4)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ inputTrack.stop()
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(1)
+ assertBlackVideoTrack(0, 720, 540)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT * 3 / 4 - 1)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+
+ jest.advanceTimersByTime(1)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(1)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledWith(blackVideoEnforcer, 'default', false)
+ expect(blackVideoTrackCount).toBe(1)
+ assertBlackVideoTrack(0, 720, 540)
+ })
+
+ test('removes output track when stopping enabled input track and then removing it', () => {
+ const inputTrack = newMediaStreamTrackMock('input')
+
+ blackVideoEnforcer._setInputTrack('default', inputTrack)
+
+ inputTrack.stop()
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT / 2)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ blackVideoEnforcer._setInputTrack('default', null)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(1)
+ assertBlackVideoTrack(0, 720, 540)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT / 2 - 1)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTracks[0].stop).toHaveBeenCalledTimes(0)
+
+ expectedTrackEnabledStateInOutputTrackSetEvent = undefined
+
+ jest.advanceTimersByTime(1)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(1)
+ expect(outputTrackSetHandler).toHaveBeenCalledWith(blackVideoEnforcer, 'default', null)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(1)
+ assertBlackVideoTrack(0, 720, 540, STOPPED)
+ })
+
+ test('removes output track when stopping disabled input track and then removing it', () => {
+ const inputTrack = newMediaStreamTrackMock('input')
+
+ inputTrack.enabled = false
+ blackVideoEnforcer._setInputTrack('default', inputTrack)
+
+ inputTrack.stop()
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT / 2)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ blackVideoEnforcer._setInputTrack('default', null)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(1)
+ assertBlackVideoTrack(0, 720, 540)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT / 2 - 1)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTracks[0].stop).toHaveBeenCalledTimes(0)
+
+ expectedTrackEnabledStateInOutputTrackSetEvent = undefined
+
+ jest.advanceTimersByTime(1)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(1)
+ expect(outputTrackSetHandler).toHaveBeenCalledWith(blackVideoEnforcer, 'default', null)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(1)
+ assertBlackVideoTrack(0, 720, 540, STOPPED)
+ })
+
+ test('sets input track as its output track when stopping enabled input track and then replacing it with another enabled input track', () => {
+ const inputTrack = newMediaStreamTrackMock('input')
+ const inputTrack2 = newMediaStreamTrackMock('input2')
+
+ blackVideoEnforcer._setInputTrack('default', inputTrack)
+
+ inputTrack.stop()
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT / 2)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ expectedTrackEnabledStateInOutputTrackSetEvent = true
+
+ inputTrack2._width = 320
+ inputTrack2._height = 180
+ blackVideoEnforcer._setInputTrack('default', inputTrack2)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(1)
+ expect(outputTrackSetHandler).toHaveBeenCalledWith(blackVideoEnforcer, 'default', inputTrack2)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(1)
+ assertBlackVideoTrack(0, 720, 540, STOPPED)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT * 5)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(1)
+ assertBlackVideoTrack(0, 720, 540, STOPPED)
+ })
+
+ test('sets black video track as its output track when stopping enabled input track and then replacing it with another disabled input track', () => {
+ const inputTrack = newMediaStreamTrackMock('input')
+ const inputTrack2 = newMediaStreamTrackMock('input2')
+
+ blackVideoEnforcer._setInputTrack('default', inputTrack)
+
+ inputTrack.stop()
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT / 2)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ expectedTrackEnabledStateInOutputTrackSetEvent = true
+
+ inputTrack2.enabled = false
+ inputTrack2._width = 320
+ inputTrack2._height = 180
+ blackVideoEnforcer._setInputTrack('default', inputTrack2)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(1)
+ expect(outputTrackSetHandler).toHaveBeenCalledWith(blackVideoEnforcer, 'default', blackVideoTracks[1])
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(2)
+ assertBlackVideoTrack(0, 720, 540, STOPPED)
+ assertBlackVideoTrack(1, 320, 180)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT - 1)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+
+ jest.advanceTimersByTime(1)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(1)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledWith(blackVideoEnforcer, 'default', false)
+ expect(blackVideoTrackCount).toBe(2)
+ assertBlackVideoTrack(0, 720, 540, STOPPED)
+ assertBlackVideoTrack(1, 320, 180)
+ })
+
+ test('sets input track as its output track when stopping disabled input track and then replacing it with another enabled input track', () => {
+ const inputTrack = newMediaStreamTrackMock('input')
+ const inputTrack2 = newMediaStreamTrackMock('input2')
+
+ inputTrack.enabled = false
+ blackVideoEnforcer._setInputTrack('default', inputTrack)
+
+ inputTrack.stop()
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT / 2)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ expectedTrackEnabledStateInOutputTrackSetEvent = true
+
+ inputTrack2._width = 320
+ inputTrack2._height = 180
+ blackVideoEnforcer._setInputTrack('default', inputTrack2)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(1)
+ expect(outputTrackSetHandler).toHaveBeenCalledWith(blackVideoEnforcer, 'default', inputTrack2)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(1)
+ assertBlackVideoTrack(0, 720, 540, STOPPED)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT * 5)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(1)
+ assertBlackVideoTrack(0, 720, 540, STOPPED)
+ })
+
+ test('sets black video track as its output track when stopping disabled input track and then replacing it with another disabled input track', () => {
+ const inputTrack = newMediaStreamTrackMock('input')
+ const inputTrack2 = newMediaStreamTrackMock('input2')
+
+ inputTrack.enabled = false
+ blackVideoEnforcer._setInputTrack('default', inputTrack)
+
+ inputTrack.stop()
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT / 2)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ expectedTrackEnabledStateInOutputTrackSetEvent = true
+
+ inputTrack2.enabled = false
+ inputTrack2._width = 320
+ inputTrack2._height = 180
+ blackVideoEnforcer._setInputTrack('default', inputTrack2)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(1)
+ expect(outputTrackSetHandler).toHaveBeenCalledWith(blackVideoEnforcer, 'default', blackVideoTracks[1])
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(2)
+ assertBlackVideoTrack(0, 720, 540, STOPPED)
+ assertBlackVideoTrack(1, 320, 180)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT - 1)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+
+ jest.advanceTimersByTime(1)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(1)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledWith(blackVideoEnforcer, 'default', false)
+ expect(blackVideoTrackCount).toBe(2)
+ assertBlackVideoTrack(0, 720, 540, STOPPED)
+ assertBlackVideoTrack(1, 320, 180)
+ })
+
+ test('does nothing when stopping a previously removed enabled input track', () => {
+ const inputTrack = newMediaStreamTrackMock('input')
+
+ blackVideoEnforcer._setInputTrack('default', inputTrack)
+
+ blackVideoEnforcer._setInputTrack('default', null)
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT * 5)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ inputTrack.stop()
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(1)
+ assertBlackVideoTrack(0, 720, 540, STOPPED)
+ })
+
+ test('does nothing when stopping a previously removed disabled input track', () => {
+ const inputTrack = newMediaStreamTrackMock('input')
+
+ inputTrack.enabled = false
+ blackVideoEnforcer._setInputTrack('default', inputTrack)
+
+ blackVideoEnforcer._setInputTrack('default', null)
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT * 5)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ inputTrack.stop()
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(1)
+ assertBlackVideoTrack(0, 720, 540, STOPPED)
+ })
+
+ test('does nothing when stopping a previously replaced enabled input track', () => {
+ const inputTrack = newMediaStreamTrackMock('input')
+ const inputTrack2 = newMediaStreamTrackMock('input2')
+
+ blackVideoEnforcer._setInputTrack('default', inputTrack)
+
+ inputTrack2._width = 320
+ inputTrack2._height = 180
+ blackVideoEnforcer._setInputTrack('default', inputTrack2)
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT * 5)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ inputTrack.stop()
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(0)
+ })
+
+ test('does nothing when stopping a previously replaced disabled input track', () => {
+ const inputTrack = newMediaStreamTrackMock('input')
+ const inputTrack2 = newMediaStreamTrackMock('input2')
+
+ inputTrack.enabled = false
+ blackVideoEnforcer._setInputTrack('default', inputTrack)
+
+ inputTrack2._width = 320
+ inputTrack2._height = 180
+ blackVideoEnforcer._setInputTrack('default', inputTrack2)
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT * 5)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ inputTrack.stop()
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(1)
+ assertBlackVideoTrack(0, 720, 540, STOPPED)
+ })
+
+ test('sets black video track as its output track when stopping input track after setting it again', () => {
+ const inputTrack = newMediaStreamTrackMock('input')
+
+ blackVideoEnforcer._setInputTrack('default', inputTrack)
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT / 4)
+
+ inputTrack._width = 320
+ inputTrack._height = 180
+ blackVideoEnforcer._setInputTrack('default', inputTrack)
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT / 4)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ inputTrack.stop()
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(1)
+ expect(outputTrackSetHandler).toHaveBeenCalledWith(blackVideoEnforcer, 'default', blackVideoTracks[0])
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(1)
+ assertBlackVideoTrack(0, 320, 180)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT - 1)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+
+ jest.advanceTimersByTime(1)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(1)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledWith(blackVideoEnforcer, 'default', false)
+ expect(blackVideoTrackCount).toBe(1)
+ assertBlackVideoTrack(0, 320, 180)
+ })
+ })
+
+ describe('update input track', () => {
+ test('sets input track as its output track when setting same enabled input track again', () => {
+ const inputTrack = newMediaStreamTrackMock('input')
+
+ blackVideoEnforcer._setInputTrack('default', inputTrack)
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT / 2)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ expectedTrackEnabledStateInOutputTrackSetEvent = true
+
+ inputTrack._width = 320
+ inputTrack._height = 180
+ blackVideoEnforcer._setInputTrack('default', inputTrack)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(1)
+ expect(outputTrackSetHandler).toHaveBeenCalledWith(blackVideoEnforcer, 'default', inputTrack)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(0)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT * 5)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(0)
+ })
+
+ test('sets black video track as its output track when setting same disabled input track again', () => {
+ const inputTrack = newMediaStreamTrackMock('input')
+
+ inputTrack.enabled = false
+ blackVideoEnforcer._setInputTrack('default', inputTrack)
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT / 2)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ expectedTrackEnabledStateInOutputTrackSetEvent = true
+
+ inputTrack._width = 320
+ inputTrack._height = 180
+ blackVideoEnforcer._setInputTrack('default', inputTrack)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(1)
+ expect(outputTrackSetHandler).toHaveBeenCalledWith(blackVideoEnforcer, 'default', blackVideoTracks[1])
+ expect(blackVideoTrackCount).toBe(2)
+ assertBlackVideoTrack(0, 720, 540, STOPPED)
+ assertBlackVideoTrack(1, 320, 180)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT - 1)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+
+ jest.advanceTimersByTime(1)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(1)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledWith(blackVideoEnforcer, 'default', false)
+ expect(blackVideoTrackCount).toBe(2)
+ assertBlackVideoTrack(0, 720, 540, STOPPED)
+ assertBlackVideoTrack(1, 320, 180)
+ })
+
+ test('sets black video track as its output track when setting same now disabled input track again', () => {
+ const inputTrack = newMediaStreamTrackMock('input')
+
+ blackVideoEnforcer._setInputTrack('default', inputTrack)
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT / 2)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ expectedTrackEnabledStateInOutputTrackSetEvent = true
+
+ inputTrack.enabled = false
+ inputTrack._width = 320
+ inputTrack._height = 180
+ blackVideoEnforcer._setInputTrack('default', inputTrack)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(1)
+ expect(outputTrackSetHandler).toHaveBeenCalledWith(blackVideoEnforcer, 'default', blackVideoTracks[0])
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(1)
+ assertBlackVideoTrack(0, 320, 180)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT - 1)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+
+ jest.advanceTimersByTime(1)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(1)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledWith(blackVideoEnforcer, 'default', false)
+ expect(blackVideoTrackCount).toBe(1)
+ assertBlackVideoTrack(0, 320, 180)
+ })
+
+ test('sets input track as its output track when setting same now enabled input track again', () => {
+ const inputTrack = newMediaStreamTrackMock('input')
+
+ inputTrack.enabled = false
+ blackVideoEnforcer._setInputTrack('default', inputTrack)
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT / 2)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ expectedTrackEnabledStateInOutputTrackSetEvent = true
+
+ inputTrack.enabled = true
+ inputTrack._width = 320
+ inputTrack._height = 180
+ blackVideoEnforcer._setInputTrack('default', inputTrack)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(1)
+ expect(outputTrackSetHandler).toHaveBeenCalledWith(blackVideoEnforcer, 'default', inputTrack)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(1)
+ assertBlackVideoTrack(0, 720, 540, STOPPED)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT * 5)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(1)
+ assertBlackVideoTrack(0, 720, 540, STOPPED)
+ })
+
+ test('sets input track as its output track when setting another enabled input track', () => {
+ const inputTrack = newMediaStreamTrackMock('input')
+ const inputTrack2 = newMediaStreamTrackMock('input2')
+
+ blackVideoEnforcer._setInputTrack('default', inputTrack)
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT / 2)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ expectedTrackEnabledStateInOutputTrackSetEvent = true
+
+ blackVideoEnforcer._setInputTrack('default', inputTrack2)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(1)
+ expect(outputTrackSetHandler).toHaveBeenCalledWith(blackVideoEnforcer, 'default', inputTrack2)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(0)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT * 5)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(0)
+ })
+
+ test('sets black video track as its output track when setting another disabled input track', () => {
+ const inputTrack = newMediaStreamTrackMock('input')
+ const inputTrack2 = newMediaStreamTrackMock('input2')
+
+ inputTrack.enabled = false
+ blackVideoEnforcer._setInputTrack('default', inputTrack)
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT / 2)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ expectedTrackEnabledStateInOutputTrackSetEvent = true
+
+ inputTrack2.enabled = false
+ inputTrack2._width = 320
+ inputTrack2._height = 180
+ blackVideoEnforcer._setInputTrack('default', inputTrack2)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(1)
+ expect(outputTrackSetHandler).toHaveBeenCalledWith(blackVideoEnforcer, 'default', blackVideoTracks[1])
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(2)
+ assertBlackVideoTrack(0, 720, 540, STOPPED)
+ assertBlackVideoTrack(1, 320, 180)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT - 1)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+
+ jest.advanceTimersByTime(1)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(1)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledWith(blackVideoEnforcer, 'default', false)
+ expect(blackVideoTrackCount).toBe(2)
+ assertBlackVideoTrack(0, 720, 540, STOPPED)
+ assertBlackVideoTrack(1, 320, 180)
+ })
+
+ test('sets black video track as its output track when setting another now disabled input track', () => {
+ const inputTrack = newMediaStreamTrackMock('input')
+ const inputTrack2 = newMediaStreamTrackMock('input2')
+
+ blackVideoEnforcer._setInputTrack('default', inputTrack)
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT / 2)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ expectedTrackEnabledStateInOutputTrackSetEvent = true
+
+ inputTrack2.enabled = false
+ inputTrack2._width = 320
+ inputTrack2._height = 180
+ blackVideoEnforcer._setInputTrack('default', inputTrack2)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(1)
+ expect(outputTrackSetHandler).toHaveBeenCalledWith(blackVideoEnforcer, 'default', blackVideoTracks[0])
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(1)
+ assertBlackVideoTrack(0, 320, 180)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT - 1)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+
+ jest.advanceTimersByTime(1)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(1)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledWith(blackVideoEnforcer, 'default', false)
+ expect(blackVideoTrackCount).toBe(1)
+ assertBlackVideoTrack(0, 320, 180)
+ })
+
+ test('sets input track as its output track when setting another now enabled input track', () => {
+ const inputTrack = newMediaStreamTrackMock('input')
+ const inputTrack2 = newMediaStreamTrackMock('input2')
+
+ inputTrack.enabled = false
+ blackVideoEnforcer._setInputTrack('default', inputTrack)
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT / 2)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ expectedTrackEnabledStateInOutputTrackSetEvent = true
+
+ inputTrack2.enabled = true
+ blackVideoEnforcer._setInputTrack('default', inputTrack2)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(1)
+ expect(outputTrackSetHandler).toHaveBeenCalledWith(blackVideoEnforcer, 'default', inputTrack2)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(1)
+ assertBlackVideoTrack(0, 720, 540, STOPPED)
+
+ outputTrackSetHandler.mockClear()
+ outputTrackEnabledHandler.mockClear()
+
+ jest.advanceTimersByTime(DISABLE_OR_REMOVE_TIMEOUT * 5)
+
+ expect(outputTrackSetHandler).toHaveBeenCalledTimes(0)
+ expect(outputTrackEnabledHandler).toHaveBeenCalledTimes(0)
+ expect(blackVideoTrackCount).toBe(1)
+ assertBlackVideoTrack(0, 720, 540, STOPPED)
+ })
+ })
+})
diff --git a/src/utils/webrtc/simplewebrtc/localmedia.js b/src/utils/webrtc/simplewebrtc/localmedia.js
index 23bf1ba7d..5fc3d8de2 100644
--- a/src/utils/webrtc/simplewebrtc/localmedia.js
+++ b/src/utils/webrtc/simplewebrtc/localmedia.js
@@ -7,6 +7,7 @@ const mockconsole = require('mockconsole')
// Only mediaDevicesManager is used, but it can not be assigned here due to not
// being initialized yet.
const webrtcIndex = require('../index.js')
+const BlackVideoEnforcer = require('../../media/pipeline/BlackVideoEnforcer.js').default
const MediaDevicesSource = require('../../media/pipeline/MediaDevicesSource.js').default
const SpeakingMonitor = require('../../media/pipeline/SpeakingMonitor.js').default
const TrackConstrainer = require('../../media/pipeline/TrackConstrainer.js').default
@@ -58,6 +59,8 @@ function LocalMedia(opts) {
this.emit('virtualBackgroundLoadFailed')
})
+ this._blackVideoEnforcer = new BlackVideoEnforcer()
+
this._speakingMonitor = new SpeakingMonitor()
this._speakingMonitor.on('speaking', () => {
this.emit('speaking')
@@ -99,7 +102,9 @@ function LocalMedia(opts) {
this._videoTrackConstrainer.connectTrackSink('default', this._virtualBackground)
this._virtualBackground.connectTrackSink('default', this._trackToStream, 'video')
- this._virtualBackground.connectTrackSink('default', this._trackToSentStream, 'video')
+ this._virtualBackground.connectTrackSink('default', this._blackVideoEnforcer, 'default')
+
+ this._blackVideoEnforcer.connectTrackSink('default', this._trackToSentStream, 'video')
}
util.inherits(LocalMedia, WildEmitter)