Persist wall assignments across restarts

This commit is contained in:
Max Goodhart
2025-06-14 19:14:36 +00:00
parent 1cea41bded
commit 3db3716101
3 changed files with 45 additions and 10 deletions

1
package-lock.json generated
View File

@@ -14348,6 +14348,7 @@
"esbuild-register": "^3.6.0", "esbuild-register": "^3.6.0",
"hls.js": "^1.5.18", "hls.js": "^1.5.18",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"lowdb": "^7.0.1",
"node-fetch": "^3.3.2", "node-fetch": "^3.3.2",
"react-hotkeys-hook": "^4.6.1", "react-hotkeys-hook": "^4.6.1",
"react-icons": "^5.4.0", "react-icons": "^5.4.0",

View File

@@ -55,6 +55,7 @@
"esbuild-register": "^3.6.0", "esbuild-register": "^3.6.0",
"hls.js": "^1.5.18", "hls.js": "^1.5.18",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"lowdb": "^7.0.1",
"node-fetch": "^3.3.2", "node-fetch": "^3.3.2",
"react-hotkeys-hook": "^4.6.1", "react-hotkeys-hook": "^4.6.1",
"react-icons": "^5.4.0", "react-icons": "^5.4.0",

View File

@@ -3,6 +3,7 @@ import * as Sentry from '@sentry/electron/main'
import { BrowserWindow, app, session } from 'electron' import { BrowserWindow, app, session } from 'electron'
import started from 'electron-squirrel-startup' import started from 'electron-squirrel-startup'
import fs from 'fs' import fs from 'fs'
import { throttle } from 'lodash-es'
import EventEmitter from 'node:events' import EventEmitter from 'node:events'
import { join } from 'node:path' import { join } from 'node:path'
import ReconnectingWebSocket from 'reconnecting-websocket' import ReconnectingWebSocket from 'reconnecting-websocket'
@@ -22,6 +23,7 @@ import {
pollDataURL, pollDataURL,
watchDataFile, watchDataFile,
} from './data' } from './data'
import { loadStorage } from './storage'
import StreamdelayClient from './StreamdelayClient' import StreamdelayClient from './StreamdelayClient'
import StreamWindow from './StreamWindow' import StreamWindow from './StreamWindow'
import TwitchBot from './TwitchBot' import TwitchBot from './TwitchBot'
@@ -281,16 +283,7 @@ async function main(argv: ReturnType<typeof parseArgs>) {
streamdelay: null, streamdelay: null,
} }
const stateDoc = new Y.Doc() function updateViewsFromStateDoc() {
const viewsState = stateDoc.getMap<Y.Map<string | undefined>>('views')
stateDoc.transact(() => {
for (let i = 0; i < argv.grid.count ** 2; i++) {
const data = new Y.Map<string | undefined>()
data.set('streamId', undefined)
viewsState.set(String(i), data)
}
})
viewsState.observeDeep(() => {
try { try {
const viewContentMap = new Map() const viewContentMap = new Map()
for (const [key, viewData] of viewsState) { for (const [key, viewData] of viewsState) {
@@ -308,8 +301,47 @@ async function main(argv: ReturnType<typeof parseArgs>) {
} catch (err) { } catch (err) {
console.error('Error updating views', err) console.error('Error updating views', err)
} }
}
const stateDoc = new Y.Doc()
const viewsState = stateDoc.getMap<Y.Map<string | undefined>>('views')
const db = await loadStorage(
join(app.getPath('userData'), 'streamwall-storage.json'),
)
if (db.data.stateDoc) {
console.log('Loading stateDoc from storage...')
try {
Y.applyUpdate(stateDoc, Buffer.from(db.data.stateDoc, 'base64'))
} catch (err) {
console.warn('Failed to restore stateDoc', err)
}
}
stateDoc.on(
'update',
throttle(() => {
db.update((data) => {
const fullDoc = Y.encodeStateAsUpdate(stateDoc)
data.stateDoc = Buffer.from(fullDoc).toString('base64')
})
}, 1000),
)
stateDoc.transact(() => {
for (let i = 0; i < argv.grid.count ** 2; i++) {
if (viewsState.has(String(i))) {
continue
}
const data = new Y.Map<string | undefined>()
data.set('streamId', undefined)
viewsState.set(String(i), data)
}
}) })
updateViewsFromStateDoc()
viewsState.observeDeep(updateViewsFromStateDoc)
const onCommand = async (msg: ControlCommand) => { const onCommand = async (msg: ControlCommand) => {
console.debug('Received message:', msg) console.debug('Received message:', msg)
if (msg.type === 'set-listening-view') { if (msg.type === 'set-listening-view') {
@@ -506,6 +538,7 @@ async function main(argv: ReturnType<typeof parseArgs>) {
console.debug('Processing streams:', rawStreams) console.debug('Processing streams:', rawStreams)
const streams = idGen.process(rawStreams) const streams = idGen.process(rawStreams)
updateState({ streams }) updateState({ streams })
updateViewsFromStateDoc()
} }
} }