disable status updates
[libreplanet-static.git] / 2017 / assets / js / stream.js
CommitLineData
fcf95e6d 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 = "http://live2.fsf.org";
50
9b35df7f 51app.icecastApiUrl = "//live2.fsf.org";
fcf95e6d 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.
d901c86f 115 video.src = app.mountToStreamUrl(mount) + "?t=" + (new Date() * 1);
fcf95e6d 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 {
132 name: "Room 123",
133 speakerMount: "/stream-123.webm",
134 desktopMount: "/slides-123.webm",
4e4421e6 135 speakerSmallMount: "/stream-123-480p.webm",
fcf95e6d 136 ircChannel: "#libreplanet_room123"
137 }, {
138 name: "Room 141",
139 speakerMount: "/stream-141.webm",
140 desktopMount: "/slides-141.webm",
4e4421e6 141 speakerSmallMount: "/stream-141-480p.webm",
fcf95e6d 142 ircChannel: "#libreplanet_room141"
7bcaa6f6
AE
143 }, {
144 name: "Room 144",
145 speakerMount: "/stream-144.webm",
146 desktopMount: "/slides-144.webm",
4e4421e6 147 speakerSmallMount: "/stream-144-480p.webm",
7bcaa6f6 148 ircChannel: "#libreplanet_room144"
fcf95e6d 149 }, {
150 name: "Room 155",
151 speakerMount: "/stream-155.webm",
152 desktopMount: "/slides-155.webm",
4e4421e6 153 speakerSmallMount: "/stream-155-480p.webm",
fcf95e6d 154 ircChannel: "#libreplanet_room155"
155 }
156];
157
158app.controller = function() {
159 this.stream = m.prop(app.streams[0]);
160 this.stats = m.prop(app.nullStats);
161 this.showDesktop = m.prop(false);
162
163 // Check stats every 10 seconds.
164 app.scheduleEvery(10000, this.updateStats.bind(this));
165};
166
167app.controller.prototype.updateStats = function() {
6cf874b9 168 //this.stats = app.streamStats(this.stream().speakerMount);
fcf95e6d 169};
170
171app.view = function(ctrl) {
172 var stream = ctrl.stream();
173 var stats = ctrl.stats() || app.nullStats;
174 var showDesktop = ctrl.showDesktop();
175
176 function renderSpeakerStream() {
177 return m("video.lp-video", {
178 id: "speaker-video",
179 controls: true,
180 autoplay: true,
181 // Sync desktop stream state as best we can.
182 onpause: app.withVideo("desktop-video", function(video) {
183 video.pause();
184 }),
185 onplay: app.withVideo("desktop-video", function(video) {
186 video.play();
187 })
188 }, [
189 m("source", {
a2ddd223 190 src: app.mountToStreamUrl(stream.speakerMount) + "?t=" + (new Date() * 1)
fcf95e6d 191 }),
192 m("p",
193 m("em", [
194 "Your browser does not support the HTML5 video tag, ",
195 m("a", { href: "TODO" }, "[ please download ]"),
196 "the video instead"
197 ]))
198 ]);
199 }
200
201 function renderDesktopStream() {
202 return m("video.lp-video", {
203 id: "desktop-video",
204 autoplay: true
205 }, [
a2ddd223 206 m("source", { src: app.mountToStreamUrl(stream.desktopMount) + "?t=" + (new Date() * 1) }),
fcf95e6d 207 m("p",
208 m("em", [
209 "Your browser does not support the HTML5 video tag, ",
210 m("a", { href: "TODO" }, "[ please download ]"),
211 "the video instead"
212 ]))
213 ]);
214 }
215
216 function renderToggleDesktopStream() {
217 var action = showDesktop ? "Hide desktop stream" : "Show desktop stream";
218
219 return m(".row", [
220 m(".col-sm-offset-4.col-sm-4",
221 m("button.btn.btn-block.btn-default", {
222 onclick: function() {
223 ctrl.showDesktop(!showDesktop);
224 }
225 }, action)
226 )
227 ]);
228 }
229
230 function renderRoomSelector() {
231 return m(".row",
232 m(".col-sm-offset-1.col-sm-10",
233 m("ol.breadcrumb.text-center", app.streams.map(function(s) {
234 return m("li", {
235 class: s === stream ? "active" : null,
236 onclick: function() {
237 var speakerVideo = document.getElementById("speaker-video");
238 var desktopVideo = document.getElementById("desktop-video");
239
240 app.changeVideoMount(speakerVideo, s.speakerMount);
241
242 // Video element doesn't exist when the user
243 // hasn't elected to show it.
244 if(desktopVideo) {
245 app.changeVideoMount(desktopVideo, s.desktopMount);
246 }
247
248 ctrl.stream(s);
249 ctrl.updateStats();
250
251 return false;
252 }
253 }, m("a.alt-a", { href: "#" }, s.name));
254 }))));
255 }
256
257 function renderStats() {
258 var info;
259
260 if(stats === app.nullStats) {
261 info = m("i", "not broadcasting");
262 } else {
263 info = m("i", "live");
264 }
265 // else if(app.validStreamInfo(stats)) {
266 // info = [
267 // m("strong", stats.server_name),
268 // " — ",
269 // m("i", stats.server_description)
270 // ];
271 // } else {
272 // info = null;
273 // }
274
3b5f5478
AE
275 //return m(".row", [
276 // m(".col-sm-8", info),
277 // m(".col-sm-4.text-right", [
278 // m("strong", stats.listeners),
279 // " watching"
280 // ])
281 //]);
282 return m("span");
fcf95e6d 283 }
284
285 return [
286 renderRoomSelector(),
287 m("h2", stream.name),
288 renderStats(),
289 renderSpeakerStream(),
290 showDesktop ? renderDesktopStream() : null,
291 renderToggleDesktopStream(),
4e4421e6 292 m("p", [ m("a", { href: app.icecastUrl + stream.speakerSmallMount }, "Low res stream" ) ] ),
fcf95e6d 293 m("h2", "IRC"),
294 m("p", "Join the discussion online!"),
295 m("ul", [
296 m("li", [
297 "Conference-wide Freenode IRC channel: ",
298 m("strong", "#libreplanet")
299 ]),
300 m("li", [
301 "Freenode IRC channel for ",
302 stream.name,
303 ": ",
304 m("strong", stream.ircChannel)
305 ]),
306 m("li", [
307 "Conference hashtag for ",
308 m("a", { href: "https://fsf.org/twitter" }, "microblogging"),
309 ": ",
a2df0d9d 310 m("strong", "#libreplanet")
fcf95e6d 311 ])
312 ])
313 ];
314};
315
316m.module(document.getElementById("stream"), app);