Merge branch 'stable' of ssh://vcs.fsf.org/libreplanet-static into stable
[libreplanet-static.git] / 2021 / assets / js / stream.js
CommitLineData
76c2419a
GF
1/**
2 * @licstart The following is the entire license notice for the JavaScript code in this page.
3 *
4 * IceCast Stream Monitor
5 * Copyright © 2015 David Thompson <davet@gnu.org>
6 *
7 * This program is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU Affero General Public License
9 * as published by the Free Software Foundation, either version 3 of
10 * the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Affero General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see
19 * <http://www.gnu.org/licenses/>.
20 *
21 * @licend The above is the entire license notice for the JavaScript code in this page
22 */
23
24if (!Array.prototype.find) {
25 Array.prototype.find = function(predicate) {
26 if (this == null) {
27 throw new TypeError('Array.prototype.find called on null or undefined');
28 }
29 if (typeof predicate !== 'function') {
30 throw new TypeError('predicate must be a function');
31 }
32 var list = Object(this);
33 var length = list.length >>> 0;
34 var thisArg = arguments[1];
35 var value;
36
37 for (var i = 0; i < length; i++) {
38 value = list[i];
39 if (predicate.call(thisArg, value, i, list)) {
40 return value;
41 }
42 }
43 return undefined;
44 };
45}
46
47var app = {};
48
49app.icecastUrl = "https://live.fsf.org";
50
51app.icecastApiUrl = "//live.fsf.org";
52
53app.scheduleEvery = function(duration, thunk) {
54 thunk();
55 setTimeout(function() {
56 app.scheduleEvery(duration, thunk);
57 }, duration);
58};
59
60app.nullStats = {
61 listeners: 0,
62 server_name: null,
63 server_description: null
64};
65
66app.publicApi = function(xhr) {
67 xhr.withCredentials = false;
68};
69
70app.streamStats = function(mount) {
71 var statsUrl = app.icecastApiUrl.concat('/status-json.xsl');
72
73 return m.request({
74 method: "GET",
75 url: statsUrl,
76 config: app.publicApi
77 }).then(function(data) {
78 // Match the end of the listen URL for the mount point.
79 var regexp = new RegExp(mount.concat('$'));
80
81 if(!data.icestats.source) {
82 return app.nullStats;
83 }
84
85 // Due to <https://trac.xiph.org/ticket/2174>, we must
86 // explicitly test if icestats.source is an array.
87 if(!(data.icestats.source instanceof Array)) {
88 data.icestats.source = [data.icestats.source];
89 }
90
91 var stats = data.icestats.source.find(function(source) {
92 return regexp.test(source.listenurl);
93 });
94
95 return stats || app.nullStats;
96 });
97};
98
99app.validStreamInfo = function(stats) {
100 var name = stats.server_name;
101 var desc = stats.server_description;
102
103 return name && desc && name !== "Unspecified name" &&
104 desc !== "Unspecified description";
105};
106
107app.mountToStreamUrl = function(mount) {
108 return app.icecastUrl.concat(mount);
109};
110
111app.changeVideoMount = function(video, mount) {
112 // This is quite hacky and doesn't feel like the Mithril way to do
113 // things, but we need to explicitly reload the video when the
114 // source URL changes.
115 video.src = app.mountToStreamUrl(mount) + "?t=" + (new Date() * 1);
116 video.load();
117 video.play();
118};
119
120app.withVideo = function(id, callback) {
121 return function() {
122 var video = document.getElementById(id);
123
124 if(video) {
125 callback(video);
126 }
127 };
128};
129
130app.streams = [
131 {
0851364a
AE
132 name: "Jupiter Room",
133 speakerMount: "/stream-room-jupiter.webm",
134 desktopMount: "/slides-room-jupiter.webm",
135 speakerSmallMount: "/stream-room-jupiter-480p.webm",
136 ircChannel: "#libreplanet_room_jupiter"
76c2419a 137 }, {
0851364a
AE
138 name: "Saturn Room",
139 speakerMount: "/stream-room-saturn.webm",
140 desktopMount: "/slides-room-saturn.webm",
141 speakerSmallMount: "/stream-room-saturn-480p.webm",
142 ircChannel: "#libreplanet_room_saturn"
76c2419a 143 }, {
0851364a
AE
144 name: "Neptune Room",
145 speakerMount: "/stream-room-neptune.webm",
146 desktopMount: "/slides-room-neptune.webm",
147 speakerSmallMount: "/stream-room-neptune-480p.webm",
148 ircChannel: "#libreplanet_room_neptune"
76c2419a
GF
149 }
150];
151
152app.controller = function() {
153 this.stream = m.prop(app.streams[0]);
154 this.stats = m.prop(app.nullStats);
155 this.showDesktop = m.prop(false);
156
157 // Check stats every 10 seconds.
158 app.scheduleEvery(10000, this.updateStats.bind(this));
159};
160
161app.controller.prototype.updateStats = function() {
162 //this.stats = app.streamStats(this.stream().speakerMount);
163};
164
165app.view = function(ctrl) {
166 var stream = ctrl.stream();
167 var stats = ctrl.stats() || app.nullStats;
168 var showDesktop = ctrl.showDesktop();
169
170 function renderSpeakerStream() {
171 return m("video.lp-video", {
172 id: "speaker-video",
173 controls: true,
174 autoplay: true,
175 // Sync desktop stream state as best we can.
176 onpause: app.withVideo("desktop-video", function(video) {
177 video.pause();
178 }),
179 onplay: app.withVideo("desktop-video", function(video) {
180 video.play();
181 })
182// onended: app.withVideo("desktop-video", function(video) {
183// setTimeout(function() {
184//
185// // we probably need to update the video stream GET request with the
186// // date hack so we get a non-cached version of the video when we
187// // try to resume.
188//
189// // we also probably need to re-try loading the video if it fails to
190// // load
191//
192// video.load();
193// video.play();
194// }, 3000);
195// })
196 }, [
197 m("source", {
198 src: app.mountToStreamUrl(stream.speakerMount) + "?t=" + (new Date() * 1)
199 }),
200 m("p",
201 m("em", [
202 "Your browser does not support the HTML5 video tag, ",
203 m("a", { href: "TODO" }, "[ please download ]"),
204 "the video instead"
205 ]))
206 ]);
207 }
208
209 function renderDesktopStream() {
210 return m("video.lp-video", {
211 id: "desktop-video",
212 controls: true,
213 autoplay: true
214// onended: app.withVideo("desktop-video", function(video) {
215// setTimeout(function() {
216// // we probably need to update the video stream GET request with the
217// // date hack so we get a non-cached version of the video when we
218// // try to resume.
219//
220// // we also probably need to re-try loading the video if it fails to
221// // load
222// video.load();
223// video.play();
224// }, 3000);
225// })
226 }, [
227 m("source", { src: app.mountToStreamUrl(stream.desktopMount) + "?t=" + (new Date() * 1) }),
228 m("p",
229 m("em", [
230 "Your browser does not support the HTML5 video tag, ",
231 m("a", { href: "TODO" }, "[ please download ]"),
232 "the video instead"
233 ]))
234 ]);
235 }
236
237 /*
238 function renderToggleDesktopStream() {
239 var action = showDesktop ? "Hide slides stream" : "Show slides stream";
240
241 return m(".row", [
242 m(".col-sm-offset-4.col-sm-4",
243 m("button.btn.btn-block.btn-default", {
244 onclick: function() {
245 ctrl.showDesktop(!showDesktop);
246 }
247 }, action)
248 )
249 ]);
250 }
251 */
252
253 function renderRoomSelector() {
254 return m(".row",
255 m(".col-sm-offset-1.col-sm-10",
256 m("ol.breadcrumb.text-center", app.streams.map(function(s) {
257 return m("li", {
258 class: s === stream ? "active" : null,
259 onclick: function() {
260 var speakerVideo = document.getElementById("speaker-video");
261 var desktopVideo = document.getElementById("desktop-video");
262
263 app.changeVideoMount(speakerVideo, s.speakerMount);
264
265 // Video element doesn't exist when the user
266 // hasn't elected to show it.
267 if(desktopVideo) {
268 app.changeVideoMount(desktopVideo, s.desktopMount);
269 }
270
271 ctrl.stream(s);
272 ctrl.updateStats();
273
274 return false;
275 }
276 }, m("a.alt-a", { href: "#" }, s.name));
277 }))));
278 }
279
280 function renderStats() {
281 var info;
282
283 if(stats === app.nullStats) {
284 info = m("i", "not broadcasting");
285 } else {
286 info = m("i", "live");
287 }
288 // else if(app.validStreamInfo(stats)) {
289 // info = [
290 // m("strong", stats.server_name),
291 // " — ",
292 // m("i", stats.server_description)
293 // ];
294 // } else {
295 // info = null;
296 // }
297
298 //return m(".row", [
299 // m(".col-sm-8", info),
300 // m(".col-sm-4.text-right", [
301 // m("strong", stats.listeners),
302 // " watching"
303 // ])
304 //]);
305 return m("span");
306 }
307
94901463
AE
308 // show or hide irc info based on page URL
309 if (!window.location.pathname.match(RegExp("\/2021\/live.*"))) {
310 var irc_info = [
68afd985
AE
311 "This room's channel: ",
312 m("strong", ["/join ", stream.ircChannel])
94901463 313 ];
c32d5b90 314 // set css to show irc links in sidebar
68afd985 315 document.querySelector('#irc-links-panel').style.display = "block";
94901463
AE
316 } else {
317 var irc_info = [
12c92727
AE
318 m("strong", [
319 m("a", { target: "_blank", href: "https://my.fsf.org/civicrm/event/info?reset=1&id=92" }, "Register"),
320 ]),
f2f84858 321 " gratis to access more IRC rooms. ",
53d4a866
AE
322 m("strong", [
323 m("a", { href: "/2021/registered/live/" }, "Already registered?"),
324 ])
94901463
AE
325 ];
326 }
327
76c2419a
GF
328 return [
329 renderRoomSelector(),
330 m("h2", stream.name),
331 renderStats(),
332 renderSpeakerStream(),
333 showDesktop ? renderDesktopStream() : null,
334 // renderToggleDesktopStream(),
335 m("p", "Join the discussion online!"),
336 m("ul", [
337 m("li", [
338 "Conference-wide Freenode IRC channel: ",
339 m("strong", "/join #libreplanet")
340 ]),
39a9abec
AE
341 m("li",
342 m("div", { class: "special-irc" },
343 irc_info)
344 ),
76c2419a
GF
345 // m("li", [
346 // "Conference-wide Mumble (voice chat) server: ",
347 // m("strong", "mumble.fsf.org")
348 // ]),
349 m("li", [
350 "Conference hashtag for ",
5b7bc4f3 351 m("a", { target: "_blank", href: "https://www.fsf.org/share" }, "microblogging"),
76c2419a
GF
352 ": ",
353 m("strong", "#libreplanet")
354 ])
355 ]),
76c2419a
GF
356 ];
357};
358
359m.module(document.getElementById("stream"), app);