| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198 | const iceConnectionLog = document.getElementById('ice-connection-state'),    iceGatheringLog = document.getElementById('ice-gathering-state'),    signalingLog = document.getElementById('signaling-state'),    dataChannelLog = document.getElementById('data-channel');const clientId = randomId(10);const websocket = new WebSocket('ws://127.0.0.1:8000/' + clientId);websocket.onopen = () => {    document.getElementById('start').disabled = false;}websocket.onmessage = async (evt) => {    if (typeof evt.data !== 'string') {        return;    }    const message = JSON.parse(evt.data);    if (message.type == "offer") {        document.getElementById('offer-sdp').textContent = message.sdp;        await handleOffer(message)    }}let pc = null;let dc = null;function createPeerConnection() {    const config = {        bundlePolicy: "max-bundle",    };    if (document.getElementById('use-stun').checked) {        config.iceServers = [{urls: ['stun:stun.l.google.com:19302']}];    }    let pc = new RTCPeerConnection(config);    // Register some listeners to help debugging    pc.addEventListener('iceconnectionstatechange', () =>        iceConnectionLog.textContent += ' -> ' + pc.iceConnectionState);    iceConnectionLog.textContent = pc.iceConnectionState;    pc.addEventListener('icegatheringstatechange', () =>        iceGatheringLog.textContent += ' -> ' + pc.iceGatheringState);    iceGatheringLog.textContent = pc.iceGatheringState;    pc.addEventListener('signalingstatechange', () =>        signalingLog.textContent += ' -> ' + pc.signalingState);    signalingLog.textContent = pc.signalingState;    // Receive audio/video track    pc.ontrack = (evt) => {        document.getElementById('media').style.display = 'block';        const video = document.getElementById('video');        // always overrite the last stream - you may want to do something more clever in practice        video.srcObject = evt.streams[0]; // The stream groups audio and video tracks        video.play();    };    // Receive data channel    pc.ondatachannel = (evt) => {        dc = evt.channel;        dc.onopen = () => {            dataChannelLog.textContent += '- open\n';            dataChannelLog.scrollTop = dataChannelLog.scrollHeight;        };        let dcTimeout = null;        dc.onmessage = (evt) => {            if (typeof evt.data !== 'string') {                return;            }            dataChannelLog.textContent += '< ' + evt.data + '\n';            dataChannelLog.scrollTop = dataChannelLog.scrollHeight;            dcTimeout = setTimeout(() => {                if (!dc) {                    return;                }                const message = `Pong ${currentTimestamp()}`;                dataChannelLog.textContent += '> ' + message + '\n';                dataChannelLog.scrollTop = dataChannelLog.scrollHeight;                dc.send(message);            }, 1000);        }        dc.onclose = () => {            clearTimeout(dcTimeout);            dcTimeout = null;            dataChannelLog.textContent += '- close\n';            dataChannelLog.scrollTop = dataChannelLog.scrollHeight;        };    }    return pc;}async function waitGatheringComplete() {    return new Promise((resolve) => {        if (pc.iceGatheringState === 'complete') {            resolve();        } else {            pc.addEventListener('icegatheringstatechange', () => {                if (pc.iceGatheringState === 'complete') {                    resolve();                }            });        }    });}async function sendAnswer(pc) {    await pc.setLocalDescription(await pc.createAnswer());    await waitGatheringComplete();    const answer = pc.localDescription;    document.getElementById('answer-sdp').textContent = answer.sdp;    websocket.send(JSON.stringify({        id: "server",        type: answer.type,        sdp: answer.sdp,    }));}async function handleOffer(offer) {    pc = createPeerConnection();    await pc.setRemoteDescription(offer);    await sendAnswer(pc);}function sendRequest() {    websocket.send(JSON.stringify({        id: "server",        type: "request",    }));}function start() {    document.getElementById('start').style.display = 'none';    document.getElementById('stop').style.display = 'inline-block';    document.getElementById('media').style.display = 'block';    sendRequest();}function stop() {    document.getElementById('stop').style.display = 'none';    document.getElementById('media').style.display = 'none';    document.getElementById('start').style.display = 'inline-block';    // close data channel    if (dc) {        dc.close();        dc = null;    }    // close transceivers    if (pc.getTransceivers) {        pc.getTransceivers().forEach((transceiver) => {            if (transceiver.stop) {                transceiver.stop();            }        });    }    // close local audio/video    pc.getSenders().forEach((sender) => {        const track = sender.track;        if (track !== null) {            sender.track.stop();        }    });    // close peer connection    pc.close();    pc = null;}// Helper function to generate a random IDfunction randomId(length) {  const characters = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';  const pickRandom = () => characters.charAt(Math.floor(Math.random() * characters.length));  return [...Array(length) ].map(pickRandom).join('');}// Helper function to generate a timestamplet startTime = null;function currentTimestamp() {    if (startTime === null) {        startTime = Date.now();        return 0;    } else {        return Date.now() - startTime;    }}
 |