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); }
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(\{(0x74)}, \{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(); } ); }