Example #1
0
        public static Task addIceCandidate(this RTCPeerConnection that, RTCIceCandidate candidate)
        {
            var x = new TaskCompletionSource <object>();

            that.addIceCandidate(candidate,
                                 successCallback: new Action(delegate { x.SetResult(null); }),
                                 failureCallback: err => { throw new Exception(new { err }.ToString()); }
                                 );

            return(x.Task);
        }
Example #2
0
 public void addIceCandidate(RTCIceCandidate candidate, IFunction successCallback, Action <object> failureCallback)
 {
 }
        /// <summary>
        /// This is a javascript application.
        /// </summary>
        /// <param name="page">HTML document rendered by the web server which can now be enhanced.</param>
        public Application(IApp page)
        {
            Native.body.Clear();

            // X:\jsc.svn\examples\javascript\p2p\RTCDataChannelExperiment\RTCDataChannelExperiment\Application.cs

            // http://caniuse.com/#feat=rtcpeerconnection
            // http://iswebrtcreadyyet.com/

            new IHTMLPre
            {
                new { flightcheck = "are you using chrome40? open on android first?",
                Native.window.navigator.userAgent
                }
            }.AttachToDocument();

            // X:\jsc.svn\examples\javascript\p2p\RTCDataChannelExperiment\RTCDataChannelExperiment\Application.cs

            new { }.With(
                async delegate
                {
                    // X:\jsc.svn\examples\javascript\p2p\RTCICELobby\RTCICELobby\Application.cs

                    new IHTMLPre { "preparing..." }.AttachToDocument();


                    // first check if we can just anwser and offer?
                    #region GetOffer remotePeerConnection
                    await base.GetOffer();

                    if (base.sdpOffer != null)
                    {
                        #region backgroundColor
                        // indexer this case lets be blue

                        //Native.body.style.backgroundColor = "#‎747D9C‬";
                        // cannot set background color via hex? wtf?
                        //Native.body.style.background = "#‎747D9C‬";

                        // X:\jsc.svn\examples\javascript\css\test\TestBackgroundBlu\TestBackgroundBlu\Application.cs
                        var rgb = new { r = 0x74, g = 0x7d, b = 0x9c };

                        //var background = "rgba(\{rgb.r}, \{rgb.g}, \{rgb.b}, 1.0)";
                        var background = "rgba(" + rgb.r + ", " + rgb.g + ", " + rgb.b + ", 1.0)";
                        new IHTMLPre { new { background } }.AttachToDocument();

                        //Native.body.style.background = "rgb(\{(0x‎74)}, \{0x7D}, \{0x9C})‬";
                        //Native.body.style.background = background;
                        Native.body.style.backgroundColor = background;
                        #endregion


                        //new IHTMLPre { "anwsering to offer...... " }.AttachToDocument();
                        new IHTMLPre { "anwsering to offer...... " + new { sdpCandidates = base.sdpOffer.sdpCandidates.Count } }.AttachToDocument();

                        var remotePeerConnection = new RTCPeerConnection(null, null);

                        #region onicecandidate
                        // onICE: It returns locally generated ICE candidates; so you can share them with other peer(s).
                        remotePeerConnection.onicecandidate += e =>
                        {
                            if (e.candidate == null)
                                return;
                            // onicecandidate: {{ sdpMLineIndex = 0, candidate = candidate:630834096 1 tcp 1518214911 192.168.43.252 0 typ host tcptype active generation 0 }}
                            new IHTMLPre { "remotePeerConnection.onicecandidate: " + new {

                                e.candidate.candidate,
                                e.candidate.sdpMLineIndex,
                                e.candidate.sdpMid
                            } }.AttachToDocument();



                            //remotePeerConnection.onicecandidate: { { candidate = candidate:3890619490 1 udp 2122255103 2001::9d38:6abd: 1474:3b35: 3f57:d403 52152 typ host generation 0, sdpMLineIndex = 0, sdpMid = audio } }
                            //remotePeerConnection.onicecandidate: { { candidate = candidate:3890619490 1 udp 2122255103 2001::9d38:6abd: 1474:3b35: 3f57:d403 52152 typ host generation 0, sdpMLineIndex = 1, sdpMid = data } }
                            //remotePeerConnection.onicecandidate: { { candidate = candidate:1796882240 1 udp 2122194687 192.168.43.252 52153 typ host generation 0, sdpMLineIndex = 0, sdpMid = audio } }
                            //remotePeerConnection.onicecandidate: { { candidate = candidate:1796882240 1 udp 2122194687 192.168.43.252 52153 typ host generation 0, sdpMLineIndex = 1, sdpMid = data } }

                            base.sdpAnwser.sdpCandidates.Add(
                                // need to send all 3 fields over..
                                new DataRTCIceCandidate
                                {
                                    candidate = e.candidate.candidate,
                                    sdpMLineIndex = e.candidate.sdpMLineIndex,
                                    sdpMid = e.candidate.sdpMid
                                }
                            );
                        };
                        #endregion


                        #region remotePeerConnection.ondatachannel
                        remotePeerConnection.ondatachannel = new Action<RTCDataChannelEvent>(
                            async (RTCDataChannelEvent e) =>
                            {
                                var mcounter = 0;
                                var data = default(XElement);
                                new IHTMLPre { () => "enter  remotePeerConnection.ondatachannel " + new { e.channel.label, mcounter, data } }.AttachToDocument();

                                var cur = new MyCursor();
                                cur.AttachToDocument();

                                e.channel.onmessage += ee =>
                                {
                                    // will we ever get a message?
                                    mcounter++;
                                    data = XElement.Parse("" + ee.data);

                                    var CursorX = (int)data.Attribute(nameof(IEvent.CursorX));
                                    var CursorY = (int)data.Attribute(nameof(IEvent.CursorY));

                                    cur.style.SetLocation(
                                        CursorX,
                                        CursorY
                                       );

                                };

                                // we should now get the data?
                                //return;

                                // http://stackoverflow.com/questions/15324500/creating-webrtc-data-channels-after-peerconnection-established
                                // too late?
                                new IHTMLPre { "remotePeerConnection.createDataChannel sendDataChannel2...... " }.AttachToDocument();

                                //var sendDataChannel2 = remotePeerConnection.createDataChannel("sendDataChannel2", new { reliable = false });
                                var sendDataChannel2 = await remotePeerConnection.openDataChannel("sendDataChannel2", new { reliable = false });

                                // will it be opened? seems so. what about the other one?
                                new IHTMLPre { () => "sendDataChannel2.onopen " + new { sendDataChannel2.label } }.AttachToDocument();

                                //sendChannel.send("hi!");

                                var mmmcounter = 0;



                                #region onmousemove
                                Native.document.body.ontouchmove +=
                                        re =>
                                        {
                                            var n = new { re.touches[0].clientX, re.touches[0].clientY };

                                            re.preventDefault();

                                            mmmcounter++;

                                            sendDataChannel2.send(
                                                new XElement("sendDataChannel2",
                                                    new XAttribute(nameof(mmmcounter), "" + mmmcounter),
                                                    new XAttribute(nameof(IEvent.CursorX), "" + n.clientX),
                                                    new XAttribute(nameof(IEvent.CursorY), "" + n.clientY)
                                                ).ToString()
                                            );


                                        };


                                Native.document.onmousemove +=
                                    re =>
                                    {
                                        mmmcounter++;

                                        sendDataChannel2.send(
                                            new XElement("sendDataChannel2",
                                                new XAttribute(nameof(mmmcounter), "" + mmmcounter),
                                                new XAttribute(nameof(IEvent.CursorX), "" + re.CursorX),
                                                new XAttribute(nameof(IEvent.CursorY), "" + re.CursorY)
                                            ).ToString()

                                        //new { mmcounter, e.CursorX, e.CursorY }.ToString()
                                        );
                                    };
                                #endregion

                            }
                        );
                        #endregion

                        var desc = new RTCSessionDescription(new { sdp = sdpOffer.sdp, type = "offer" });

                        new IHTMLPre { "remotePeerConnection setRemoteDescription..." }.AttachToDocument();
                        await remotePeerConnection.setRemoteDescription(desc);

                        new IHTMLPre { "createAnswer..." }.AttachToDocument();
                        var a = await remotePeerConnection.createAnswer();

                        base.sdpAnwser = new DataWithCandidates
                        {
                            sdp = a.sdp
                        };



                        new IHTMLPre { "setLocalDescription..." }.AttachToDocument();
                        await remotePeerConnection.setLocalDescription(a);


                        new IHTMLPre { () => "awaiting for any sdpAnwserCandidates... " + new { base.sdpAnwser.sdpCandidates.Count } + " atleast connect to local hotspot" }.AttachToDocument();

                        while (!base.sdpAnwser.sdpCandidates.Any())
                            await Task.Delay(1000 / 15);

                        new IHTMLPre { "Anwser... " + new { base.sdpAnwser.sdpCandidates.Count } }.AttachToDocument();

                        await base.Anwser();

                        new IHTMLPre { "Anwser... done. await ondatachannel?" }.AttachToDocument();

                        #region addIceCandidate
                        new IHTMLPre { "addIceCandidate..." }.AttachToDocument();
                        foreach (var c in base.sdpOffer.sdpCandidates)
                        {
                            var cc = new RTCIceCandidate(new { c.candidate, c.sdpMLineIndex, c.sdpMid });

                            //if (c.sdpMid == "audio" || c.candidate.Contains("::"))
                            if (c.sdpMid == "audio")
                            {
                                new IHTMLPre { "skip audio remotePeerConnection addIceCandidate... " + new { c.candidate, c.sdpMLineIndex, c.sdpMid } }.AttachToDocument();
                            }
                            else if (c.candidate.Contains("::"))
                            {
                                new IHTMLPre { "skip ip6 remotePeerConnection addIceCandidate... " + new { c.candidate, c.sdpMLineIndex, c.sdpMid } }.AttachToDocument();
                            }
                            else
                            {
                                new IHTMLPre { "remotePeerConnection addIceCandidate... " + new { c.candidate, c.sdpMLineIndex, c.sdpMid } }.AttachToDocument();
                                await remotePeerConnection.addIceCandidate(cc);
                            }

                        }
                        new IHTMLPre { "addIceCandidate... done. awaiting sendChannel.onopen.." }.AttachToDocument();
                        #endregion


                        await new TaskCompletionSource<object>().Task;
                    }
                    #endregion



                    //await new IHTMLButton { "lets go" }.AttachToDocument().async.onclick;

                    // we also have the crypto private key avaiable if this is https

                    //
                    // https://hacks.mozilla.org/2013/07/webrtc-and-the-early-api/
                    // http://www.webrtc.org/web-apis/chrome
                    // RTP data channels will be capped at 64 kbps, to prevent uncontrolled use of bandwidth.
                    // https://groups.google.com/forum/#!topic/discuss-webrtc/y2A97iCByTU
                    // If you use RTP data channels in your application, with the b= modifier, you should work on migrating to SCTP data channels immediately
                    // Failed to construct 'RTCPeerConnection': Malformed constraints object.
                    //var localPeerConnection = new RTCPeerConnection(null, new { RtpDataChannels = true });
                    var localPeerConnection = new RTCPeerConnection(null, null);

                    //{
                    //    // https://www.webrtc-experiment.com/docs/how-to-use-rtcdatachannel.html#sctp-chrome
                    //    mandatory = new
                    //    {
                    //        OfferToReceiveAudio = false,
                    //        OfferToReceiveVideo = false
                    //        //,
                    //        //RtpDataChannels = true
                    //    }
                    //}
                    //);


                    #region onicecandidate
                    // no events without a call to openDataChannel ?
                    // onICE: It returns locally generated ICE candidates; so you can share them with other peer(s).
                    localPeerConnection.onicecandidate += e =>
                    {
                        if (e.candidate == null)
                            return;

                        // onicecandidate: {{ sdpMLineIndex = 0, candidate = candidate:630834096 1 tcp 1518214911 192.168.43.252 0 typ host tcptype active generation 0 }}
                        new IHTMLPre { "localPeerConnection.onicecandidate: " + new {
                                e.candidate.candidate,
                                e.candidate.sdpMLineIndex,
                                 e.candidate.sdpMid
    }
}.AttachToDocument();



                        //remotePeerConnection.onicecandidate: { { candidate = candidate:3890619490 1 udp 2122255103 2001::9d38:6abd: 1474:3b35: 3f57:d403 52152 typ host generation 0, sdpMLineIndex = 0, sdpMid = audio } }
                        //remotePeerConnection.onicecandidate: { { candidate = candidate:3890619490 1 udp 2122255103 2001::9d38:6abd: 1474:3b35: 3f57:d403 52152 typ host generation 0, sdpMLineIndex = 1, sdpMid = data } }
                        //remotePeerConnection.onicecandidate: { { candidate = candidate:1796882240 1 udp 2122194687 192.168.43.252 52153 typ host generation 0, sdpMLineIndex = 0, sdpMid = audio } }
                        //remotePeerConnection.onicecandidate: { { candidate = candidate:1796882240 1 udp 2122194687 192.168.43.252 52153 typ host generation 0, sdpMLineIndex = 1, sdpMid = data } }

                        base.sdpCandidates.Add(
                    // need to send all 3 fields over..
                    new DataRTCIceCandidate
                    {
                        candidate = e.candidate.candidate,
                        sdpMLineIndex = e.candidate.sdpMLineIndex,
                        sdpMid = e.candidate.sdpMid
                    }
                );
                    };
                    #endregion


                    //var nn = default(object);
                    //var reliable = nn != null;

                    // https://sites.google.com/a/jsc-solutions.net/backlog/knowledge-base/2015/20150301
                    new { }.With(
                        async delegate
                        {
                            var sendChannel = await localPeerConnection.openDataChannel("sendDataChannel", new { reliable = false });

                            // await async.onopen
                            new IHTMLPre { () => "sendChannel.onopen " + new { sendChannel.label, sendChannel.protocol, sendChannel.reliable } }.AttachToDocument();

                            //sendChannel.send("hi!");

                            var mmcounter = 0;



                            #region onmousemove
                            Native.document.body.ontouchmove +=
                           e =>
                           {
                               var n = new { e.touches[0].clientX, e.touches[0].clientY };

                               e.preventDefault();

                               mmcounter++;

                               sendChannel.send(
                                                   new XElement("sendDataChannel",
                                                       new XAttribute(nameof(mmcounter), "" + mmcounter),
                                                       new XAttribute(nameof(IEvent.CursorX), "" + n.clientX),
                                                       new XAttribute(nameof(IEvent.CursorY), "" + n.clientY)
                                                   ).ToString()
                                               );


                           };


                            Native.document.onmousemove +=
                                e =>
                                {
                                    mmcounter++;

                                    sendChannel.send(
                                        new XElement("sendDataChannel",
                                            new XAttribute(nameof(mmcounter), "" + mmcounter),
                                            new XAttribute(nameof(IEvent.CursorX), "" + e.CursorX),
                                            new XAttribute(nameof(IEvent.CursorY), "" + e.CursorY)
                                        ).ToString()

                                    //new { mmcounter, e.CursorX, e.CursorY }.ToString()
                                    );
                                };
                            #endregion
                        }
                   );

                    #region ondatachannel
                    localPeerConnection.ondatachannel = new Action<RTCDataChannelEvent>(
                        (RTCDataChannelEvent e) =>
                        {
                            var mcounter = 0;
                            var data = default(XElement);
                            new IHTMLPre { () => "enter  localPeerConnection.ondatachannel " + new { e.channel.label, mcounter, data } }.AttachToDocument();

                            var cur = new MyCursor();
                            cur.AttachToDocument();

                            e.channel.onmessage += ee =>
                            {
                                mcounter++;
                                data = XElement.Parse("" + ee.data);

                                var CursorX = (int)data.Attribute(nameof(IEvent.CursorX));
                                var CursorY = (int)data.Attribute(nameof(IEvent.CursorY));

                                cur.style.SetLocation(
                                                                    CursorX,
                                                                    CursorY
                                                                   );

                            };


                        }
                    );
                    #endregion

                    new IHTMLPre { "localPeerConnection.createOffer..." }.AttachToDocument();
                    var o = await localPeerConnection.createOffer();

                    new IHTMLPre { "localPeerConnection.setLocalDescription..." }.AttachToDocument();
                    await localPeerConnection.setLocalDescription(o);

                    base.sdpAdvert = o.sdp;

                    new IHTMLPre { () => "awaiting for any sdpCandidates... " + new { base.sdpCandidates.Count } + " (atleast connect to local hotspot, openDataChannel)" }.AttachToDocument();

                    // cannot wait for candidates before we do openDataChannel
                    while (!base.sdpCandidates.Any())
                        await Task.Delay(1000 / 15);

                    new IHTMLPre { "letting the server know we made a new offer... " + new { base.sdpCandidates.Count } }.AttachToDocument();

                    await base.Offer();

                    new IHTMLPre { "letting the server know we made a new offer... done. open a new tab! even on android?" }.AttachToDocument();

                    var sw = Stopwatch.StartNew();
                    var counter = 0;

                    // lets have a new status line
                    // using
                    new IHTMLPre { () => "awaiting for answer " + new { counter, sw.Elapsed } }.AttachToDocument();

                    // now poll the server, for any other offers?
                    while (this.sdpAnwser == null)
                    {
                        await Task.Delay(5000);
                        await base.CheckAnswer();
                        counter++;
                    }

                    // end using

                    new IHTMLPre { "localPeerConnection.setRemoteDescription..." }.AttachToDocument();

                    await localPeerConnection.setRemoteDescription(new RTCSessionDescription(new { base.sdpAnwser.sdp, type = "answer" }));

                    #region addIceCandidate
                    new IHTMLPre { "addIceCandidate..." }.AttachToDocument();
                    foreach (var c in base.sdpAnwser.sdpCandidates)
                    {
                        var cc = new RTCIceCandidate(new { c.candidate, c.sdpMLineIndex, c.sdpMid });

                        //if (c.sdpMid == "audio" || c.candidate.Contains("::"))
                        if (c.sdpMid == "audio")
                        {
                            new IHTMLPre { "skip audio localPeerConnection addIceCandidate... " + new { c.candidate, c.sdpMLineIndex, c.sdpMid } }.AttachToDocument();
                        }
                        else if (c.candidate.Contains("::"))
                        {
                            new IHTMLPre { "skip ip6 localPeerConnection addIceCandidate... " + new { c.candidate, c.sdpMLineIndex, c.sdpMid } }.AttachToDocument();
                        }
                        else
                        {
                            new IHTMLPre { "localPeerConnection addIceCandidate... " + new { c.candidate, c.sdpMLineIndex, c.sdpMid } }.AttachToDocument();
                            await localPeerConnection.addIceCandidate(cc);
                        }

                    }
                    #endregion


                    // https://developer.mozilla.org/en-US/docs/Web/API/RTCDataChannel/readyState

                    // https://sites.google.com/a/jsc-solutions.net/backlog/knowledge-base/2015/20150301
                    new IHTMLPre { "awaiting localPeerConnection.openDataChannel... will it get stuck? sometimes?" }.AttachToDocument().style.color = "red";

                    //Task.WaitAny

                    //var sendChannel = localPeerConnection.createDataChannel("sendDataChannel", new { reliable = false });
                    //var sendChannel = await asendchannel;




                    // wtf?
                    // reliable false makes it work?

                }
            );


        }
        // https://sites.google.com/a/jsc-solutions.net/work/knowledge-base/15-dualvr/20151202/rtc
        // https://diafygi.github.io/webrtc-ips/

        /// <summary>
        /// This is a javascript application.
        /// </summary>
        /// <param name="page">HTML document rendered by the web server which can now be enhanced.</param>
        public Application(IApp page)
        {
            // http://caniuse.com/#feat=rtcpeerconnection
            // http://iswebrtcreadyyet.com/

            new IHTMLPre
            {
                new { flightcheck = "are you using chrome40? open on android first?",
                Native.window.navigator.userAgent
                }
            }.AttachToDocument();

            // X:\jsc.svn\examples\javascript\p2p\RTCDataChannelExperiment\RTCDataChannelExperiment\Application.cs

            new { }.With(
                async delegate
                {
                    // X:\jsc.svn\examples\javascript\p2p\RTCICELobby\RTCICELobby\Application.cs

                    new IHTMLPre { "preparing..." }.AttachToDocument();

                    // first check if we can just anwser and offer?
                    #region GetOffer
                    await base.GetOffer();

                    if (!string.IsNullOrEmpty(sdpOffer))
                    {
                        new IHTMLPre { "anwsering and offer......" }.AttachToDocument();

                        var remotePeerConnection = new RTCPeerConnection(null, null);

                        #region onicecandidate
                        // onICE: It returns locally generated ICE candidates; so you can share them with other peer(s).
                        remotePeerConnection.onicecandidate += e =>
                        {
                            if (e.candidate == null)
                                return;
                            // onicecandidate: {{ sdpMLineIndex = 0, candidate = candidate:630834096 1 tcp 1518214911 192.168.43.252 0 typ host tcptype active generation 0 }}
                            new IHTMLPre { "remotePeerConnection.onicecandidate: " + new {

                                e.candidate.candidate,
                                e.candidate.sdpMLineIndex,
                                e.candidate.sdpMid
                            } }.AttachToDocument();

                            base.sdpAnwserCandidates.Add(e.candidate.candidate);
                        };
                        #endregion

                        #region ondatachannel
                        remotePeerConnection.ondatachannel = new Action<RTCDataChannelEvent>(
                            (RTCDataChannelEvent e) =>
                            {
                                // gearvr never gets here?

                                var mcounter = 0;
                                var data = default(XElement);
                                new IHTMLPre { () => "enter  remotePeerConnection.ondatachannel " + new { e.channel.label, mcounter, data } }.AttachToDocument();

                                var cur = new MyCursor();
                                cur.AttachToDocument();

                                e.channel.onmessage += ee =>
                                {
                                    mcounter++;
                                    data = XElement.Parse("" + ee.data);

                                    var CursorX = (int)data.Attribute("CursorX");
                                    var CursorY = (int)data.Attribute("CursorY");
                                    //var CursorX = (int)data.Attribute(nameof(IEvent.CursorX));
                                    //var CursorY = (int)data.Attribute(nameof(IEvent.CursorY));

                                    cur.style.SetLocation(
                                        CursorX,
                                        CursorY
                                       );

                                };


                            }
                        );
                        #endregion

                        var desc = new RTCSessionDescription(new { sdp = sdpOffer, type = "offer" });

                        new IHTMLPre { "setRemoteDescription..." }.AttachToDocument();
                        await remotePeerConnection.setRemoteDescription(desc);

                        new IHTMLPre { "createAnswer..." }.AttachToDocument();
                        var a = await remotePeerConnection.createAnswer();

                        new IHTMLPre { "setLocalDescription..." }.AttachToDocument();
                        await remotePeerConnection.setLocalDescription(a);

                        base.sdpAnwser = a.sdp;

                        new IHTMLPre { () => "awaiting for any sdpAnwserCandidates... " + new { base.sdpAnwserCandidates.Count } }.AttachToDocument();

                        while (!base.sdpAnwserCandidates.Any())
                            await Task.Delay(1000 / 15);

                        new IHTMLPre { "Anwser... " + new { base.sdpAnwserCandidates.Count } }.AttachToDocument();

                        await base.Anwser();

                        // gearvr never gets past this?
                        new IHTMLPre { "Anwser... done. await ondatachannel? gearvr stops here?" }.AttachToDocument();

                        // enter  remotePeerConnection.ondatachannel { label = sendDataChannel, mcounter = 335

                        // this will never return.
                        await new TaskCompletionSource<object>().Task;
                    }
                    #endregion

                    // we also have the crypto private key avaiable if this is https
                    var localPeerConnection = new RTCPeerConnection(null, null);


                    #region onicecandidate
                    // onICE: It returns locally generated ICE candidates; so you can share them with other peer(s).
                    localPeerConnection.onicecandidate += e =>
                    {
                        if (e.candidate == null)
                            return;

                        // onicecandidate: {{ sdpMLineIndex = 0, candidate = candidate:630834096 1 tcp 1518214911 192.168.43.252 0 typ host tcptype active generation 0 }}
                        new IHTMLPre { "localPeerConnection.onicecandidate: " + new {
                                e.candidate.candidate,
                                e.candidate.sdpMLineIndex,
                                 e.candidate.sdpMid
                            } }.AttachToDocument();

                        base.sdpCandidates.Add(e.candidate.candidate);
                    };
                    #endregion



                    var sendChannel = localPeerConnection.createDataChannel("sendDataChannel", new { reliable = false });

                    // await async.onopen
                    #region onopen
                    sendChannel.onopen = new Action(
                        async delegate
                        {
                            new IHTMLPre { () => "sendChannel.onopen " + new { sendChannel.label } }.AttachToDocument();

                            //sendChannel.send("hi!");

                            var mmcounter = 0;



                            Native.document.body.ontouchmove +=
                                    e =>
                                    {
                                        var n = new { e.touches[0].clientX, e.touches[0].clientY };

                                        e.preventDefault();

                                        mmcounter++;

                                        sendChannel.send(
                                            new XElement("sendDataChannel",
                                                new XAttribute("mmcounter", mmcounter),
                                                new XAttribute("CursorX", "" + n.clientX),
                                                new XAttribute("CursorY", "" + n.clientY)

                                                //    new XAttribute(nameof(mmcounter), mmcounter),
                                            //new XAttribute(nameof(IEvent.CursorX), "" + n.clientX),
                                            //new XAttribute(nameof(IEvent.CursorY), "" + n.clientY)
                                            ).ToString()
                                        );


                                    };


                            Native.document.onmousemove +=
                                e =>
                                {
                                    mmcounter++;

                                    sendChannel.send(
                                        new XElement("sendDataChannel",
                                        //new XAttribute(nameof(mmcounter), mmcounter),
                                        //new XAttribute(nameof(IEvent.CursorX), "" + e.CursorX),
                                        //new XAttribute(nameof(IEvent.CursorY), "" + e.CursorY)

                                              new XAttribute("mmcounter", mmcounter),
                                            new XAttribute("CursorX", "" + e.CursorX),
                                            new XAttribute("CursorY", "" + e.CursorY)
                                        ).ToString()

                                    //new { mmcounter, e.CursorX, e.CursorY }.ToString()
                                    );
                                };
                        }
                    );
                    #endregion


                    new IHTMLPre { "createOffer..." }.AttachToDocument();
                    var o = await localPeerConnection.createOffer();

                    new IHTMLPre { "setLocalDescription..." }.AttachToDocument();

                    await localPeerConnection.setLocalDescription(o);

                    base.sdp = o.sdp;

                    new IHTMLPre { () => "awaiting for any sdpCandidates... " + new { base.sdpCandidates.Count } }.AttachToDocument();

                    while (!base.sdpCandidates.Any())
                        await Task.Delay(1000 / 15);

                    new IHTMLPre { "letting the server know we made a new offer... " + new { base.sdpCandidates.Count } }.AttachToDocument();

                    await base.Offer();

                    new IHTMLPre { "letting the server know we made a new offer... done. open a new tab! even on android, gearvr?" }.AttachToDocument();


                    var sw = Stopwatch.StartNew();
                    var counter = 0;

                    // lets have a new status line

                    // using
                    new IHTMLPre { () => "awaiting for answer " + new { counter, sw.Elapsed } }.AttachToDocument();

                    // now poll the server, for any other offers?
                    while (string.IsNullOrEmpty(this.sdpAnwser))
                    {
                        await Task.Delay(5000);
                        await base.CheckAnswer();
                        counter++;
                    }

                    // end using

                    new IHTMLPre { "setRemoteDescription..." }.AttachToDocument();

                    await localPeerConnection.setRemoteDescription(new RTCSessionDescription(new { sdp = base.sdpAnwser, type = "answer" }));

                    new IHTMLPre { "addIceCandidate..." }.AttachToDocument();
                    foreach (var candidate in base.sdpAnwserCandidates)
                    {
                        var c = new RTCIceCandidate(new { candidate, sdpMLineIndex = 0, sdpMid = "data" });

                        new IHTMLPre { "addIceCandidate... " + new { candidate } }.AttachToDocument();

                        await localPeerConnection.addIceCandidate(c);
                    }
                    new IHTMLPre { "addIceCandidate... done. awaiting sendChannel.onopen.." }.AttachToDocument();
                }
            );



        }
 public void addIceCandidate(RTCIceCandidate candidate, IFunction successCallback, Action<object> failureCallback) { }
        public static Task addIceCandidate(this RTCPeerConnection that, RTCIceCandidate candidate)
        {
            var x = new TaskCompletionSource<object>();
            that.addIceCandidate(candidate,
                successCallback: new Action(delegate { x.SetResult(null); }),
                failureCallback: err => { throw new Exception(new { err }.ToString()); }
            );

            return x.Task;
        }
        /// <summary>
        /// This is a javascript application.
        /// </summary>
        /// <param name="page">HTML document rendered by the web server which can now be enhanced.</param>
        public Application(IApp page)
        {
            // how can we connect VR headset webview and chrome app mouse lock?
            // X:\jsc.svn\examples\javascript\p2p\RTCPeerIPAddress\RTCPeerIPAddress\Application.cs

            // http://www.html5rocks.com/en/tutorials/webrtc/basics/
            // http://simpl.info/rtcdatachannel/
            // http://www.webrtc.org/
            // http://www.html5rocks.com/en/tutorials/webrtc/basics/#toc-rtcdatachannel
            // https://bloggeek.me/webrtc-data-channel-uses/
            // http://www.slideshare.net/victorpascual/webrtc-datachannels-demystified
            // http://simpl.info/rtcdatachannel/js/main.js
            // http://stackoverflow.com/questions/20396160/how-to-transfer-file-between-two-browsers-with-webrtc
            // http://blog.printf.net/articles/2013/05/17/webrtc-without-a-signaling-server/
            // https://www.webrtc-experiment.com/docs/how-file-broadcast-works.html
            // http://lists.w3.org/Archives/Public/public-webrtc/2012Jul/0008.html
            // https://code.google.com/p/webrtc/issues/detail?id=1430
            // http://blog.printf.net/articles/2014/07/01/serverless-webrtc-continued/
            // https://github.com/muaz-khan/WebRTC-Experiment/tree/master/DataChannel
            // https://www.webrtc-experiment.com/docs/how-to-use-rtcdatachannel-and-rtcpeerconnectionjs.html
            // http://omshiv.github.io/impressions/jsfoo/webrtc-talk/#/6
            // https://groups.google.com/forum/#!topic/discuss-webrtc/CNWplOqtV_w
            // https://groups.google.com/forum/#!topic/discuss-webrtc/9zs21EBciNM

            Native.body.Clear();

            new { }.With(
                async delegate
                {
                    var yellow = new IHTMLDiv { }.AttachToDocument();

                    yellow.style.position = IStyle.PositionEnum.absolute;
                    yellow.style.right = "0px";
                    yellow.style.top = "0px";
                    yellow.style.backgroundColor = "cyan";



                    #region remotePeerConnection

                    var remotePeerConnection = new RTCPeerConnection(null,
                         null
                         //new { optional = new[] { new { RtpDataChannels = true } } }
                         );


                    #region onicecandidate
                    // onICE: It returns locally generated ICE candidates; so you can share them with other peer(s).
                    remotePeerConnection.onicecandidate = new Action<RTCPeerConnectionIceEvent>(
                        (RTCPeerConnectionIceEvent e) =>
                        {
                            if (e.candidate == null)
                                return;


                            // onicecandidate: {{ sdpMLineIndex = 0, candidate = candidate:630834096 1 tcp 1518214911 192.168.43.252 0 typ host tcptype active generation 0 }}


                            new IHTMLPre { "remotePeerConnection.onicecandidate: " + new { e.candidate.sdpMLineIndex, e.candidate.sdpMid } }.AttachTo(yellow);

                            new IHTMLTextArea
                            {
                                value = e.candidate.candidate,

                                title = "for local addIceCandidate"

                            }.AttachTo(yellow);

                        }
                    );
                    #endregion

                    #region ondatachannel
                    remotePeerConnection.ondatachannel = new Action<RTCDataChannelEvent>(
                        (RTCDataChannelEvent e) =>
                        {
                            new IHTMLPre { "enter  remotePeerConnection.ondatachannel " }.AttachTo(yellow);


                            var data = default(object);
                            var counter = 0;

                            new IHTMLPre { () => "onmessage: " + new { data, counter } }.AttachTo(yellow);

                            //Native.document.title

                            e.channel.onmessage +=
                                m =>
                                {
                                    counter++;
                                    data = m.data;
                                };

                        }
                    );

                    #endregion


                    #endregion

#if RRTCIceCandidate
                     //remotePeerConnection.addIceCandidate(event.candidate);
                    var xcandidate = new IHTMLTextArea { }.AttachToDocument();

                    await new IHTMLButton { "addIceCandidate" }.AttachToDocument().async.onclick;


                    var candidate = new RTCIceCandidate(new { candidate = xcandidate.value, sdpMLineIndex = 0, sdpMid = "data" });

                    #region   remotePeerConnection.addIceCandidate
                    // http://stackoverflow.com/questions/23325510/not-able-to-add-remote-ice-candidate-in-webrtc
                    // ??? wtf

                    new IHTMLPre { "before remotePeerConnection.addIceCandidate" }.AttachToDocument();

                    // TypeError: Failed to execute 'addIceCandidate' on 'RTCPeerConnection': Valid arities are: [1, 3], but 2 arguments provided.
                    remotePeerConnection.addIceCandidate(
                        candidate,
                        new Action(
                            delegate
                            {
                                new IHTMLPre { "at  remotePeerConnection.addIceCandidate" }.AttachToDocument();

                            }
                        ),
                        new Action<object>(
                            err =>
                            {
                                // All callback-based methods have been moved to a legacy section, and replaced by same-named overloads using Promises instead
                                // err  remotePeerConnection.addIceCandidate {{ err = Error processing ICE candidate }}

                                new IHTMLPre { "err  remotePeerConnection.addIceCandidate " + new { err } }.AttachToDocument();

                            }
                        )
                    );

                    new IHTMLPre { "after remotePeerConnection.addIceCandidate" }.AttachToDocument();
                    #endregion
#endif


                    var x = new IHTMLTextArea { }.AttachTo(yellow);

                    await new IHTMLButton { "setRemoteDescription" }.AttachTo(yellow).async.onclick;

                    //await x.async.onch





                    var desc = new RTCSessionDescription(new { sdp = x.value, type = "offer" });

                    new IHTMLPre { "before  remotePeerConnection.setRemoteDescription" }.AttachTo(yellow);

                    remotePeerConnection.setRemoteDescription(desc,
                        new Action(
                            delegate
                            {

                                new IHTMLPre { "at  remotePeerConnection.setRemoteDescription" }.AttachTo(yellow);


                                // the second parameter to setRemoteDescription is a completion callback.  Call createAnswer from within the callback to avoid a race condition


                                new IHTMLPre { "before  remotePeerConnection.createAnswer" }.AttachTo(yellow);

                                remotePeerConnection.createAnswer(
                                    e =>
                                    {
                                        new IHTMLPre { "at  remotePeerConnection.createAnswer" }.AttachTo(yellow);


                                        remotePeerConnection.setLocalDescription(e,
                                            new Action(
                                                delegate { }
                                            )
                                        );

                                        new IHTMLTextArea { readOnly = true, value = e.sdp, title = "for localPeerConnection.setRemoteDescription" }.AttachTo(yellow);
                                        // localPeerConnection.setRemoteDescription(desc);



                                    }
                                );
                            }
                        )
                    );






                }
            );

            new { }.With(
                async delegate
                {
                    new IHTMLHorizontalRule { }.AttachToDocument();

                    //(await new IHTMLButton { "start" }.AttachToDocument().async.onclick).Element.Orphanize();
                    await new IHTMLButton { "createOffer" }.AttachToDocument().async.onclick;

                    // createConnection


                    // TypeError: Failed to construct 'RTCPeerConnection': Malformed constraints object.
                    var localPeerConnection = new RTCPeerConnection(null,
                        null
                        //new { optional = new[] { new { RtpDataChannels = true } } }
                        );


                    #region onicecandidate
                    // onICE: It returns locally generated ICE candidates; so you can share them with other peer(s).
                    localPeerConnection.onicecandidate = new Action<RTCPeerConnectionIceEvent>(
                        (RTCPeerConnectionIceEvent e) =>
                        {
                            if (e.candidate == null)
                                return;


                            // onicecandidate: {{ sdpMLineIndex = 0, candidate = candidate:630834096 1 tcp 1518214911 192.168.43.252 0 typ host tcptype active generation 0 }}
                            new IHTMLPre { "localPeerConnection.onicecandidate: " + new {



                                e.candidate.candidate,

                                e.candidate.sdpMLineIndex,

                                 e.candidate.sdpMid
                            } }.AttachToDocument();
                        }
                    );
                    #endregion

                    #region sendChannel
                    var sendChannel = localPeerConnection.createDataChannel(
                        "sendDataChannel", new { reliable = false });


                    sendChannel.onopen = new Action(
                        async delegate
                        {
                            new IHTMLPre { "sendChannel.onopen" }.AttachToDocument();


                            sendChannel.send("hi!");

                            var counter = 0;

                            Native.body.onmousemove +=
                                e =>
                                {
                                    if (Native.document.pointerLockElement != Native.body) return;

                                    counter++;

                                    // rpc switchTo
                                    sendChannel.send(
                                        new { counter, e.movementX, e.movementY }.ToString()

                                        );

                                    //await for new message to continue?
                                    // webrtc meets async
                                };


                            var btn = new IHTMLButton("pointerlock").AttachToDocument();


                            while (await btn.async.onclick)
                            {

                                new IHTMLPre { "requestPointerLock" }.AttachToDocument();

                                Native.body.requestPointerLock();
                            }

                        }
                    );

                    sendChannel.onclose = new Action(
                        delegate
                        {

                            new IHTMLPre { "sendChannel.onclose" }.AttachToDocument();
                        }
                    );
                    #endregion


                    localPeerConnection.createOffer(
                        x =>
                        {
                            new IHTMLPre { "enter localPeerConnection.createOffer > setLocalDescription" }.AttachToDocument();

                            new IHTMLTextArea { readOnly = true, value = x.sdp }.AttachToDocument();

                            localPeerConnection.setLocalDescription(x,
                                new Action(
                                    async delegate
                                    {
                                        new IHTMLPre { "enter localPeerConnection.createOffer > setLocalDescription done" }.AttachToDocument();

                                        var z = new IHTMLTextArea { }.AttachToDocument();

                                        await new IHTMLButton { "setRemoteDescription" }.AttachToDocument().async.onclick;


                                        localPeerConnection.setRemoteDescription(
                                            new RTCSessionDescription(new { sdp = z.value, type = "answer" }),
                                            new Action(
                                                async delegate
                                                {
                                                    new IHTMLPre { "? done" }.AttachToDocument();

                                                    //// ncaught InvalidStateError: Failed to execute 'send' on 'RTCDataChannel': RTCDataChannel.readyState is not 'open'
                                                    //sendChannel.send("hi?");


                                                    //remotePeerConnection.addIceCandidate(event.candidate);
                                                    var xcandidate = new IHTMLTextArea { }.AttachToDocument();

                                                    await new IHTMLButton { "addIceCandidate" }.AttachToDocument().async.onclick;


                                                    var candidate = new RTCIceCandidate(new { candidate = xcandidate.value, sdpMLineIndex = 0, sdpMid = "data" });

                                                    #region   remotePeerConnection.addIceCandidate
                                                    // http://stackoverflow.com/questions/23325510/not-able-to-add-remote-ice-candidate-in-webrtc
                                                    // ??? wtf

                                                    new IHTMLPre { "before localPeerConnection.addIceCandidate" }.AttachToDocument();

                                                    // TypeError: Failed to execute 'addIceCandidate' on 'RTCPeerConnection': Valid arities are: [1, 3], but 2 arguments provided.
                                                    localPeerConnection.addIceCandidate(
                                                        candidate,
                                                        new Action(
                                                            delegate
                                                            {
                                                                new IHTMLPre { "at  localPeerConnection.addIceCandidate" }.AttachToDocument();

                                                            }
                                                        ),
                                                        new Action<object>(
                                                            err =>
                                                            {
                                                                // All callback-based methods have been moved to a legacy section, and replaced by same-named overloads using Promises instead
                                                                // err  remotePeerConnection.addIceCandidate {{ err = Error processing ICE candidate }}

                                                                new IHTMLPre { "err  localPeerConnection.addIceCandidate " + new { err } }.AttachToDocument();

                                                            }
                                                        )
                                                    );

                                                    new IHTMLPre { "after localPeerConnection.addIceCandidate" }.AttachToDocument();
                                                    #endregion

                                                }
                                            )
                                        );
                                    }
                                )
                            );
                        }
                    );

                    // The PeerConnection won't start gathering candidates until you call setLocalDescription(); the information supplied to setLocalDescription tells PeerConnection how many candidates need to be gathered. 

                    //  localPeerConnection.createOffer(gotLocalDescription);

                    //new IHTMLPre { "now what? " + new { sendChannel } }.AttachToDocument();

                }
            );



        }