summaryrefslogtreecommitdiffstats
path: root/virtual-programs
diff options
context:
space:
mode:
Diffstat (limited to 'virtual-programs')
-rw-r--r--virtual-programs/web/webrtc.folk199
1 files changed, 199 insertions, 0 deletions
diff --git a/virtual-programs/web/webrtc.folk b/virtual-programs/web/webrtc.folk
new file mode 100644
index 00000000..282f70e5
--- /dev/null
+++ b/virtual-programs/web/webrtc.folk
@@ -0,0 +1,199 @@
+try {
+ exec gst-webrtc-signalling-server &
+} on error {e} {
+ error "gst-webrtc-signalling-server not found, you probably need to install gst-plugins-rs"
+}
+
+try {
+ exec gst-inspect-1.0 rswebrtc
+} on error {e} {
+ error "gstreamer plugin 'rswebrtc' not found, you probably need to install gst-plugins-rs"
+}
+
+try {
+ exec gst-inspect-1.0 webrtc
+} on error {e} {
+ error "gstreamer plugin 'webrtc' not found, you probably need to install gst-plugins-bad"
+}
+
+When /someone/ wishes /p/ receives webrtc video /feed/ &\
+ /someone/ claims webrtc video /feed/ streams from peer /peer-id/ {
+ set uri gstwebrtc://127.0.0.1:8443?peer-id=${peer-id}
+ When the gstreamer pipeline "uridecodebin uri=$uri" frame is /image/ at /ts/ {
+ Claim $p has webrtc video $feed frame $image at $ts
+ }
+}
+
+When when /p/ has webrtc video /feed/ frame /image/ at /ts/ /lambda/ with environment /e/ {
+ Wish $p receives webrtc video $feed
+}
+
+Wish the web server handles route "/webrtc$" with handler {
+ html {
+ <!DOCTYPE html>
+ <html>
+ <body>
+ <span id="status">Status</span>
+ <h1>active feeds</h1>
+ <div id="feeds">
+ </div>
+
+ <script src="/lib/folk.js"></script>
+ <script>
+ const ws = new FolkWS(document.getElementById('status'));
+ ws.watch('/someone/ wishes /someone/ receives webrtc video /feed/', {
+ add: ({ feed }) => {
+ const link = document.createElement('a');
+ link.id = `feed-${feed}`;
+ link.style = "display: block";
+ link.innerText = `stream to ${feed}`;
+ link.href = `/webrtc/${encodeURIComponent(feed)}/send`;
+ feeds.append(link);
+ },
+ remove: ({ feed }) => {
+ document.getElementById(`feed-${feed}`).remove();
+ }
+ });
+ </script>
+ </body>
+ </html>
+ }
+}
+
+Wish the web server handles route {/webrtc/(.*)/send$} with handler {
+ regexp {/webrtc/(.*)/send$} $path -> feed
+ html [format {
+ <!DOCTYPE html>
+ <html style="display: flex; min-height: 100vh">
+ <head>
+ </head>
+ <body style="display: flex; flex-direction: column; flex: 1;">
+ <span id="status">Status</span>
+ <h1>share video to feed %s</h1>
+ <div>
+ <button id="camera-button">start camera</button>
+ <button id="screen-button">start screenshare</button>
+ <button id="stop-button">stop</button>
+ </div>
+ <video style="flex: 1; background: #000; margin: auto; max-width: 100%%;" id="preview"></video>
+
+ <script src="/lib/folk.js"></script>
+ <script src="/vendor/gstwebrtc/gstwebrtc-api-2.0.0.min.js"></script>
+ <script>
+ const protocol = window.location.protocol.replace("http", "ws");
+ const api = new GstWebRTCAPI({
+ meta: { name: `WebClient-${Date.now()}` },
+ signalingServerUrl: `${protocol}//${window.location.hostname}:8443/webrtc`,
+ });
+ const ws = new FolkWS(document.getElementById("status"));
+
+ const feed = '%s';
+ let peerId = null;
+ let session;
+
+ const videoElement = document.getElementById("preview");
+
+ api.registerConnectionListener({
+ connected: (clientId) => { peerId = clientId; },
+ disconnected: () => { peerId = null; }
+ });
+
+ const handleStream = (stream) => {
+ const sess = api.createProducerSession(stream);
+
+ if (!sess) {
+ for (const track of stream.getTracks()) {
+ track.stop();
+ }
+ // captureSection.classList.remove("starting");
+ return;
+ }
+
+ sess.addEventListener("error", (event) => console.error(event.message, event.error));
+
+ sess.addEventListener("closed", () => {
+ videoElement.pause();
+ videoElement.srcObject = null;
+ ws.send(tcl`Commit {}`);
+ session = null;
+ // captureSection.classList.remove("has-sess", "starting");
+ });
+
+ sess.addEventListener("stateChanged", (event) => {
+ if ((event.target.state === GstWebRTCAPI.SessionState.streaming)) {
+ videoElement.srcObject = stream;
+ videoElement.play().catch(() => {});
+ // captureSection.classList.remove("starting");
+
+ // sess ready, announce to folk consumers!
+ ws.send(tcl`Commit {
+ Claim webrtc video ${feed} streams from peer ${peerId}
+ }`);
+ }
+ });
+
+ /*
+ sess.addEventListener("clientConsumerAdded", (event) => {
+ if (captureSection._producerSession === sess) {
+ console.info(`client consumer added: ${event.detail.peerId}`);
+ }
+ });
+
+ sess.addEventListener("clientConsumerRemoved", (event) => {
+ if (captureSection._producerSession === sess) {
+ console.info(`client consumer removed: ${event.detail.peerId}`);
+ }
+ });
+ */
+
+ // captureSection.classList.add("has-sess");
+ sess.start();
+ return sess;
+ };
+
+ document.getElementById("stop-button").onclick = (event) => {
+ event.preventDefault();
+ if (session) {
+ session.close();
+ }
+ };
+
+ document.getElementById("camera-button").onclick = (event) => {
+ event.preventDefault();
+ if (session) return;
+
+ session = true;
+
+ const constraints = { video: true, audio: false };
+ navigator.mediaDevices.getUserMedia(constraints)
+ .then(handleStream)
+ .then(
+ (s) => { session = s; },
+ (error) => {
+ console.error("cannot have access to webcam and microphone", error);
+ session = null;
+ }
+ );
+ };
+
+ document.getElementById("screen-button").onclick = (event) => {
+ event.preventDefault();
+ if (session) return;
+
+ session = true;
+ const constraints = { video: true, audio: false };
+ navigator.mediaDevices.getDisplayMedia(constraints)
+ .then(handleStream)
+ .then(
+ (s) => { session = s; },
+ (error) => {
+ console.error("cannot have access to screen", error);
+ session = null;
+ }
+ );
+ };
+ </script>
+ </body>
+ </html>
+ } $feed $feed]
+}