Compare commits

...
Author SHA1 Message Date
branderson 1306a88b0e fix(streaming): hide Flash Player's menu bar without full screen
Some games break in Flash Player's full-screen mode: in Bloons darts
make a sound but never fly. Instead of pressing Ctrl+F, the streamer
stretches the projector's window to the display and moves it up by the
menu bar's height, measured from the window's opening height and the
movie's. The session manager now passes the game's exact size as
GAME_WIDTH/GAME_HEIGHT and rounds only the display size to even.
2026-10-14 17:25:27 -07:00
branderson c5b1f750bc Merge pull request 'Streaming: log session lifecycles and why streamers fail' (#7) from session-manager-logging into main
Images / app (push) Successful in 4m52s
Images / session-manager (push) Successful in 1m44s
Images / streamer (push) Successful in 3m37s
Reviewed-on: #7
Reviewed-by: branderson <brad@branderson.io>
2026-10-14 16:02:27 -07:00
branderson 26f920c0da Merge pull request 'Docs: drop the old /stream/ proxy route when upgrading' (#6) from docs-stream-route into main
Images / app (push) Successful in 4m48s
Images / session-manager (push) Successful in 1m46s
Images / streamer (push) Successful in 3m34s
Reviewed-on: #6
Reviewed-by: branderson <brad@branderson.io>
2026-10-14 16:02:05 -07:00
branderson b982e0a541 docs: tell upgraders to drop their /stream/ proxy route
The upgrade guide still said the /stream/ route was unchanged, but the
app now proxies streams itself and the session manager publishes no
port; an old route times out with a 504.
2026-10-14 00:47:25 -07:00
6 changed files with 38 additions and 22 deletions
+6 -1
View File
@@ -33,4 +33,9 @@ What changes:
- **Streaming config.** Streaming is now part of the same compose file. Replace
the separate `API_TOKEN` and `STREAMING_API_URL` with `STREAMING_API_TOKEN`
and `COMPOSE_PROFILES=streaming` (see [Streaming](../10-features/13-streaming.md)).
The `/stream/` proxy route is unchanged.
- **No separate `/stream/` route.** The app now proxies streams to the session
manager itself, and the session manager no longer publishes port 8081.
Remove any `location /stream/` block from your reverse proxy, or streams
will time out with a 504. Move its WebSocket and timeout settings to the
location that serves the app (see
[Reverse proxy](../10-features/13-streaming.md#reverse-proxy)).
+5 -2
View File
@@ -46,8 +46,11 @@ export class DockerDriver implements Driver {
`PUID=${uid}`,
`PGID=${gid}`,
`SUBFOLDER=${streamPath(id)}`,
`SELKIES_MANUAL_WIDTH=${size.width}`,
`SELKIES_MANUAL_HEIGHT=${size.height}`,
// The game's exact size, and the display's, rounded up to even for H.264
`GAME_WIDTH=${size.width}`,
`GAME_HEIGHT=${size.height}`,
`SELKIES_MANUAL_WIDTH=${size.width + (size.width % 2)}`,
`SELKIES_MANUAL_HEIGHT=${size.height + (size.height % 2)}`,
'SELKIES_UI_SHOW_SIDEBAR=false',
'HARDEN_DESKTOP=true',
'RESTART_APP=true',
+6 -11
View File
@@ -59,7 +59,7 @@ export class GameCatalog {
return null;
}
const page = await this.readContent(gameId, row, row.cmd).catch(() => null);
return even((page && appletSize(page.toString('latin1'))) ?? DEFAULT_SIZE);
return ((page && appletSize(page.toString('latin1'))) ?? DEFAULT_SIZE);
}
const program = await this.launchProgram(row);
if (!program) {
@@ -68,19 +68,19 @@ export class GameCatalog {
const url = row.cmd.match(/https?:\/\/[^\s"']+/)?.[0];
if (!url) {
const exe = await readFile(program).catch(() => null);
return even((exe && projectorStageSize(exe)) ?? DEFAULT_SIZE);
return ((exe && projectorStageSize(exe)) ?? DEFAULT_SIZE);
}
const content = await this.readContent(gameId, row, url).catch(() => null);
const extension = path.extname(new URL(url).pathname).toLowerCase();
if (['.dcr', '.dir', '.dxr', '.cct', '.cst'].includes(extension)) {
return even((content && directorStageSize(content)) ?? DEFAULT_SIZE);
return ((content && directorStageSize(content)) ?? DEFAULT_SIZE);
}
if (extension === '.swf') {
// The exact stage: in full screen, games that set noScale keep their native size
return even((content && swfStageSize(content)) ?? DEFAULT_SIZE);
// The exact stage, so the movie fills the display without scaling
return ((content && swfStageSize(content)) ?? DEFAULT_SIZE);
}
const embed = content && embedSize(content.toString('latin1'));
return even(embed ? { width: embed.width + 2 * PAGE_MARGIN, height: embed.height + 2 * PAGE_MARGIN } : BROWSER_SIZE);
return (embed ? { width: embed.width + 2 * PAGE_MARGIN, height: embed.height + 2 * PAGE_MARGIN } : BROWSER_SIZE);
}
/**
@@ -151,11 +151,6 @@ async function resolveCaseInsensitive(root: string, relative: string): Promise<s
return (await stat(current).catch(() => null))?.isFile() ? current : null;
}
/** H.264 needs even dimensions. */
function even({ width, height }: Size): Size {
return { width: width + (width % 2), height: height + (height % 2) };
}
/** Reads the stage size from a SWF header: a rect of twips after the 8-byte header. */
export function swfStageSize(swf: Buffer): Size | null {
const signature = swf.toString('latin1', 0, 3);
+3 -3
View File
@@ -177,11 +177,11 @@ test('GameCatalog streams allowed platforms whose launcher resolves, sized from
legacy: { width: 300, height: 200 },
missing: { width: 800, height: 600 },
webstart: null,
spr: { width: 500, height: 426 },
oldcpu: { width: 500, height: 426 },
spr: { width: 500, height: 425 },
oldcpu: { width: 500, height: 425 },
secure: { width: 640, height: 480 },
navigator: { width: 528, height: 400 },
flash: { width: 500, height: 376 },
flash: { width: 500, height: 375 },
unity: null,
'no-program': null,
exe: { width: 800, height: 600 },
+5 -3
View File
@@ -52,6 +52,8 @@ writable mirror of the install, since they write beside themselves; a
Director projector's `Prefs`, where it saves, is linked into `/config/saves`, as
is Flash Player's save directory, so the session manager syncs both to the
player's browser. OldCPUSimulator hangs under Wine, so its target runs under
`cpulimit` at the same share of the CPU. Flash projectors switch to full screen,
which hides their menu bar and scales the movie to the display, and Flashpoint
Navigator's toolbars are hidden.
`cpulimit` at the same share of the CPU. A Flash projector's window is
stretched to the display and moved up by its menu bar's height, which hides the
menu bar; the height is the window's opening height less the movie's
(`GAME_HEIGHT`). Its full-screen mode isn't used, since some games, such as
Bloons, don't play right in it. Flashpoint Navigator's toolbars are hidden.
@@ -47,8 +47,19 @@ case ${app,,} in
mkdir -p "$HOME/saves/Flash_Player" "$HOME/.macromedia"
[[ -e $HOME/.macromedia/Flash_Player ]] || ln -s "$HOME/saves/Flash_Player" "$HOME/.macromedia/Flash_Player"
if [[ ${app,,} == 'fpsoftware\flash\'* ]]; then
# Full screen hides the projector's menu bar and scales the movie to the display
(xdotool search --sync --onlyvisible --name 'Flash Player' windowactivate --sync key ctrl+f) &
# Hide the projector's menu bar by moving it above the display, and fill
# the display with the movie. Not full screen, which some games, such as
# Bloons, don't play right in. The window opens at the movie's size plus
# the menu bar, which gives the menu bar's height.
(
wid=$(xdotool search --sync --onlyvisible --name 'Flash Player' | head -1)
eval "$(xdotool getwindowgeometry --shell "$wid")"
read -r display_width display_height < <(xdotool getdisplaygeometry)
menu=$((HEIGHT - ${GAME_HEIGHT:-$display_height}))
((menu > 0 && menu < 100)) || menu=0
xdotool windowsize "$wid" "$display_width" $((display_height + menu)) \
windowmove "$wid" 0 $((-menu)) windowactivate "$wid"
) &
fi
cd "${argv[0]}"
exec "${argv[@]:1}" ;;