From c2aa59ef00dc8c4da6561c9d0487849bf68a10d2 Mon Sep 17 00:00:00 2001 From: Max Goodhart Date: Mon, 26 Jan 2026 00:29:30 -0800 Subject: [PATCH] Display spinner when media re-acquiring --- packages/streamwall/src/main/StreamWindow.ts | 3 +++ packages/streamwall/src/main/viewStateMachine.ts | 12 ++++++++++++ packages/streamwall/src/preload/mediaPreload.ts | 8 ++++++-- packages/streamwall/src/renderer/overlay.tsx | 4 +++- 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/packages/streamwall/src/main/StreamWindow.ts b/packages/streamwall/src/main/StreamWindow.ts index 2e89125..70dd08f 100644 --- a/packages/streamwall/src/main/StreamWindow.ts +++ b/packages/streamwall/src/main/StreamWindow.ts @@ -126,6 +126,9 @@ export default class StreamWindow extends EventEmitter { ipcMain.on('view-loaded', (ev) => { this.views.get(ev.sender.id)?.send?.({ type: 'VIEW_LOADED' }) }) + ipcMain.on('view-stalled', (ev) => { + this.views.get(ev.sender.id)?.send?.({ type: 'VIEW_STALLED' }) + }) ipcMain.on('view-info', (ev, { info }) => { this.views.get(ev.sender.id)?.send?.({ type: 'VIEW_INFO', info }) }) diff --git a/packages/streamwall/src/main/viewStateMachine.ts b/packages/streamwall/src/main/viewStateMachine.ts index 0220db9..f8f7ae7 100644 --- a/packages/streamwall/src/main/viewStateMachine.ts +++ b/packages/streamwall/src/main/viewStateMachine.ts @@ -44,6 +44,7 @@ const viewStateMachine = setup({ } | { type: 'VIEW_INIT' } | { type: 'VIEW_LOADED' } + | { type: 'VIEW_STALLED' } | { type: 'VIEW_INFO'; info: ContentViewInfo } | { type: 'VIEW_ERROR'; error: unknown } | { type: 'MUTE' } @@ -290,6 +291,17 @@ const viewStateMachine = setup({ ], }, states: { + playback: { + initial: 'playing', + on: { + VIEW_STALLED: '.stalled', + VIEW_LOADED: '.playing', + }, + states: { + playing: {}, + stalled: {}, + }, + }, audio: { initial: 'muted', on: { diff --git a/packages/streamwall/src/preload/mediaPreload.ts b/packages/streamwall/src/preload/mediaPreload.ts index fe9c01d..2bf8c61 100644 --- a/packages/streamwall/src/preload/mediaPreload.ts +++ b/packages/streamwall/src/preload/mediaPreload.ts @@ -300,6 +300,8 @@ async function main() { const media = await findMedia(content.kind, elementTimeout) console.log('media acquired', media) + ipcRenderer.send('view-loaded') + if (content.kind === 'video' && media instanceof HTMLVideoElement) { rotationController = new RotationController(media) snapshotInterval = window.setInterval(() => { @@ -311,7 +313,10 @@ async function main() { 'emptied', async () => { console.warn('media emptied, re-acquiring', media) + + ipcRenderer.send('view-stalled') clearInterval(snapshotInterval) + const newMedia = await acquireMedia(REACQUIRE_ELEMENT_TIMEOUT) if (newMedia !== media) { media.remove() @@ -332,10 +337,9 @@ async function main() { }) } else if (content.kind === 'web') { webFrame.insertCSS(NO_SCROLL_STYLE, { cssOrigin: 'user' }) + ipcRenderer.send('view-loaded') } - ipcRenderer.send('view-loaded') - function updateOptions(options: ContentDisplayOptions) { if (rotationController) { rotationController.setCustom(options.rotation) diff --git a/packages/streamwall/src/renderer/overlay.tsx b/packages/streamwall/src/renderer/overlay.tsx index 6fb077b..54ef398 100644 --- a/packages/streamwall/src/renderer/overlay.tsx +++ b/packages/streamwall/src/renderer/overlay.tsx @@ -62,7 +62,9 @@ function Overlay({ 'displaying.running.video.blurred', state, ) - const isLoading = matchesState('displaying.loading', state) + const isLoading = + matchesState('displaying.loading', state) || + matchesState('displaying.running.playback.stalled', state) const hasTitle = data && (data.label || data.source) const position = data?.labelPosition ?? 'top-left' return (