443 lines
11 KiB
JavaScript
443 lines
11 KiB
JavaScript
/* 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 = '<i class="fa fa-pause fa-fw"></i>'
|
|
showBigBtn(false)
|
|
} else {
|
|
if (ws) ws.send('stop ' + STREAM_NAME)
|
|
vid.pause()
|
|
btn.innerHTML = '<i class="fa fa-play fa-fw"></i>'
|
|
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 = '<i class="fa fa-compress fa-fw"></i>'
|
|
} else {
|
|
fullscreenbtn.innerHTML = '<i class="fa fa-expand fa-fw"></i>'
|
|
}
|
|
}
|
|
|
|
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 = '<i class="fa fa-link" aria-hidden="true"></i>'
|
|
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()
|