/* global alert, XMLHttpRequest, STREAM_SERVER, STREAM_NAME */ import Hls from 'hls.js' // Elements let player = document.querySelector('.livecnt') let vid = player.querySelector('#stream') let overlay = player.querySelector('.overlay') let btn = overlay.querySelector('#playbtn') let time = overlay.querySelector('#duration') let fullscreenbtn = overlay.querySelector('#fullscrbtn') let playbtn = overlay.querySelector('#playbtn') let mutebtn = overlay.querySelector('#mutebtn') let lstat = overlay.querySelector('.live') let opts = overlay.querySelector('.controls') let bigbtn = overlay.querySelector('.bigplaybtn') let volumebar = overlay.querySelector('#volume_seek') let volumeseek = volumebar.querySelector('.seeker') let volumeseekInner = volumeseek.querySelector('.seekbar') let viewers = overlay.querySelector('.viewers') let links let linksList // Variables let hls let hideTimeout let retryTimeout let vidReady = false let shouldHide = true let inFullscreen = false let errored = false let ws function GET (url, istext) { return new Promise((resolve, reject) => { var xmlHttp = new XMLHttpRequest() xmlHttp.onreadystatechange = function () { if (xmlHttp.readyState === 4 && xmlHttp.status === 200) { resolve(xmlHttp.responseText) } else if (xmlHttp.readyState === 4 && xmlHttp.status >= 400) { let err = new Error(xmlHttp.status) err.request = xmlHttp reject(err) } } xmlHttp.open('GET', url, true) istext && (xmlHttp.responseType = 'text') xmlHttp.send(null) }) } function clampAddition (val) { let volume = vid.volume if (volume + val > 1) { volume = 1 } else if (volume + val < 0) { volume = 0 } else { volume += val } return volume.toFixed(2) } function showBigBtn (show) { if (show) { bigbtn.className = 'bigplaybtn' } else { bigbtn.className = 'bigplaybtn hidden' } } function updateVolume () { volumeseekInner.style.width = vid.volume * 100 + '%' } function viewersCount (res) { viewers.style.display = 'block' viewers.innerHTML = res.length + ' watching' } function handleWebSocket (live) { if (!live && ws) { ws.onerror = ws.onopen = ws.onclose = null ws.close() ws = null return } if (ws) return ws = new WebSocket(`ws://${location.host}`) ws.onerror = function(e) { console.error('Socket errored, retrying..', e) handleWebSocket(false) setTimeout(() => handleWebSocket(vidReady), 5000) } ws.onopen = function() { console.log('Upstream socket connection established') if (!vid.paused) ws.send('watch ' + STREAM_NAME) ws.onmessage = function (event) { if (!event) return let message = event.data if (message.indexOf('viewlist ') === 0) { let str = message.substring(9) let list = str.split(',') if (str === '') list = [] viewersCount(list) } } } ws.onclose = function() { console.error('Socket died, retrying..') ws = null setTimeout(() => handleWebSocket(vidReady), 5000) } } function liveStatus (status) { if (status) { lstat.innerHTML = 'live now' lstat.className = 'badge live' clearTimeout(retryTimeout) if (vid.paused) { showBigBtn(true) } handleWebSocket(true) } else { lstat.innerHTML = 'offline' lstat.className = 'badge live offline' viewers.style.display = 'none' handleWebSocket(false) retryTimeout = setTimeout(() => { if (vidReady) return loadSource() }, 10000) } } function hide () { if (vid.paused || !shouldHide) { overlay.className = 'overlay' return } overlay.className = 'overlay hidden' } function resetHide () { overlay.className = 'overlay' clearTimeout(hideTimeout) if (vid.paused) return if (!shouldHide) return hideTimeout = setTimeout(() => { hide() }, 5000) } function updateTime () { let minutes = Math.floor(vid.currentTime / 60) let seconds = Math.floor(vid.currentTime - minutes * 60) time.innerHTML = minutes + ':' + (seconds < 10 ? '0' + seconds : seconds) } function toggleStream () { if (!vid) return if (!vidReady) return if (vid.paused) { if (ws) ws.send('watch ' + STREAM_NAME) vid.play() btn.innerHTML = '' showBigBtn(false) } else { if (ws) ws.send('stop ' + STREAM_NAME) vid.pause() btn.innerHTML = '' showBigBtn(true) } } function toggleSound () { let muteicon = mutebtn.querySelector('.fa') if (vid.muted) { vid.muted = false muteicon.className = 'fa fa-volume-up fa-fw' } else { vid.muted = true muteicon.className = 'fa fa-volume-off fa-fw' } } function exitHandler () { if (!(document.fullScreen || document.webkitIsFullScreen || document.mozFullScreen)) { inFullscreen = false } if (inFullscreen) { fullscreenbtn.innerHTML = '' } else { fullscreenbtn.innerHTML = '' } } function toggleFullscreen () { if (vid.enterFullscreen) { if (!document.fullScreen) { player.requestFullScreen() inFullscreen = true } else { document.cancelFullScreen() } } else if (vid.webkitEnterFullscreen) { if (!document.webkitIsFullScreen) { player.webkitRequestFullScreen() inFullscreen = true } else { document.webkitCancelFullScreen() } } else if (vid.mozRequestFullScreen) { if (!document.mozFullScreen) { player.mozRequestFullScreen() inFullscreen = true } else { document.mozCancelFullScreen() } } else { alert('Your browser doesn\'t support fullscreen!') } } window.addEventListener('onblur', () => { shouldHide = true }, false) window.addEventListener('onfocus', () => { shouldHide = true }, false) player.addEventListener('mousemove', resetHide) opts.addEventListener('mouseenter', () => { shouldHide = false }) opts.addEventListener('mouseleave', () => { shouldHide = true }) opts.addEventListener('mousemove', () => { shouldHide = false }) bigbtn.addEventListener('click', () => { toggleStream() }) volumeseek.addEventListener('click', (e) => { vid.volume = ((e.pageX - volumeseek.offsetLeft) / volumeseek.clientWidth) updateVolume() }) let mousewheelevt = (/Firefox/i.test(navigator.userAgent)) ? 'DOMMouseScroll' : 'mousewheel' volumebar.addEventListener(mousewheelevt, (e) => { e.preventDefault() let scrollAmnt = (e.wheelDelta == null ? e.detail * -40 : e.wheelDelta) if (scrollAmnt < 0) { vid.volume = clampAddition(-0.1) } else { vid.volume = clampAddition(0.1) } updateVolume() }, false) function loadSource () { if (!hls) return hls.loadSource(STREAM_SERVER + STREAM_NAME + '.m3u8') } if (Hls.isSupported()) { hls = new Hls() hls.attachMedia(vid) loadSource() hls.on(Hls.Events.MANIFEST_PARSED, () => { vidReady = true }) hls.on(Hls.Events.ERROR, (e) => { vidReady = false if (!vid.paused) { toggleStream() resetHide() } }) } else { alert('Your browser does not support HLS streaming!') } // helper function to get an element's exact position function getPosition(el) { let xPosition = 0 let yPosition = 0 while (el) { if (el.tagName == 'BODY') { // deal with browser quirks with body/window/document and page scroll let xScrollPos = el.scrollLeft || document.documentElement.scrollLeft let yScrollPos = el.scrollTop || document.documentElement.scrollTop xPosition += (el.offsetLeft - xScrollPos + el.clientLeft) yPosition += (el.offsetTop - yScrollPos + el.clientTop) } else { xPosition += (el.offsetLeft - el.scrollLeft + el.clientLeft) yPosition += (el.offsetTop - el.scrollTop + el.clientTop) } el = el.offsetParent } return { x: xPosition, y: yPosition } } function hideOnClickOutside (element) { const isVisible = (element) => { return element.style.display === 'block' } const outsideClickListener = event => { if ((!element.contains(event.target) && isVisible(element)) && (event.target !== links && !links.contains(event.target))) { element.style.display = 'none' removeClickListener() } } const removeClickListener = () => { document.removeEventListener('click', outsideClickListener) } document.addEventListener('click', outsideClickListener) } function updateLinks (srcs) { if (srcs.length === 0) { if (links) links.style.display = 'none' return } if (!links) { links = document.createElement('div') links.className = 'right button' links.id = 'links' links.innerHTML = '' overlay.getElementsByClassName('controls')[0].insertBefore(links, fullscreenbtn) linksList = document.createElement('div') linksList.className = 'links overlay-list' linksList.style = 'display: none;' overlay.appendChild(linksList) links.addEventListener('click', function (e) { e.preventDefault() if (linksList.style.display === 'block') { linksList.style = 'display: none;' return } hideOnClickOutside(linksList) let pos = getPosition(links) linksList.style = 'display: block;' pos.x -= linksList.offsetWidth - links.offsetWidth / 2 pos.y -= linksList.offsetHeight linksList.style = 'display: block; left: ' + pos.x + 'px; top: ' + pos.y + 'px;' }) } links.style.display = 'block' linksList.innerHTML = '' for (let i in srcs) { let link = srcs[i] let el = document.createElement('a') el.href = link.url el.innerText = link.name el.target = '_blank' linksList.appendChild(el) } } function getStreamStatus () { GET('/api/channel/' + STREAM_NAME).then((data) => { let jd = JSON.parse(data) if (jd.error) { errored = true return alert(jd.error) } if (jd.links) updateLinks(jd.links) if (ws) ws.send('viewers ' + STREAM_NAME) if (jd.live && !vidReady) loadSource() liveStatus(jd.live) }, (e) => { errored = true liveStatus(false) }) if (!errored) setTimeout(getStreamStatus, 8000) } playbtn.addEventListener('click', toggleStream) mutebtn.addEventListener('click', toggleSound) fullscreenbtn.addEventListener('click', toggleFullscreen) document.addEventListener('webkitfullscreenchange', exitHandler, false) document.addEventListener('mozfullscreenchange', exitHandler, false) document.addEventListener('fullscreenchange', exitHandler, false) document.addEventListener('MSFullscreenChange', exitHandler, false) window.addEventListener('resize', function () { if (!linksList || linksList.style.display !== 'block') return linksList.style = 'display: none;' }) vid.addEventListener('timeupdate', updateTime, false) getStreamStatus()