重新协商 webrtc 连接时,流变为“空”

When renegotiating a webrtc connection, stream gets "empty"

提问人:Creepy 提问时间:10/4/2023 更新时间:10/4/2023 访问量:19

问:

我目前正在使用 webrtc 进行点对点视频聊天,并提供打开和关闭麦克风和摄像头的选项。初始设置工作正常,如果我选择了麦克风,只有我的麦克风会发送给其他用户,反之亦然,视频源或两者兼而有之,但是如果我尝试更改它(将自己静音或关闭相机)在“第一次提供”参与者上,其他参与者将一无所获,即使我尝试重新打开它, 它仍然显示一个空的提要。有时我可以设法让它再次工作,但我不知道是什么模式使它工作。

我的主要 js、saveOffer、getOffer、getAnswer、setAnswer 等都是对服务器的 ajax 请求,其中 sdp-s 作为“信令通道”作为文件存储

let element = document.getElementById('mysdp');
function SignalingChannel() {
    // Create an array to store event listeners
    this.listeners = [];
  
    // Method to add an event listener
    this.addEventListener = function (eventName, callback) {
      this.listeners.push({ eventName, callback });
    };
  
    // Method to trigger the "message" event
    this.postMessage = function (data) {
      // Emit the "message" event
      this.emitEvent("message", data);
    };
  
    // Method to trigger the "send" function
    this.send = function (data) {
      // Display the data
      //console.log("Sent data: " + data);
    };
  
    // Method to emit events
    this.emitEvent = function (eventName, data) {
      // Find the listeners for the given event
      const eventListeners = this.listeners.filter(
        (listener) => listener.eventName === eventName
      );
  
      // Call the callback functions for each listener
      eventListeners.forEach((listener) => {
        listener.callback(data);
      });
    };
  }


const signalingChannel = new SignalingChannel();
var options = { offerToReceiveAudio: true, offerToReceiveVideo: true };
signalingChannel.addEventListener('message', message => {
    console.log(message);
});

const configuration = {'iceServers': [{'urls': 'stun:stun.l.google.com:19302'}]}
const peerConnection = new RTCPeerConnection(configuration);

let dataChannel;
let sender;


function addTrack(stream){
    console.log("AddTrack start");
    const senders = peerConnection.getSenders();
    senders.forEach((sender) => peerConnection.removeTrack(sender));
    console.log("Previous tracks removed");
    stream.getTracks().forEach(track => {
        console.log("Adding track: ");
        console.log(track);
        sender = peerConnection.addTrack(track, localStream);
    });
    console.log("AddTrack end");
}

//Used for renegotiation
async function changeCall(roomid){
    console.log("Creating offer");
    let offer = await peerConnection.createOffer(options);
    console.log("New offer created");
    await peerConnection.setLocalDescription(offer);
    console.log("Local description set");
    let lsdp = JSON.stringify(peerConnection.localDescription);
    let lsdpoffer = {'offer' : lsdp};
    console.log("Renegotiating, saving offer to server");
    await saveOffer(lsdpoffer,roomid);
    //await dataChannel.send('{"renegotiation":true}');
    console.log("Sending renegotiation ping");
    await dataChannel.send('{"renegotiation":true}');
}
//Used for initiating a call
async function makeCall(roomid) {
    console.log("call");
    signalingChannel.addEventListener('message', async message => {
        message = JSON.parse(message);
        if (message.answer) {
            const remoteDesc = new RTCSessionDescription(message.answer);
            await peerConnection.setRemoteDescription(remoteDesc);
            console.log("remote description set");
        }
    });
    dataChannel = peerConnection.createDataChannel("Channel");
    dataChannel.onmessage = e => {
        try {
            console.log(e.data);
            let json = JSON.parse(e.data);
            if(json.renegotiation){
                getOffer(roomid);
            }
        } catch (e) {
            
            return false;
        }
    };
    dataChannel.onopen = e => console.log("open!!!!");
    dataChannel.onclose = e => leave(roomid);

    peerConnection.onnegotiationneeded = e => {
        renegotiateStart(roomid)
    }
    peerConnection.onicecandidate = e =>  {
        console.log(" NEW ice candidate!! on localconnection reprinting SDP " )
        let lsdp = JSON.stringify(peerConnection.localDescription);
        let lsdpoffer = {'offer' : lsdp};
        saveOffer(lsdpoffer,roomid);
        
        tryUntilAnswer(roomid);
        //console.log(lsdpoffer);
    }
    let offer = await peerConnection.createOffer(options);
    await peerConnection.setLocalDescription(offer);
    console.log("Local description set");
    let lsdp = JSON.stringify(peerConnection.localDescription);
    let lsdpoffer = {'offer' : lsdp};
    //console.log(lsdpoffer);
    console.log("Calling");
    saveOffer(lsdpoffer,roomid);
    
    //console.log(JSON.stringify(peerConnection.localDescription))
    //console.log(tosendoffer);
}

//Answering to a call
signalingChannel.addEventListener('message', async message => {
    message = JSON.parse(message);
    
    if (message.offer) {
        peerConnection.setRemoteDescription(message.offer);

        peerConnection.onnegotiationneeded = e => {
            renegotiateStart(roomid)
        }

        peerConnection.onicecandidate = e =>  {
            console.log(" NEW ice candidate!! on localconnection reprinting SDP " )
            let lsdp = JSON.stringify(peerConnection.localDescription);
            let lsdpanswer = {'answer':lsdp};
            //console.log(lsdpanswer);
            setAnswer(lsdpanswer,roomid);
        }

        peerConnection.ondatachannel= e => {

            dataChannel = e.channel;
            dataChannel.onmessage =e =>  {
                try {
                    console.log(e.data);
                    let json = JSON.parse(e.data);
                    if(json.renegotiation){
                        getOffer(roomid);
                    }
                } catch (e) {
                    
                    return false;
                }
            };
            dataChannel.onopen = e => console.log("open!!!!");
            dataChannel.onclose =e => leave(roomid);
            peerConnection.channel = dataChannel;
    
        }
        let answer = await peerConnection.createAnswer(options);
        await peerConnection.setLocalDescription(answer);

        
    }
});

// Listen for connectionstatechange on the local RTCPeerConnection
const remoteVideo = document.getElementById('remoteVideo');
peerConnection.addEventListener('track', async (event) => {
    console.log("type: "+typeof(event.streams));
    console.log("streams: "+event.streams);
    const [remoteStream] = event.streams;
    remoteVideo.srcObject = remoteStream;
    console.log("New remoteStream:", remoteStream);
});



peerConnection.addEventListener('connectionstatechange', event => {
    if (peerConnection.connectionState === 'connected') {
        // Peers connected!
        console.log("Connected!");
    }
});

切换摄像头和麦克风的代码

function toggleVisible(eid){
    let e = document.getElementById(eid);
    if(e.classList.contains('d-none')){
        e.classList.remove('d-none');
    }
    else{
        e.classList.add('d-none');
    }
}

async function toggleCamera(){
    
    console.log(localStream);
    let togbut = document.getElementById("toggleCameraButton");
    let togmic = document.getElementById("toggleMicrophoneButton");
    let audio = false;
    if(localStream){
        localStream.getTracks().forEach((track) => track.stop());
        localStream = null;
    }
    if(togmic.classList.contains('on')){
        audio = true;
    }
    let localPlayer = document.getElementById('localVideo');
    
    if(togbut.classList.contains('off')){
        if(await playVideoFromCamera(true, audio) == false) {
            return false;
        }
        togbut.src = "/ozekiservices/chatvideo/attachments/camera-video.svg";
        togbut.classList.remove('off');
        togbut.classList.add('on');
        return;
    }
    
    togbut.src = "/ozekiservices/chatvideo/attachments/camera-video-off.svg";
    
    
    localPlayer.pause();
    if(audio){
        if(await playVideoFromCamera(false, audio) == false){
            return false;
        } 
    }
    togbut.classList.add('off');
    togbut.classList.remove('on');
    localPlayer.classList.add('d-none');
    //makeCall(roomid);
    
}


async function toggleMicrophone(){
    
    console.log(localStream);
    console.log("toggleMic");
    let togbut = document.getElementById("toggleMicrophoneButton");
    let togcam = document.getElementById("toggleCameraButton");
    console.log("Stopping tracks");
    if(localStream){
        localStream.getTracks().forEach((track) => track.stop());
        localStream = null;
    }
    console.log("Tracks stopped");
    let video = false;
    if(togcam.classList.contains('on')){
        console.log("CAM feed ON");
        video = true;
    }
    let localPlayer = document.getElementById('localVideo');

    if(togbut.classList.contains('off')){
        console.log("Turn MIC ON");
        if(await playVideoFromCamera(video, true) == false) return false;
        togbut.src = "/ozekiservices/chatvideo/attachments/mic.svg";
        togbut.classList.remove('off');
        togbut.classList.add('on');
        return;
    }
    togbut.src = "/ozekiservices/chatvideo/attachments/mic-mute.svg";

    localPlayer.pause();
    if(video){
        if(await playVideoFromCamera(video, false) == false) return false;
    }
    togbut.classList.add('off');
    togbut.classList.remove('on');
    if(!video){

        localPlayer.classList.add('d-none');
    }
    
}

打开相机源的代码

let minWidth= 200;
let minHeight= 200;
function cameratooption(camera)
{   
    const cameraOption = document.createElement('option');
    cameraOption.label = camera.label;
    cameraOption.value = camera.deviceId;
    return cameraOption;
}

function updateCameraList(cameras) {
    const listElement = document.getElementById('availableCameras');
    listElement.innerHTML = '';


    let cameraoptions = cameras.map(camera => cameratooption(camera));
    console.log(cameraoptions);
    cameraoptions.forEach(cameraOption => listElement.add(cameraOption));
}

// Fetch an array of devices of a certain type
async function getConnectedDevices(type) {
    const devices = await navigator.mediaDevices.enumerateDevices();
    let cameras = devices.filter(device => device.kind === type);
    updateCameraList(cameras);
}

// Get the initial set of cameras connected
const videoCameras = getConnectedDevices('videoinput');

// Listen for changes to media devices and update the list accordingly
navigator.mediaDevices.addEventListener('devicechange', event => {
    const newCameraList = getConnectedDevices('videoinput');
});


// Open camera with at least minWidth and minHeight capabilities
async function openCamera(cameraId, minWidth, minHeight, video, audio) {
    if(video){
        video = {
            'deviceId': cameraId,
            'width': {'min': minWidth},
            'height': {'min': minHeight}
            }
    }
    if(audio){
        audio = {'echoCancellation': true}
    }
    const constraints = {
        'audio': audio,
        'video': video
        }
    let result = false;
    try{
        result = await navigator.mediaDevices.getUserMedia(constraints);
    }
    catch{
        console.log("cam not accesible");
    }

    return result;
}
var localStream;


async function playVideoFromCamera(video, audio) {
    if(!(video || audio)){
        return;
    }
    try {
        
        e = document.getElementById('availableCameras');
        cameraId = e.options[e.selectedIndex].value;
        console.log(cameraId);
        const stream = await openCamera(cameraId, minWidth, minHeight, video, audio);
        if(stream == false){
            return false;
        }
        localStream = stream;
        const videoElement = document.querySelector('video#localVideo');
        if(video){
            videoElement.classList.remove('d-none');
        }
        let newStream = new MediaStream(stream.getTracks());

        
        let videoTrack = newStream.getVideoTracks()[0];
        let audioTrack = newStream.getAudioTracks()[0];
        if(audioTrack){

            newStream.removeTrack(audioTrack);
        }

        videoElement.srcObject = newStream;
        addTrack(stream);
        console.log("streamdata: "+stream);
    } catch(error) {
        console.error('Error opening video camera.', error);
    }
}

我尝试调试代码的基本上每个部分(由于我的代码拙劣,这非常困难),尝试了不同的方法,但没有运气。如果有人能告诉我我做错了什么,我会非常感激,因为我在这一点上没有想法,老实说只是卡住了。

JavaScript WebRTC 视频聊天

评论


答: 暂无答案