Skip to content

Commit 24e1de4

Browse files
committed
add webrtc wrapper around gstreamer, web client
1 parent a4b94bb commit 24e1de4

File tree

3 files changed

+220
-0
lines changed

3 files changed

+220
-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: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
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+
const ws = new FolkWS(document.getElementById('status'));
44+
ws.watch('/someone/ wishes /someone/ receives webrtc video /feed/', {
45+
add: ({ feed }) => {
46+
const link = document.createElement('a');
47+
link.id = `feed-${feed}`;
48+
link.style = "display: block";
49+
link.innerText = `stream to ${feed}`;
50+
link.href = `/webrtc/${encodeURIComponent(feed)}/send`;
51+
feeds.append(link);
52+
},
53+
remove: ({ feed }) => document.getElementById(`feed-${feed}`).remove(),
54+
});
55+
</script>
56+
</body>
57+
</html>
58+
}
59+
}
60+
61+
Wish the web server handles route {/webrtc/(.*)/send$} with handler {
62+
regexp {/webrtc/(.*)/send$} $path -> feed
63+
html [format {
64+
<!DOCTYPE html>
65+
<html style="display: flex; min-height: 100vh">
66+
<head>
67+
</head>
68+
<body style="display: flex; flex-direction: column; flex: 1;">
69+
<span id="status">Status</span>
70+
<h1>share video to feed %s</h1>
71+
<div>
72+
<button id="camera-button">start camera</button>
73+
<button id="screen-button">start screenshare</button>
74+
<button id="stop-button">stop</button>
75+
</div>
76+
<video style="flex: 1; background: #000; margin: auto; max-width: 100%%;" id="preview"></video>
77+
78+
<script src="/lib/folk.js"></script>
79+
<script src="/vendor/gstwebrtc/gstwebrtc-api-2.0.0.min.js"></script>
80+
<script>
81+
const protocol = window.location.protocol.replace("http", "ws");
82+
const api = new GstWebRTCAPI({
83+
meta: { name: `WebClient-${Date.now()}` },
84+
signalingServerUrl: `${protocol}//${window.location.hostname}:8443/webrtc`,
85+
});
86+
const ws = new FolkWS(document.getElementById("status"));
87+
88+
const feed = '%s';
89+
let peerId = null;
90+
let session;
91+
92+
const videoElement = document.getElementById("preview");
93+
94+
api.registerConnectionListener({
95+
connected: (clientId) => { peerId = clientId; },
96+
disconnected: () => { peerId = null; }
97+
});
98+
99+
const handleStream = (stream) => {
100+
const sess = api.createProducerSession(stream);
101+
102+
if (!sess) {
103+
for (const track of stream.getTracks()) {
104+
track.stop();
105+
}
106+
// captureSection.classList.remove("starting");
107+
return;
108+
}
109+
110+
sess.addEventListener("error", (event) => console.error(event.message, event.error));
111+
112+
sess.addEventListener("closed", () => {
113+
videoElement.pause();
114+
videoElement.srcObject = null;
115+
ws.send(tcl`Commit {}`);
116+
session = null;
117+
// captureSection.classList.remove("has-sess", "starting");
118+
});
119+
120+
sess.addEventListener("stateChanged", (event) => {
121+
if ((event.target.state === GstWebRTCAPI.SessionState.streaming)) {
122+
videoElement.srcObject = stream;
123+
videoElement.play().catch(() => {});
124+
// captureSection.classList.remove("starting");
125+
126+
// sess ready, announce to folk consumers!
127+
ws.send(tcl`Commit {
128+
Claim webrtc video ${feed} streams from peer ${peerId}
129+
}`);
130+
}
131+
});
132+
133+
/*
134+
sess.addEventListener("clientConsumerAdded", (event) => {
135+
if (captureSection._producerSession === sess) {
136+
console.info(`client consumer added: ${event.detail.peerId}`);
137+
}
138+
});
139+
140+
sess.addEventListener("clientConsumerRemoved", (event) => {
141+
if (captureSection._producerSession === sess) {
142+
console.info(`client consumer removed: ${event.detail.peerId}`);
143+
}
144+
});
145+
*/
146+
147+
// captureSection.classList.add("has-sess");
148+
sess.start();
149+
return sess;
150+
};
151+
152+
document.getElementById("stop-button").onclick = (event) => {
153+
event.preventDefault();
154+
if (session) {
155+
session.close();
156+
}
157+
};
158+
159+
document.getElementById("camera-button").onclick = (event) => {
160+
event.preventDefault();
161+
if (session) return;
162+
163+
session = true;
164+
const constraints = { video: true, audio: false };
165+
navigator.mediaDevices.getUserMedia(constraints)
166+
.then(handleStream)
167+
.then(
168+
(s) => { session = s; },
169+
(error) => {
170+
console.error("cannot have access to webcam and microphone", error);
171+
session = null;
172+
}
173+
);
174+
};
175+
176+
document.getElementById("screen-button").onclick = (event) => {
177+
event.preventDefault();
178+
if (session) return;
179+
180+
session = true;
181+
const constraints = { video: true, audio: false };
182+
navigator.mediaDevices.getDisplayMedia(constraints)
183+
.then(handleStream)
184+
.then(
185+
(s) => { session = s; },
186+
(error) => {
187+
console.error("cannot have access to screen", error);
188+
session = null;
189+
}
190+
);
191+
};
192+
</script>
193+
</body>
194+
</html>
195+
} $feed $feed]
196+
}

0 commit comments

Comments
 (0)