b2b611ad09a81b03506e922d668f3e48366c1ddc
2 * @licstart The following is the entire license notice for the JavaScript code in this page.
4 * IceCast Stream Monitor
5 * Copyright © 2015 David Thompson <davet@gnu.org>
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.
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.
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/>.
21 * @licend The above is the entire license notice for the JavaScript code in this page
24 if (!Array
.prototype.find
) {
25 Array
.prototype.find = function(predicate
) {
27 throw new TypeError('Array.prototype.find called on null or undefined');
29 if (typeof predicate
!== 'function') {
30 throw new TypeError('predicate must be a function');
32 var list
= Object(this);
33 var length
= list
.length
>>> 0;
34 var thisArg
= arguments
[1];
37 for (var i
= 0; i
< length
; i
++) {
39 if (predicate
.call(thisArg
, value
, i
, list
)) {
49 app
.icecastUrl
= "//live2.fsf.org";
51 app
.icecastApiUrl
= "//live2.fsf.org";
53 app
.scheduleEvery = function(duration
, thunk
) {
55 setTimeout(function() {
56 app
.scheduleEvery(duration
, thunk
);
63 server_description
: null
66 app
.publicApi = function(xhr
) {
67 xhr
.withCredentials
= false;
70 app
.streamStats = function(mount
) {
71 var statsUrl
= app
.icecastApiUrl
.concat('/status-json.xsl');
77 }).then(function(data
) {
78 // Match the end of the listen URL for the mount point.
79 var regexp
= new RegExp(mount
.concat('$'));
81 if(!data
.icestats
.source
) {
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
];
91 var stats
= data
.icestats
.source
.find(function(source
) {
92 return regexp
.test(source
.listenurl
);
95 return stats
|| app
.nullStats
;
99 app
.validStreamInfo = function(stats
) {
100 var name
= stats
.server_name
;
101 var desc
= stats
.server_description
;
103 return name
&& desc
&& name
!== "Unspecified name" &&
104 desc
!== "Unspecified description";
107 app
.mountToStreamUrl = function(mount
) {
108 return app
.icecastUrl
.concat(mount
);
111 app
.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
);
120 app
.withVideo = function(id
, callback
) {
122 var video
= document
.getElementById(id
);
133 speakerMount
: "/stream-123.webm",
134 desktopMount
: "/slides-123.webm",
135 ircChannel
: "#libreplanet_room123"
138 speakerMount
: "/stream-141.ogv",
139 desktopMount
: "/slides-141.ogv",
140 ircChannel
: "#libreplanet_room141"
143 speakerMount
: "/stream-155.ogv",
144 desktopMount
: "/slides-155.ogv",
145 ircChannel
: "#libreplanet_room155"
149 app
.controller = function() {
150 this.stream
= m
.prop(app
.streams
[0]);
151 this.stats
= m
.prop(app
.nullStats
);
152 this.showDesktop
= m
.prop(false);
154 // Check stats every 10 seconds.
155 app
.scheduleEvery(10000, this.updateStats
.bind(this));
158 app
.controller
.prototype.updateStats = function() {
159 this.stats
= app
.streamStats(this.stream().speakerMount
);
162 app
.view = function(ctrl
) {
163 var stream
= ctrl
.stream();
164 var stats
= ctrl
.stats() || app
.nullStats
;
165 var showDesktop
= ctrl
.showDesktop();
167 function renderSpeakerStream() {
168 return m("video.lp-video", {
172 // Sync desktop stream state as best we can.
173 onpause
: app
.withVideo("desktop-video", function(video
) {
176 onplay
: app
.withVideo("desktop-video", function(video
) {
181 src
: app
.mountToStreamUrl(stream
.speakerMount
)
185 "Your browser does not support the HTML5 video tag, ",
186 m("a", { href
: "TODO" }, "[ please download ]"),
192 function renderDesktopStream() {
193 return m("video.lp-video", {
197 m("source", { src
: app
.mountToStreamUrl(stream
.desktopMount
) }),
200 "Your browser does not support the HTML5 video tag, ",
201 m("a", { href
: "TODO" }, "[ please download ]"),
207 function renderToggleDesktopStream() {
208 var action
= showDesktop
? "Hide desktop stream" : "Show desktop stream";
211 m(".col-sm-offset-4.col-sm-4",
212 m("button.btn.btn-block.btn-default", {
213 onclick: function() {
214 ctrl
.showDesktop(!showDesktop
);
221 function renderRoomSelector() {
223 m(".col-sm-offset-1.col-sm-10",
224 m("ol.breadcrumb.text-center", app
.streams
.map(function(s
) {
226 class: s
=== stream
? "active" : null,
227 onclick: function() {
228 var speakerVideo
= document
.getElementById("speaker-video");
229 var desktopVideo
= document
.getElementById("desktop-video");
231 app
.changeVideoMount(speakerVideo
, s
.speakerMount
);
233 // Video element doesn't exist when the user
234 // hasn't elected to show it.
236 app
.changeVideoMount(desktopVideo
, s
.desktopMount
);
244 }, m("a.alt-a", { href
: "#" }, s
.name
));
248 function renderStats() {
251 if(stats
=== app
.nullStats
) {
252 info
= m("i", "not broadcasting");
254 info
= m("i", "live");
256 // else if(app.validStreamInfo(stats)) {
258 // m("strong", stats.server_name),
260 // m("i", stats.server_description)
267 m(".col-sm-8", info
),
268 m(".col-sm-4.text-right", [
269 m("strong", stats
.listeners
),
276 renderRoomSelector(),
277 m("h2", stream
.name
),
279 renderSpeakerStream(),
280 showDesktop
? renderDesktopStream() : null,
281 renderToggleDesktopStream(),
283 m("p", "Join the discussion online!"),
286 "Conference-wide Freenode IRC channel: ",
287 m("strong", "#libreplanet")
290 "Freenode IRC channel for ",
293 m("strong", stream
.ircChannel
)
296 "Conference hashtag for ",
297 m("a", { href
: "https://fsf.org/twitter" }, "microblogging"),
299 m("strong", "#lp2015")
305 m
.module(document
.getElementById("stream"), app
);