streamer-node 1.0.30 → 1.0.37
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/dist/package.json +6 -2
- package/dist/scripts/README.md +670 -0
- package/dist/scripts/install-streamer-node.sh +117 -0
- package/dist/scripts/reframe_ball.py +354 -0
- package/dist/src/application/interfaces/StartStreamUseCase.types.d.ts +3 -1
- package/dist/src/application/interfaces/StartStreamUseCase.types.d.ts.map +1 -1
- package/dist/src/application/interfaces/StartStreamUseCase.types.js.map +1 -1
- package/dist/src/application/services/HttpClient.d.ts +21 -0
- package/dist/src/application/services/HttpClient.d.ts.map +1 -1
- package/dist/src/application/services/HttpClient.js +37 -5
- package/dist/src/application/services/HttpClient.js.map +1 -1
- package/dist/src/application/services/StreamManagerService.d.ts.map +1 -1
- package/dist/src/application/services/StreamManagerService.js +2 -0
- package/dist/src/application/services/StreamManagerService.js.map +1 -1
- package/dist/src/application/use-cases/CaptureHighlightUseCase.d.ts +52 -0
- package/dist/src/application/use-cases/CaptureHighlightUseCase.d.ts.map +1 -0
- package/dist/src/application/use-cases/CaptureHighlightUseCase.js +169 -0
- package/dist/src/application/use-cases/CaptureHighlightUseCase.js.map +1 -0
- package/dist/src/application/use-cases/StartStreamUseCase.d.ts +9 -1
- package/dist/src/application/use-cases/StartStreamUseCase.d.ts.map +1 -1
- package/dist/src/application/use-cases/StartStreamUseCase.js +125 -2
- package/dist/src/application/use-cases/StartStreamUseCase.js.map +1 -1
- package/dist/src/application/use-cases/StopStreamUseCase.d.ts +5 -1
- package/dist/src/application/use-cases/StopStreamUseCase.d.ts.map +1 -1
- package/dist/src/application/use-cases/StopStreamUseCase.js +7 -1
- package/dist/src/application/use-cases/StopStreamUseCase.js.map +1 -1
- package/dist/src/domain/events/StreamEvent.d.ts +5 -0
- package/dist/src/domain/events/StreamEvent.d.ts.map +1 -1
- package/dist/src/domain/services/BallReframer.d.ts +35 -0
- package/dist/src/domain/services/BallReframer.d.ts.map +1 -0
- package/dist/src/domain/services/BallReframer.js +3 -0
- package/dist/src/domain/services/BallReframer.js.map +1 -0
- package/dist/src/domain/services/FFmpegService.d.ts +6 -2
- package/dist/src/domain/services/FFmpegService.d.ts.map +1 -1
- package/dist/src/domain/services/HighlightSignalSource.d.ts +113 -0
- package/dist/src/domain/services/HighlightSignalSource.d.ts.map +1 -0
- package/dist/src/domain/services/HighlightSignalSource.js +3 -0
- package/dist/src/domain/services/HighlightSignalSource.js.map +1 -0
- package/dist/src/domain/services/SSEService.d.ts +1 -0
- package/dist/src/domain/services/SSEService.d.ts.map +1 -1
- package/dist/src/infrastructure/config/Config.d.ts +32 -0
- package/dist/src/infrastructure/config/Config.d.ts.map +1 -1
- package/dist/src/infrastructure/config/Config.js +60 -0
- package/dist/src/infrastructure/config/Config.js.map +1 -1
- package/dist/src/infrastructure/listeners/SupabaseListener.d.ts +5 -0
- package/dist/src/infrastructure/listeners/SupabaseListener.d.ts.map +1 -1
- package/dist/src/infrastructure/listeners/SupabaseListener.js +43 -5
- package/dist/src/infrastructure/listeners/SupabaseListener.js.map +1 -1
- package/dist/src/infrastructure/logging/LogShippingService.d.ts +3 -1
- package/dist/src/infrastructure/logging/LogShippingService.d.ts.map +1 -1
- package/dist/src/infrastructure/logging/LogShippingService.js +13 -4
- package/dist/src/infrastructure/logging/LogShippingService.js.map +1 -1
- package/dist/src/infrastructure/logging/RemoteLogger.d.ts +1 -0
- package/dist/src/infrastructure/logging/RemoteLogger.d.ts.map +1 -1
- package/dist/src/infrastructure/logging/RemoteLogger.js +1 -1
- package/dist/src/infrastructure/logging/RemoteLogger.js.map +1 -1
- package/dist/src/infrastructure/services/AdDownloaderService.d.ts +85 -0
- package/dist/src/infrastructure/services/AdDownloaderService.d.ts.map +1 -0
- package/dist/src/infrastructure/services/AdDownloaderService.js +530 -0
- package/dist/src/infrastructure/services/AdDownloaderService.js.map +1 -0
- package/dist/src/infrastructure/services/AdRotator.d.ts +63 -0
- package/dist/src/infrastructure/services/AdRotator.d.ts.map +1 -0
- package/dist/src/infrastructure/services/AdRotator.js +223 -0
- package/dist/src/infrastructure/services/AdRotator.js.map +1 -0
- package/dist/src/infrastructure/services/HighlightBufferManager.d.ts +65 -0
- package/dist/src/infrastructure/services/HighlightBufferManager.d.ts.map +1 -0
- package/dist/src/infrastructure/services/HighlightBufferManager.js +203 -0
- package/dist/src/infrastructure/services/HighlightBufferManager.js.map +1 -0
- package/dist/src/infrastructure/services/HighlightBufferRegistry.d.ts +23 -0
- package/dist/src/infrastructure/services/HighlightBufferRegistry.d.ts.map +1 -0
- package/dist/src/infrastructure/services/HighlightBufferRegistry.js +44 -0
- package/dist/src/infrastructure/services/HighlightBufferRegistry.js.map +1 -0
- package/dist/src/infrastructure/services/HighlightExtractorService.d.ts +65 -0
- package/dist/src/infrastructure/services/HighlightExtractorService.d.ts.map +1 -0
- package/dist/src/infrastructure/services/HighlightExtractorService.js +264 -0
- package/dist/src/infrastructure/services/HighlightExtractorService.js.map +1 -0
- package/dist/src/infrastructure/services/HighlightRendererService.d.ts +38 -0
- package/dist/src/infrastructure/services/HighlightRendererService.d.ts.map +1 -0
- package/dist/src/infrastructure/services/HighlightRendererService.js +182 -0
- package/dist/src/infrastructure/services/HighlightRendererService.js.map +1 -0
- package/dist/src/infrastructure/services/NodeFFmpegService.d.ts +6 -4
- package/dist/src/infrastructure/services/NodeFFmpegService.d.ts.map +1 -1
- package/dist/src/infrastructure/services/NodeFFmpegService.js +154 -32
- package/dist/src/infrastructure/services/NodeFFmpegService.js.map +1 -1
- package/dist/src/infrastructure/services/NodeSSEService.d.ts +8 -0
- package/dist/src/infrastructure/services/NodeSSEService.d.ts.map +1 -1
- package/dist/src/infrastructure/services/NodeSSEService.js +71 -27
- package/dist/src/infrastructure/services/NodeSSEService.js.map +1 -1
- package/dist/src/infrastructure/services/NullBallReframer.d.ts +11 -0
- package/dist/src/infrastructure/services/NullBallReframer.d.ts.map +1 -0
- package/dist/src/infrastructure/services/NullBallReframer.js +16 -0
- package/dist/src/infrastructure/services/NullBallReframer.js.map +1 -0
- package/dist/src/infrastructure/services/PythonBallReframer.d.ts +35 -0
- package/dist/src/infrastructure/services/PythonBallReframer.d.ts.map +1 -0
- package/dist/src/infrastructure/services/PythonBallReframer.js +238 -0
- package/dist/src/infrastructure/services/PythonBallReframer.js.map +1 -0
- package/dist/src/infrastructure/services/SerialHighlightListener.d.ts +45 -0
- package/dist/src/infrastructure/services/SerialHighlightListener.d.ts.map +1 -0
- package/dist/src/infrastructure/services/SerialHighlightListener.js +440 -0
- package/dist/src/infrastructure/services/SerialHighlightListener.js.map +1 -0
- package/dist/src/infrastructure/services/YouTubeUploadService.d.ts +36 -0
- package/dist/src/infrastructure/services/YouTubeUploadService.d.ts.map +1 -0
- package/dist/src/infrastructure/services/YouTubeUploadService.js +402 -0
- package/dist/src/infrastructure/services/YouTubeUploadService.js.map +1 -0
- package/dist/src/infrastructure/utils/ResourceSampler.d.ts +47 -0
- package/dist/src/infrastructure/utils/ResourceSampler.d.ts.map +1 -0
- package/dist/src/infrastructure/utils/ResourceSampler.js +102 -0
- package/dist/src/infrastructure/utils/ResourceSampler.js.map +1 -0
- package/dist/src/infrastructure/utils/lowPriority.d.ts +29 -0
- package/dist/src/infrastructure/utils/lowPriority.d.ts.map +1 -0
- package/dist/src/infrastructure/utils/lowPriority.js +31 -0
- package/dist/src/infrastructure/utils/lowPriority.js.map +1 -0
- package/dist/src/infrastructure/utils/paths.d.ts +27 -0
- package/dist/src/infrastructure/utils/paths.d.ts.map +1 -0
- package/dist/src/infrastructure/utils/paths.js +69 -0
- package/dist/src/infrastructure/utils/paths.js.map +1 -0
- package/dist/src/infrastructure/utils/spawnToFile.d.ts +20 -0
- package/dist/src/infrastructure/utils/spawnToFile.d.ts.map +1 -0
- package/dist/src/infrastructure/utils/spawnToFile.js +98 -0
- package/dist/src/infrastructure/utils/spawnToFile.js.map +1 -0
- package/dist/src/main.js +228 -2
- package/dist/src/main.js.map +1 -1
- package/package.json +6 -2
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.SerialHighlightListener = void 0;
|
|
37
|
+
const events_1 = require("events");
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
/**
|
|
40
|
+
* Reads the ESP32's USB-serial output to detect whether the highlight hardware
|
|
41
|
+
* is attached and alive (presence detection). Presence drives automatic
|
|
42
|
+
* enablement of the highlight buffer — see HighlightSignalSource.
|
|
43
|
+
*
|
|
44
|
+
* ── IN SIMPLE WORDS ──
|
|
45
|
+
* The court ESP32 plugs into the box over USB and prints one JSON object per
|
|
46
|
+
* line — a heartbeat every 5s, score updates, logs, and a `button` packet when
|
|
47
|
+
* someone presses the physical highlight button. This class keeps the serial
|
|
48
|
+
* port open and notes the last time it heard a valid line. If we've heard the
|
|
49
|
+
* device recently, it's "present" and the box should record highlights; if the
|
|
50
|
+
* box has no ESP32, we never hear it and `isDevicePresent()` stays false.
|
|
51
|
+
*
|
|
52
|
+
* Protocol contract: docs/esp32/STREAMER_INTEGRATION.md (authoritative — the
|
|
53
|
+
* desk harness in lib/esp-serial-com is NOT the spec; it only pretty-prints
|
|
54
|
+
* three of the four types).
|
|
55
|
+
*
|
|
56
|
+
* ── BUSINESS RULES ──
|
|
57
|
+
* - Highlight capture is enabled per-box by HARDWARE PRESENCE (decision
|
|
58
|
+
* 2026-07-18): a box with the ESP32 attached records the buffer; one without
|
|
59
|
+
* never does. `isDevicePresent()` is that signal.
|
|
60
|
+
* - Presence is proven by the ESP32's own heartbeat, emitted every 5s
|
|
61
|
+
* unconditionally (needs no mesh, no court). Freshness window = 15s
|
|
62
|
+
* (tolerate 3 missed beats).
|
|
63
|
+
* - A highlight is a `{"type":"button","event":"press"}` packet. It comes from
|
|
64
|
+
* a SEPARATE ESP8266 to the scoreboard one, on its own power and mesh nodeId,
|
|
65
|
+
* provisioned with the same courtId — that shared court ID is what correlates
|
|
66
|
+
* a press with the court it happened on.
|
|
67
|
+
* - COURT FILTERING IS MANDATORY. All units share one set of mesh credentials,
|
|
68
|
+
* so at a venue with two courts in WiFi range this ESP32 relays the other
|
|
69
|
+
* court's traffic verbatim (the firmware does no filtering — by design; the
|
|
70
|
+
* spec puts it on the streamer). The courtId rides on HighlightSignal and is
|
|
71
|
+
* matched downstream before any clip is cut.
|
|
72
|
+
*
|
|
73
|
+
* ── WHY IT'S BUILT THIS WAY (change at your peril) ──
|
|
74
|
+
* - Presence is CONTENT-based (a recognized packet seen recently), not merely
|
|
75
|
+
* port-open-based: an open port proves nothing about what's on the other end,
|
|
76
|
+
* so trusting it would let an unrelated serial gadget switch on the buffer.
|
|
77
|
+
* Content-gating means a wrong/other device reads as absent (no false
|
|
78
|
+
* positive) and we don't need the ESP32's USB vendor/product id (unconfirmed)
|
|
79
|
+
* to identify it. The ONE bounded exception is a short post-open grace (see
|
|
80
|
+
* OPEN_GRACE_MS) that bridges the gap before the first heartbeat on a fresh
|
|
81
|
+
* boot; it expires quickly and then content-gating governs.
|
|
82
|
+
* - The native `serialport` module is loaded defensively (lazy require in a
|
|
83
|
+
* try/catch). If it can't load on a given box, highlights just stay off
|
|
84
|
+
* forever — the live streaming app must never fail to boot over this.
|
|
85
|
+
*
|
|
86
|
+
* ── DO NOT ──
|
|
87
|
+
* - Do NOT make `isDevicePresent()` throw or do I/O — it's on the stream-start
|
|
88
|
+
* hot path. It is a couple of timestamp comparisons.
|
|
89
|
+
* - Do NOT treat port-open as *durable* presence (only the bounded grace).
|
|
90
|
+
* - Do NOT let a serial error/disconnect crash the process — port AND parser
|
|
91
|
+
* errors are handled and route to a backoff reconnect (mirrors NodeSSEService).
|
|
92
|
+
*/
|
|
93
|
+
// The ESP32 heartbeats every 5s (esp32-leader.ino). Allow ~3 missed beats
|
|
94
|
+
// before declaring the device gone, so a single dropped line doesn't flap.
|
|
95
|
+
const PRESENCE_FRESHNESS_MS = 15000;
|
|
96
|
+
// Bounded startup grace: a freshly-opened matched device counts as present for
|
|
97
|
+
// just over one heartbeat interval, to bridge the window before the first
|
|
98
|
+
// heartbeat on a cold boot. After this, only real traffic keeps it present.
|
|
99
|
+
const OPEN_GRACE_MS = 7000;
|
|
100
|
+
// Reconnect backoff bounds (mirror NodeSSEService.scheduleRetry).
|
|
101
|
+
const RECONNECT_BASE_MS = 1000;
|
|
102
|
+
const RECONNECT_MAX_MS = 30000;
|
|
103
|
+
class SerialHighlightListener extends events_1.EventEmitter {
|
|
104
|
+
constructor(portPath, // "auto" or an explicit device path
|
|
105
|
+
baudRate, logger,
|
|
106
|
+
// Debug/test only (default off): force presence true, and/or fire a
|
|
107
|
+
// highlight whenever `triggerFile` appears on disk — so the pipeline can be
|
|
108
|
+
// exercised on a box without the ESP32 button hardware.
|
|
109
|
+
forcePresent = false, triggerFile = "") {
|
|
110
|
+
super();
|
|
111
|
+
this.portPath = portPath;
|
|
112
|
+
this.baudRate = baudRate;
|
|
113
|
+
this.logger = logger;
|
|
114
|
+
this.forcePresent = forcePresent;
|
|
115
|
+
this.triggerFile = triggerFile;
|
|
116
|
+
this.port = null;
|
|
117
|
+
this.parser = null;
|
|
118
|
+
this.lastTrafficAtMs = 0;
|
|
119
|
+
this.portOpenedAtMs = 0;
|
|
120
|
+
this.started = false;
|
|
121
|
+
this.reconnectTimer = null;
|
|
122
|
+
this.reconnectAttempts = 0;
|
|
123
|
+
this.triggerTimer = null;
|
|
124
|
+
// Native serial libs, lazily required in start(). Null when unavailable, in
|
|
125
|
+
// which case the listener is inert and isDevicePresent() stays false.
|
|
126
|
+
this.serialLib = null;
|
|
127
|
+
this.parserLib = null;
|
|
128
|
+
}
|
|
129
|
+
onHighlight(listener) {
|
|
130
|
+
this.on("highlight", listener);
|
|
131
|
+
}
|
|
132
|
+
onScore(listener) {
|
|
133
|
+
this.on("score", listener);
|
|
134
|
+
}
|
|
135
|
+
start() {
|
|
136
|
+
if (this.started)
|
|
137
|
+
return;
|
|
138
|
+
this.started = true;
|
|
139
|
+
this.startTriggerFileWatch();
|
|
140
|
+
try {
|
|
141
|
+
this.serialLib = require("serialport");
|
|
142
|
+
this.parserLib = require("@serialport/parser-readline");
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
this.logger.warn("serialport module unavailable; highlight hardware detection disabled", { error: error instanceof Error ? error.message : String(error) });
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
void this.openPort();
|
|
149
|
+
}
|
|
150
|
+
stop() {
|
|
151
|
+
this.started = false;
|
|
152
|
+
if (this.reconnectTimer) {
|
|
153
|
+
clearTimeout(this.reconnectTimer);
|
|
154
|
+
this.reconnectTimer = null;
|
|
155
|
+
}
|
|
156
|
+
if (this.triggerTimer) {
|
|
157
|
+
clearInterval(this.triggerTimer);
|
|
158
|
+
this.triggerTimer = null;
|
|
159
|
+
}
|
|
160
|
+
this.teardownPort();
|
|
161
|
+
this.lastTrafficAtMs = 0;
|
|
162
|
+
}
|
|
163
|
+
// Debug/test: when a trigger file path is configured, fire a highlight
|
|
164
|
+
// whenever that file appears (then remove it). Lets staging exercise the
|
|
165
|
+
// capture pipeline via `touch <file>` without the ESP32 button. No-op unless
|
|
166
|
+
// HIGHLIGHT_TRIGGER_FILE is set.
|
|
167
|
+
startTriggerFileWatch() {
|
|
168
|
+
if (!this.triggerFile)
|
|
169
|
+
return;
|
|
170
|
+
this.logger.info("Highlight manual trigger file watch enabled", {
|
|
171
|
+
triggerFile: this.triggerFile,
|
|
172
|
+
});
|
|
173
|
+
this.triggerTimer = setInterval(() => {
|
|
174
|
+
try {
|
|
175
|
+
if (!fs.existsSync(this.triggerFile))
|
|
176
|
+
return;
|
|
177
|
+
fs.unlinkSync(this.triggerFile);
|
|
178
|
+
this.logger.info("Highlight signal received (manual trigger file)");
|
|
179
|
+
this.emit("highlight", { receivedAtMs: Date.now() });
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
/* transient fs error; retry next tick */
|
|
183
|
+
}
|
|
184
|
+
}, 1000);
|
|
185
|
+
}
|
|
186
|
+
isDevicePresent() {
|
|
187
|
+
if (this.forcePresent)
|
|
188
|
+
return true; // debug/test override
|
|
189
|
+
const now = Date.now();
|
|
190
|
+
// Durable presence: recognized traffic within the freshness window.
|
|
191
|
+
if (now - this.lastTrafficAtMs <= PRESENCE_FRESHNESS_MS)
|
|
192
|
+
return true;
|
|
193
|
+
// Bounded startup grace: matched device just opened, first heartbeat not
|
|
194
|
+
// yet due. Expires into content-gating.
|
|
195
|
+
if (this.portOpenedAtMs && now - this.portOpenedAtMs <= OPEN_GRACE_MS) {
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
async openPort() {
|
|
201
|
+
if (!this.started || !this.serialLib || !this.parserLib)
|
|
202
|
+
return;
|
|
203
|
+
// Clear any lingering port/listeners before (re)opening so reconnects never
|
|
204
|
+
// accumulate orphaned SerialPort/parser objects and their listeners.
|
|
205
|
+
this.teardownPort();
|
|
206
|
+
let path = "";
|
|
207
|
+
try {
|
|
208
|
+
path = await this.resolvePortPath();
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
this.scheduleReconnect(`serial discovery failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (!path) {
|
|
215
|
+
this.scheduleReconnect("no candidate serial device found");
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
try {
|
|
219
|
+
const { SerialPort } = this.serialLib;
|
|
220
|
+
const { ReadlineParser } = this.parserLib;
|
|
221
|
+
const port = new SerialPort({
|
|
222
|
+
path,
|
|
223
|
+
baudRate: this.baudRate,
|
|
224
|
+
autoOpen: false,
|
|
225
|
+
});
|
|
226
|
+
port.open((err) => {
|
|
227
|
+
if (err) {
|
|
228
|
+
this.scheduleReconnect(`open ${path} failed: ${err.message}`);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
this.reconnectAttempts = 0;
|
|
232
|
+
this.portOpenedAtMs = Date.now(); // start the bounded presence grace
|
|
233
|
+
this.logger.info("Highlight serial port opened", { path });
|
|
234
|
+
});
|
|
235
|
+
// ESP32 uses println (\r\n); split on \n and trim the trailing \r.
|
|
236
|
+
const parser = port.pipe(new ReadlineParser({ delimiter: "\n" }));
|
|
237
|
+
parser.on("data", (line) => this.handleLine(String(line)));
|
|
238
|
+
// A parser-side stream error would otherwise be an unhandled 'error'
|
|
239
|
+
// event and crash the process — route it to the same reconnect path.
|
|
240
|
+
parser.on("error", (err) => {
|
|
241
|
+
this.logger.warn("Highlight serial parser error", {
|
|
242
|
+
error: err.message,
|
|
243
|
+
});
|
|
244
|
+
this.handlePortDown();
|
|
245
|
+
});
|
|
246
|
+
port.on("error", (err) => {
|
|
247
|
+
this.logger.warn("Highlight serial port error", { error: err.message });
|
|
248
|
+
this.handlePortDown();
|
|
249
|
+
});
|
|
250
|
+
port.on("close", () => this.handlePortDown());
|
|
251
|
+
this.port = port;
|
|
252
|
+
this.parser = parser;
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
this.scheduleReconnect(`serial open threw: ${error instanceof Error ? error.message : String(error)}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
handleLine(raw) {
|
|
259
|
+
// Trailing \r: the ESP32 uses Serial.println (CRLF) while we split on \n.
|
|
260
|
+
const line = raw.trim();
|
|
261
|
+
if (!line)
|
|
262
|
+
return;
|
|
263
|
+
// NOT every line is JSON. esp32-leader relays mesh messages it can't parse
|
|
264
|
+
// verbatim rather than dropping them, and boot-time chatter appears on the
|
|
265
|
+
// port too. A parse failure means "ignore this line" — never an error path,
|
|
266
|
+
// or the first line of ESP8266 boot noise takes the streamer down.
|
|
267
|
+
let msg;
|
|
268
|
+
try {
|
|
269
|
+
const parsed = JSON.parse(line);
|
|
270
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
271
|
+
return;
|
|
272
|
+
msg = parsed;
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
const type = msg.type;
|
|
278
|
+
if (typeof type !== "string")
|
|
279
|
+
return;
|
|
280
|
+
// PRESENCE: any line that parsed as JSON and carries a string `type` proves
|
|
281
|
+
// the USB link is alive. Deliberately NOT gated on courtId — the ESP32's own
|
|
282
|
+
// heartbeat is {"type":"heartbeat","source":"ESP32"} with no court (it is the
|
|
283
|
+
// local leader), so requiring one would leave presence false forever.
|
|
284
|
+
const now = Date.now();
|
|
285
|
+
const wasFresh = now - this.lastTrafficAtMs <= PRESENCE_FRESHNESS_MS;
|
|
286
|
+
this.lastTrafficAtMs = now;
|
|
287
|
+
this.reconnectAttempts = 0;
|
|
288
|
+
if (!wasFresh) {
|
|
289
|
+
this.logger.info("Highlight hardware detected (ESP32 serial traffic)", {
|
|
290
|
+
type,
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
// Switch on `type` and IGNORE unknown types — the protocol reserves the
|
|
294
|
+
// right to add more. Four exist today: score, button, heartbeat, log.
|
|
295
|
+
if (type === "score") {
|
|
296
|
+
this.handleScore(msg);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
if (type !== "button")
|
|
300
|
+
return;
|
|
301
|
+
const courtId = typeof msg.courtId === "string" ? msg.courtId : undefined;
|
|
302
|
+
const seq = typeof msg.seq === "number" ? msg.seq : undefined;
|
|
303
|
+
// Press edge only, debounced 50ms in firmware — one physical click is one
|
|
304
|
+
// message, so no debounce of our own is needed.
|
|
305
|
+
if (typeof msg.event === "string" && msg.event !== "press")
|
|
306
|
+
return;
|
|
307
|
+
// `seq` is monotonic per boot, +1 per press. A gap means the mesh broadcast
|
|
308
|
+
// was lost (best-effort, no retry); going backwards means the node rebooted.
|
|
309
|
+
// Log it as a delivery signal — do not try to recover missed presses.
|
|
310
|
+
if (seq !== undefined && this.lastButtonSeq !== undefined) {
|
|
311
|
+
if (seq > this.lastButtonSeq + 1) {
|
|
312
|
+
this.logger.warn("Highlight button presses lost in mesh transit", {
|
|
313
|
+
courtId,
|
|
314
|
+
expected: this.lastButtonSeq + 1,
|
|
315
|
+
got: seq,
|
|
316
|
+
missed: seq - this.lastButtonSeq - 1,
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
else if (seq < this.lastButtonSeq) {
|
|
320
|
+
this.logger.info("Highlight button node rebooted (seq reset)", {
|
|
321
|
+
courtId,
|
|
322
|
+
previous: this.lastButtonSeq,
|
|
323
|
+
got: seq,
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
if (seq !== undefined)
|
|
328
|
+
this.lastButtonSeq = seq;
|
|
329
|
+
this.logger.info("Highlight signal received (button press)", {
|
|
330
|
+
courtId,
|
|
331
|
+
seq,
|
|
332
|
+
});
|
|
333
|
+
this.emit("highlight", { receivedAtMs: now, courtId });
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* A `score` packet: the board's current state, forwarded verbatim.
|
|
337
|
+
*
|
|
338
|
+
* Score packets are the one type that carries NO `source` field, so anything
|
|
339
|
+
* keying on `source` being present would drop them — switch on `type` only.
|
|
340
|
+
* Values are passed through untouched: interpreting "AD" or mapping the mode
|
|
341
|
+
* is the consumer's job, not the transport's.
|
|
342
|
+
*/
|
|
343
|
+
handleScore(msg) {
|
|
344
|
+
const courtId = typeof msg.courtId === "string" ? msg.courtId : undefined;
|
|
345
|
+
// Without a court we cannot address the write, and at a multi-court venue we
|
|
346
|
+
// could not tell whose score it is — drop rather than guess.
|
|
347
|
+
if (!courtId)
|
|
348
|
+
return;
|
|
349
|
+
const scoreA = msg.scoreA;
|
|
350
|
+
const scoreB = msg.scoreB;
|
|
351
|
+
if (scoreA === undefined || scoreB === undefined)
|
|
352
|
+
return;
|
|
353
|
+
this.emit("score", {
|
|
354
|
+
courtId,
|
|
355
|
+
mode: typeof msg.mode === "string" ? msg.mode : undefined,
|
|
356
|
+
// Coerced with String() rather than assumed: the firmware sends strings,
|
|
357
|
+
// but a number here must not become "undefined" downstream.
|
|
358
|
+
scoreA: String(scoreA),
|
|
359
|
+
scoreB: String(scoreB),
|
|
360
|
+
gamesA: typeof msg.gamesA === "number" ? msg.gamesA : undefined,
|
|
361
|
+
gamesB: typeof msg.gamesB === "number" ? msg.gamesB : undefined,
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
async resolvePortPath() {
|
|
365
|
+
if (this.portPath && this.portPath !== "auto") {
|
|
366
|
+
return this.portPath;
|
|
367
|
+
}
|
|
368
|
+
// Auto-discovery: open ONLY an ESP32-looking device. Deliberately no "first
|
|
369
|
+
// available port" fallback — grabbing an arbitrary tty (e.g. a system debug
|
|
370
|
+
// console) could disturb unrelated hardware. If nothing matches, return ""
|
|
371
|
+
// so openPort() backs off and re-scans later (the ESP32 may be plugged in
|
|
372
|
+
// after boot).
|
|
373
|
+
//
|
|
374
|
+
// Match on MANUFACTURER, not product id: the boards ship with either a
|
|
375
|
+
// CP2102 (Silicon Labs) or a CH340 (wch.cn / QinHeng) bridge, so no single
|
|
376
|
+
// PID covers the fleet. Path matching alone is not enough — on a box that
|
|
377
|
+
// also has the ESP8266 debug cable or an Arduino FTDI attached, "first
|
|
378
|
+
// USB-ish tty" can open the wrong device and then sit there hearing
|
|
379
|
+
// nothing. Manufacturer is checked first for that reason.
|
|
380
|
+
const { SerialPort } = this.serialLib;
|
|
381
|
+
const ports = await SerialPort.list();
|
|
382
|
+
const isEsp32 = (p) => {
|
|
383
|
+
const mfr = p.manufacturer ?? "";
|
|
384
|
+
if (/silicon labs|wch\.cn|qinheng/i.test(mfr))
|
|
385
|
+
return true;
|
|
386
|
+
// Linux reports the bare USB vendor ID for the CH340 instead of a name.
|
|
387
|
+
if (p.vendorId?.toLowerCase() === "1a86")
|
|
388
|
+
return true;
|
|
389
|
+
return /usbserial|ttyusb|ttyacm|cu\.usb|tty\.usb/i.test(p.path);
|
|
390
|
+
};
|
|
391
|
+
// Prefer a manufacturer/vendor match over a bare path match.
|
|
392
|
+
const byIdentity = ports.find((p) => /silicon labs|wch\.cn|qinheng/i.test(p.manufacturer ?? "") ||
|
|
393
|
+
p.vendorId?.toLowerCase() === "1a86");
|
|
394
|
+
return (byIdentity ?? ports.find(isEsp32))?.path ?? "";
|
|
395
|
+
}
|
|
396
|
+
handlePortDown() {
|
|
397
|
+
this.teardownPort();
|
|
398
|
+
this.scheduleReconnect("serial port closed/errored");
|
|
399
|
+
}
|
|
400
|
+
// Remove listeners and close the current port/parser, resetting the grace.
|
|
401
|
+
// Idempotent and never throws.
|
|
402
|
+
teardownPort() {
|
|
403
|
+
if (this.parser) {
|
|
404
|
+
try {
|
|
405
|
+
this.parser.removeAllListeners();
|
|
406
|
+
}
|
|
407
|
+
catch {
|
|
408
|
+
/* ignore */
|
|
409
|
+
}
|
|
410
|
+
this.parser = null;
|
|
411
|
+
}
|
|
412
|
+
if (this.port) {
|
|
413
|
+
try {
|
|
414
|
+
this.port.removeAllListeners();
|
|
415
|
+
this.port.close(() => { });
|
|
416
|
+
}
|
|
417
|
+
catch {
|
|
418
|
+
/* already closing/closed */
|
|
419
|
+
}
|
|
420
|
+
this.port = null;
|
|
421
|
+
}
|
|
422
|
+
this.portOpenedAtMs = 0;
|
|
423
|
+
}
|
|
424
|
+
scheduleReconnect(reason) {
|
|
425
|
+
if (!this.started || this.reconnectTimer)
|
|
426
|
+
return;
|
|
427
|
+
this.reconnectAttempts = Math.min(this.reconnectAttempts + 1, 10);
|
|
428
|
+
const delay = Math.min(RECONNECT_BASE_MS * Math.pow(2, this.reconnectAttempts - 1), RECONNECT_MAX_MS);
|
|
429
|
+
this.logger.warn("Highlight serial reconnect scheduled", {
|
|
430
|
+
reason,
|
|
431
|
+
delayMs: delay,
|
|
432
|
+
});
|
|
433
|
+
this.reconnectTimer = setTimeout(() => {
|
|
434
|
+
this.reconnectTimer = null;
|
|
435
|
+
void this.openPort();
|
|
436
|
+
}, delay);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
exports.SerialHighlightListener = SerialHighlightListener;
|
|
440
|
+
//# sourceMappingURL=SerialHighlightListener.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SerialHighlightListener.js","sourceRoot":"","sources":["../../../../src/infrastructure/services/SerialHighlightListener.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Logger } from "../../application/interfaces/Logger";
|
|
2
|
+
export interface ReelUploadRequest {
|
|
3
|
+
filePath: string;
|
|
4
|
+
courtId: string;
|
|
5
|
+
streamKey: string;
|
|
6
|
+
title: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
preferShort?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare class YouTubeUploadService {
|
|
11
|
+
private readonly baseUrl;
|
|
12
|
+
private readonly groundId;
|
|
13
|
+
private readonly streamingApiKey;
|
|
14
|
+
private readonly logger;
|
|
15
|
+
constructor(baseUrl: string, groundId: string, streamingApiKey: string, logger: Logger);
|
|
16
|
+
/**
|
|
17
|
+
* Upload `filePath` for `courtId`. Returns the YouTube video id on success,
|
|
18
|
+
* or null on any failure (logged). Reports the outcome to the backend either
|
|
19
|
+
* way (so a device-reported failure is recorded).
|
|
20
|
+
*/
|
|
21
|
+
upload(req: ReelUploadRequest): Promise<string | null>;
|
|
22
|
+
private createSession;
|
|
23
|
+
private putFile;
|
|
24
|
+
private reportComplete;
|
|
25
|
+
private readChunk;
|
|
26
|
+
private parseRangeEnd;
|
|
27
|
+
private isRatio;
|
|
28
|
+
private probeDimensions;
|
|
29
|
+
private probeDurationSec;
|
|
30
|
+
private fetchWithTimeout;
|
|
31
|
+
private safeText;
|
|
32
|
+
/** Elapsed time + average throughput, so a slow link is visible in the logs. */
|
|
33
|
+
private transferStats;
|
|
34
|
+
private delay;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=YouTubeUploadService.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"YouTubeUploadService.d.ts","sourceRoot":"","sources":["../../../../src/infrastructure/services/YouTubeUploadService.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,qCAAqC,CAAC;AA4C7D,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IAGrB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,qBAAa,oBAAoB;IAE7B,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAHN,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,eAAe,EAAE,MAAM,EACvB,MAAM,EAAE,MAAM;IAGjC;;;;OAIG;IACU,MAAM,CAAC,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;YAuGrD,aAAa;YA8Cb,OAAO;YAsFP,cAAc;IAmC5B,OAAO,CAAC,SAAS;IAajB,OAAO,CAAC,aAAa;IAMrB,OAAO,CAAC,OAAO;IAWf,OAAO,CAAC,eAAe;IAsBvB,OAAO,CAAC,gBAAgB;YAkBV,gBAAgB;YAchB,QAAQ;IAQtB,gFAAgF;IAChF,OAAO,CAAC,aAAa;IAWrB,OAAO,CAAC,KAAK;CAGd"}
|