var RE_WHITESPACE = /[\t\r\n\f]/g var ajax = { get: function(url, data) { if (typeof data == 'object') { var index = 0; for (var key in data) { var val = data[key]; url += index === 0 && url.indexOf('?') === -1 ? '?' : '&'; url += encodeURIComponent(key) + '=' + encodeURIComponent(val); } } return this.raw(url); }, post: function(url, body) { var opts = { method: 'POST' }; if (body) opts.body = body; return this.raw(url, opts); }, raw: function(url, options) { if (!options) options = {} return fetch(url, Object.assign({ headers: { 'X-Requested-With': 'XMLHttpRequest', } }, options)) .then(resp => { return resp.json() }) } }; function extend(a, b) { return Object.assign(a, b); } function ge(id) { return document.getElementById(id); } (function() { var ua = navigator.userAgent.toLowerCase(); window.browserInfo = { version: (ua.match(/.+(?:me|ox|on|rv|it|ra|ie)[\/: ]([\d.]+)/) || [0,'0'])[1], //opera: /opera/i.test(ua), msie: (/msie/i.test(ua) && !/opera/i.test(ua)) || /trident/i.test(ua), mozilla: /firefox/i.test(ua), android: /android/i.test(ua), mac: /mac/i.test(ua), samsungBrowser: /samsungbrowser/i.test(ua), chrome: /chrome/i.test(ua), safari: /safari/i.test(ua), mobile: /iphone|ipod|ipad|opera mini|opera mobi|iemobile|android/i.test(ua), operaMini: /opera mini/i.test(ua), ios: /iphone|ipod|ipad|watchos/i.test(ua) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1), }; })(); function isTouchDevice() { return 'ontouchstart' in window || navigator.msMaxTouchPoints; } function hasClass(el, name) { if (!el) throw new Error('hasClass: invalid element') if (el.nodeType !== 1) throw new Error('hasClass: expected nodeType is 1, got' + el.nodeType) if (window.DOMTokenList && el.classList instanceof DOMTokenList) { return el.classList.contains(name) } else { return (" " + el.className + " ").replace(RE_WHITESPACE, " ").indexOf(" " + name + " ") >= 0 } } function addClass(el, name) { if (!hasClass(el, name)) { el.className = (el.className ? el.className + ' ' : '') + name; return true } return false } function removeClass(el, name) { if (!el) throw new Error('removeClass: invalid element') if (Array.isArray(name)) { for (var i = 0; i < name.length; i++) removeClass(el, name[i]); return; } el.className = ((el.className || '').replace((new RegExp('(\\s|^)' + name + '(\\s|$)')), ' ')).trim() } function indexInit() { var blocks = ['zones', 'all']; for (var i = 0; i < blocks.length; i++) { var button = ge('cam_'+blocks[i]+'_btn'); button.addEventListener('click', function(e) { var selected = e.target.getAttribute('data-id'); for (var j = 0; j < blocks.length; j++) { var button = ge('cam_'+blocks[j]+'_btn'); var content = ge('cam_'+blocks[j]); if (blocks[j] === selected) { addClass(button, 'active'); content.style.display = ''; } else { removeClass(button, 'active'); content.style.display = 'none'; } } }); } } var Cameras = { hlsOptions: null, host: null, proto: null, hlsDebugVideoEvents: false, getUrl: function(name) { return this.proto + '://' + this.host + '/ipcam/' + name + '/live.m3u8'; }, setupHls: function(video, name, useHls) { var src = this.getUrl(name); // hls.js is not supported on platforms that do not have Media Source Extensions (MSE) enabled. // When the browser has built-in HLS support (check using `canPlayType`), we can provide an HLS manifest (i.e. .m3u8 URL) directly to the video element through the `src` property. // This is using the built-in support of the plain video element, without using hls.js. if (useHls) { var config = this.hlsOptions; config.xhrSetup = function (xhr,url) { xhr.withCredentials = true; }; var hls = new Hls(config); hls.loadSource(src); hls.attachMedia(video); hls.on(Hls.Events.MEDIA_ATTACHED, function () { video.muted = true; video.play(); }); } else { console.warn('hls.js is not supported, trying the native way...') video.autoplay = true; video.muted = true; video.playsInline = true; video.autoplay = true; if (window.browserInfo.ios) video.setAttribute('controls', 'controls'); video.src = src; var events = ['canplay']; if (this.hlsDebugVideoEvents) events.push('canplay', 'canplaythrough', 'durationchange', 'ended', 'loadeddata', 'loadedmetadata', 'pause', 'play', 'playing', 'progress', 'seeked', 'seeking', 'stalled', 'suspend', 'timeupdate', 'waiting'); for (var i = 0; i < events.length; i++) { var evt = events[i]; (function(evt, video, name) { video.addEventListener(evt, function(e) { if (this.debugVideoEvents) console.log(name + ': ' + evt, e); if (!window.browserInfo.ios && ['canplay', 'loadedmetadata'].includes(evt)) video.play(); }) })(evt, video, name); } } }, init: function(opts) { this.proto = opts.proto; this.host = opts.host; this.hlsOptions = opts.hlsConfig; var useHls; if (opts.hlsConfig !== undefined) { useHls = Hls.isSupported(); if (!useHls && !this.hasFallbackSupport()) { alert('Neither HLS nor vnd.apple.mpegurl is not supported by your browser.'); return; } } for (var i = 0; i < opts.cams.length; i++) { var camId = opts.cams[i]+'-low'; var video = document.createElement('video'); video.setAttribute('id', 'video-'+camId); document.getElementById('videos').appendChild(video); this.setupHls(video, camId, useHls); } }, hasFallbackSupport: function() { var video = document.createElement('video'); return video.canPlayType('application/vnd.apple.mpegurl'); }, }; class ModemStatusUpdater { constructor(id) { this.id = id; this.elem = ge('modem_data_'+id); this.fetch() } fetch() { ajax.get('/modems/info.ajx', { id: this.id }).then(({response}) => { const {html} = response; this.elem.innerHTML = html; // TODO enqueue rerender }); } } var ModemStatus = { _modems: [], init: function(modems) { for (var i = 0; i < modems.length; i++) { var modem = modems[i]; this._modems.push(new ModemStatusUpdater(modem)); } } }; var Inverter = { poll: function () { setInterval(this._tick, 1000); }, _tick: function() { ajax.get('/inverter.ajx') .then(({response}) => { if (response) { var el = document.getElementById('inverter_status'); el.innerHTML = response.html; } }); } };