Search code examples
webrtcpeerpeerjs

Detect if MediaStreamTrack is black/blank


I'm creating videochat with peerjs.

I'm toggling camera (on/off) with the following function:

function toggleCamera() {
    localStream.getVideoTracks()[0].enabled = !(localStream.getVideoTracks()[0].enabled);
}

After calling this function, video goes black and receiver gets just black screen (which works as intended). Now I want to detect black/blank screen so I can show user some message or icon that camera is disabled and there is no stream.

How do I do detect that?


Solution

  • After some time I've managed to get solution:

    var previousBytes = 0;
    var previousTS = 0;
    var currentBytes = 0;
    var currentTS = 0;
    
    // peer - new Peer()
    // stream - local camera stream (received from navigator.mediaDevices.getUserMedia(constraints))
    let connection = peer.call(peerID, stream);
    
    // peerConnection - reference to RTCPeerConnection (https://peerjs.com/docs.html#dataconnection-peerconnection)
    connection.peerConnection.getStats(null).then(stats => {
        stats.forEach(report => {
            if (report.type === "inbound-rtp") {
    
                currentBytes = report.bytesReceived;
                currentTS = report.timestamp;
    
                if (previousBytes == 0) {
                    previousBytes = currentBytes;
                    previousTS = currentTS;
                    return;
                }
    
                console.log({ previousBytes })
                console.log({ currentBytes })
    
                var deltaBytes = currentBytes - previousBytes;
                var deltaTS = currentTS - previousTS;
    
                console.log("Delta: " + (deltaBytes / deltaTS) + " kB/s")
                previousBytes = currentBytes;
                previousTS = currentTS;
    
            }
        });
    });
    

    This code is actually in function which gets called every second. When camera is turned on and it's not covered, deltaBytes is between 100 and 250, when camera is turned off (programmatically) or covered (with a napkin or something), so camera stream is black/blank, deltaBytes is med 1.5-3kbps. After you turn camera back on, there is a spike in deltaBytes, which reaches around 500kbps.

    This is short console log:

        124.52747252747253 kB/s
        202.213 kB/s
        194.64764764764766 kB/s
        15.313 kB/s (this is where camera is covered)
        11.823823823823824 kB/s
        11.862137862137862 kB/s
        2.164 kB/s
        2.005 kB/s
        2.078078078078078 kB/s
        1.99 kB/s
        2.059 kB/s
        1.992992992992993 kB/s
        159.89810189810188 kB/s (uncovered camera)
        502.669 kB/s
        314.7927927927928 kB/s
        255.0909090909091 kB/s
        220.042 kB/s
        213.46353646353646 kB/s
    

    EDIT: So in the end I did as @Philipp Hancke said. I created master connection which is open from when the page loads until user closes it. Over this connection I'm sending commands for initiating video call, canceling video session, turning on/off camera,... Then on the other side I'm parsing these commands and executing functions.

    function sendMutedMicCommand() { masterConnection.send(`${commands.MutedMic}`); }
    function sendUnmutedMicCommand() { masterConnection.send(`${commands.UnmutedMic}`); }
    function sendPromptVideoCallCommand() { masterConnection.send(`${commands.PromptVideoCall}`); }
    function sendAcceptVideoCallCommand() { masterConnection.send(`${commands.AcceptVideoCall}`); }
    function sendDeclineVideoCallCommand() { masterConnection.send(`${commands.DeclineVideoCall}`); }
    
    Function which handles data:
    function handleData(data) {
        let actionType = data;
        switch (actionType) {
            case commands.MutedMic: ShowMuteIconOnReceivingVideo(true); break;
            case commands.UnmutedMic: ShowMuteIconOnReceivingVideo(false); break;
            case commands.PromptVideoCall: showVideoCallModal(); break;
            case commands.AcceptVideoCall: startVideoConference(); break;
            case commands.DeclineVideoCall: showDeclinedCallAlert(); break;
            default: break;
        }
    }
    
    
    const commands = {
        MutedMic: "mutedMic",
        UnmutedMic: "unmutedMic",
        PromptVideoCall: "promptVideoCall",
        AcceptVideoCall: "acceptVideoCall",
        DeclineVideoCall: "declineVideoCall",
    }
    

    And then when I receive mutedMic command, I show icon with crossed mic. When I receive AcceptVideoCall command I create another peer, videoCallPeer with random ID, which is then then sent to other side. Other side then created another peer with random ID and initiated video session with received ID.