bug fixes
This commit is contained in:
50
broadcast.ts
50
broadcast.ts
@@ -191,15 +191,53 @@ function textLines(
|
|||||||
const words = text.split(/\s+/).filter(Boolean);
|
const words = text.split(/\s+/).filter(Boolean);
|
||||||
const lines: string[] = [];
|
const lines: string[] = [];
|
||||||
let current = "";
|
let current = "";
|
||||||
|
const maxChunkLength = 1;
|
||||||
|
|
||||||
|
const splitWordToFit = (word: string): string[] => {
|
||||||
|
if (!word) return [];
|
||||||
|
if (ctx.measureText(word).width <= maxWidth) return [word];
|
||||||
|
|
||||||
|
const pieces: string[] = [];
|
||||||
|
let remaining = word;
|
||||||
|
while (remaining.length > 0) {
|
||||||
|
let low = maxChunkLength;
|
||||||
|
let high = remaining.length;
|
||||||
|
let best = maxChunkLength;
|
||||||
|
|
||||||
|
while (low <= high) {
|
||||||
|
const mid = Math.floor((low + high) / 2);
|
||||||
|
const candidate = remaining.slice(0, mid);
|
||||||
|
if (ctx.measureText(candidate).width <= maxWidth) {
|
||||||
|
best = mid;
|
||||||
|
low = mid + 1;
|
||||||
|
} else {
|
||||||
|
high = mid - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pieces.push(remaining.slice(0, best));
|
||||||
|
remaining = remaining.slice(best);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pieces;
|
||||||
|
};
|
||||||
|
|
||||||
for (const word of words) {
|
for (const word of words) {
|
||||||
const candidate = current ? `${current} ${word}` : word;
|
const segments = splitWordToFit(word);
|
||||||
if (ctx.measureText(candidate).width <= maxWidth) {
|
let isFirstSegment = true;
|
||||||
current = candidate;
|
for (const segment of segments) {
|
||||||
continue;
|
const prefix = isFirstSegment && current ? " " : "";
|
||||||
|
const candidate = current ? `${current}${prefix}${segment}` : segment;
|
||||||
|
if (ctx.measureText(candidate).width <= maxWidth) {
|
||||||
|
current = candidate;
|
||||||
|
isFirstSegment = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (current) lines.push(current);
|
||||||
|
current = segment;
|
||||||
|
isFirstSegment = false;
|
||||||
|
if (lines.length >= maxLines - 1) break;
|
||||||
}
|
}
|
||||||
if (current) lines.push(current);
|
|
||||||
current = word;
|
|
||||||
if (lines.length >= maxLines - 1) break;
|
if (lines.length >= maxLines - 1) break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -149,6 +149,7 @@ async function main() {
|
|||||||
stdout: mode === "dryrun" ? "pipe" : "inherit",
|
stdout: mode === "dryrun" ? "pipe" : "inherit",
|
||||||
stderr: "inherit",
|
stderr: "inherit",
|
||||||
});
|
});
|
||||||
|
let ffmpegWritable = true;
|
||||||
|
|
||||||
let ffplay: Bun.Subprocess | null = null;
|
let ffplay: Bun.Subprocess | null = null;
|
||||||
let ffplayPump: Promise<void> | null = null;
|
let ffplayPump: Promise<void> | null = null;
|
||||||
@@ -187,6 +188,7 @@ async function main() {
|
|||||||
firstChunkResolve = resolve;
|
firstChunkResolve = resolve;
|
||||||
firstChunkReject = reject;
|
firstChunkReject = reject;
|
||||||
});
|
});
|
||||||
|
let shutdown: (() => Promise<void>) | null = null;
|
||||||
|
|
||||||
const chunkServer = Bun.serve({
|
const chunkServer = Bun.serve({
|
||||||
port: 0,
|
port: 0,
|
||||||
@@ -199,7 +201,9 @@ async function main() {
|
|||||||
},
|
},
|
||||||
websocket: {
|
websocket: {
|
||||||
message(_ws, message) {
|
message(_ws, message) {
|
||||||
if (!ffmpeg.stdin) return;
|
if (!ffmpegWritable || !ffmpeg.stdin || typeof ffmpeg.stdin === "number") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (typeof message === "string") return;
|
if (typeof message === "string") return;
|
||||||
|
|
||||||
let chunk: Uint8Array | null = null;
|
let chunk: Uint8Array | null = null;
|
||||||
@@ -220,8 +224,12 @@ async function main() {
|
|||||||
firstChunkResolve = null;
|
firstChunkResolve = null;
|
||||||
firstChunkReject = null;
|
firstChunkReject = null;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
ffmpegWritable = false;
|
||||||
const detail = error instanceof Error ? error : new Error(String(error));
|
const detail = error instanceof Error ? error : new Error(String(error));
|
||||||
firstChunkReject?.(detail);
|
firstChunkReject?.(detail);
|
||||||
|
firstChunkResolve = null;
|
||||||
|
firstChunkReject = null;
|
||||||
|
void shutdown?.();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -263,9 +271,10 @@ async function main() {
|
|||||||
console.log(`Streaming from ${broadcastUrl} in ${mode} mode`);
|
console.log(`Streaming from ${broadcastUrl} in ${mode} mode`);
|
||||||
|
|
||||||
let shuttingDown = false;
|
let shuttingDown = false;
|
||||||
const shutdown = async () => {
|
shutdown = async () => {
|
||||||
if (shuttingDown) return;
|
if (shuttingDown) return;
|
||||||
shuttingDown = true;
|
shuttingDown = true;
|
||||||
|
ffmpegWritable = false;
|
||||||
try {
|
try {
|
||||||
chunkServer.stop(true);
|
chunkServer.stop(true);
|
||||||
} catch {}
|
} catch {}
|
||||||
@@ -290,14 +299,20 @@ async function main() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
process.on("SIGINT", () => {
|
const ffmpegExit = ffmpeg.exited.then((code) => {
|
||||||
void shutdown();
|
ffmpegWritable = false;
|
||||||
});
|
void shutdown?.();
|
||||||
process.on("SIGTERM", () => {
|
return code;
|
||||||
void shutdown();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const exitCode = await ffmpeg.exited;
|
process.on("SIGINT", () => {
|
||||||
|
void shutdown?.();
|
||||||
|
});
|
||||||
|
process.on("SIGTERM", () => {
|
||||||
|
void shutdown?.();
|
||||||
|
});
|
||||||
|
|
||||||
|
const exitCode = await ffmpegExit;
|
||||||
if (ffplayPump) {
|
if (ffplayPump) {
|
||||||
await ffplayPump.catch(() => {
|
await ffplayPump.catch(() => {
|
||||||
// Ignore downstream pipe failures on shutdown.
|
// Ignore downstream pipe failures on shutdown.
|
||||||
@@ -306,7 +321,7 @@ async function main() {
|
|||||||
if (ffplay) {
|
if (ffplay) {
|
||||||
await ffplay.exited;
|
await ffplay.exited;
|
||||||
}
|
}
|
||||||
await shutdown();
|
await shutdown?.();
|
||||||
|
|
||||||
if (exitCode !== 0) {
|
if (exitCode !== 0) {
|
||||||
process.exit(exitCode);
|
process.exit(exitCode);
|
||||||
|
|||||||
6
todo.md
6
todo.md
@@ -5,6 +5,6 @@
|
|||||||
|
|
||||||
## TODO Bugfixes
|
## TODO Bugfixes
|
||||||
|
|
||||||
- [ ] Text wrapping on long single-strings
|
- [x] Text wrapping on long single-strings
|
||||||
- [ ] Stream crashing on fresh deployment (because the ffmpeg pipe dies, we should handle this in the ffmpeg script)
|
- [x] Stream crashing on fresh deployment (because the ffmpeg pipe dies, we should handle this in the ffmpeg script)
|
||||||
- [ ] Throttle view count updates
|
- [x] Throttle view count updates
|
||||||
|
|||||||
Reference in New Issue
Block a user