accelr8 3.7.1 → 3.8.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.
@@ -5,11 +5,15 @@ import {
5
5
  tzToCoords
6
6
  } from "./chunk-HY6ULYGH.js";
7
7
  import {
8
- DEFAULT_SERVER
9
- } from "./chunk-P3CEHEKI.js";
8
+ DEFAULT_SERVER,
9
+ VERSION
10
+ } from "./chunk-UTWXIX5N.js";
10
11
 
11
12
  // src/radio/app.ts
12
13
  import tk from "terminal-kit";
14
+ import fs from "node:fs";
15
+ import os from "node:os";
16
+ import path from "node:path";
13
17
  import { WebSocket } from "ws";
14
18
 
15
19
  // src/radio/asciivideo.ts
@@ -5463,6 +5467,15 @@ var SyncedAudio = class {
5463
5467
  };
5464
5468
 
5465
5469
  // src/radio/app.ts
5470
+ function rlog(msg) {
5471
+ try {
5472
+ const dir = path.join(os.homedir(), ".accelr8");
5473
+ fs.mkdirSync(dir, { recursive: true });
5474
+ fs.appendFileSync(path.join(dir, "radio.log"), `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
5475
+ `);
5476
+ } catch {
5477
+ }
5478
+ }
5466
5479
  var term = tk.terminal;
5467
5480
  var ScreenBuffer = tk.ScreenBuffer;
5468
5481
  var RENDER_FPS = 20;
@@ -5561,6 +5574,14 @@ function applyStation(m) {
5561
5574
  const broadcast = m.broadcast || null;
5562
5575
  state = { ...state, schedule: sched, listeners, broadcast, connected: true };
5563
5576
  if (audio) audio.setSchedule(sched);
5577
+ if (m.skip) {
5578
+ skipVotes = m.skip.votes ?? 0;
5579
+ skipNeeded = m.skip.needed ?? 1;
5580
+ if (m.skip.trackId && m.skip.trackId !== skipTrackId) {
5581
+ skipTrackId = m.skip.trackId;
5582
+ youVoted = false;
5583
+ }
5584
+ }
5564
5585
  }
5565
5586
  function startBackground(opts) {
5566
5587
  const tools = detectVoiceTools();
@@ -5594,42 +5615,44 @@ function makeScreen() {
5594
5615
  screen = new ScreenBuffer({ dst: term, width: W(), height: H() });
5595
5616
  video?.resize(W(), H());
5596
5617
  }
5597
- var volMsg = "";
5598
- var volMsgUntil = 0;
5599
- function flashVol() {
5600
- if (!audio) return;
5601
- const v = audio.volume, bars = 12, on = Math.round(v / 100 * bars);
5602
- volMsg = audio.isMuted ? "\u266B muted (m to unmute)" : `\u266B ${"\u2588".repeat(on)}${"\u2591".repeat(bars - on)} ${v}%`;
5603
- volMsgUntil = Date.now() + 1800;
5604
- }
5605
5618
  function volUp() {
5606
5619
  if (audio) {
5607
5620
  if (audio.isMuted) audio.toggleMute();
5608
5621
  audio.setVolume(audio.volume + 10);
5609
- flashVol();
5610
5622
  }
5611
5623
  }
5612
5624
  function volDown() {
5613
5625
  if (audio) {
5614
5626
  if (audio.isMuted) audio.toggleMute();
5615
5627
  audio.setVolume(audio.volume - 10);
5616
- flashVol();
5617
5628
  }
5618
5629
  }
5619
5630
  function muteToggle() {
5620
- if (audio) {
5621
- audio.toggleMute();
5622
- flashVol();
5623
- }
5624
- }
5625
- function drawVolToast() {
5626
- if (Date.now() >= volMsgUntil || !volMsg) return;
5627
- const y = Math.max(1, Math.floor(screen.height / 3));
5628
- const s = ` ${volMsg} `;
5629
- const x = Math.max(0, Math.floor((screen.width - s.length) / 2));
5630
- for (let i = 0; i < s.length && x + i < screen.width; i++)
5631
- screen.put({ x: x + i, y, attr: { color: 231, bgColor: 16, bold: true }, dx: 1, dy: 0, wrap: false }, s[i]);
5632
- }
5631
+ if (audio) audio.toggleMute();
5632
+ }
5633
+ var skipVotes = 0;
5634
+ var skipNeeded = 1;
5635
+ var youVoted = false;
5636
+ var skipTrackId = "";
5637
+ function toggleSkip() {
5638
+ youVoted = !youVoted;
5639
+ if (stationWs && stationWs.readyState === 1) stationWs.send(JSON.stringify({ t: youVoted ? "voteSkip" : "unvoteSkip" }));
5640
+ }
5641
+ function drawControls() {
5642
+ const w = screen.width, y = 1;
5643
+ if (screen.height < 3) return;
5644
+ for (let x = 0; x < w; x++) screen.put({ x, y, attr: { color: 231, bgColor: 234 }, dx: 1, dy: 0, wrap: false }, " ");
5645
+ const put2 = (x, s, color = 231, bold = false) => {
5646
+ for (let i = 0; i < s.length && x + i < w; i++) if (x + i >= 0) screen.put({ x: x + i, y, attr: { color, bgColor: 234, bold }, dx: 1, dy: 0, wrap: false }, s[i]);
5647
+ };
5648
+ const vol = audio ? audio.isMuted ? "MUTED" : `vol ${audio.volume}%` : "no audio";
5649
+ put2(1, `\u266A ${vol} [m] mute [+/-] volume [q] quit`);
5650
+ const met = skipVotes >= skipNeeded && skipVotes > 0;
5651
+ const label = youVoted ? "[s] voted \u2713" : "[s] vote skip";
5652
+ const right = `${label} skip ${skipVotes}/${skipNeeded} `;
5653
+ put2(w - right.length, right, met ? C_GOOD : 231, youVoted);
5654
+ }
5655
+ var C_GOOD = 46;
5633
5656
  var BG = 16;
5634
5657
  function blitVideo() {
5635
5658
  const w = screen.width, h = screen.height;
@@ -5656,21 +5679,26 @@ function blitOverlay(cells) {
5656
5679
  screen.put({ x: c.x, y: c.y, attr: { color: c.color ?? 231, bgColor: bg, bold: !!c.bold }, dx: 1, dy: 0, wrap: false }, c.ch || " ");
5657
5680
  }
5658
5681
  }
5682
+ var renderErrs = 0;
5659
5683
  function renderFrame() {
5660
5684
  if (!running) return;
5661
- const tMs = Date.now() - startedAt;
5662
- if (visuals) {
5663
- lastFrame = visuals.frame(tMs, screen.width, screen.height);
5664
- frameW = screen.width;
5665
- frameH = screen.height;
5666
- }
5667
- const serverNowMs = serverNow();
5668
- state = { ...state, serverNowMs, nowPlaying: nowPlaying(state.schedule, serverNowMs), nowShowing: nowShowing(state.schedule, serverNowMs) };
5669
- blitVideo();
5670
- const cells = renderOverlay({ w: screen.width, h: screen.height, tMs, state });
5671
- blitOverlay(cells);
5672
- drawVolToast();
5673
- if (screen.dst) screen.draw({ delta: true });
5685
+ try {
5686
+ const tMs = Date.now() - startedAt;
5687
+ if (visuals) {
5688
+ lastFrame = visuals.frame(tMs, screen.width, screen.height);
5689
+ frameW = screen.width;
5690
+ frameH = screen.height;
5691
+ }
5692
+ const serverNowMs = serverNow();
5693
+ state = { ...state, serverNowMs, nowPlaying: nowPlaying(state.schedule, serverNowMs), nowShowing: nowShowing(state.schedule, serverNowMs) };
5694
+ blitVideo();
5695
+ const cells = renderOverlay({ w: screen.width, h: screen.height, tMs, state });
5696
+ blitOverlay(cells);
5697
+ drawControls();
5698
+ if (screen.dst) screen.draw({ delta: true });
5699
+ } catch (e) {
5700
+ if (renderErrs++ < 5) rlog(`renderFrame error @${screen?.width}x${screen?.height}: ${e?.stack || e?.message || e}`);
5701
+ }
5674
5702
  }
5675
5703
  function teardown() {
5676
5704
  running = false;
@@ -5705,33 +5733,56 @@ function quit() {
5705
5733
  process.exit(0);
5706
5734
  }
5707
5735
  function runRadio(opts) {
5708
- const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
5736
+ let tz = "UTC";
5737
+ try {
5738
+ tz = Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
5739
+ } catch {
5740
+ }
5709
5741
  me = { id: "me_" + Math.random().toString(36).slice(2, 10), name: opts.name || "you", tz, color: 39 };
5710
5742
  state = baseState(me);
5743
+ try {
5744
+ const tools = detectVoiceTools();
5745
+ rlog(`\u2500\u2500 radio start v${VERSION} \xB7 ${process.platform} \xB7 node ${process.version} \xB7 term ${process.stdout.columns}x${process.stdout.rows} \xB7 TERM=${process.env.TERM} \xB7 ffmpeg=${!!tools.ffmpegPath} ffplay=${!!tools.ffplayPath}`);
5746
+ } catch (e) {
5747
+ rlog(`start probe failed: ${e?.stack || e}`);
5748
+ }
5711
5749
  const die = (err) => {
5750
+ const detail = err instanceof Error ? err.stack || err.message : String(err);
5751
+ rlog(`CRASH: ${detail}`);
5712
5752
  teardown();
5713
- console.error("accelr8 radio crashed:", err instanceof Error ? err.stack || err.message : err);
5714
- process.exit(1);
5753
+ console.error("\naccelr8 radio crashed:\n" + detail + "\n\nlog: " + path.join(os.homedir(), ".accelr8", "radio.log") + "\n(press any key to close)");
5754
+ try {
5755
+ process.stdin.setRawMode?.(true);
5756
+ process.stdin.resume();
5757
+ process.stdin.once("data", () => process.exit(1));
5758
+ setTimeout(() => process.exit(1), 3e4);
5759
+ } catch {
5760
+ process.exit(1);
5761
+ }
5715
5762
  };
5716
5763
  process.on("uncaughtException", die);
5717
5764
  process.on("unhandledRejection", die);
5718
- term.fullscreen(true);
5719
- term.hideCursor();
5720
- term.grabInput(true);
5721
- makeScreen();
5722
- running = true;
5723
- startBackground(opts);
5724
- connectStation(opts);
5765
+ try {
5766
+ term.fullscreen(true);
5767
+ term.hideCursor();
5768
+ term.grabInput(true);
5769
+ makeScreen();
5770
+ running = true;
5771
+ startBackground(opts);
5772
+ connectStation(opts);
5773
+ } catch (e) {
5774
+ return die(e);
5775
+ }
5725
5776
  term.on("key", (name) => {
5726
5777
  if (name === "CTRL_C" || name === "q" || name === "ESCAPE") return quit();
5727
5778
  if (name === "m") return muteToggle();
5779
+ if (name === "s") return toggleSkip();
5728
5780
  if (name === "+" || name === "=" || name === "UP") return volUp();
5729
5781
  if (name === "-" || name === "_" || name === "DOWN") return volDown();
5730
5782
  });
5731
5783
  term.on("resize", () => makeScreen());
5732
5784
  process.on("SIGINT", quit);
5733
5785
  process.on("SIGTERM", quit);
5734
- flashVol();
5735
5786
  renderTimer = setInterval(renderFrame, Math.round(1e3 / RENDER_FPS));
5736
5787
  renderFrame();
5737
5788
  }
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/protocol.ts
4
- var VERSION = true ? "3.7.1" : "3.7.1";
4
+ var VERSION = true ? "3.8.1" : "3.8.1";
5
5
  var PROTO = 3;
6
6
  var PORT = 8787;
7
7
  var DEFAULT_SERVER = "wss://accelr8-canvas.patonchain.workers.dev";
@@ -32,7 +32,7 @@ import {
32
32
  VERSION,
33
33
  decode,
34
34
  encode
35
- } from "./chunk-P3CEHEKI.js";
35
+ } from "./chunk-UTWXIX5N.js";
36
36
 
37
37
  // src/client.ts
38
38
  import tk from "terminal-kit";
@@ -6,7 +6,7 @@ import {
6
6
  LIMITS,
7
7
  PORT,
8
8
  PROTO
9
- } from "./chunk-P3CEHEKI.js";
9
+ } from "./chunk-UTWXIX5N.js";
10
10
 
11
11
  // src/server.ts
12
12
  import { WebSocketServer, WebSocket } from "ws";
package/dist/cli.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  DEFAULT_SERVER,
4
4
  PORT,
5
5
  VERSION
6
- } from "./chunks/chunk-P3CEHEKI.js";
6
+ } from "./chunks/chunk-UTWXIX5N.js";
7
7
 
8
8
  // src/cli.ts
9
9
  var HELP = `
@@ -53,7 +53,7 @@ async function main() {
53
53
  console.error(`invalid port: ${argVal(args, "--port")}`);
54
54
  process.exit(1);
55
55
  }
56
- const { runServer } = await import("./chunks/server-NZ5JUOCF.js");
56
+ const { runServer } = await import("./chunks/server-V56IHFUH.js");
57
57
  runServer(port);
58
58
  return;
59
59
  }
@@ -67,7 +67,7 @@ async function main() {
67
67
  console.error("accelr8 radio needs an interactive terminal (TTY).");
68
68
  process.exit(1);
69
69
  }
70
- const { runRadio } = await import("./chunks/app-EOVXVIEE.js");
70
+ const { runRadio } = await import("./chunks/app-7YGYMFSZ.js");
71
71
  runRadio({
72
72
  name: argVal(args, "--name"),
73
73
  server: argVal(args, "--server"),
@@ -90,7 +90,7 @@ async function main() {
90
90
  const { cachedUpdate, refreshUpdateCache } = await import("./chunks/update-UEPNGB6Y.js");
91
91
  refreshUpdateCache();
92
92
  const update = cachedUpdate(VERSION);
93
- const { runClient } = await import("./chunks/client-OIVTEENE.js");
93
+ const { runClient } = await import("./chunks/client-3RLQY3ZB.js");
94
94
  runClient({
95
95
  name: argVal(args, "--name"),
96
96
  server: argVal(args, "--server"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "accelr8",
3
- "version": "3.7.1",
3
+ "version": "3.8.1",
4
4
  "description": "Collaborative ascii canvas in your terminal. Real multiplayer, live cursors, mouse-driven TUI.",
5
5
  "type": "module",
6
6
  "license": "ISC",