Search code examples
webrtcgetusermediamediastream

Closing WebRTC track will not close camera device or tab camera indicator


Banging my head to the wall with this one, I can't seem to understand what is holding on the camera video stream and not closing when MediaStreamTrack.stop() called.

I have a typescript class where I handle getting the WebRTC stream track and passing it using an observable event to a functional reactjs component, the below code is the component registering to the event and using state for the stream track.

const [videoStreamTrack, setVideoStreamTrack] = useState < MediaStreamTrack > (
    null
)

useEffect(() => {
    return () => {
        videoStreamTrack?.stop()
        videoElement.current.srcObject.getVideoTracks().forEach((track) => {
            track.stop()
            videoElement.current.srcObject.removeTrack(track)
        })
        videoElement.current.srcObject = null
    }
}, [])

case RoomEvents.WebcamProducerAdded:
case RoomEvents.VideoStreamReplaced: {
  if (result.data?.track) {
      if (result.data.track.kind === 'video') {
        previewVideoStreamTrack?.stop()
        setPreviewVideoStreamTrack(null)
        setVideoStreamTrack(result.data.track)
      }
    }
  break
}

In the "Room" class I use the below code to grab the stream.

const videoDevice = this.webcam.device
if (!videoDevice) {
    throw new Error('no webcam devices')
}

const userMedia = await navigator.mediaDevices.getUserMedia({
    video: this.environmentPlatformService.isMobile ?
        true : {
            deviceId: {
                exact: this.webcam.device.deviceId
            },
            ...VIDEO_CONSTRAINS[this.webcam.resolution],
        },
})

const videoTrack = userMedia.getVideoTracks()[0]

this.eventSubject.next({
    eventName: RoomEvents.WebcamProducerAdded,
    data: {
        track: videoTrack,
    },
})

I am holding to this.webcam.device details using the code below.

async updateInputOutputMediaDevices(): Promise < MediaDeviceInfo[] > {
    await navigator.mediaDevices.getUserMedia({
        audio: true,
        video: true
    })
    const devices = await navigator.mediaDevices.enumerateDevices()
    await this.updateWebcams(devices)
    await this.updateAudioInputs(devices)
    await this.updateAudioOutputs(devices)
    return devices
}

private async updateWebcams(devices: MediaDeviceInfo[]) {
    this.webcams = new Map < string, MediaDeviceInfo > ()

    for (const device of devices.filter((d) => d.kind === 'videoinput')) {
        this.webcams.set(device.deviceId, device)
    }

    const array = Array.from(this.webcams.values())

    this.eventSubject.next({
        eventName: RoomEvents.CanChangeWebcam,
        data: {
            canChangeWebcam: array.length > 1,
            mediaDevices: array,
        },
    })
}

Refreshing the page will close the camera and tab indicator.


Solution

  • useEffect(() => {
        return () => {
            videoStreamTrack?.stop()
            videoElement.current.srcObject.getVideoTracks().forEach((track) => {
                track.stop()
                videoElement.current.srcObject.removeTrack(track)
            })
            videoElement.current.srcObject = null
        }
    }, [])
    

    So here you are search and destroying video tracks. Seems right-ish; we'll see

    async updateInputOutputMediaDevices(): Promise < MediaDeviceInfo[] > {
        await navigator.mediaDevices.getUserMedia({
            audio: true,
            video: true
        })
        const devices = await navigator.mediaDevices.enumerateDevices()
        await this.updateWebcams(devices)
        await this.updateAudioInputs(devices)
        await this.updateAudioOutputs(devices)
        return devices
    }
    

    Above I see there's a call for audio might be where the hiccups are? Can't overly examine but maybe you're opening both and closing just video? Try doing a loop through all tracks not just video and see what's there?