accelr8 3.8.1 → 4.0.0-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +298 -64
- package/dist/chunks/accounts-CCKXHSQQ.js +54 -0
- package/dist/chunks/{agentcli-ZI2O7ID7.js → agentcli-HL6V26E7.js} +6 -3
- package/dist/chunks/{app-7YGYMFSZ.js → app-JMUXGMWK.js} +272 -75
- package/dist/chunks/authclient-AUBBFWCI.js +34 -0
- package/dist/chunks/authpage-X2AKCMVH.js +21 -0
- package/dist/chunks/chunk-3OTVRBTJ.js +265 -0
- package/dist/chunks/chunk-5QXGOLQ3.js +101 -0
- package/dist/chunks/chunk-62O2F73J.js +13 -0
- package/dist/chunks/chunk-CAQJPW54.js +211 -0
- package/dist/chunks/chunk-CUUNLVID.js +124 -0
- package/dist/chunks/chunk-EQPM4FIJ.js +419 -0
- package/dist/chunks/chunk-FVC2UPMV.js +306 -0
- package/dist/chunks/{chunk-3JNUWI22.js → chunk-NRXIFJGR.js} +3 -155
- package/dist/chunks/chunk-RZ4OBBLX.js +129 -0
- package/dist/chunks/chunk-UMPU6SAD.js +860 -0
- package/dist/chunks/{client-3RLQY3ZB.js → client-UCMRJVMA.js} +1621 -437
- package/dist/chunks/livekit-client-bundle-HLYFRJB2.js +15 -0
- package/dist/chunks/livekit-terminal-22Q2NFB5.js +535 -0
- package/dist/chunks/livekit-window-XVOCBQOM.js +626 -0
- package/dist/chunks/server-A4T45QZ4.js +2707 -0
- package/dist/chunks/{update-UEPNGB6Y.js → update-RGDNBI4T.js} +25 -7
- package/dist/cli.js +132 -10
- package/package.json +10 -7
- package/dist/chunks/chunk-HY6ULYGH.js +0 -559
- package/dist/chunks/chunk-UTWXIX5N.js +0 -64
- package/dist/chunks/server-V56IHFUH.js +0 -1024
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
detectVoiceTools,
|
|
4
3
|
tzCity,
|
|
5
4
|
tzToCoords
|
|
6
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-CUUNLVID.js";
|
|
6
|
+
import {
|
|
7
|
+
detectVoiceTools
|
|
8
|
+
} from "./chunk-UMPU6SAD.js";
|
|
7
9
|
import {
|
|
8
10
|
DEFAULT_SERVER,
|
|
9
11
|
VERSION
|
|
10
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-5QXGOLQ3.js";
|
|
13
|
+
import {
|
|
14
|
+
accelr8Dir
|
|
15
|
+
} from "./chunk-62O2F73J.js";
|
|
11
16
|
|
|
12
17
|
// src/radio/app.ts
|
|
13
18
|
import tk from "terminal-kit";
|
|
14
19
|
import fs from "node:fs";
|
|
15
|
-
import os from "node:os";
|
|
16
20
|
import path from "node:path";
|
|
17
21
|
import { WebSocket } from "ws";
|
|
18
22
|
|
|
@@ -318,7 +322,7 @@ var matrix = {
|
|
|
318
322
|
frame(tMs, w, h) {
|
|
319
323
|
const out = new Array(w * h);
|
|
320
324
|
const t = tMs / 1e3;
|
|
321
|
-
const
|
|
325
|
+
const CH2 = "01<>+*=~^:;|/\\ACELR8#$%&?!";
|
|
322
326
|
for (let x = 0; x < w; x++) {
|
|
323
327
|
let h0 = 0, l0 = 1, h1 = 0, l1 = 1;
|
|
324
328
|
for (let d = 0; d < 2; d++) {
|
|
@@ -345,7 +349,7 @@ var matrix = {
|
|
|
345
349
|
continue;
|
|
346
350
|
}
|
|
347
351
|
const rate = 1.5 + 4 * hash2(x, y);
|
|
348
|
-
const ch =
|
|
352
|
+
const ch = CH2[Math.floor(hashU(x * 31 + y * 57 + Math.floor(t * rate) * 7919) * CH2.length)];
|
|
349
353
|
const isHead = q0 > 0 && d0 < 1 || q1 > 0 && d1 < 1;
|
|
350
354
|
out[y * w + x] = isHead ? { ch, color: col(200 + 55 * q, 252, 252) } : { ch, color: col(18 + 40 * q, 55 + 155 * q, 60 + 195 * q) };
|
|
351
355
|
}
|
|
@@ -5345,123 +5349,273 @@ function renderOverlay(ctx, opts) {
|
|
|
5345
5349
|
}
|
|
5346
5350
|
|
|
5347
5351
|
// src/radio/audio.ts
|
|
5348
|
-
import { spawn as spawn2 } from "node:child_process";
|
|
5349
|
-
|
|
5352
|
+
import { spawn as spawn2, spawnSync } from "node:child_process";
|
|
5353
|
+
var SR = 44100;
|
|
5354
|
+
var CH = 2;
|
|
5355
|
+
function nextAudioAction(sched, serverNowMs, mem, driftBudgetMs = 1500, stallMs = 4e3) {
|
|
5350
5356
|
const pos = nowPlaying(sched, serverNowMs);
|
|
5351
5357
|
if (!pos || !pos.item.url) return { kind: "none" };
|
|
5352
|
-
const
|
|
5353
|
-
if (
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5358
|
+
const play = (reason) => ({ kind: "play", track: pos.item, offsetMs: pos.offsetMs, reason });
|
|
5359
|
+
if (pos.item.id !== mem.playingTrackId) return play(mem.playingTrackId ? "track" : "start");
|
|
5360
|
+
if (!mem.procAlive) return play("dead");
|
|
5361
|
+
if (mem.lastDataAtServerMs !== void 0) {
|
|
5362
|
+
const quietFor = serverNowMs - (mem.lastDataAtServerMs || mem.spawnedAtServerMs);
|
|
5363
|
+
if (quietFor > (mem.lastDataAtServerMs ? stallMs : stallMs * 2)) return play("stall");
|
|
5364
|
+
}
|
|
5365
|
+
const implied = mem.spawnedOffsetMs + (serverNowMs - mem.spawnedAtServerMs);
|
|
5366
|
+
if (Math.abs(implied - pos.offsetMs) > driftBudgetMs) return play("drift");
|
|
5360
5367
|
return { kind: "none" };
|
|
5361
5368
|
}
|
|
5362
|
-
function
|
|
5363
|
-
|
|
5369
|
+
function applyGain(buf, fromGain, target, rampPerSample = 1 / 1400) {
|
|
5370
|
+
let g = fromGain;
|
|
5371
|
+
for (let i = 0; i + 1 < buf.length; i += 2) {
|
|
5372
|
+
if (g < target) g = g + rampPerSample > target ? target : g + rampPerSample;
|
|
5373
|
+
else if (g > target) g = g - rampPerSample < target ? target : g - rampPerSample;
|
|
5374
|
+
let s = Math.round(buf.readInt16LE(i) * g);
|
|
5375
|
+
if (s > 32767) s = 32767;
|
|
5376
|
+
else if (s < -32768) s = -32768;
|
|
5377
|
+
buf.writeInt16LE(s, i);
|
|
5378
|
+
}
|
|
5379
|
+
return g;
|
|
5380
|
+
}
|
|
5381
|
+
function decoderArgs(url, offsetMs) {
|
|
5382
|
+
const args = ["-hide_banner", "-loglevel", "error", "-re"];
|
|
5383
|
+
if (/^https?:/i.test(url)) args.push("-rw_timeout", "8000000");
|
|
5384
|
+
if (offsetMs > 0) args.push("-ss", (offsetMs / 1e3).toFixed(3));
|
|
5385
|
+
args.push("-i", url, "-f", "s16le", "-ar", String(SR), "-ac", String(CH), "-");
|
|
5386
|
+
return args;
|
|
5387
|
+
}
|
|
5388
|
+
var chFlagCache = null;
|
|
5389
|
+
function ffplayStereoFlag(ffplayPath) {
|
|
5390
|
+
if (chFlagCache) return chFlagCache;
|
|
5391
|
+
let modern = true;
|
|
5392
|
+
try {
|
|
5393
|
+
const out = spawnSync(ffplayPath, ["-hide_banner", "-version"], { encoding: "utf8", timeout: 3e3 });
|
|
5394
|
+
const m = ((out.stdout || "") + (out.stderr || "")).match(/version\s+n?(\d+)\.(\d+)/i);
|
|
5395
|
+
if (m) {
|
|
5396
|
+
const major = +m[1], minor = +m[2];
|
|
5397
|
+
modern = major > 5 || major === 5 && minor >= 1;
|
|
5398
|
+
}
|
|
5399
|
+
} catch {
|
|
5400
|
+
}
|
|
5401
|
+
chFlagCache = modern ? ["-ch_layout", "stereo"] : ["-ac", String(CH)];
|
|
5402
|
+
return chFlagCache;
|
|
5403
|
+
}
|
|
5404
|
+
function sinkArgs(ffplayPath) {
|
|
5405
|
+
return [
|
|
5364
5406
|
"-hide_banner",
|
|
5365
5407
|
"-loglevel",
|
|
5366
5408
|
"error",
|
|
5367
5409
|
"-nodisp",
|
|
5368
|
-
"-
|
|
5369
|
-
"
|
|
5370
|
-
|
|
5410
|
+
"-sync",
|
|
5411
|
+
"audio",
|
|
5412
|
+
"-infbuf",
|
|
5413
|
+
"-f",
|
|
5414
|
+
"s16le",
|
|
5415
|
+
"-ar",
|
|
5416
|
+
String(SR),
|
|
5417
|
+
...ffplayStereoFlag(ffplayPath),
|
|
5418
|
+
"-i",
|
|
5419
|
+
"-"
|
|
5371
5420
|
];
|
|
5372
|
-
|
|
5373
|
-
|
|
5374
|
-
return
|
|
5421
|
+
}
|
|
5422
|
+
function coreAudioSinkArgs(ffmpegPath) {
|
|
5423
|
+
return [
|
|
5424
|
+
"-hide_banner",
|
|
5425
|
+
"-loglevel",
|
|
5426
|
+
"error",
|
|
5427
|
+
"-f",
|
|
5428
|
+
"s16le",
|
|
5429
|
+
"-ar",
|
|
5430
|
+
String(SR),
|
|
5431
|
+
...ffplayStereoFlag(ffmpegPath),
|
|
5432
|
+
"-i",
|
|
5433
|
+
"-",
|
|
5434
|
+
"-f",
|
|
5435
|
+
"audiotoolbox",
|
|
5436
|
+
"-"
|
|
5437
|
+
];
|
|
5438
|
+
}
|
|
5439
|
+
var coreAudioCache = null;
|
|
5440
|
+
function hasCoreAudioSink(ffmpegPath) {
|
|
5441
|
+
if (coreAudioCache !== null) return coreAudioCache;
|
|
5442
|
+
coreAudioCache = false;
|
|
5443
|
+
if (process.platform !== "darwin") return false;
|
|
5444
|
+
try {
|
|
5445
|
+
const out = spawnSync(ffmpegPath, ["-hide_banner", "-devices"], { encoding: "utf8", timeout: 3e3 });
|
|
5446
|
+
coreAudioCache = /\baudiotoolbox\b/.test((out.stdout || "") + (out.stderr || ""));
|
|
5447
|
+
} catch {
|
|
5448
|
+
}
|
|
5449
|
+
return coreAudioCache;
|
|
5450
|
+
}
|
|
5451
|
+
function pickSink(ffmpegPath, ffplayPath, pref = "auto") {
|
|
5452
|
+
const core = pref === "coreaudio" || pref === "auto" && hasCoreAudioSink(ffmpegPath);
|
|
5453
|
+
return core ? { kind: "coreaudio", bin: ffmpegPath, args: coreAudioSinkArgs(ffmpegPath) } : { kind: "ffplay", bin: ffplayPath, args: sinkArgs(ffplayPath) };
|
|
5375
5454
|
}
|
|
5376
5455
|
var SyncedAudio = class {
|
|
5377
|
-
|
|
5456
|
+
sink = null;
|
|
5457
|
+
decoder = null;
|
|
5378
5458
|
mem = { playingTrackId: null, spawnedAtServerMs: 0, spawnedOffsetMs: 0, procAlive: false };
|
|
5379
5459
|
opts = null;
|
|
5380
5460
|
timer = null;
|
|
5381
|
-
|
|
5382
|
-
//
|
|
5461
|
+
gain = 0.7;
|
|
5462
|
+
// current (ramping) gain
|
|
5463
|
+
target = 0.7;
|
|
5464
|
+
// desired volume (0..1)
|
|
5383
5465
|
muted = false;
|
|
5466
|
+
sinkKind = "";
|
|
5467
|
+
lastFeedAt = 0;
|
|
5468
|
+
// wall ms of the last write to the sink (music or silence)
|
|
5469
|
+
written = 0;
|
|
5470
|
+
// bytes into the current sink, to keep whole s16 stereo frames
|
|
5471
|
+
keepalive = null;
|
|
5384
5472
|
start(opts) {
|
|
5385
5473
|
this.stop();
|
|
5386
5474
|
this.opts = opts;
|
|
5475
|
+
this.startSink();
|
|
5387
5476
|
this.tick();
|
|
5388
5477
|
this.timer = setInterval(() => this.tick(), 1e3);
|
|
5478
|
+
this.keepalive = setInterval(() => {
|
|
5479
|
+
const quiet = Date.now() - this.lastFeedAt;
|
|
5480
|
+
if (quiet > 60) this.feed(Buffer.alloc(Math.floor(Math.min(quiet, 250) * SR / 1e3) * CH * 2));
|
|
5481
|
+
}, 40);
|
|
5482
|
+
}
|
|
5483
|
+
/** Every write to the sink goes through here. Pads to a whole 4-byte frame
|
|
5484
|
+
* first, so a decoder killed mid-frame can't shift the stream into noise. */
|
|
5485
|
+
feed(buf) {
|
|
5486
|
+
const sink = this.sink;
|
|
5487
|
+
if (!sink || !sink.stdin || sink.stdin.destroyed) return true;
|
|
5488
|
+
const pad = (4 - this.written % 4) % 4;
|
|
5489
|
+
if (pad && buf.length) {
|
|
5490
|
+
sink.stdin.write(Buffer.alloc(pad));
|
|
5491
|
+
this.written += pad;
|
|
5492
|
+
}
|
|
5493
|
+
this.written += buf.length;
|
|
5494
|
+
this.lastFeedAt = Date.now();
|
|
5495
|
+
return sink.stdin.write(buf);
|
|
5496
|
+
}
|
|
5497
|
+
/** Open a fresh sink, so it binds to whatever output is current NOW (the TV
|
|
5498
|
+
* while screen sharing, the speakers after). The old one is dropped first
|
|
5499
|
+
* so its exit handler doesn't treat this as a crash. */
|
|
5500
|
+
restartSink() {
|
|
5501
|
+
const old = this.sink;
|
|
5502
|
+
this.sink = null;
|
|
5503
|
+
this.startSink();
|
|
5504
|
+
if (old) {
|
|
5505
|
+
try {
|
|
5506
|
+
old.kill("SIGKILL");
|
|
5507
|
+
} catch {
|
|
5508
|
+
}
|
|
5509
|
+
}
|
|
5389
5510
|
}
|
|
5390
|
-
/** Swap in a new schedule (worker pushed a new playlist) without a restart. */
|
|
5391
5511
|
setSchedule(sched) {
|
|
5392
5512
|
if (this.opts) this.opts.schedule = sched;
|
|
5393
5513
|
}
|
|
5394
5514
|
get volume() {
|
|
5395
|
-
return this.
|
|
5515
|
+
return Math.round(this.target * 100);
|
|
5396
5516
|
}
|
|
5397
5517
|
get isMuted() {
|
|
5398
5518
|
return this.muted;
|
|
5399
5519
|
}
|
|
5400
|
-
|
|
5401
|
-
|
|
5520
|
+
get nowTrackId() {
|
|
5521
|
+
return this.mem.playingTrackId;
|
|
5522
|
+
}
|
|
5523
|
+
/** Smooth: just move the target; the per-chunk gain ramp glides to it. */
|
|
5402
5524
|
setVolume(v) {
|
|
5403
|
-
this.
|
|
5404
|
-
|
|
5525
|
+
this.target = Math.max(0, Math.min(100, Math.round(v))) / 100;
|
|
5526
|
+
this.muted = false;
|
|
5405
5527
|
}
|
|
5406
|
-
/** Toggle mute. Muting stops the process (no CPU); unmuting resyncs + resumes. */
|
|
5407
5528
|
toggleMute() {
|
|
5408
5529
|
this.muted = !this.muted;
|
|
5409
|
-
if (this.muted) {
|
|
5410
|
-
this.killProc();
|
|
5411
|
-
this.mem.playingTrackId = null;
|
|
5412
|
-
this.mem.procAlive = false;
|
|
5413
|
-
} else this.tick();
|
|
5414
5530
|
return this.muted;
|
|
5415
5531
|
}
|
|
5416
|
-
|
|
5532
|
+
// gain ramps to 0 / back, no cut
|
|
5533
|
+
effTarget() {
|
|
5534
|
+
return this.muted ? 0 : this.target;
|
|
5535
|
+
}
|
|
5536
|
+
startSink() {
|
|
5417
5537
|
if (!this.opts) return;
|
|
5418
|
-
const
|
|
5419
|
-
|
|
5538
|
+
const env = process.env.ACCELR8_RADIO_SINK;
|
|
5539
|
+
const pref = env === "ffplay" || env === "coreaudio" ? env : this.opts.sink ?? "auto";
|
|
5540
|
+
const pick4 = pickSink(this.opts.ffmpegPath, this.opts.ffplayPath, pref);
|
|
5541
|
+
this.opts.onSink?.(pick4.kind);
|
|
5542
|
+
const sink = spawn2(pick4.bin, pick4.args, { stdio: ["pipe", "ignore", "pipe"] });
|
|
5543
|
+
this.sink = sink;
|
|
5544
|
+
this.sinkKind = pick4.kind;
|
|
5545
|
+
this.written = 0;
|
|
5546
|
+
sink.stdin?.on("error", () => {
|
|
5547
|
+
});
|
|
5548
|
+
sink.stdin?.on("drain", () => this.decoder?.stdout?.resume());
|
|
5549
|
+
let err = "";
|
|
5550
|
+
sink.stderr?.on("data", (d) => {
|
|
5551
|
+
err = (err + d.toString()).slice(-200);
|
|
5552
|
+
});
|
|
5553
|
+
sink.on("exit", () => {
|
|
5554
|
+
if (this.sink !== sink) return;
|
|
5555
|
+
this.sink = null;
|
|
5556
|
+
if (err) this.opts?.onError?.(err.split("\n").pop() || err);
|
|
5557
|
+
if (this.opts) {
|
|
5558
|
+
this.startSink();
|
|
5559
|
+
this.mem.procAlive = false;
|
|
5560
|
+
}
|
|
5561
|
+
});
|
|
5420
5562
|
}
|
|
5421
5563
|
tick() {
|
|
5422
|
-
if (!this.opts
|
|
5564
|
+
if (!this.opts) return;
|
|
5565
|
+
if (!this.sink) this.startSink();
|
|
5423
5566
|
const action = nextAudioAction(this.opts.schedule, this.opts.serverNow(), this.mem);
|
|
5424
|
-
if (action.kind === "play")
|
|
5567
|
+
if (action.kind === "play") {
|
|
5568
|
+
if (action.reason !== "track") this.opts.onRespawn?.(action.reason, action.track, action.offsetMs);
|
|
5569
|
+
if (action.reason === "track" && this.sinkKind === "coreaudio") this.restartSink();
|
|
5570
|
+
this.spawnDecoder(action.track, action.offsetMs);
|
|
5571
|
+
}
|
|
5425
5572
|
}
|
|
5426
|
-
|
|
5573
|
+
spawnDecoder(track, offsetMs) {
|
|
5427
5574
|
if (!this.opts) return;
|
|
5428
|
-
this.
|
|
5429
|
-
const
|
|
5430
|
-
this.
|
|
5431
|
-
this.mem = { playingTrackId: track.id, spawnedAtServerMs: this.opts.serverNow(), spawnedOffsetMs: offsetMs, procAlive: true };
|
|
5575
|
+
this.killDecoder();
|
|
5576
|
+
const dec = spawn2(this.opts.ffmpegPath, decoderArgs(track.url, offsetMs), { stdio: ["ignore", "pipe", "ignore"] });
|
|
5577
|
+
this.decoder = dec;
|
|
5578
|
+
this.mem = { playingTrackId: track.id, spawnedAtServerMs: this.opts.serverNow(), spawnedOffsetMs: offsetMs, procAlive: true, lastDataAtServerMs: 0 };
|
|
5432
5579
|
this.opts.onTrack?.(track);
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
5580
|
+
dec.stdout?.on("data", (chunk) => {
|
|
5581
|
+
if (this.decoder !== dec) return;
|
|
5582
|
+
if (this.opts) this.mem.lastDataAtServerMs = this.opts.serverNow();
|
|
5583
|
+
this.gain = applyGain(chunk, this.gain, this.effTarget());
|
|
5584
|
+
if (this.feed(chunk) === false) dec.stdout?.pause();
|
|
5436
5585
|
});
|
|
5437
|
-
|
|
5438
|
-
this.mem.procAlive = false;
|
|
5439
|
-
this.opts?.onError?.(e.message);
|
|
5586
|
+
dec.on("error", () => {
|
|
5587
|
+
if (this.decoder === dec) this.mem.procAlive = false;
|
|
5440
5588
|
});
|
|
5441
|
-
|
|
5442
|
-
if (this.
|
|
5443
|
-
this.mem.procAlive = false;
|
|
5444
|
-
if (err) this.opts?.onError?.(err.split("\n").pop() || err);
|
|
5589
|
+
dec.on("exit", () => {
|
|
5590
|
+
if (this.decoder === dec) this.mem.procAlive = false;
|
|
5445
5591
|
});
|
|
5446
5592
|
}
|
|
5447
|
-
|
|
5448
|
-
if (this.
|
|
5593
|
+
killDecoder() {
|
|
5594
|
+
if (this.decoder) {
|
|
5449
5595
|
try {
|
|
5450
|
-
this.
|
|
5596
|
+
this.decoder.kill("SIGKILL");
|
|
5451
5597
|
} catch {
|
|
5452
5598
|
}
|
|
5453
|
-
this.
|
|
5599
|
+
this.decoder = null;
|
|
5454
5600
|
}
|
|
5455
5601
|
}
|
|
5456
|
-
get nowTrackId() {
|
|
5457
|
-
return this.mem.playingTrackId;
|
|
5458
|
-
}
|
|
5459
5602
|
stop() {
|
|
5460
5603
|
if (this.timer) {
|
|
5461
5604
|
clearInterval(this.timer);
|
|
5462
5605
|
this.timer = null;
|
|
5463
5606
|
}
|
|
5464
|
-
this.
|
|
5607
|
+
if (this.keepalive) {
|
|
5608
|
+
clearInterval(this.keepalive);
|
|
5609
|
+
this.keepalive = null;
|
|
5610
|
+
}
|
|
5611
|
+
this.killDecoder();
|
|
5612
|
+
if (this.sink) {
|
|
5613
|
+
try {
|
|
5614
|
+
this.sink.kill("SIGKILL");
|
|
5615
|
+
} catch {
|
|
5616
|
+
}
|
|
5617
|
+
this.sink = null;
|
|
5618
|
+
}
|
|
5465
5619
|
this.mem = { playingTrackId: null, spawnedAtServerMs: 0, spawnedOffsetMs: 0, procAlive: false };
|
|
5466
5620
|
}
|
|
5467
5621
|
};
|
|
@@ -5469,7 +5623,7 @@ var SyncedAudio = class {
|
|
|
5469
5623
|
// src/radio/app.ts
|
|
5470
5624
|
function rlog(msg) {
|
|
5471
5625
|
try {
|
|
5472
|
-
const dir =
|
|
5626
|
+
const dir = accelr8Dir();
|
|
5473
5627
|
fs.mkdirSync(dir, { recursive: true });
|
|
5474
5628
|
fs.appendFileSync(path.join(dir, "radio.log"), `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
|
|
5475
5629
|
`);
|
|
@@ -5490,6 +5644,7 @@ var frameH = 0;
|
|
|
5490
5644
|
var renderTimer = null;
|
|
5491
5645
|
var running = false;
|
|
5492
5646
|
var startedAt = Date.now();
|
|
5647
|
+
var VISUAL_EPOCH = Date.UTC(2026, 0, 1);
|
|
5493
5648
|
var skewMs = 0;
|
|
5494
5649
|
var serverNow = () => Date.now() + skewMs;
|
|
5495
5650
|
var W = () => Math.max(20, term.width || process.stdout.columns || 100);
|
|
@@ -5535,10 +5690,29 @@ function connectStation(opts) {
|
|
|
5535
5690
|
return;
|
|
5536
5691
|
}
|
|
5537
5692
|
stationWs = ws;
|
|
5693
|
+
let lastHeard = Date.now();
|
|
5694
|
+
const beat = setInterval(() => {
|
|
5695
|
+
if (ws.readyState !== WebSocket.OPEN) return;
|
|
5696
|
+
if (Date.now() - lastHeard > 25e3) {
|
|
5697
|
+
rlog("station socket silent 25s, reconnecting");
|
|
5698
|
+
ws.terminate();
|
|
5699
|
+
return;
|
|
5700
|
+
}
|
|
5701
|
+
try {
|
|
5702
|
+
ws.ping();
|
|
5703
|
+
} catch {
|
|
5704
|
+
}
|
|
5705
|
+
}, 1e4);
|
|
5706
|
+
ws.on("pong", () => {
|
|
5707
|
+
lastHeard = Date.now();
|
|
5708
|
+
});
|
|
5538
5709
|
ws.on("open", () => {
|
|
5710
|
+
lastHeard = Date.now();
|
|
5539
5711
|
ws.send(JSON.stringify({ t: "hello", id: me.id, name: me.name, tz: me.tz, color: me.color }));
|
|
5712
|
+
if (youVoted && skipTrackId) ws.send(JSON.stringify({ t: "voteSkip", trackId: skipTrackId }));
|
|
5540
5713
|
});
|
|
5541
5714
|
ws.on("message", (raw) => {
|
|
5715
|
+
lastHeard = Date.now();
|
|
5542
5716
|
let m;
|
|
5543
5717
|
try {
|
|
5544
5718
|
m = JSON.parse(raw.toString());
|
|
@@ -5548,6 +5722,7 @@ function connectStation(opts) {
|
|
|
5548
5722
|
if (m && m.t === "radioState") applyStation(m);
|
|
5549
5723
|
});
|
|
5550
5724
|
ws.on("close", () => {
|
|
5725
|
+
clearInterval(beat);
|
|
5551
5726
|
if (stationWs === ws) {
|
|
5552
5727
|
state = { ...state, connected: false };
|
|
5553
5728
|
scheduleReconnect(opts);
|
|
@@ -5555,7 +5730,7 @@ function connectStation(opts) {
|
|
|
5555
5730
|
});
|
|
5556
5731
|
ws.on("error", () => {
|
|
5557
5732
|
try {
|
|
5558
|
-
ws.
|
|
5733
|
+
ws.terminate();
|
|
5559
5734
|
} catch {
|
|
5560
5735
|
}
|
|
5561
5736
|
});
|
|
@@ -5578,8 +5753,14 @@ function applyStation(m) {
|
|
|
5578
5753
|
skipVotes = m.skip.votes ?? 0;
|
|
5579
5754
|
skipNeeded = m.skip.needed ?? 1;
|
|
5580
5755
|
if (m.skip.trackId && m.skip.trackId !== skipTrackId) {
|
|
5756
|
+
if (youVoted && skipTrackId) {
|
|
5757
|
+
skipNote = "skipped \u2713";
|
|
5758
|
+
skipNoteUntil = Date.now() + 4e3;
|
|
5759
|
+
}
|
|
5581
5760
|
skipTrackId = m.skip.trackId;
|
|
5582
5761
|
youVoted = false;
|
|
5762
|
+
} else if (Array.isArray(m.skip.voters) && Date.now() - lastToggleAt > 2e3) {
|
|
5763
|
+
youVoted = m.skip.voters.includes(me.id);
|
|
5583
5764
|
}
|
|
5584
5765
|
}
|
|
5585
5766
|
}
|
|
@@ -5606,9 +5787,16 @@ function startBackground(opts) {
|
|
|
5606
5787
|
} else {
|
|
5607
5788
|
visuals = new Showrunner(ALL_SCENES);
|
|
5608
5789
|
}
|
|
5609
|
-
if (tools.ffplayPath) {
|
|
5790
|
+
if (tools.ffmpegPath && tools.ffplayPath) {
|
|
5610
5791
|
audio = new SyncedAudio();
|
|
5611
|
-
audio.start({
|
|
5792
|
+
audio.start({
|
|
5793
|
+
ffplayPath: tools.ffplayPath,
|
|
5794
|
+
ffmpegPath: tools.ffmpegPath,
|
|
5795
|
+
schedule: state.schedule,
|
|
5796
|
+
serverNow,
|
|
5797
|
+
onRespawn: (reason, track, offsetMs) => rlog(`audio respawn: ${reason} \xB7 ${track.id} @ ${(offsetMs / 1e3).toFixed(1)}s`),
|
|
5798
|
+
onSink: (kind) => rlog(`audio sink: ${kind}`)
|
|
5799
|
+
});
|
|
5612
5800
|
}
|
|
5613
5801
|
}
|
|
5614
5802
|
function makeScreen() {
|
|
@@ -5634,9 +5822,18 @@ var skipVotes = 0;
|
|
|
5634
5822
|
var skipNeeded = 1;
|
|
5635
5823
|
var youVoted = false;
|
|
5636
5824
|
var skipTrackId = "";
|
|
5825
|
+
var skipNote = "";
|
|
5826
|
+
var skipNoteUntil = 0;
|
|
5827
|
+
var lastToggleAt = 0;
|
|
5637
5828
|
function toggleSkip() {
|
|
5829
|
+
if (!stationWs || stationWs.readyState !== WebSocket.OPEN) {
|
|
5830
|
+
skipNote = "offline, retrying";
|
|
5831
|
+
skipNoteUntil = Date.now() + 3e3;
|
|
5832
|
+
return;
|
|
5833
|
+
}
|
|
5638
5834
|
youVoted = !youVoted;
|
|
5639
|
-
|
|
5835
|
+
lastToggleAt = Date.now();
|
|
5836
|
+
stationWs.send(JSON.stringify({ t: youVoted ? "voteSkip" : "unvoteSkip", trackId: skipTrackId || void 0 }));
|
|
5640
5837
|
}
|
|
5641
5838
|
function drawControls() {
|
|
5642
5839
|
const w = screen.width, y = 1;
|
|
@@ -5648,7 +5845,7 @@ function drawControls() {
|
|
|
5648
5845
|
const vol = audio ? audio.isMuted ? "MUTED" : `vol ${audio.volume}%` : "no audio";
|
|
5649
5846
|
put2(1, `\u266A ${vol} [m] mute [+/-] volume [q] quit`);
|
|
5650
5847
|
const met = skipVotes >= skipNeeded && skipVotes > 0;
|
|
5651
|
-
const label = youVoted ? "[s] voted \u2713" : "[s] vote skip";
|
|
5848
|
+
const label = Date.now() < skipNoteUntil ? `[s] ${skipNote}` : youVoted ? "[s] voted \u2713" : "[s] vote skip";
|
|
5652
5849
|
const right = `${label} skip ${skipVotes}/${skipNeeded} `;
|
|
5653
5850
|
put2(w - right.length, right, met ? C_GOOD : 231, youVoted);
|
|
5654
5851
|
}
|
|
@@ -5683,7 +5880,7 @@ var renderErrs = 0;
|
|
|
5683
5880
|
function renderFrame() {
|
|
5684
5881
|
if (!running) return;
|
|
5685
5882
|
try {
|
|
5686
|
-
const tMs =
|
|
5883
|
+
const tMs = serverNow() - VISUAL_EPOCH;
|
|
5687
5884
|
if (visuals) {
|
|
5688
5885
|
lastFrame = visuals.frame(tMs, screen.width, screen.height);
|
|
5689
5886
|
frameW = screen.width;
|
|
@@ -5750,7 +5947,7 @@ function runRadio(opts) {
|
|
|
5750
5947
|
const detail = err instanceof Error ? err.stack || err.message : String(err);
|
|
5751
5948
|
rlog(`CRASH: ${detail}`);
|
|
5752
5949
|
teardown();
|
|
5753
|
-
console.error("\naccelr8 radio crashed:\n" + detail + "\n\nlog: " + path.join(
|
|
5950
|
+
console.error("\naccelr8 radio crashed:\n" + detail + "\n\nlog: " + path.join(accelr8Dir(), "radio.log") + "\n(press any key to close)");
|
|
5754
5951
|
try {
|
|
5755
5952
|
process.stdin.setRawMode?.(true);
|
|
5756
5953
|
process.stdin.resume();
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
AuthNetworkError,
|
|
4
|
+
approveSignIn,
|
|
5
|
+
approveUrl,
|
|
6
|
+
deviceLogin,
|
|
7
|
+
ensureSignedIn,
|
|
8
|
+
getAccessToken,
|
|
9
|
+
httpBase,
|
|
10
|
+
legacyProofs,
|
|
11
|
+
logout,
|
|
12
|
+
olderServerMessage,
|
|
13
|
+
pendingSignIn,
|
|
14
|
+
refreshCredentials,
|
|
15
|
+
whoami
|
|
16
|
+
} from "./chunk-3OTVRBTJ.js";
|
|
17
|
+
import "./chunk-FVC2UPMV.js";
|
|
18
|
+
import "./chunk-5QXGOLQ3.js";
|
|
19
|
+
import "./chunk-62O2F73J.js";
|
|
20
|
+
export {
|
|
21
|
+
AuthNetworkError,
|
|
22
|
+
approveSignIn,
|
|
23
|
+
approveUrl,
|
|
24
|
+
deviceLogin,
|
|
25
|
+
ensureSignedIn,
|
|
26
|
+
getAccessToken,
|
|
27
|
+
httpBase,
|
|
28
|
+
legacyProofs,
|
|
29
|
+
logout,
|
|
30
|
+
olderServerMessage,
|
|
31
|
+
pendingSignIn,
|
|
32
|
+
refreshCredentials,
|
|
33
|
+
whoami
|
|
34
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
DIFFERENT_NETWORK,
|
|
4
|
+
ago,
|
|
5
|
+
confirmPage,
|
|
6
|
+
deniedPage,
|
|
7
|
+
donePage,
|
|
8
|
+
enterCodePage,
|
|
9
|
+
esc,
|
|
10
|
+
platformName
|
|
11
|
+
} from "./chunk-RZ4OBBLX.js";
|
|
12
|
+
export {
|
|
13
|
+
DIFFERENT_NETWORK,
|
|
14
|
+
ago,
|
|
15
|
+
confirmPage,
|
|
16
|
+
deniedPage,
|
|
17
|
+
donePage,
|
|
18
|
+
enterCodePage,
|
|
19
|
+
esc,
|
|
20
|
+
platformName
|
|
21
|
+
};
|