(function() { var RE_WHITESPACE = /[\t\r\n\f]/g window.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() }) } }; window.extend = function(a, b) { return Object.assign(a, b); } window.ge = function(id) { return document.getElementById(id); } 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), }; window.isTouchDevice = function() { return 'ontouchstart' in window || navigator.msMaxTouchPoints; } window.hasClass = function(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 } } window.addClass = function(el, name) { if (!hasClass(el, name)) { el.className = (el.className ? el.className + ' ' : '') + name; return true } return false } window.Cameras = { hlsOptions: null, h265webjsOptions: 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); } } }, setupH265WebJS: function(videoContainer, name) { var containerHeightFixed = false; var config = { player: 'video-'+name, width: videoContainer.offsetWidth, height: parseInt(videoContainer.offsetWidth * 9 / 16, 10), accurateSeek: true, token: this.h265webjsOptions.token, extInfo: { moovStartFlag: true, readyShow: true, autoPlay: true, rawFps: 15, } }; var mediaInfo; var player = window.new265webjs(this.getUrl(name), config); player.onSeekStart = (pts) => { console.log(name + ": onSeekStart:" + pts); }; player.onSeekFinish = () => { console.log(name + ": onSeekFinish"); }; player.onPlayFinish = () => { console.log(name + ": onPlayFinish"); }; player.onRender = (width, height, imageBufferY, imageBufferB, imageBufferR) => { // console.log(name + ": onRender"); if (!containerHeightFixed) { var ratio = height / width; videoContainer.style.width = parseInt(videoContainer.offsetWidth * ratio, 10)+'px'; containerHeightFixed = true; } }; player.onOpenFullScreen = () => { console.log(name + ": onOpenFullScreen"); }; player.onCloseFullScreen = () => { console.log(name + ": onCloseFullScreen"); }; player.onSeekFinish = () => { console.log(name + ": onSeekFinish"); }; player.onLoadCache = () => { console.log(name + ": onLoadCache"); }; player.onLoadCacheFinshed = () => { console.log(name + ": onLoadCacheFinshed"); }; player.onReadyShowDone = () => { // console.log(name + ": onReadyShowDone:【You can play now】"); player.play() }; player.onLoadFinish = () => { console.log(name + ": onLoadFinish"); player.setVoice(1.0); mediaInfo = player.mediaInfo(); console.log("onLoadFinish mediaInfo===========>", mediaInfo); var codecName = "h265"; if (mediaInfo.meta.isHEVC === false) { console.log(name + ": onLoadFinish is Not HEVC/H.265"); codecName = "h264"; } else { console.log(name + ": onLoadFinish is HEVC/H.265"); } console.log(name + ": onLoadFinish media Codec:" + codecName); console.log(name + ": onLoadFinish media FPS:" + mediaInfo.meta.fps); console.log(name + ": onLoadFinish media size:" + mediaInfo.meta.size.width + "x" + mediaInfo.meta.size.height); if (mediaInfo.meta.audioNone) { console.log(name + ": onLoadFinish media no Audio"); } else { console.log(name + ": onLoadFinish media sampleRate:" + mediaInfo.meta.sampleRate); } if (mediaInfo.videoType == "vod") { console.log(name + ": onLoadFinish media is VOD"); console.log(name + ": onLoadFinish media dur:" + Math.ceil(mediaInfo.meta.durationMs) / 1000.0); } else { console.log(name + ": onLoadFinish media is LIVE"); } }; player.onCacheProcess = (cPts) => { console.log(name + ": onCacheProcess:" + cPts); }; player.onPlayTime = (videoPTS) => { if (mediaInfo.videoType == "vod") { console.log(name + ": onPlayTime:" + videoPTS); } else { // LIVE } }; player.do(); // console.log('setupH265WebJS: video: ', video.offsetWidth, video.offsetHeight) }, init: function(opts) { this.proto = opts.proto; this.host = opts.host; this.hlsOptions = opts.hlsConfig; this.h265webjsOptions = opts.h265webjsConfig; 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 camId in opts.camsByType) { var name = camId + ''; if (opts.isLow) name += '-low'; var type = opts.camsByType[camId]; switch (type) { case 'h265': var videoContainer = document.createElement('div'); videoContainer.setAttribute('id', 'video-'+name); videoContainer.setAttribute('style', 'position: relative'); // a hack to fix an error in h265webjs lib videoContainer.className = 'video-container'; document.getElementById('videos').appendChild(videoContainer); try { this.setupH265WebJS(videoContainer, name); } catch (e) { console.error('cam'+camId+': error', e) } break; case 'h264': var video = document.createElement('video'); video.setAttribute('id', 'video-'+name); document.getElementById('videos').appendChild(video); this.setupHls(video, name, useHls); break; } } }, 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)); } } };