Skip to content

Commit ade7dcd

Browse files
committed
add webrtc wrapper around gstreamer, web client
1 parent 9d40814 commit ade7dcd

File tree

4 files changed

+234
-0
lines changed

4 files changed

+234
-0
lines changed

test/webrtc.tcl

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
loadVirtualPrograms [list "virtual-programs/web/webrtc.folk" "virtual-programs/web/web-keyboards.folk" "virtual-programs/keyboard.folk" "virtual-programs/gstreamer.folk" "virtual-programs/images.folk" "virtual-programs/web/new-program-web-editor.folk"]
2+
Step
3+
4+
# Assert <unknown> wishes the-moon receives webrtc video earth
5+
6+
When the-moon has webrtc video earth frame /image/ at /ts/ {
7+
Wish the web server handles route "/rtc-image/$" with handler [list apply {{im} {
8+
set filename "/tmp/web-image-frame.png"
9+
image saveAsPng $im $filename
10+
set fsize [file size $filename]
11+
set fd [open $filename r]
12+
fconfigure $fd -encoding binary -translation binary
13+
set body [read $fd $fsize]
14+
close $fd
15+
dict create statusAndHeaders "HTTP/1.1 200 OK\nConnection: close\nContent-Type: image/png\nContent-Length: $fsize\n\n" body $body
16+
}} $image]
17+
}
18+
19+
forever { Step }

vendor/gstwebrtc/gstwebrtc-api-2.0.0.min.js

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

virtual-programs/web/webrtc.folk

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
try {
2+
exec gst-webrtc-signalling-server &
3+
} on error {e} {
4+
error "gst-webrtc-signalling-server not found, you probably need to install gst-plugins-rs"
5+
}
6+
7+
try {
8+
exec gst-inspect-1.0 rswebrtc
9+
} on error {e} {
10+
error "gstreamer plugin 'rswebrtc' not found, you probably need to install gst-plugins-rs"
11+
}
12+
13+
try {
14+
exec gst-inspect-1.0 webrtc
15+
} on error {e} {
16+
error "gstreamer plugin 'webrtc' not found, you probably need to install gst-plugins-bad"
17+
}
18+
19+
When /someone/ wishes /p/ receives webrtc video /feed/ &\
20+
/someone/ claims webrtc video /feed/ streams from peer /peer-id/ {
21+
set uri gstwebrtc://127.0.0.1:8443?peer-id=${peer-id}
22+
When the gstreamer pipeline "uridecodebin uri=$uri" frame is /image/ at /ts/ {
23+
Claim $p has webrtc video $feed frame $image at $ts
24+
}
25+
}
26+
27+
When when /p/ has webrtc video /feed/ frame /image/ at /ts/ /lambda/ with environment /e/ {
28+
Wish $p receives webrtc video $feed
29+
}
30+
31+
Wish the web server handles route "/webrtc$" with handler {
32+
html {
33+
<!DOCTYPE html>
34+
<html>
35+
<body>
36+
<span id="status">Status</span>
37+
<h1>active feeds</h1>
38+
<div id="feeds">
39+
</div>
40+
41+
<script src="/lib/folk.js"></script>
42+
<script>
43+
async function init() {
44+
ws = new FolkWS(document.getElementById('status'));
45+
for await (const {action, match} of ws.watch('/someone/ wishes /someone/ receives webrtc video /feed/')) {
46+
const [feed] = match;
47+
if (action === "add") {
48+
const link = document.createElement('a');
49+
link.id = `feed-${feed}`;
50+
link.style = "display: block";
51+
link.innerText = `stream to ${feed}`;
52+
link.href = `/webrtc/${encodeURIComponent(feed)}/send`;
53+
feeds.append(link);
54+
} else if (action === "del") {
55+
document.getElementById(`feed-${feed}`).remove();
56+
}
57+
}
58+
}
59+
init();
60+
</script>
61+
</body>
62+
</html>
63+
}
64+
}
65+
66+
Wish the web server handles route {/webrtc/(.*)/send$} with handler {
67+
regexp {/webrtc/(.*)/send$} $path -> feed
68+
html [format {
69+
<!DOCTYPE html>
70+
<html style="display: flex; min-height: 100vh">
71+
<head>
72+
</head>
73+
<body style="display: flex; flex-direction: column; flex: 1;">
74+
<span id="status">Status</span>
75+
<h1>share video to feed %s</h1>
76+
<div>
77+
<button id="camera-button">start camera</button>
78+
<button id="screen-button">start screenshare</button>
79+
<button id="stop-button">stop</button>
80+
</div>
81+
<video style="flex: 1; background: #000; margin: auto; max-width: 100%;" id="preview"></video>
82+
83+
<script src="/lib/folk.js"></script>
84+
<script src="/vendor/gstwebrtc/gstwebrtc-api-2.0.0.min.js"></script>
85+
<script>
86+
const protocol = window.location.protocol.replace("http", "ws");
87+
const api = new GstWebRTCAPI({
88+
meta: { name: `WebClient-${Date.now()}` },
89+
signalingServerUrl: `${protocol}//${window.location.hostname}:8443/webrtc`,
90+
});
91+
const ws = new FolkWS(document.getElementById("status"));
92+
93+
const feed = '%s';
94+
let peerId = null;
95+
let session;
96+
97+
const videoElement = document.getElementById("preview");
98+
99+
api.registerConnectionListener({
100+
connected: (clientId) => { peerId = clientId; },
101+
disconnected: () => { peerId = null; }
102+
});
103+
104+
const handleStream = (stream) => {
105+
const sess = api.createProducerSession(stream);
106+
107+
if (!sess) {
108+
for (const track of stream.getTracks()) {
109+
track.stop();
110+
}
111+
// captureSection.classList.remove("starting");
112+
return;
113+
}
114+
115+
sess.addEventListener("error", (event) => console.error(event.message, event.error));
116+
117+
sess.addEventListener("closed", () => {
118+
videoElement.pause();
119+
videoElement.srcObject = null;
120+
ws.send(tcl`Commit {}`);
121+
session = null;
122+
// captureSection.classList.remove("has-sess", "starting");
123+
});
124+
125+
sess.addEventListener("stateChanged", (event) => {
126+
if ((event.target.state === GstWebRTCAPI.SessionState.streaming)) {
127+
videoElement.srcObject = stream;
128+
videoElement.play().catch(() => {});
129+
// captureSection.classList.remove("starting");
130+
131+
// sess ready, announce to folk consumers!
132+
ws.send(tcl`Commit {
133+
Claim webrtc video ${feed} streams from peer ${peerId}
134+
}`);
135+
}
136+
});
137+
138+
/*
139+
sess.addEventListener("clientConsumerAdded", (event) => {
140+
if (captureSection._producerSession === sess) {
141+
console.info(`client consumer added: ${event.detail.peerId}`);
142+
}
143+
});
144+
145+
sess.addEventListener("clientConsumerRemoved", (event) => {
146+
if (captureSection._producerSession === sess) {
147+
console.info(`client consumer removed: ${event.detail.peerId}`);
148+
}
149+
});
150+
*/
151+
152+
// captureSection.classList.add("has-sess");
153+
sess.start();
154+
return sess;
155+
};
156+
157+
document.getElementById("stop-button").onclick = (event) => {
158+
event.preventDefault();
159+
if (session) {
160+
session.close();
161+
}
162+
};
163+
164+
document.getElementById("camera-button").onclick = (event) => {
165+
event.preventDefault();
166+
if (session) return;
167+
168+
session = true;
169+
170+
const constraints = { video: true, audio: false };
171+
navigator.mediaDevices.getUserMedia(constraints)
172+
.then(handleStream)
173+
.then(
174+
(s) => { session = s; },
175+
(error) => {
176+
console.error("cannot have access to webcam and microphone", error);
177+
session = null;
178+
}
179+
);
180+
};
181+
182+
document.getElementById("screen-button").onclick = (event) => {
183+
event.preventDefault();
184+
if (session) return;
185+
186+
session = true;
187+
const constraints = { video: true, audio: false };
188+
navigator.mediaDevices.getDisplayMedia(constraints)
189+
.then(handleStream)
190+
.then(
191+
(s) => { session = s; },
192+
(error) => {
193+
console.error("cannot have access to screen", error);
194+
session = null;
195+
}
196+
);
197+
};
198+
</script>
199+
</body>
200+
</html>
201+
} $feed $feed]
202+
}

web.tcl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,14 @@ proc handlePage {path httpStatusVar contentTypeVar} {
128128
"/statements.pdf" {
129129
getDotAsPdf [Statements::dot] contentType
130130
}
131+
"/lib/folk.js" {
132+
set contentType "text/javascript"
133+
readFile "lib/folk.js" contentType
134+
}
135+
"/vendor/gstwebrtc/gstwebrtc-api-2.0.0.min.js" {
136+
set contentType "text/javascript"
137+
readFile "vendor/gstwebrtc/gstwebrtc-api-2.0.0.min.js" contentType
138+
}
131139
default {
132140
upvar $httpStatusVar httpStatus
133141
set httpStatus "HTTP/1.1 404 Not Found"

0 commit comments

Comments
 (0)