Files
streamwall/src/node/viewStateMachine.js
2020-06-14 23:28:45 -07:00

202 lines
5.1 KiB
JavaScript

import { Machine, assign } from 'xstate'
const viewStateMachine = Machine(
{
id: 'view',
initial: 'empty',
context: {
view: null,
pos: null,
url: null,
info: {},
},
on: {
CLEAR: 'empty',
DISPLAY: 'displaying',
},
states: {
empty: {
entry: [
assign({
pos: { url: null },
info: {},
}),
'hideView',
],
invoke: {
src: 'clearView',
onError: {
target: '#view.error',
},
},
},
displaying: {
id: 'displaying',
initial: 'loading',
entry: assign({
pos: (context, event) => event.pos,
url: (context, event) => event.url,
}),
on: {
DISPLAY: {
actions: assign({
pos: (context, event) => event.pos,
}),
cond: 'urlUnchanged',
},
},
states: {
loading: {
initial: 'page',
states: {
page: {
invoke: {
src: 'loadURL',
onDone: {
target: 'video',
},
onError: {
target: '#view.error',
},
},
},
video: {
invoke: {
src: 'startVideo',
onDone: {
target: '#view.displaying.running',
actions: assign({
info: (context, event) => event.data,
}),
},
onError: {
target: '#view.error',
},
},
},
},
},
running: {
initial: 'muted',
entry: 'positionView',
on: {
DISPLAY: {
actions: [
assign({
pos: (context, event) => event.pos,
}),
'positionView',
],
cond: 'urlUnchanged',
},
MUTE: '.muted',
UNMUTE: '.listening',
},
states: {
muted: {
entry: 'muteAudio',
},
listening: {
entry: 'unmuteAudio',
},
},
},
},
},
error: {
entry: 'logError',
},
},
},
{
actions: {
logError: (context, event) => {
console.warn(event)
},
muteAudio: (context, event) => {
context.view.webContents.audioMuted = true
},
unmuteAudio: (context, event) => {
context.view.webContents.audioMuted = false
},
},
guards: {
urlUnchanged: (context, event) => {
return context.url === event.url
},
},
services: {
clearView: async (context, event) => {
await context.view.webContents.loadURL('about:blank')
},
loadURL: async (context, event) => {
const { url, view } = context
const wc = view.webContents
wc.audioMuted = true
await wc.loadURL(url)
wc.insertCSS(
`
* {
display: none !important;
pointer-events: none;
}
html, body, video {
display: block !important;
background: black !important;
}
html, body {
overflow: hidden !important;
background: black !important;
}
video {
display: block !important;
position: fixed !important;
left: 0 !important;
right: 0 !important;
top: 0 !important;
bottom: 0 !important;
width: 100% !important;
height: 100% !important;
object-fit: cover !important;
z-index: 999999 !important;
}
`,
{ cssOrigin: 'user' },
)
},
startVideo: async (context, event) => {
const wc = context.view.webContents
const info = await wc.executeJavaScript(`
const sleep = ms => new Promise((resolve) => setTimeout(resolve, ms))
async function waitForVideo() {
// Give the client side a little time to load. In particular, YouTube seems to need a delay.
await sleep(1000)
let tries = 0
let video
while (!video && tries < 20) {
video = document.querySelector('video')
tries++
await sleep(200)
}
if (!video) {
throw new Error('could not find video')
}
document.body.appendChild(video)
video.muted = false
video.autoPlay = true
video.play()
setInterval(() => video.play(), 1000)
const info = {title: document.title}
return info
}
waitForVideo()
`)
return info
},
},
},
)
export default viewStateMachine