jikjoo / Bangul_webOS

"차량용 스마트 펫케어 서비스 : 방울이가타고있어요"의 차량용 소프트웨어입니다
MIT License
2 stars 1 forks source link

스트리밍 라즈베리파이랑 연결하기 #37

Open jikjoo opened 4 years ago

jikjoo commented 4 years ago

브랜치 streaming, jik_streaming

jikjoo commented 4 years ago

http://www.linux-projects.org/webrtc-signalling/

RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection;
RTCSessionDescription = window.RTCSessionDescription;
RTCIceCandidate = window.RTCIceCandidate;

function signal(url, onStream, onError, onClose, onMessage) {
    if ("WebSocket" in window) {
        console.log("opening web socket: " + url);
        var ws = new WebSocket(url);
        var pc;
        var iceCandidates = [];
        var hasRemoteDesc = false;

        function addIceCandidates() {
            if (hasRemoteDesc) {
                iceCandidates.forEach(function (candidate) {
                    pc.addIceCandidate(candidate,
                        function () {
                            console.log("IceCandidate added: " + JSON.stringify(candidate));
                        },
                        function (error) {
                            console.error("addIceCandidate error: " + error);
                        }
                    );
                });
                iceCandidates = [];
            }
        }

        ws.onopen = function () {
            /* First we create a peer connection */
            var config = {"iceServers": [{"urls": ["stun:stun.l.google.com:19302"]}]};
            var options = {optional: []};
            pc = new RTCPeerConnection(config, options);
            iceCandidates = [];
            hasRemoteDesc = false;

            pc.onicecandidate = function (event) {
                if (event.candidate) {
                    var candidate = {
                        sdpMLineIndex: event.candidate.sdpMLineIndex,
                        sdpMid: event.candidate.sdpMid,
                        candidate: event.candidate.candidate
                    };
                    var request = {
                        what: "addIceCandidate",
                        data: JSON.stringify(candidate)
                    };
                    ws.send(JSON.stringify(request));
                } else {
                    console.log("end of candidates.");
                }
            };

            if ('ontrack' in pc) {
                pc.ontrack = function (event) {
                    onStream(event.streams[0]);
                };
            } else {  // onaddstream() deprecated
                pc.onaddstream = function (event) {
                    onStream(event.stream);
                };
            }

            pc.onremovestream = function (event) {
                console.log("the stream has been removed: do your stuff now");
            };

            pc.ondatachannel = function (event) {
                console.log("a data channel is available: do your stuff with it");
                // For an example, see https://www.linux-projects.org/uv4l/tutorials/webrtc-data-channels/
            };

            /* kindly signal the remote peer that we would like to initiate a call */
            var request = {
                what: "call",
                options: {
                    // If forced, the hardware codec depends on the arch.
                    // (e.g. it's H264 on the Raspberry Pi)
                    // Make sure the browser supports the codec too.
                    force_hw_vcodec: true,
                    vformat: 30, /* 30=640x480, 30 fps */
                    trickle_ice: true
                }
            };
            console.log("send message " + JSON.stringify(request));
            ws.send(JSON.stringify(request));
        };

        ws.onmessage = function (evt) {
            var msg = JSON.parse(evt.data);
            var what = msg.what;
            var data = msg.data;

            console.log("received message " + JSON.stringify(msg));

            switch (what) {
                case "offer":
                    var mediaConstraints = {
                        optional: [],
                        mandatory: {
                            OfferToReceiveAudio: true,
                            OfferToReceiveVideo: true
                        }
                    };
                    pc.setRemoteDescription(new RTCSessionDescription(JSON.parse(data)),
                            function onRemoteSdpSuccess() {
                                hasRemoteDesc = true;
                                addIceCandidates();
                                pc.createAnswer(function (sessionDescription) {
                                    pc.setLocalDescription(sessionDescription);
                                    var request = {
                                        what: "answer",
                                        data: JSON.stringify(sessionDescription)
                                    };
                                    ws.send(JSON.stringify(request));
                                }, function (error) {
                                    onError("failed to create answer: " + error);
                                }, mediaConstraints);
                            },
                            function onRemoteSdpError(event) {
                                onError('failed to set the remote description: ' + event);
                                ws.close();
                            }
                    );

                    break;

                case "answer":
                    break;

                case "message":
                    if (onMessage) {
                        onMessage(msg.data);
                    }
                    break;

                case "iceCandidate": // received when trickle ice is used (see the "call" request)
                    if (!msg.data) {
                        console.log("Ice Gathering Complete");
                        break;
                    }
                    var elt = JSON.parse(msg.data);
                    let candidate = new RTCIceCandidate({sdpMLineIndex: elt.sdpMLineIndex, candidate: elt.candidate});
                    iceCandidates.push(candidate);
                    addIceCandidates(); // it internally checks if the remote description has been set
                    break;

                case "iceCandidates": // received when trickle ice is NOT used (see the "call" request)
                    var candidates = JSON.parse(msg.data);
                    for (var i = 0; candidates && i < candidates.length; i++) {
                        var elt = candidates[i];
                        let candidate = new RTCIceCandidate({sdpMLineIndex: elt.sdpMLineIndex, candidate: elt.candidate});
                        iceCandidates.push(candidate);
                    }
                    addIceCandidates();
                    break;
            }
        };

        ws.onclose = function (event) {
            console.log('socket closed with code: ' + event.code);
            if (pc) {
                pc.close();
                pc = null;
                ws = null;
            }
            if (onClose) {
                onClose();
            }
        };

        ws.onerror = function (event) {
            onError("An error has occurred on the websocket (make sure the address is correct)!");
        };

        this.hangup = function() {
            if (ws) {
                var request = {
                    what: "hangup"
                };
                console.log("send message " + JSON.stringify(request));
                ws.send(JSON.stringify(request));
            }
        };

    } else {
        onError("Sorry, this browser does not support Web Sockets. Bye.");
    }
}
jikjoo commented 4 years ago

위에 사이트에서 추천한 React App

https://github.com/lproj/screen-sharing-app

jikjoo commented 4 years ago

선종이가 보낸 라즈베리파이 코드


<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>UV4L WebRTC</title>
        <!--script src="https://raw.githubusercontent.com/dorukeker/gyronorm.js/master/dist/gyronorm.complete.min.js" async></script-->
        <!--script src="https://rawgit.com/dorukeker/gyronorm.js/master/dist/gyronorm.complete.min.js" async></script-->
        <script type="text/javascript">

            function httpGetAsync(theUrl, callback) {
                try {
                    var xmlHttp = new XMLHttpRequest();
                    xmlHttp.onreadystatechange = function () {
                        if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
                            callback(xmlHttp.responseText);
                        }
                    };
                    xmlHttp.open("GET", theUrl, true); // true for asynchronous
                    xmlHttp.send(null);
                } catch (e) {
                    console.error(e);
                }
            }

            function addGyronormScript() {
                var srcUrl = "https://rawgit.com/dorukeker/gyronorm.js/master/dist/gyronorm.complete.min.js"
                httpGetAsync(srcUrl, function (text) {
                    var script = document.createElement("script");
                    script.setAttribute("src", srcUrl);
                    document.getElementsByTagName("head")[0].appendChild(script);
                });
            }

            var signalling_server_hostname = location.hostname || "192.168.137.112";
            var signalling_server_address = signalling_server_hostname + ':' + (location.port || (location.protocol === 'https:' ? 443 : 80));
            var isFirefox = typeof InstallTrigger !== 'undefined';// Firefox 1.0+

            addEventListener("DOMContentLoaded", function () {
                document.getElementById('signalling_server').value = signalling_server_address;
                var cast_not_allowed = !('MediaSource' in window) || location.protocol !== "https:";
                if (cast_not_allowed || !isFirefox) {
                    if (document.getElementById('cast_tab'))
                        document.getElementById('cast_tab').disabled = true;
                    if (cast_not_allowed) { // chrome supports if run with --enable-usermedia-screen-capturing
                        document.getElementById('cast_screen').disabled = true;
                    }
                    document.getElementById('cast_window').disabled = true;
                    document.getElementById('cast_application').disabled = true;
                    document.getElementById('note2').style.display = "none";
                    document.getElementById('note4').style.display = "none";
                } else {
                    document.getElementById('note1').style.display = "none";
                    document.getElementById('note3').style.display = "none";
                }
                addGyronormScript();
            });

            var ws = null;
            var pc;
            var gn;
            var datachannel, localdatachannel;
            var audio_video_stream;
            var recorder = null;
            var recordedBlobs;
            var pcConfig = {/*sdpSemantics : "plan-b"*,*/ "iceServers": [
                    {"urls": ["stun:stun.l.google.com:19302", "stun:" + signalling_server_hostname + ":3478"]}
                ]};
            var pcOptions = {
                optional: [
                    // Deprecated:
                    //{RtpDataChannels: false},
                    //{DtlsSrtpKeyAgreement: true}
                ]
            };
            var mediaConstraints = {
                optional: [],
                mandatory: {
                    OfferToReceiveAudio: true,
                    OfferToReceiveVideo: true
                }
            };
            var keys = [];
            var trickle_ice = true;
            var remoteDesc = false;
            var iceCandidates = [];

            RTCPeerConnection = window.RTCPeerConnection || /*window.mozRTCPeerConnection ||*/ window.webkitRTCPeerConnection;
            RTCSessionDescription = /*window.mozRTCSessionDescription ||*/ window.RTCSessionDescription;
            RTCIceCandidate = /*window.mozRTCIceCandidate ||*/ window.RTCIceCandidate;
            navigator.getUserMedia = navigator.getUserMedia || navigator.mozGetUserMedia || navigator.webkitGetUserMedia || navigator.msGetUserMedia;
            var URL = window.URL || window.webkitURL;

            function createPeerConnection() {
                try {
                    var pcConfig_ = pcConfig;
                    try {
                        ice_servers = document.getElementById('ice_servers').value;
                        if (ice_servers) {
                            pcConfig_.iceServers = JSON.parse(ice_servers);
                        }
                    } catch (e) {
                        alert(e + "\nExample: "
                                + '\n[ {"urls": "stun:stun1.example.net"}, {"urls": "turn:turn.example.org", "username": "user", "credential": "myPassword"} ]'
                                + "\nContinuing with built-in RTCIceServer array");
                    }
                    console.log(JSON.stringify(pcConfig_));
                    pc = new RTCPeerConnection(pcConfig_, pcOptions);
                    pc.onicecandidate = onIceCandidate;
                    if ('ontrack' in pc) {
                        pc.ontrack = onTrack;
                    } else {
                        pc.onaddstream = onRemoteStreamAdded; // deprecated
                    }
                    pc.onremovestream = onRemoteStreamRemoved;
                    pc.ondatachannel = onDataChannel;
                    console.log("peer connection successfully created!");
                } catch (e) {
                    console.error("createPeerConnection() failed");
                }
            }

            function onDataChannel(event) {
                console.log("onDataChannel()");
                datachannel = event.channel;

                event.channel.onopen = function () {
                    console.log("Data Channel is open!");
                    document.getElementById('datachannels').disabled = false;
                };

                event.channel.onerror = function (error) {
                    console.error("Data Channel Error:", error);
                };

                event.channel.onmessage = function (event) {
                    console.log("Got Data Channel Message:", event.data);
                    document.getElementById('datareceived').value = event.data;
                };

                event.channel.onclose = function () {
                    datachannel = null;
                    document.getElementById('datachannels').disabled = true;
                    console.log("The Data Channel is Closed");
                };
            }

            function onIceCandidate(event) {
                if (event.candidate) {
                    var candidate = {
                        sdpMLineIndex: event.candidate.sdpMLineIndex,
                        sdpMid: event.candidate.sdpMid,
                        candidate: event.candidate.candidate
                    };
                    var request = {
                        what: "addIceCandidate",
                        data: JSON.stringify(candidate)
                    };
                    ws.send(JSON.stringify(request));
                } else {
                    console.log("End of candidates.");
                }
            }

            function addIceCandidates() {
                iceCandidates.forEach(function (candidate) {
                    pc.addIceCandidate(candidate,
                        function () {
                            console.log("IceCandidate added: " + JSON.stringify(candidate));
                        },
                        function (error) {
                            console.error("addIceCandidate error: " + error);
                        }
                    );
                });
                iceCandidates = [];
            }

            function onRemoteStreamAdded(event) {
                console.log("Remote stream added:", event.stream);
                var remoteVideoElement = document.getElementById('remote-video');
                remoteVideoElement.srcObect = event.stream;
                //remoteVideoElement.play();
            }

            function onTrack(event) {
                console.log("Remote track!");
                var remoteVideoElement = document.getElementById('remote-video');
                remoteVideoElement.srcObject = event.streams[0];
                //remoteVideoElement.play();
            }

            function onRemoteStreamRemoved(event) {
                var remoteVideoElement = document.getElementById('remote-video');
                remoteVideoElement.srcObject = null;
                remoteVideoElement.src = ''; // TODO: remove
            }

            function start() {
                if ("WebSocket" in window) {
                    document.getElementById("stop").disabled = false;
                    document.getElementById("start").disabled = true;
                    document.documentElement.style.cursor = 'wait';
                    var server = document.getElementById("signalling_server").value.toLowerCase();

                    var protocol = location.protocol === "https:" ? "wss:" : "ws:";
                    ws = new WebSocket(protocol + '//' + server + '/stream/webrtc');

                    function call(stream) {
                        iceCandidates = [];
                        remoteDesc = false;
                        createPeerConnection();
                        if (stream) {
                            pc.addStream(stream);
                        }
                        var request = {
                            what: "call",
                            options: {
                                force_hw_vcodec: document.getElementById("remote_hw_vcodec").checked,
                                vformat: document.getElementById("remote_vformat").value,
                                trickle_ice: trickleice_selection()
                            }
                        };
                        ws.send(JSON.stringify(request));
                        console.log("call(), request=" + JSON.stringify(request));
                    }

                    ws.onopen = function () {
                        console.log("onopen()");

                        audio_video_stream = null;
                        var cast_mic = document.getElementById("cast_mic").checked;
                        var cast_tab = document.getElementById("cast_tab") ? document.getElementById("cast_tab").checked : false;
                        var cast_camera = document.getElementById("cast_camera").checked;
                        var cast_screen = document.getElementById("cast_screen").checked;
                        var cast_window = document.getElementById("cast_window").checked;
                        var cast_application = document.getElementById("cast_application").checked;
                        var echo_cancellation = document.getElementById("echo_cancellation").checked;
                        var localConstraints = {};
                        if (cast_mic) {
                            if (echo_cancellation)
                                localConstraints['audio'] = isFirefox ? {echoCancellation: true} : {optional: [{echoCancellation: true}]};
                            else
                                localConstraints['audio'] = isFirefox ? {echoCancellation: false} : {optional: [{echoCancellation: false}]};
                        } else if (cast_tab) {
                            localConstraints['audio'] = {mediaSource: "audioCapture"};
                        } else {
                            localConstraints['audio'] = false;
                        }
                        if (cast_camera) {
                            localConstraints['video'] = true;
                        } else if (cast_screen) {
                            if (isFirefox) {
                                localConstraints['video'] = {frameRate: {ideal: 30, max: 30},
                                    //width: {min: 640, max: 960},
                                    //height: {min: 480, max: 720},
                                    mozMediaSource: "screen",
                                    mediaSource: "screen"};
                            } else {
                                // chrome://flags#enable-usermedia-screen-capturing
                                document.getElementById("cast_mic").checked = false;
                                localConstraints['audio'] = false; // mandatory for chrome
                                localConstraints['video'] = {'mandatory': {'chromeMediaSource':'screen'}};
                            }
                        } else if (cast_window)
                            localConstraints['video'] = {frameRate: {ideal: 30, max: 30},
                                //width: {min: 640, max: 960},
                                //height: {min: 480, max: 720},
                                mozMediaSource: "window",
                                mediaSource: "window"};
                        else if (cast_application)
                            localConstraints['video'] = {frameRate: {ideal: 30, max: 30},
                                //width: {min: 640, max: 960},
                                //height:  {min: 480, max: 720},
                                mozMediaSource: "application",
                                mediaSource: "application"};
                        else
                            localConstraints['video'] = false;

                        var localVideoElement = document.getElementById('local-video');
                        if (localConstraints.audio || localConstraints.video) {
                            if (navigator.getUserMedia) {
                                navigator.getUserMedia(localConstraints, function (stream) {
                                    audio_video_stream = stream;
                                    call(stream);
                                    localVideoElement.muted = true;
                                    localVideoElement.srcObject = stream;
                                    localVideoElement.play();
                                }, function (error) {
                                    stop();
                                    alert("An error has occurred. Check media device, permissions on media and origin.");
                                    console.error(error);
                                });
                            } else {
                                console.log("getUserMedia not supported");
                            }
                        } else {
                            call();
                        }
                    };

                    ws.onmessage = function (evt) {
                        var msg = JSON.parse(evt.data);
                        if (msg.what !== 'undefined') {
                            var what = msg.what;
                            var data = msg.data;
                        }
                        //console.log("message=" + msg);
                        console.log("message =" + what);

                        switch (what) {
                            case "offer":
                                pc.setRemoteDescription(new RTCSessionDescription(JSON.parse(data)),
                                        function onRemoteSdpSuccess() {
                                            remoteDesc = true;
                                            addIceCandidates();
                                            console.log('onRemoteSdpSucces()');
                                            pc.createAnswer(function (sessionDescription) {
                                                pc.setLocalDescription(sessionDescription);
                                                var request = {
                                                    what: "answer",
                                                    data: JSON.stringify(sessionDescription)
                                                };
                                                ws.send(JSON.stringify(request));
                                                console.log(request);

                                            }, function (error) {
                                                alert("Failed to createAnswer: " + error);

                                            }, mediaConstraints);
                                        },
                                        function onRemoteSdpError(event) {
                                            alert('Failed to set remote description (unsupported codec on this browser?): ' + event);
                                            stop();
                                        }
                                );

                                /*
                                 * No longer needed, it's implicit in "call"
                                var request = {
                                    what: "generateIceCandidates"
                                };
                                console.log(request);
                                ws.send(JSON.stringify(request));
                                */
                                break;

                            case "answer":
                                break;

                            case "message":
                                alert(msg.data);
                                break;

                            case "iceCandidate": // when trickle is enabled
                                if (!msg.data) {
                                    console.log("Ice Gathering Complete");
                                    break;
                                }
                                var elt = JSON.parse(msg.data);
                                let candidate = new RTCIceCandidate({sdpMLineIndex: elt.sdpMLineIndex, candidate: elt.candidate});
                                iceCandidates.push(candidate);
                                if (remoteDesc)
                                    addIceCandidates();
                                document.documentElement.style.cursor = 'default';
                                break;

                            case "iceCandidates": // when trickle ice is not enabled
                                var candidates = JSON.parse(msg.data);
                                for (var i = 0; candidates && i < candidates.length; i++) {
                                    var elt = candidates[i];
                                    let candidate = new RTCIceCandidate({sdpMLineIndex: elt.sdpMLineIndex, candidate: elt.candidate});
                                    iceCandidates.push(candidate);
                                }
                                if (remoteDesc)
                                    addIceCandidates();
                                document.documentElement.style.cursor = 'default';
                                break;
                        }
                    };

                    ws.onclose = function (evt) {
                        if (pc) {
                            pc.close();
                            pc = null;
                        }
                        document.getElementById("stop").disabled = true;
                        document.getElementById("start").disabled = false;
                        document.documentElement.style.cursor = 'default';
                    };

                    ws.onerror = function (evt) {
                        alert("An error has occurred!");
                        ws.close();
                    };

                } else {
                    alert("Sorry, this browser does not support WebSockets.");
                }
            }

            function stop() {
                if (datachannel) {
                    console.log("closing data channels");
                    datachannel.close();
                    datachannel = null;
                    document.getElementById('datachannels').disabled = true;
                }
                if (localdatachannel) {
                    console.log("closing local data channels");
                    localdatachannel.close();
                    localdatachannel = null;
                }
                if (audio_video_stream) {
                    try {
                        if (audio_video_stream.getVideoTracks().length)
                            audio_video_stream.getVideoTracks()[0].stop();
                        if (audio_video_stream.getAudioTracks().length)
                            audio_video_stream.getAudioTracks()[0].stop();
                        audio_video_stream.stop(); // deprecated
                    } catch (e) {
                        for (var i = 0; i < audio_video_stream.getTracks().length; i++)
                            audio_video_stream.getTracks()[i].stop();
                    }
                    audio_video_stream = null;
                }
                stop_record();
                document.getElementById('remote-video').srcObject = null;
                document.getElementById('local-video').srcObject = null;
                document.getElementById('remote-video').src = ''; // TODO; remove
                document.getElementById('local-video').src = ''; // TODO: remove
                if (pc) {
                    pc.close();
                    pc = null;
                }
                if (ws) {
                    ws.close();
                    ws = null;
                }
                document.getElementById("stop").disabled = true;
                document.getElementById("start").disabled = false;
                document.documentElement.style.cursor = 'default';
            }

            function mute() {
                var remoteVideo = document.getElementById("remote-video");
                remoteVideo.muted = !remoteVideo.muted;
            }

            function pause() {
                var remoteVideo = document.getElementById("remote-video");
                if (remoteVideo.paused)
                    remoteVideo.play();
                else
                    remoteVideo.pause();
            }

            function fullscreen() {
                var remoteVideo = document.getElementById("remote-video");
                if (remoteVideo.requestFullScreen) {
                    remoteVideo.requestFullScreen();
                } else if (remoteVideo.webkitRequestFullScreen) {
                    remoteVideo.webkitRequestFullScreen();
                } else if (remoteVideo.mozRequestFullScreen) {
                    remoteVideo.mozRequestFullScreen();
                }
            }

            function handleDataAvailable(event) {
                //console.log(event);
                if (event.data && event.data.size > 0) {
                    recordedBlobs.push(event.data);
                }
            }

            function handleStop(event) {
                console.log('Recorder stopped: ', event);
                document.getElementById('record').innerHTML = 'Start Recording';
                recorder = null;
                var superBuffer = new Blob(recordedBlobs, {type: 'video/webm'});
                var recordedVideoElement = document.getElementById('recorded-video');
                recordedVideoElement.src = URL.createObjectURL(superBuffer);
            }

            function discard_recording() {
                var recordedVideoElement = document.getElementById('recorded-video');
                recordedVideoElement.srcObject = null;
                recordedVideoElement.src = '';
            }

            function stop_record() {
                if (recorder) {
                    recorder.stop();
                    console.log("recording stopped");
                    document.getElementById('record-detail').open = true;
                }
            }

            function startRecording(stream) {
                recordedBlobs = [];
                var options = {mimeType: 'video/webm;codecs=vp9'};
                if (!MediaRecorder.isTypeSupported(options.mimeType)) {
                    console.log(options.mimeType + ' is not Supported');
                    options = {mimeType: 'video/webm;codecs=vp8'};
                    if (!MediaRecorder.isTypeSupported(options.mimeType)) {
                        console.log(options.mimeType + ' is not Supported');
                        options = {mimeType: 'video/webm;codecs=h264'};
                        if (!MediaRecorder.isTypeSupported(options.mimeType)) {
                            console.log(options.mimeType + ' is not Supported');
                            options = {mimeType: 'video/webm'};
                            if (!MediaRecorder.isTypeSupported(options.mimeType)) {
                                console.log(options.mimeType + ' is not Supported');
                                options = {mimeType: ''};
                            }
                        }
                    }
                }
                try {
                    recorder = new MediaRecorder(stream, options);
                } catch (e) {
                    console.error('Exception while creating MediaRecorder: ' + e);
                    alert('Exception while creating MediaRecorder: ' + e + '. mimeType: ' + options.mimeType);
                    return;
                }
                console.log('Created MediaRecorder', recorder, 'with options', options);
                //recorder.ignoreMutedMedia = true;
                recorder.onstop = handleStop;
                recorder.ondataavailable = handleDataAvailable;
                recorder.onwarning = function (e) {
                    console.log('Warning: ' + e);
                };
                recorder.start();
                console.log('MediaRecorder started', recorder);
            }

            function start_stop_record() {
                if (pc && !recorder) {
                    var streams = pc.getRemoteStreams();
                    if (streams.length) {
                        console.log("starting recording");
                        startRecording(streams[0]);
                        document.getElementById('record').innerHTML = 'Stop Recording';
                    }
                } else {
                    stop_record();
                }
            }

            function download() {
                if (recordedBlobs !== undefined) {
                    var blob = new Blob(recordedBlobs, {type: 'video/webm'});
                    var url = window.URL.createObjectURL(blob);
                    var a = document.createElement('a');
                    a.style.display = 'none';
                    a.href = url;
                    a.download = 'video.webm';
                    document.body.appendChild(a);
                    a.click();
                    setTimeout(function () {
                        document.body.removeChild(a);
                        window.URL.revokeObjectURL(url);
                    }, 100);
                }
            }

            function remote_hw_vcodec_selection() {
                if (!document.getElementById('remote_hw_vcodec').checked)
                    unselect_remote_hw_vcodec();
                else
                    select_remote_hw_vcodec();
            }

            function remote_hw_vcodec_format_selection() {
                if (document.getElementById('remote_hw_vcodec').checked)
                    remote_hw_vcodec_selection();
            }

            function select_remote_hw_vcodec() {
                document.getElementById('remote_hw_vcodec').checked = true;
                var vformat = document.getElementById('remote_vformat').value;
                switch (vformat) {
                    case '5':
                        document.getElementById('remote-video').style.width = "320px";
                        document.getElementById('remote-video').style.height = "240px";
                        break;
                    case '10':
                        document.getElementById('remote-video').style.width = "320px";
                        document.getElementById('remote-video').style.height = "240px";
                        break;
                    case '20':
                        document.getElementById('remote-video').style.width = "352px";
                        document.getElementById('remote-video').style.height = "288px";
                        break;
                    case '25':
                        document.getElementById('remote-video').style.width = "640px";
                        document.getElementById('remote-video').style.height = "480px";
                        break;
                    case '30':
                        document.getElementById('remote-video').style.width = "640px";
                        document.getElementById('remote-video').style.height = "480px";
                        break;
                    case '35':
                        document.getElementById('remote-video').style.width = "800px";
                        document.getElementById('remote-video').style.height = "480px";
                        break;
                    case '40':
                        document.getElementById('remote-video').style.width = "960px";
                        document.getElementById('remote-video').style.height = "720px";
                        break;
                    case '50':
                        document.getElementById('remote-video').style.width = "1024px";
                        document.getElementById('remote-video').style.height = "768px";
                        break;
                    case '55':
                        document.getElementById('remote-video').style.width = "1280px";
                        document.getElementById('remote-video').style.height = "720px";
                        break;
                    case '60':
                        document.getElementById('remote-video').style.width = "1280px";
                        document.getElementById('remote-video').style.height = "720px";
                        break;
                    case '63':
                        document.getElementById('remote-video').style.width = "1280px";
                        document.getElementById('remote-video').style.height = "720px";
                        break;
                    case '65':
                        document.getElementById('remote-video').style.width = "1280px";
                        document.getElementById('remote-video').style.height = "768px";
                        break;
                    case '70':
                        document.getElementById('remote-video').style.width = "1280px";
                        document.getElementById('remote-video').style.height = "768px";
                        break;
                    case '75':
                        document.getElementById('remote-video').style.width = "1536px";
                        document.getElementById('remote-video').style.height = "768px";
                        break;
                    case '80':
                        document.getElementById('remote-video').style.width = "1280px";
                        document.getElementById('remote-video').style.height = "960px";
                        break;
                    case '90':
                        document.getElementById('remote-video').style.width = "1600px";
                        document.getElementById('remote-video').style.height = "768px";
                        break;
                    case '95':
                        document.getElementById('remote-video').style.width = "1640px";
                        document.getElementById('remote-video').style.height = "1232px";
                        break;
                    case '97':
                        document.getElementById('remote-video').style.width = "1640px";
                        document.getElementById('remote-video').style.height = "1232px";
                        break;
                    case '98':
                        document.getElementById('remote-video').style.width = "1792px";
                        document.getElementById('remote-video').style.height = "896px";
                        break;
                    case '99':
                        document.getElementById('remote-video').style.width = "1792px";
                        document.getElementById('remote-video').style.height = "896px";
                        break;
                    case '100':
                        document.getElementById('remote-video').style.width = "1920px";
                        document.getElementById('remote-video').style.height = "1080px";
                        break;
                    case '105':
                        document.getElementById('remote-video').style.width = "1920px";
                        document.getElementById('remote-video').style.height = "1080px";
                        break;
                    default:
                        document.getElementById('remote-video').style.width = "1280px";
                        document.getElementById('remote-video').style.height = "720px";
                }
                /*
                 // Disable video casting. Not supported at the moment with hw codecs.
                 var elements = document.getElementsByName('video_cast');
                 for(var i = 0; i < elements.length; i++) {
                 elements[i].checked = false;
                 }
                 */
            }

            function unselect_remote_hw_vcodec() {
                document.getElementById('remote_hw_vcodec').checked = false;
                document.getElementById('remote-video').style.width = "640px";
                document.getElementById('remote-video').style.height = "480px";
            }

            function singleselection(name, id) {
                var old = document.getElementById(id).checked;
                var elements = document.getElementsByName(name);
                for (var i = 0; i < elements.length; i++) {
                    elements[i].checked = false;
                }
                document.getElementById(id).checked = old ? true : false;
                /*
                 // Disable video hw codec. Not supported at the moment when casting.
                 if (name === 'video_cast') {
                 unselect_remote_hw_vcodec();
                 }
                 */
            }

            function send_message() {
                var msg = document.getElementById('datamessage').value;
                datachannel.send(msg);
                console.log("message sent: ", msg);
            }

            function create_localdatachannel() {
                if (pc && localdatachannel)
                    return;
                localdatachannel = pc.createDataChannel('datachannel');
                localdatachannel.onopen = function(event) {
                    if (localdatachannel.readyState === "open") {
                        localdatachannel.send("datachannel created!");
                    }
                };
                console.log("data channel created");
            }

            function close_localdatachannel() {
                if (localdatachannel) {
                    localdatachannel.close();
                    localdatachannel = null;
                }
                console.log("local data channel closed");
            }

            function handleOrientation(event) {
                var data = {
                    "do": {
                        "alpha": event.alpha.toFixed(1), // In degree in the range [0,360]
                        "beta": event.beta.toFixed(1), // In degree in the range [-180,180]
                        "gamma": event.gamma.toFixed(1), // In degree in the range [-90,90]
                        "absolute": event.absolute
                    }
                };
                if (datachannel)
                    datachannel.send(JSON.stringify(data));
            }

            function isGyronormPresent() {
                var url = "gyronorm.complete.min.js";
                var scripts = document.getElementsByTagName('script');
                for (var i = scripts.length; i--; ) {
                    if (scripts[i].src.indexOf(url) > -1)
                        return true;
                }
                return false;
            }

            function handleGyronorm(data) {
                // Process:
                // data.do.alpha    ( deviceorientation event alpha value )
                // data.do.beta     ( deviceorientation event beta value )
                // data.do.gamma    ( deviceorientation event gamma value )
                // data.do.absolute ( deviceorientation event absolute value )

                // data.dm.x        ( devicemotion event acceleration x value )
                // data.dm.y        ( devicemotion event acceleration y value )
                // data.dm.z        ( devicemotion event acceleration z value )

                // data.dm.gx       ( devicemotion event accelerationIncludingGravity x value )
                // data.dm.gy       ( devicemotion event accelerationIncludingGravity y value )
                // data.dm.gz       ( devicemotion event accelerationIncludingGravity z value )

                // data.dm.alpha    ( devicemotion event rotationRate alpha value )
                // data.dm.beta     ( devicemotion event rotationRate beta value )
                // data.dm.gamma    ( devicemotion event rotationRate gamma value )
                if (datachannel && document.getElementById('orientationsend').checked)
                    datachannel.send(JSON.stringify(data));
            }

            function orientationsend_selection() {
                if (document.getElementById('orientationsend').checked) {
                    if (isGyronormPresent()) {
                        console.log("gyronorm.js library found!");
                        if (gn) {
                            gn.setHeadDirection();
                            return;
                        }
                        try {
                            gn = new GyroNorm();
                        } catch (e) {
                            console.log(e);
                            document.getElementById('orientationsend').checked = false;
                            return;
                        }
                        var args = {
                            frequency: 60, // ( How often the object sends the values - milliseconds )
                            gravityNormalized: true, // ( If the gravity related values to be normalized )
                            orientationBase: GyroNorm.GAME, // ( Can be GyroNorm.GAME or GyroNorm.WORLD. gn.GAME returns orientation values with respect to the head direction of the device. gn.WORLD returns the orientation values with respect to the actual north direction of the world. )
                            decimalCount: 1, // ( How many digits after the decimal point will there be in the return values )
                            logger: null, // ( Function to be called to log messages from gyronorm.js )
                            screenAdjusted: false            // ( If set to true it will return screen adjusted values. )
                        };
                        gn.init(args).then(function () {
                            gn.start(handleGyronorm);
                            gn.setHeadDirection(); // only with gn.GAME
                        }).catch(function (e) {
                            console.log("DeviceOrientation or DeviceMotion might not be supported by this browser or device");
                        });
                    }
                    if (!gn) {
                        window.addEventListener('deviceorientation', handleOrientation, true);
                        console.log("gyronorm.js library not found, using defaults");
                    }
                } else {
                    if (!gn) {
                        window.removeEventListener('deviceorientation', handleOrientation, true);
                    }
                }
            }

            function getKeycodesArray(arr) {
                var newArr = new Array();
                for (var i = 0; i < arr.length; i++) {
                    if (typeof arr[i] == "number") {
                        newArr[newArr.length] = arr[i];
                    }
                }
                return newArr;
            }

            function convertKeycodes(arr) {
                var map = {
                    /*Space*/ 32: 57,
                    /*Enter*/13: 28,
                    /*Tab*/ 9: 15,
                    /*Esc*/27: 1,
                    /*Backspace*/8: 14,
                    /*Shift*/16: 42,
                    /*Control*/ 17: 29,
                    /*Alt Left*/ 18: 56,
                    /*Alt Right*/ 225: 100,
                    /*Caps Lock*/ 20: 58,
                    /*Num Lock*/ 144: 69,
                    /*a*/ 65: 30,
                    /*b*/ 66: 48,
                    /*c*/ 67: 46,
                    /*d*/ 68: 32,
                    /*e*/ 69: 18,
                    /*f*/ 70: 33,
                    /*g*/ 71: 34,
                    /*h*/ 72: 35,
                    /*i*/ 73: 23,
                    /*j*/ 74: 36,
                    /*k*/ 75: 37,
                    /*l*/ 76: 38,
                    /*m*/ 77: 50,
                    /*n*/ 78: 49,
                    /*o*/ 79: 24,
                    /*p*/ 80: 25,
                    /*q*/ 81: 16,
                    /*r*/ 82: 19,
                    /*s*/ 83: 31,
                    /*t*/ 84: 20,
                    /*u*/ 85: 22,
                    /*v*/ 86: 47,
                    /*w*/ 87: 17,
                    /*x*/ 88: 45,
                    /*y*/ 89: 21,
                    /*z*/ 90: 44,
                    /*1*/ 49: 2,
                    /*2*/ 50: 3,
                    /*3*/ 51: 4,
                    /*4*/ 52: 5,
                    /*5*/ 53: 6,
                    /*6*/ 54: 7,
                    /*7*/ 55: 8,
                    /*8*/ 56: 9,
                    /*9*/ 57: 10,
                    /*0*/ 48: 11,
                    /*; (firefox)*/ 59: 39,
                    /*; (chrome)*/ 186: 39,
                    /*=(firefox)*/ 61: 13,
                    /*=(chrome)*/ 187: 13,
                    /*,*/ 188: 51,
                    /*-(minus in firefox)*/ 173: 12,
                    /*-(dash in chrome)*/ 189: 12,
                    /*.*/ 190: 52,
                    /*/*/ 191: 53,
                    /*`*/ 192: 41,
                    /*{*/ 219: 26,
                    /*\*/ 220: 43,
                    /*}*/ 221: 27,
                    /*'*/ 222: 40,
                    /*left-arrow*/ 37: 105,
                    /*up-arrow*/ 38: 103,
                    /*right-arrow*/ 39: 106,
                    /*down-arrow*/ 40: 108,
                    /*Insert*/ 45: 110,
                    /*Delete*/ 46: 111,
                    /*Home*/ 36: 102,
                    /*End*/ 35: 107,
                    /*Page Up*/ 33: 104,
                    /*Page Down*/ 34: 109,
                    /*F1 */ 112: 59,
                    /*F2 */ 113: 60,
                    /*F3 */ 114: 61,
                    /*F4 */ 115: 62,
                    /*F5 */ 116: 63,
                    /*F6 */ 117: 64,
                    /*F7 */ 118: 65,
                    /*F8 */ 119: 66,
                    /*F9 */ 120: 67,
                    /*F10 */ 121: 68,
                    /*F11 */ 122: 87,
                    /*F12 */ 123: 88,
                    /*. Del*/ 110: 83,
                    /*0 Ins*/ 96: 82,
                    /*1 End*/ 97: 79,
                    /*2 down-arrow*/ 98: 80,
                    /*3 Pg Dn*/ 99: 81,
                    /*4 left-arrow*/ 100: 75,
                    /*5*/ 101: 76,
                    /*6 right-arrow*/ 102: 77,
                    /*7 Home*/    103: 71,
                    /*8 up-arrow*/ 104: 72,
                    /*9 Pg Up*/ 105: 73,
                    /*+*/ 107: 78,
                    /*-*/ 109: 74,
                    /***/ 106: 55,
                    /*/*/ 111: 98,
                    /*Keypad Enter*/ 13: 28
                };
                var convertedKeys = [];
                arr.forEach(function (a) {
                    if (map[a] !== undefined)
                        convertedKeys.push(map[a]);
                    //else
                    //    convertedKeys.push(a);
                });
                return convertedKeys;
            }

            function convertCharCode(ch) {
                var arr = [];
                if (ch >= 48 && ch <= 57) { /* 0..9 */
                    arr[0] = ch;
                    arr = convertKeycodes(arr);
                } else if (ch >= 97 && ch <= 122) { /* a..z */
                    arr[0] = ch - 32;
                    arr = convertKeycodes(arr);
                } else if (ch >= 65 && ch <= 90) { /* A..Z */
                    arr[0] = 16;
                    arr[1] = ch;
                    arr = convertKeycodes(arr);
                } else if (ch == 46) { // .
                    arr[0] = 52;
                } else if (ch == 33) { // !
                    arr[0] = 42;
                    arr[1] = 2;
                } else if (ch == 63) { // ?
                    arr[0] = 42;
                    arr[1] = 53;
                } else if (ch == 44) { // ,
                    arr[0] = 51;
                } else if (ch == 34) { // "
                    arr[0] = 42;
                    arr[1] = 40;
                } else if (ch == 39) { // '
                    arr[0] = 40;
                } else if (ch == 58) { // :
                    arr[0] = 42;
                    arr[1] = 39;
                } else if (ch == 40) { // (
                    arr[0] = 42;
                    arr[1] = 10;
                } else if (ch == 41) { // )
                    arr[0] = 42;
                    arr[1] = 11;
                } else if (ch == 126) { // ~
                    arr[0] = 42;
                    arr[1] = 41;
                } else if (ch == 42) { // *
                    arr[0] = 42;
                    arr[1] = 9;
                } else if (ch == 45) { // -
                    arr[0] = 12;
                } else if (ch == 47) { // /
                    arr[0] = 53;
                } else if (ch == 64) { // @
                    arr[0] = 42;
                    arr[1] = 3;
                } else if (ch == 95) { // _
                    arr[0] = 42;
                    arr[1] = 12;
                }
                return arr;
            }

            function toKeyCode() {
                var getCharCode = function (str) {
                    return str.charCodeAt(str.length - 1);
                };
                var cc = getCharCode(this.value);
                document.getElementById("datamessage").removeEventListener("keyup", toKeyCode);
                this.value = "";
                var keysArray = convertCharCode(cc);
                if (datachannel && document.getElementById('keypresssend').checked && keysArray.length) {
                    var keycodes = {
                        keycodes: keysArray
                    };
                    datachannel.send(JSON.stringify(keycodes));
                }
            }
            ;

            function keydown(e) {
                if (e.keyCode == 0 || e.keyCode == 229) { // on mobile
                    return;
                }
                e.preventDefault();
                e.stopPropagation();
                e.stopImmediatePropagation();
                keys[e.keyCode] = e.keyCode;
                for (var i = keys.length; i >= 0; i--) {
                    if (keys[i] !== 16 && keys[i] !== 17 && keys[i] !== 18 && keys[i] !== 225 && keys[i] !== e.keyCode)
                        keys[i] = false;
                }
                var keysArray = convertKeycodes(getKeycodesArray(keys));
                if (datachannel && document.getElementById('keypresssend').checked && keysArray.length) {
                    var keycodes = {
                        keycodes: keysArray
                    };
                    datachannel.send(JSON.stringify(keycodes));
                }
            }
            ;

            function keyup(e) {
                if (e.keyCode == 0 || e.keyCode == 229) { // on mobile
                    document.getElementById("datamessage").addEventListener("keyup", toKeyCode);
                    return;
                }
                e.preventDefault();
                e.stopPropagation();
                e.stopImmediatePropagation();
                keys[e.keyCode] = false;
            }
            ;

            function keypresssend_selection() {
                if (document.getElementById('keypresssend').checked) {
                    window.addEventListener('keydown', keydown, true);
                    window.addEventListener('keyup', keyup, true);
                } else {
                    keys = [];
                    window.removeEventListener('keydown', keydown, true);
                    window.removeEventListener('keyup', keyup, true);
                }
            }

            function trickleice_selection() {
                if (document.getElementById('trickleice').value === "false") {
                    trickle_ice = false;
                } else if (document.getElementById('trickleice').value === "true") {
                    trickle_ice = true;
                } else {
                    trickle_ice = null;
                }
                return trickle_ice;
            }

            window.onload = function () {
                if (window.MediaRecorder === undefined) {
                    document.getElementById('record').disabled = true;
                }
                if (false) {
                    start();
                }
            };

            window.onbeforeunload = function () {
                if (ws) {
                    ws.onclose = function () {}; // disable onclose handler first
                    stop();
                }
            };

        </script>
        <style>
            #container {
                display: flex;
                flex-flow: row nowrap;
                align-items: flex-end;
            }
            video {
                background: #eee none repeat scroll 0 0;
                border: 1px solid #aaa;
            }
            .overlayWrapper {
                position: relative;
            }
            .overlayWrapper .overlay {
                position: absolute;
                top: 0;
                left: 5px;
            }
            p {
                margin: 0.125em;
            }
            small {
                font-size: smaller;
            }
        </style>
    </head>
    <body>
        <h1>
            <span>WebRTC two-way Audio/Video/Data Intercom & Recorder</span>
        </h1>
        <h3 style="color:red" >
            <span>WARNING! Some browsers do not allow to access local media on insecure origins.</span>
            <span>Consider switching the UV4L Streaming Server to secure HTTPS instead.</span>
        </h3>
        <div id="container">
            <div class="overlayWrapper">
                <video id="remote-video" autoplay="" width="640" height="480">
                    Your browser does not support the video tag.
                </video>
                <p class="overlay">remote</p>
            </div>
            <div class="overlayWrapper">
                <video id="local-video" autoplay="" width="320" height="240">
                    Your browser does not support the video tag.
                </video>
                <p class="overlay">local</p>
            </div>
        </div>
        <div id="controls">
            <button type=button id="pause" onclick="pause();" title="pause or resume local player">Pause/Resume</button>
            <button type=button id="mute" onclick="mute();" title="mute or unmute remote audio source">Mute/Unmute</button>
            <button type=button id="fullscreen" onclick="fullscreen();">Fullscreen</button>
            <button type=button id="record" onclick="start_stop_record();" title="start or stop recording audio/video">Start Recording</button>
        </div>
        <fieldset>
            <legend><b>Remote peer options</b></legend>
            <div>
                <span>Video:</span>
                <label><input type="checkbox" onclick="remote_hw_vcodec_selection();"  id="remote_hw_vcodec" name="remote_hw_vcodec" value="remote_hw_vcodec" title="try to force the use of the hardware codec for both encoding and decoding if enabled and supported">force use of hardware codec for</label>
                <select id="remote_vformat" name="remote_vformat" onclick="remote_hw_vcodec_format_selection();" title="available resolutions and frame rates at the min., max. and start configured bitrates for adaptive streaming which will be scaled from the base 720p 30fps">
                    <option value="5">320x240 15 fps</option>
                    <option value="10">320x240 30 fps</option>
                    <option value="20">352x288 30 fps</option>
                    <option value="25">640x480 15 fps</option>
                    <option value="30">640x480 30 fps</option>
                    <option value="35">800x480 30 fps</option>
                    <option value="40">960x720 30 fps</option>
                    <option value="50">1024x768 30 fps</option>
                    <option value="55">1280x720 15 fps</option>
                    <option value="60" selected="selected">1280x720 30 fps, kbps min.800 max.4000 start1200</option>
                    <option value="63">1280x720 60 fps</option>
                    <option value="65">1280x768 15 fps</option>
                    <option value="70">1280x768 30 fps</option>
                    <option value="75">1536x768 30 fps</option>
                    <option value="80">1280x960 30 fps</option>
                    <option value="90">1600x768 30 fps</option>
                    <option value="95">1640x1232 15 fps</option>
                    <option value="97">1640x1232 30 fps</option>
                    <option value="98">1792x896 15 fps</option>
                    <option value="99">1792x896 30 fps</option>
                    <option value="100">1920x1080 15 fps</option>
                    <option value="105">1920x1080 30 fps</option>
                </select>
                <p id="note1_"><small>NOTE: if your browser does not support the hardware codec yet, try Firefox with the codec plugin enabled or a recent version of Chrome.</small></p>
            </div>
        </fieldset>
        <div>
            <details id="record-detail">
                <summary><b>Recorded Audio/Video stream</b></summary>
                <div>
                    <div class="overlayWrapper">
                        <video id="recorded-video" controls>
                            Your browser does not support the video tag.
                        </video>
                        <p class="overlay">recorded</p>
                    </div>
                </div>
                <div>
                    <p><small>NOTE: some old Chrome version may generate corrupted video if the audio track is not present as well (use Firefox in this case)</small></p>
                    <button type=button id="discard" onclick="discard_recording();" title="discard recorded audio/video">Discard</button>
                    <button type=button id="download" onclick="download();" title="save recorded audio/video">Save as</button>
                </div>
            </details>
        </div><br>
        <fieldset>
            <legend><b>Cast local Audio/Video sources to remote peer</b></legend>
            <div>
                <span>Audio:</span>
                <label><input type="checkbox" onclick="singleselection('audio_cast', 'cast_mic');" id="cast_mic" name="audio_cast" value="microphone">microphone/other input</label>
                <label><input type="checkbox" id="echo_cancellation" name="audio_processing" title="disable any audio processing when casting music" checked>echo cancellation</label>
                <!--label><input type="checkbox" onclick="singleselection('audio_cast', 'cast_tab');" id="cast_tab" name="audio_cast" value="system">tab</label-->
            </div>
            <div>
                <span>Video:</span>
                <label><input type="checkbox" onclick="singleselection('video_cast', 'cast_camera');" id="cast_camera" name="video_cast" value="camera">camera</label>
                <label><input type="checkbox" onclick="singleselection('video_cast', 'cast_screen');" id="cast_screen" name="video_cast" value="screen">screen</label>
                <label><input type="checkbox" onclick="singleselection('video_cast', 'cast_window');" id="cast_window" name="video_cast" value="screen">window</label>
                <label><input type="checkbox" onclick="singleselection('video_cast', 'cast_application');" id="cast_application" name="video_cast" value="application">application</label>
            </div>
            <div>
                <p id="note1"><small>NOTE: camera and screen can be casted over HTTPS only in Chrome. For the screen the --enable-usermedia-screen-capturing flag must be set. window or application casting is only supported in Firefox 44 on.</small></p>
                <p id="note2"><small>NOTE: except for camera, to enable screen, window or application casting open <i>about:config</i> URL
                        and set <i>media.getusermedia.screensharing.enabled</i> to <i>true</i>
                        and permanently add the current domain to the list in <i>media.getusermedia.screensharing.allowed_domains.</i></small>
                </p>
                <p id="note3"><small>NOTE: if you want to cast music, for better audio quality disable <i>echo-cancellation.</i></small></p>
                <p id="note4"><small>NOTE: if you want to cast music, for better audio quality disable <i>echo-cancellation</i>,
                        and <i>aec</i>, <i>noise-suppression</i>, <i>agc</i> in the browser configuration <i>(about:config).</i></small>
                </p>
            </div>
        </fieldset>
        <div>
            <details>
                <summary><b>Data Channels</b></summary>
                <fieldset id="datachannels" disabled>
                    <span>message: </span><input type="text" id="datamessage" value="" title="message to send to the remote peer"/>
                    <button id="datasend" onclick="send_message();">Send</button>
                    <span>received: </span><input type="text" readonly="readonly" id="datareceived" size="40" title="data received from the remote peer"/><br>
                    <label><input type="checkbox" onclick="orientationsend_selection();" id="orientationsend" name="orientationsend" title="send device orientation angles when they change">send device orientation angles alpha, beta, gamma</label>
                    <label><input type="checkbox" onclick="keypresssend_selection();" id="keypresssend" name="keypresssend" title="send keyboard events. Assume US layout. For users with virtual keyboard: put the focus on the 'message' input text item.">send key codes (US layout)</label>
                    <label><input type="checkbox" onclick="alert('not implemented yet');" id="mousesend" name="mousesend" title="send mouse events">send mouse events</label>
                </fieldset>
                <!--fieldset id="localdatachannels">
                    <button id="datacreate" onclick="create_localdatachannel();">Create</button>
                    <button id="dataclose" onclick="close_localdatachannel();">Close</button>
                </fieldset-->
            </details>
        </div><br>
        <div id="commands">
            <details open>
                <summary><b>Advanced options</b></summary>
                <fieldset>
                    <span>Remote Peer/Signalling Server Address: </span><input required type="text" id="signalling_server" value="192.168.137.112:8080" title="<host>:<port>, default address is autodetected"/><br>
                    <span>Optional ICE Servers (STUN/TURN): </span><input type="text" id="ice_servers" value="" title="array of RTCIceServer objects as valid JSON string"/><br>
                    <span>Trickle ICE: </span>
                    <select onclick="trickleice_selection();" id="trickleice" name="trickleice" title="enable trickle ice">
                        <option value="auto">auto</option>
                        <option value="true" selected="selected">true</option>
                        <option value="false">false</option>
                    </select>
                </fieldset>
            </details>
            <button id="start" style="background-color: green; color: white" onclick="start();">Call!</button>
            <button disabled id="stop" style="background-color: red; color: white" onclick="stop();">Hang up</button>
        </div><br>
        <a target="_top" href="/">home</a>&nbsp;<a href="/panel" target="_blank" title="change the image settings on-the-fly">control panel</a>
    </body>
</html>
jikjoo commented 4 years ago

같은 public network(공유기) 안에서는 스트리밍이 되는데, public network가 다를 땐, 스트리밍이 안됨. remote signaling server를 연결할 때 문제가 있어서 소켓통신이 안되는 거 같음.


해결하기 위해서

  1. 만나서 remote peer 서버에 다양한 값들을 넣어보거나,
  2. 방울 서버를 signaling 서버로 설정하고, 소켓을 간접 연결해주는 방식이 있을 거같음
    • 이 방식으로 할 때는, room을 나누는 거에 문제가 있을 수 있음. 기기 하나만 있으면 상관 없는 문제임.
jikjoo commented 4 years ago

KakaoTalk_20200829_224153605

jikjoo commented 4 years ago

@Chaeoon-Park 이 보고하는 거

https://ohcoach.tistory.com/17

Python simple-websocket-server를 이용해서 웹소켓 서버를 만들고,

클라이언트에서는 getUserMedia로 이미지 따와서 보내기.

webRTC용 연결인 Peer 연결(ICE 서버 이용 등)은 안 되어있고,

서버에서 이미지를 받으면 decode 해서 바로 출력하는 코드

jikjoo commented 4 years ago

@Chaeoon-Park

서버에서 socket.io 로그 확인해봤는데,

처음에 연결할때만 신호 주고 받고,

영상 연결하고 나서는 신호 나온거 없었음.