Browse Source
* objectify app away from window. wip * fix messaging obj binding; put logo behind video; fix /null issue with temp logo image * first pass at js refactor * remove unused files that had been consolidated during refactor * set up vue before getting config * add a few comments * dont use big arrow function, just bind, for safari * add airplay after instantiating video; check if input exists before disabling it;: * only set poster on pause during playback, and onEnded; take out sample videoJS tech options * disable chat after 5mins after going offline * move 'online' class to video container as it conflicts with dynamically change classnames from non-vue sources * disable chat based on lastdisconnecttime * fix typo; do offline mode onEnded instead of status offline * move offline ui display things to offline mode function; move poster setting on pause to main app to keep player obj cleaner; use opacity to hide video element on offline as sometimes control bars may still linger with vis:hidden * fixes' * don't autoplay. just show play button when stream is online so that it's easier to start playign without looking for the unmute button * clean up console logs Co-authored-by: Gabe Kangas <gabek@real-ity.com>pull/68/head
10 changed files with 502 additions and 324 deletions
@ -1,98 +1,279 @@ |
|||||||
async function setupApp() { |
class Owncast { |
||||||
Vue.filter('plural', pluralize); |
constructor() { |
||||||
|
this.player; |
||||||
window.app = new Vue({ |
this.streamStatus = null; |
||||||
el: "#app-container", |
|
||||||
data: { |
this.websocket = null; |
||||||
streamStatus: MESSAGE_OFFLINE, // Default state.
|
this.configData; |
||||||
viewerCount: 0, |
this.vueApp; |
||||||
sessionMaxViewerCount: 0, |
this.messagingInterface = null; |
||||||
overallMaxViewerCount: 0, |
|
||||||
messages: [], |
// timers
|
||||||
extraUserContent: "", |
this.websocketReconnectTimer = null; |
||||||
isOnline: false, |
this.playerRestartTimer = null; |
||||||
layout: "desktop", |
this.offlineTimer = null; |
||||||
|
this.statusTimer = null; |
||||||
// from config
|
this.disableChatTimer = null; |
||||||
logo: null, |
|
||||||
socialHandles: [], |
// misc
|
||||||
streamerName: "", |
this.streamIsOnline = false; |
||||||
summary: "", |
this.lastDisconnectTime = null; |
||||||
tags: [], |
|
||||||
title: "", |
Vue.filter('plural', pluralize); |
||||||
appVersion: "", |
|
||||||
}, |
// bindings
|
||||||
watch: { |
this.vueAppMounted = this.vueAppMounted.bind(this); |
||||||
messages: { |
this.setConfigData = this.setConfigData.bind(this); |
||||||
deep: true, |
this.setupWebsocket = this.setupWebsocket.bind(this); |
||||||
handler: function (newMessages, oldMessages) { |
this.getStreamStatus = this.getStreamStatus.bind(this); |
||||||
if (newMessages.length !== oldMessages.length) { |
this.getExtraUserContent = this.getExtraUserContent.bind(this); |
||||||
// jump to bottom
|
this.updateStreamStatus = this.updateStreamStatus.bind(this); |
||||||
jumpToBottom(appMessaging.scrollableMessagesContainer); |
this.handleNetworkingError = this.handleNetworkingError.bind(this); |
||||||
} |
this.handleOfflineMode = this.handleOfflineMode.bind(this); |
||||||
|
this.handleOnlineMode = this.handleOnlineMode.bind(this); |
||||||
|
this.handleNetworkingError = this.handleNetworkingError.bind(this); |
||||||
|
this.handlePlayerReady = this.handlePlayerReady.bind(this); |
||||||
|
this.handlePlayerPlaying = this.handlePlayerPlaying.bind(this); |
||||||
|
this.handlePlayerEnded = this.handlePlayerEnded.bind(this); |
||||||
|
this.handlePlayerError = this.handlePlayerError.bind(this); |
||||||
|
} |
||||||
|
|
||||||
|
init() { |
||||||
|
this.messagingInterface = new MessagingInterface(); |
||||||
|
this.websocket = this.setupWebsocket(); |
||||||
|
|
||||||
|
this.vueApp = new Vue({ |
||||||
|
el: '#app-container', |
||||||
|
data: { |
||||||
|
isOnline: false, |
||||||
|
layout: hasTouchScreen() ? 'touch' : 'desktop', |
||||||
|
messages: [], |
||||||
|
overallMaxViewerCount: 0, |
||||||
|
sessionMaxViewerCount: 0, |
||||||
|
streamStatus: MESSAGE_OFFLINE, // Default state.
|
||||||
|
viewerCount: 0, |
||||||
|
|
||||||
|
// from config
|
||||||
|
appVersion: '', |
||||||
|
extraUserContent: '', |
||||||
|
logo: TEMP_IMAGE, |
||||||
|
logoLarge: TEMP_IMAGE, |
||||||
|
socialHandles: [], |
||||||
|
streamerName: '', |
||||||
|
summary: '', |
||||||
|
tags: [], |
||||||
|
title: '', |
||||||
|
}, |
||||||
|
watch: { |
||||||
|
messages: { |
||||||
|
deep: true, |
||||||
|
handler: this.messagingInterface.onReceivedMessages, |
||||||
}, |
}, |
||||||
}, |
}, |
||||||
}, |
mounted: this.vueAppMounted, |
||||||
}); |
}); |
||||||
|
} |
||||||
|
// do all these things after Vue.js has mounted, else we'll get weird DOM issues.
|
||||||
|
vueAppMounted() { |
||||||
|
this.getConfig(); |
||||||
|
this.messagingInterface.init(); |
||||||
|
|
||||||
|
this.player = new OwncastPlayer(); |
||||||
|
this.player.setupPlayerCallbacks({ |
||||||
|
onReady: this.handlePlayerReady, |
||||||
|
onPlaying: this.handlePlayerPlaying, |
||||||
|
onEnded: this.handlePlayerEnded, |
||||||
|
onError: this.handlePlayerError, |
||||||
|
}); |
||||||
|
this.player.init(); |
||||||
|
}; |
||||||
|
|
||||||
|
setConfigData(data) { |
||||||
|
this.vueApp.appVersion = data.version; |
||||||
|
this.vueApp.logo = data.logo.small; |
||||||
|
this.vueApp.logoLarge = data.logo.large; |
||||||
|
this.vueApp.socialHandles = data.socialHandles; |
||||||
|
this.vueApp.streamerName = data.name; |
||||||
|
this.vueApp.summary = data.summary && addNewlines(data.summary); |
||||||
|
this.vueApp.tags = data.tags; |
||||||
|
this.vueApp.title = data.title; |
||||||
|
|
||||||
|
window.document.title = data.title; |
||||||
|
|
||||||
|
this.getExtraUserContent(`${URL_PREFIX}${data.extraUserInfoFileName}`); |
||||||
|
|
||||||
|
this.configData = data; |
||||||
|
} |
||||||
|
|
||||||
// init messaging interactions
|
// websocket for messaging
|
||||||
var appMessaging = new Messaging(); |
setupWebsocket() { |
||||||
appMessaging.init(); |
var ws = new WebSocket(URL_WEBSOCKET); |
||||||
|
ws.onopen = (e) => { |
||||||
|
if (this.websocketReconnectTimer) { |
||||||
|
clearTimeout(this.websocketReconnectTimer); |
||||||
|
} |
||||||
|
}; |
||||||
|
ws.onclose = (e) => { |
||||||
|
// connection closed, discard old websocket and create a new one in 5s
|
||||||
|
this.websocket = null; |
||||||
|
this.messagingInterface.disableChat(); |
||||||
|
this.handleNetworkingError('Websocket closed.'); |
||||||
|
this.websocketReconnectTimer = setTimeout(this.setupWebsocket, TIMER_WEBSOCKET_RECONNECT); |
||||||
|
}; |
||||||
|
// On ws error just close the socket and let it re-connect again for now.
|
||||||
|
ws.onerror = e => { |
||||||
|
this.handleNetworkingError(`Stream status: ${e}`); |
||||||
|
ws.close(); |
||||||
|
}; |
||||||
|
ws.onmessage = (e) => { |
||||||
|
const model = JSON.parse(e.data); |
||||||
|
// Ignore non-chat messages (such as keepalive PINGs)
|
||||||
|
if (model.type !== SOCKET_MESSAGE_TYPES.CHAT) { |
||||||
|
return; |
||||||
|
} |
||||||
|
const message = new Message(model); |
||||||
|
const existing = this.vueApp.messages.filter(function (item) { |
||||||
|
return item.id === message.id; |
||||||
|
}) |
||||||
|
if (existing.length === 0 || !existing) { |
||||||
|
this.vueApp.messages = [...this.vueApp.messages, message]; |
||||||
|
} |
||||||
|
}; |
||||||
|
this.websocket = ws; |
||||||
|
this.messagingInterface.setWebsocket(this.websocket); |
||||||
|
}; |
||||||
|
|
||||||
const config = await new Config().init(); |
// fetch /config data
|
||||||
app.logo = config.logo.small; |
getConfig() { |
||||||
app.socialHandles = config.socialHandles; |
fetch(URL_CONFIG) |
||||||
app.streamerName = config.name; |
.then(response => { |
||||||
app.summary = config.summary && addNewlines(config.summary); |
if (!response.ok) { |
||||||
app.tags = config.tags; |
throw new Error(`Network response was not ok ${response.ok}`); |
||||||
app.appVersion = config.version; |
} |
||||||
app.title = config.title; |
return response.json(); |
||||||
window.document.title = config.title; |
}) |
||||||
|
.then(json => { |
||||||
|
this.setConfigData(json); |
||||||
|
}) |
||||||
|
.catch(error => { |
||||||
|
this.handleNetworkingError(`Fetch config: ${error}`); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
getExtraUserContent(`${URL_PREFIX}${config.extraUserInfoFileName}`); |
// fetch stream status
|
||||||
} |
getStreamStatus() { |
||||||
|
fetch(URL_STATUS) |
||||||
|
.then(response => { |
||||||
|
if (!response.ok) { |
||||||
|
throw new Error(`Network response was not ok ${response.ok}`); |
||||||
|
} |
||||||
|
return response.json(); |
||||||
|
}) |
||||||
|
.then(json => { |
||||||
|
this.updateStreamStatus(json); |
||||||
|
}) |
||||||
|
.catch(error => { |
||||||
|
this.handleOfflineMode(); |
||||||
|
this.handleNetworkingError(`Stream status: ${error}`); |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
var websocketReconnectTimer; |
// fetch content.md
|
||||||
function setupWebsocket() { |
getExtraUserContent(path) { |
||||||
clearTimeout(websocketReconnectTimer); |
fetch(path) |
||||||
|
.then(response => { |
||||||
|
if (!response.ok) { |
||||||
|
throw new Error(`Network response was not ok ${response.ok}`); |
||||||
|
} |
||||||
|
return response.text(); |
||||||
|
}) |
||||||
|
.then(text => { |
||||||
|
const descriptionHTML = new showdown.Converter().makeHtml(text); |
||||||
|
this.vueApp.extraUserContent = descriptionHTML; |
||||||
|
}) |
||||||
|
.catch(error => { |
||||||
|
this.handleNetworkingError(`Fetch extra content: ${error}`); |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
var ws = new WebSocket(URL_WEBSOCKET); |
// handle UI things from stream status result
|
||||||
|
updateStreamStatus(status) { |
||||||
|
// update UI
|
||||||
|
this.vueApp.streamStatus = status.online ? MESSAGE_ONLINE : MESSAGE_OFFLINE; |
||||||
|
this.vueApp.viewerCount = status.viewerCount; |
||||||
|
this.vueApp.sessionMaxViewerCount = status.sessionMaxViewerCount; |
||||||
|
this.vueApp.overallMaxViewerCount = status.overallMaxViewerCount; |
||||||
|
|
||||||
ws.onmessage = (e) => { |
this.lastDisconnectTime = status.lastDisconnectTime; |
||||||
const model = JSON.parse(e.data); |
|
||||||
|
|
||||||
// Ignore non-chat messages (such as keepalive PINGs)
|
if (status.online && !this.streamIsOnline) { |
||||||
if (model.type !== SOCKET_MESSAGE_TYPES.CHAT) { return; } |
// stream has just come online.
|
||||||
|
this.handleOnlineMode(); |
||||||
|
} else if (!status.online && !this.streamStatus) { |
||||||
|
// stream has just gone offline.
|
||||||
|
// display offline mode the first time we get status, and it's offline.
|
||||||
|
this.handleOfflineMode(); |
||||||
|
} |
||||||
|
|
||||||
const message = new Message(model); |
if (status.online) { |
||||||
|
// only do this if video is paused, so no unnecessary img fetches
|
||||||
const existing = this.app.messages.filter(function (item) { |
if (this.player.vjsPlayer && this.player.vjsPlayer.paused()) { |
||||||
return item.id === message.id; |
this.player.setPoster(); |
||||||
}) |
} |
||||||
|
|
||||||
if (existing.length === 0 || !existing) { |
|
||||||
this.app.messages = [...this.app.messages, message]; |
|
||||||
} |
} |
||||||
} |
|
||||||
|
|
||||||
ws.onclose = (e) => { |
this.streamStatus = status; |
||||||
// connection closed, discard old websocket and create a new one in 5s
|
}; |
||||||
ws = null; |
|
||||||
console.log("Websocket closed.") |
handleNetworkingError(error) { |
||||||
websocketReconnectTimer = setTimeout(setupWebsocket, 5000); |
console.log(`>>> App Error: ${error}`) |
||||||
} |
}; |
||||||
|
|
||||||
|
// basically hide video and show underlying "poster"
|
||||||
|
handleOfflineMode() { |
||||||
|
this.streamIsOnline = false; |
||||||
|
this.vueApp.isOnline = false; |
||||||
|
this.vueApp.streamStatus = MESSAGE_OFFLINE; |
||||||
|
|
||||||
|
if (this.lastDisconnectTime) { |
||||||
|
const remainingChatTime = TIMER_DISABLE_CHAT_AFTER_OFFLINE - (Date.now() - new Date(this.lastDisconnectTime)); |
||||||
|
const countdown = (remainingChatTime < 0) ? 0 : remainingChatTime; |
||||||
|
this.disableChatTimer = setTimeout(this.messagingInterface.disableChat, countdown); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
// On ws error just close the socket and let it re-connect again for now.
|
// play video!
|
||||||
ws.onerror = (e) => { |
handleOnlineMode() { |
||||||
console.log("Websocket error: ", e); |
this.streamIsOnline = true; |
||||||
ws.close(); |
this.vueApp.isOnline = true; |
||||||
|
this.vueApp.streamStatus = MESSAGE_ONLINE; |
||||||
|
|
||||||
|
this.player.startPlayer(); |
||||||
|
clearTimeout(this.disableChatTimer); |
||||||
|
this.disableChatTimer = null; |
||||||
|
this.messagingInterface.enableChat(); |
||||||
} |
} |
||||||
|
|
||||||
window.ws = ws; |
// when videojs player is ready, start polling for stream
|
||||||
} |
handlePlayerReady() { |
||||||
|
this.getStreamStatus(); |
||||||
|
this.statusTimer = setInterval(this.getStreamStatus, TIMER_STATUS_UPDATE); |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
handlePlayerPlaying() { |
||||||
|
// do something?
|
||||||
|
}; |
||||||
|
|
||||||
setupApp(); |
|
||||||
|
|
||||||
setupWebsocket(); |
handlePlayerEnded() { |
||||||
|
// do something?
|
||||||
|
this.handleOfflineMode(); |
||||||
|
}; |
||||||
|
|
||||||
|
handlePlayerError() { |
||||||
|
// do something?
|
||||||
|
this.handleOfflineMode(); |
||||||
|
// stop timers?
|
||||||
|
}; |
||||||
|
}; |
||||||
|
@ -1,16 +0,0 @@ |
|||||||
// add more to the promises later.
|
|
||||||
class Config { |
|
||||||
async init() { |
|
||||||
const configFileLocation = "/config"; |
|
||||||
|
|
||||||
try { |
|
||||||
const response = await fetch(configFileLocation); |
|
||||||
const configData = await response.json(); |
|
||||||
Object.assign(this, configData); |
|
||||||
return this; |
|
||||||
} catch(error) { |
|
||||||
console.log(error); |
|
||||||
// No config file present. That's ok. It's not required.
|
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,115 @@ |
|||||||
|
// https://docs.videojs.com/player
|
||||||
|
|
||||||
|
class OwncastPlayer { |
||||||
|
constructor() { |
||||||
|
window.VIDEOJS_NO_DYNAMIC_STYLE = true; // style override
|
||||||
|
|
||||||
|
this.vjsPlayer = null; |
||||||
|
|
||||||
|
this.appPlayerReadyCallback = null; |
||||||
|
this.appPlayerPlayingCallback = null; |
||||||
|
this.appPlayerEndedCallback = null; |
||||||
|
|
||||||
|
// bind all the things because safari
|
||||||
|
this.startPlayer = this.startPlayer.bind(this); |
||||||
|
this.handleReady = this.handleReady.bind(this); |
||||||
|
this.handlePlaying = this.handlePlaying.bind(this); |
||||||
|
this.handleEnded = this.handleEnded.bind(this); |
||||||
|
this.handleError = this.handleError.bind(this); |
||||||
|
} |
||||||
|
init() { |
||||||
|
this.vjsPlayer = videojs(VIDEO_ID, VIDEO_OPTIONS); |
||||||
|
this.addAirplay(); |
||||||
|
this.vjsPlayer.ready(this.handleReady); |
||||||
|
} |
||||||
|
|
||||||
|
setupPlayerCallbacks(callbacks) { |
||||||
|
const { onReady, onPlaying, onEnded, onError } = callbacks; |
||||||
|
|
||||||
|
this.appPlayerReadyCallback = onReady; |
||||||
|
this.appPlayerPlayingCallback = onPlaying; |
||||||
|
this.appPlayerEndedCallback = onEnded; |
||||||
|
this.appPlayerErrorCallback = onError; |
||||||
|
} |
||||||
|
|
||||||
|
// play
|
||||||
|
startPlayer() { |
||||||
|
this.log('Start playing'); |
||||||
|
this.vjsPlayer.src(VIDEO_SRC); |
||||||
|
// this.vjsPlayer.play();
|
||||||
|
}; |
||||||
|
|
||||||
|
handleReady() { |
||||||
|
this.log('on Ready'); |
||||||
|
this.vjsPlayer.on('error', this.handleError); |
||||||
|
this.vjsPlayer.on('playing', this.handlePlaying); |
||||||
|
this.vjsPlayer.on('ended', this.handleEnded); |
||||||
|
|
||||||
|
if (this.appPlayerReadyCallback) { |
||||||
|
// start polling
|
||||||
|
this.appPlayerReadyCallback(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
handlePlaying() { |
||||||
|
this.log('on Playing'); |
||||||
|
if (this.appPlayerPlayingCallback) { |
||||||
|
// start polling
|
||||||
|
this.appPlayerPlayingCallback(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
handleEnded() { |
||||||
|
this.log('on Ended'); |
||||||
|
if (this.appPlayerEndedCallback) { |
||||||
|
this.appPlayerEndedCallback(); |
||||||
|
} |
||||||
|
this.setPoster(); |
||||||
|
} |
||||||
|
|
||||||
|
handleError(e) { |
||||||
|
this.log(`on Error: ${JSON.stringify(e)}`); |
||||||
|
if (this.appPlayerEndedCallback) { |
||||||
|
this.appPlayerEndedCallback(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
setPoster() { |
||||||
|
const cachebuster = Math.round(new Date().getTime() / 1000); |
||||||
|
const poster = POSTER_THUMB + "?okhi=" + cachebuster; |
||||||
|
|
||||||
|
this.vjsPlayer.poster(poster); |
||||||
|
} |
||||||
|
|
||||||
|
log(message) { |
||||||
|
console.log(`>>> Player: ${message}`); |
||||||
|
} |
||||||
|
|
||||||
|
addAirplay() { |
||||||
|
videojs.hookOnce('setup', function (player) { |
||||||
|
if (window.WebKitPlaybackTargetAvailabilityEvent) { |
||||||
|
var videoJsButtonClass = videojs.getComponent('Button'); |
||||||
|
var concreteButtonClass = videojs.extend(videoJsButtonClass, { |
||||||
|
|
||||||
|
// The `init()` method will also work for constructor logic here, but it is
|
||||||
|
// deprecated. If you provide an `init()` method, it will override the
|
||||||
|
// `constructor()` method!
|
||||||
|
constructor: function () { |
||||||
|
videoJsButtonClass.call(this, player); |
||||||
|
}, // notice the comma
|
||||||
|
|
||||||
|
handleClick: function () { |
||||||
|
const videoElement = document.getElementsByTagName('video')[0]; |
||||||
|
videoElement.webkitShowPlaybackTargetPicker(); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
var concreteButtonInstance = this.vjsPlayer.controlBar.addChild(new concreteButtonClass()); |
||||||
|
concreteButtonInstance.addClass("vjs-airplay"); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
|
|
@ -1,23 +0,0 @@ |
|||||||
videojs.hookOnce('setup', function (player) { |
|
||||||
if (window.WebKitPlaybackTargetAvailabilityEvent) { |
|
||||||
var videoJsButtonClass = videojs.getComponent('Button'); |
|
||||||
var concreteButtonClass = videojs.extend(videoJsButtonClass, { |
|
||||||
|
|
||||||
// The `init()` method will also work for constructor logic here, but it is
|
|
||||||
// deprecated. If you provide an `init()` method, it will override the
|
|
||||||
// `constructor()` method!
|
|
||||||
constructor: function () { |
|
||||||
videoJsButtonClass.call(this, player); |
|
||||||
}, // notice the comma
|
|
||||||
|
|
||||||
handleClick: function () { |
|
||||||
const videoElement = document.getElementsByTagName('video')[0]; |
|
||||||
videoElement.webkitShowPlaybackTargetPicker(); |
|
||||||
} |
|
||||||
}); |
|
||||||
|
|
||||||
var concreteButtonInstance = player.controlBar.addChild(new concreteButtonClass()); |
|
||||||
concreteButtonInstance.addClass("vjs-airplay"); |
|
||||||
} |
|
||||||
|
|
||||||
}); |
|
@ -1,75 +0,0 @@ |
|||||||
// const streamURL = '/hls/stream.m3u8';
|
|
||||||
const streamURL = '/hls/stream.m3u8'; // Uncomment me to point to remote video
|
|
||||||
|
|
||||||
// style hackings
|
|
||||||
window.VIDEOJS_NO_DYNAMIC_STYLE = true; |
|
||||||
|
|
||||||
// Create the player for the first time
|
|
||||||
const player = videojs(VIDEO_ID, null, function () { |
|
||||||
getStatus(); |
|
||||||
setInterval(getStatus, 5000); |
|
||||||
setupPlayerEventHandlers(); |
|
||||||
}) |
|
||||||
|
|
||||||
player.ready(function () { |
|
||||||
console.log('Player ready.') |
|
||||||
resetPlayer(player); |
|
||||||
}); |
|
||||||
|
|
||||||
function resetPlayer(player) { |
|
||||||
player.reset(); |
|
||||||
player.src({ type: 'application/x-mpegURL', src: URL_STREAM }); |
|
||||||
setVideoPoster(app.isOnline); |
|
||||||
} |
|
||||||
|
|
||||||
function setupPlayerEventHandlers() { |
|
||||||
const player = videojs(VIDEO_ID); |
|
||||||
|
|
||||||
player.on('error', function (e) { |
|
||||||
console.log("Player error: ", e); |
|
||||||
}) |
|
||||||
|
|
||||||
// player.on('loadeddata', function (e) {
|
|
||||||
// console.log("loadeddata");
|
|
||||||
// })
|
|
||||||
|
|
||||||
player.on('ended', function (e) { |
|
||||||
console.log("ended"); |
|
||||||
resetPlayer(player); |
|
||||||
}) |
|
||||||
//
|
|
||||||
// player.on('abort', function (e) {
|
|
||||||
// console.log("abort");
|
|
||||||
// })
|
|
||||||
//
|
|
||||||
// player.on('durationchange', function (e) {
|
|
||||||
// console.log("durationchange");
|
|
||||||
// })
|
|
||||||
//
|
|
||||||
// player.on('stalled', function (e) {
|
|
||||||
// console.log("stalled");
|
|
||||||
// })
|
|
||||||
//
|
|
||||||
player.on('playing', function (e) { |
|
||||||
if (playerRestartTimer) { |
|
||||||
clearTimeout(playerRestartTimer); |
|
||||||
} |
|
||||||
}) |
|
||||||
//
|
|
||||||
// player.on('waiting', function (e) {
|
|
||||||
// // console.log("waiting");
|
|
||||||
// })
|
|
||||||
} |
|
||||||
|
|
||||||
function restartPlayer() { |
|
||||||
try { |
|
||||||
const player = videojs(VIDEO_ID); |
|
||||||
player.pause(); |
|
||||||
player.src(player.src()); // Reload the same video
|
|
||||||
player.load(); |
|
||||||
player.play(); |
|
||||||
} catch (e) { |
|
||||||
console.log(e) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,44 +0,0 @@ |
|||||||
var playerRestartTimer; |
|
||||||
|
|
||||||
|
|
||||||
function handleStatus(status) { |
|
||||||
clearTimeout(playerRestartTimer); |
|
||||||
if (!app.isOnline && status.online) { |
|
||||||
// The stream was offline, but now it's online. Force start of playback after an arbitrary delay to make sure the stream has actual data ready to go.
|
|
||||||
playerRestartTimer = setTimeout(restartPlayer, 3000); |
|
||||||
} |
|
||||||
|
|
||||||
app.streamStatus = status.online ? MESSAGE_ONLINE : MESSAGE_OFFLINE; |
|
||||||
|
|
||||||
app.viewerCount = status.viewerCount; |
|
||||||
app.sessionMaxViewerCount = status.sessionMaxViewerCount; |
|
||||||
app.overallMaxViewerCount = status.overallMaxViewerCount; |
|
||||||
app.isOnline = status.online; |
|
||||||
// setVideoPoster(app.isOnline);
|
|
||||||
} |
|
||||||
|
|
||||||
function handleOffline() { |
|
||||||
const player = videojs(VIDEO_ID); |
|
||||||
player.poster(POSTER_DEFAULT); |
|
||||||
app.streamStatus = MESSAGE_OFFLINE; |
|
||||||
app.viewerCount = 0; |
|
||||||
} |
|
||||||
|
|
||||||
function getStatus() { |
|
||||||
const options = { |
|
||||||
// mode: 'no-cors',
|
|
||||||
} |
|
||||||
fetch(URL_STATUS, options) |
|
||||||
.then(response => { |
|
||||||
if (!response.ok) { |
|
||||||
throw new Error(`Network response was not ok ${response.ok}`); |
|
||||||
} |
|
||||||
return response.json(); |
|
||||||
}) |
|
||||||
.then(json => { |
|
||||||
handleStatus(json); |
|
||||||
}) |
|
||||||
.catch(error => { |
|
||||||
handleOffline(); |
|
||||||
}); |
|
||||||
} |
|
Loading…
Reference in new issue