diff --git a/webroot/index2.html b/webroot/index2.html index 5ef518b28..8f2c70227 100644 --- a/webroot/index2.html +++ b/webroot/index2.html @@ -30,6 +30,7 @@ + diff --git a/webroot/js/app2.js b/webroot/js/app2.js index 8ee9c1aaf..05d672952 100644 --- a/webroot/js/app2.js +++ b/webroot/js/app2.js @@ -3,11 +3,28 @@ import htm from 'https://unpkg.com/htm?module'; const html = htm.bind(h); +import SocialIcon from './social.js'; import UsernameForm from './chat/username.js'; import Chat from './chat/chat.js'; import Websocket from './websocket.js'; +import { OwncastPlayer } from './player.js'; -import { getLocalStorage, generateAvatar, generateUsername, URL_OWNCAST, URL_CONFIG, URL_STATUS, addNewlines } from './utils.js'; +import { + getLocalStorage, + generateAvatar, + generateUsername, + URL_OWNCAST, + URL_CONFIG, + URL_STATUS, + addNewlines, + pluralize, + TIMER_STATUS_UPDATE, + TIMER_DISABLE_CHAT_AFTER_OFFLINE, + TIMER_STREAM_DURATION_COUNTER, + TEMP_IMAGE, + MESSAGE_OFFLINE, + MESSAGE_ONLINE, +} from './utils.js'; import { KEY_USERNAME, KEY_AVATAR, } from './utils/chat.js'; export default class App extends Component { @@ -16,13 +33,22 @@ export default class App extends Component { this.state = { websocket: new Websocket(), - chatEnabled: true, // always true for standalone chat + displayChat: false, // chat panel state + chatEnabled: false, // chat input box state username: getLocalStorage(KEY_USERNAME) || generateUsername(), userAvatarImage: getLocalStorage(KEY_AVATAR) || generateAvatar(`${this.username}${Date.now()}`), - streamStatus: null, - player: null, configData: {}, + extraUserContent: '', + + playerActive: false, // player object is active + streamOnline: false, // stream is active/online + + //status + streamStatusMessage: MESSAGE_OFFLINE, + viewerCount: '', + sessionMaxViewerCount: '', + overallMaxViewerCount: '', }; // timers @@ -32,25 +58,48 @@ export default class App extends Component { this.disableChatTimer = null; this.streamDurationTimer = null; + // misc dom events + this.handleChatPanelToggle = this.handleChatPanelToggle.bind(this); this.handleUsernameChange = this.handleUsernameChange.bind(this); + + this.handleOfflineMode = this.handleOfflineMode.bind(this); + this.handleOnlineMode = this.handleOnlineMode.bind(this); + this.disableChatInput = this.disableChatInput.bind(this); + + // player events + this.handlePlayerReady = this.handlePlayerReady.bind(this); + this.handlePlayerPlaying = this.handlePlayerPlaying.bind(this); + this.handlePlayerEnded = this.handlePlayerEnded.bind(this); + this.handlePlayerError = this.handlePlayerError.bind(this); + + // fetch events this.getConfig = this.getConfig.bind(this); this.getStreamStatus = this.getStreamStatus.bind(this); this.getExtraUserContent = this.getExtraUserContent.bind(this); + } componentDidMount() { this.getConfig(); - // DO LATER.. - // this.player = new OwncastPlayer(); - // this.player.setupPlayerCallbacks({ - // onReady: this.handlePlayerReady, - // onPlaying: this.handlePlayerPlaying, - // onEnded: this.handlePlayerEnded, - // onError: this.handlePlayerError, - // }); - // this.player.init(); + this.player = new OwncastPlayer(); + this.player.setupPlayerCallbacks({ + onReady: this.handlePlayerReady, + onPlaying: this.handlePlayerPlaying, + onEnded: this.handlePlayerEnded, + onError: this.handlePlayerError, + }); + this.player.init(); + } + + componentWillUnmount() { + // clear all the timers + clearInterval(this.playerRestartTimer); + clearInterval(this.offlineTimer); + clearInterval(this.statusTimer); + clearTimeout(this.disableChatTimer); + clearInterval(this.streamDurationTimer); } // fetch /config data @@ -98,8 +147,9 @@ export default class App extends Component { return response.text(); }) .then(text => { - const descriptionHTML = new showdown.Converter().makeHtml(text); - this.vueApp.extraUserContent = descriptionHTML; + this.setState({ + extraUserContent: new showdown.Converter().makeHtml(text), + }); }) .catch(error => { this.handleNetworkingError(`Fetch extra content: ${error}`); @@ -128,66 +178,89 @@ export default class App extends Component { if (!status) { return; } - // update UI - this.vueApp.viewerCount = status.viewerCount; - this.vueApp.sessionMaxViewerCount = status.sessionMaxViewerCount; - this.vueApp.overallMaxViewerCount = status.overallMaxViewerCount; + const { + viewerCount, + sessionMaxViewerCount, + overallMaxViewerCount, + online, + } = status; + const { streamOnline: curStreamOnline } = this.state; this.lastDisconnectTime = status.lastDisconnectTime; - if (!this.streamStatus) { - // display offline mode the first time we get status, and it's offline. - if (!status.online) { - this.handleOfflineMode(); - } else { - this.handleOnlineMode(); - } - } else { - if (status.online && !this.streamStatus.online) { - // stream has just come online. - this.handleOnlineMode(); - } else if (!status.online && this.streamStatus.online) { - // stream has just flipped offline. - this.handleOfflineMode(); - } + if (status.online && !curStreamOnline) { + // stream has just come online. + this.handleOnlineMode(); + } else if (!status.online && curStreamOnline) { + // stream has just flipped offline. + this.handleOfflineMode(); } - - // keep a local copy - this.streamStatus = status; - if (status.online) { // only do this if video is paused, so no unnecessary img fetches if (this.player.vjsPlayer && this.player.vjsPlayer.paused()) { this.player.setPoster(); } } + this.setState({ + viewerCount, + sessionMaxViewerCount, + overallMaxViewerCount, + streamOnline: online, + }); + } + + // when videojs player is ready, start polling for stream + handlePlayerReady() { + this.getStreamStatus(); + this.statusTimer = setInterval(this.getStreamStatus, TIMER_STATUS_UPDATE); + } + + handlePlayerPlaying() { + // do something? + } + + + // likely called some time after stream status has gone offline. + // basically hide video and show underlying "poster" + handlePlayerEnded() { + this.setState({ + playerActive: false, + }); + } + + handlePlayerError() { + // do something? + this.handleOfflineMode(); + this.handlePlayerEnded(); } // stop status timer and disable chat after some time. handleOfflineMode() { - this.vueApp.isOnline = false; clearInterval(this.streamDurationTimer); - this.vueApp.streamStatus = MESSAGE_OFFLINE; - if (this.streamStatus) { - 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); - } + const remainingChatTime = TIMER_DISABLE_CHAT_AFTER_OFFLINE - (Date.now() - new Date(this.lastDisconnectTime)); + const countdown = (remainingChatTime < 0) ? 0 : remainingChatTime; + this.disableChatTimer = setTimeout(this.disableChatInput, countdown); + this.setState({ + streamOnline: false, + streamStatusMessage: MESSAGE_OFFLINE, + }); } // play video! handleOnlineMode() { - this.vueApp.playerOn = true; - this.vueApp.isOnline = true; - this.vueApp.streamStatus = MESSAGE_ONLINE; - this.player.startPlayer(); clearTimeout(this.disableChatTimer); this.disableChatTimer = null; - this.messagingInterface.enableChat(); this.streamDurationTimer = setInterval(this.setCurrentStreamDuration, TIMER_STREAM_DURATION_COUNTER); + + this.setState({ + playerActive: true, + streamOnline: true, + chatEnabled: true, + streamStatusMessage: MESSAGE_ONLINE, + }); } @@ -198,10 +271,16 @@ export default class App extends Component { }); } - handleChatToggle() { - const { chatEnabled: curChatEnabled } = this.state; + handleChatPanelToggle() { + const { displayChat: curDisplayed } = this.state; this.setState({ - chatEnabled: !curChatEnabled, + displayChat: !curDisplayed, + }); + } + + disableChatInput() { + this.setState({ + chatEnabled: false, }); } @@ -210,49 +289,81 @@ export default class App extends Component { } render(props, state) { - const { username, userAvatarImage, websocket, configData } = state; + const { + username, + userAvatarImage, + websocket, + configData, + extraUserContent, + displayChat, + viewerCount, + sessionMaxViewerCount, + overallMaxViewerCount, + playerActive, + streamOnline, + streamStatusMessage, + chatEnabled, + } = state; const { version: appVersion, logo = {}, - socialHandles, - name: streamnerName, + socialHandles = [], + name: streamerName, summary, - tags, + tags = [], title, } = configData; - const { small: smallLogo, large: largeLogo } = logo; + const { small: smallLogo = TEMP_IMAGE, large: largeLogo = TEMP_IMAGE } = logo; const bgLogo = { backgroundImage: `url(${smallLogo})` }; const bgLogoLarge = { backgroundImage: `url(${largeLogo})` }; - // not needed for standalone, just messages only. remove later. + const tagList = !tags.length ? + null : + tags.map((tag, index) => html` +