streamer-node 1.0.31 → 1.0.38

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.
Files changed (123) hide show
  1. package/dist/package.json +6 -2
  2. package/dist/scripts/README.md +670 -0
  3. package/dist/scripts/install-streamer-node.sh +117 -0
  4. package/dist/scripts/reframe_ball.py +354 -0
  5. package/dist/src/application/interfaces/StartStreamUseCase.types.d.ts +3 -1
  6. package/dist/src/application/interfaces/StartStreamUseCase.types.d.ts.map +1 -1
  7. package/dist/src/application/interfaces/StartStreamUseCase.types.js.map +1 -1
  8. package/dist/src/application/services/HttpClient.d.ts +21 -0
  9. package/dist/src/application/services/HttpClient.d.ts.map +1 -1
  10. package/dist/src/application/services/HttpClient.js +37 -5
  11. package/dist/src/application/services/HttpClient.js.map +1 -1
  12. package/dist/src/application/services/StreamManagerService.d.ts.map +1 -1
  13. package/dist/src/application/services/StreamManagerService.js +2 -0
  14. package/dist/src/application/services/StreamManagerService.js.map +1 -1
  15. package/dist/src/application/use-cases/CaptureHighlightUseCase.d.ts +52 -0
  16. package/dist/src/application/use-cases/CaptureHighlightUseCase.d.ts.map +1 -0
  17. package/dist/src/application/use-cases/CaptureHighlightUseCase.js +169 -0
  18. package/dist/src/application/use-cases/CaptureHighlightUseCase.js.map +1 -0
  19. package/dist/src/application/use-cases/StartStreamUseCase.d.ts +9 -1
  20. package/dist/src/application/use-cases/StartStreamUseCase.d.ts.map +1 -1
  21. package/dist/src/application/use-cases/StartStreamUseCase.js +125 -2
  22. package/dist/src/application/use-cases/StartStreamUseCase.js.map +1 -1
  23. package/dist/src/application/use-cases/StopStreamUseCase.d.ts +5 -1
  24. package/dist/src/application/use-cases/StopStreamUseCase.d.ts.map +1 -1
  25. package/dist/src/application/use-cases/StopStreamUseCase.js +7 -1
  26. package/dist/src/application/use-cases/StopStreamUseCase.js.map +1 -1
  27. package/dist/src/domain/events/StreamEvent.d.ts +5 -0
  28. package/dist/src/domain/events/StreamEvent.d.ts.map +1 -1
  29. package/dist/src/domain/services/BallReframer.d.ts +35 -0
  30. package/dist/src/domain/services/BallReframer.d.ts.map +1 -0
  31. package/dist/src/domain/services/BallReframer.js +3 -0
  32. package/dist/src/domain/services/BallReframer.js.map +1 -0
  33. package/dist/src/domain/services/FFmpegService.d.ts +6 -2
  34. package/dist/src/domain/services/FFmpegService.d.ts.map +1 -1
  35. package/dist/src/domain/services/HighlightSignalSource.d.ts +113 -0
  36. package/dist/src/domain/services/HighlightSignalSource.d.ts.map +1 -0
  37. package/dist/src/domain/services/HighlightSignalSource.js +3 -0
  38. package/dist/src/domain/services/HighlightSignalSource.js.map +1 -0
  39. package/dist/src/domain/services/SSEService.d.ts +1 -0
  40. package/dist/src/domain/services/SSEService.d.ts.map +1 -1
  41. package/dist/src/infrastructure/config/Config.d.ts +32 -0
  42. package/dist/src/infrastructure/config/Config.d.ts.map +1 -1
  43. package/dist/src/infrastructure/config/Config.js +60 -0
  44. package/dist/src/infrastructure/config/Config.js.map +1 -1
  45. package/dist/src/infrastructure/listeners/SupabaseListener.d.ts +5 -0
  46. package/dist/src/infrastructure/listeners/SupabaseListener.d.ts.map +1 -1
  47. package/dist/src/infrastructure/listeners/SupabaseListener.js +43 -5
  48. package/dist/src/infrastructure/listeners/SupabaseListener.js.map +1 -1
  49. package/dist/src/infrastructure/logging/LogShippingService.d.ts +3 -1
  50. package/dist/src/infrastructure/logging/LogShippingService.d.ts.map +1 -1
  51. package/dist/src/infrastructure/logging/LogShippingService.js +13 -4
  52. package/dist/src/infrastructure/logging/LogShippingService.js.map +1 -1
  53. package/dist/src/infrastructure/logging/RemoteLogger.d.ts +1 -0
  54. package/dist/src/infrastructure/logging/RemoteLogger.d.ts.map +1 -1
  55. package/dist/src/infrastructure/logging/RemoteLogger.js +1 -1
  56. package/dist/src/infrastructure/logging/RemoteLogger.js.map +1 -1
  57. package/dist/src/infrastructure/services/AdDownloaderService.d.ts +85 -0
  58. package/dist/src/infrastructure/services/AdDownloaderService.d.ts.map +1 -0
  59. package/dist/src/infrastructure/services/AdDownloaderService.js +530 -0
  60. package/dist/src/infrastructure/services/AdDownloaderService.js.map +1 -0
  61. package/dist/src/infrastructure/services/AdRotator.d.ts +63 -0
  62. package/dist/src/infrastructure/services/AdRotator.d.ts.map +1 -0
  63. package/dist/src/infrastructure/services/AdRotator.js +223 -0
  64. package/dist/src/infrastructure/services/AdRotator.js.map +1 -0
  65. package/dist/src/infrastructure/services/HighlightBufferManager.d.ts +65 -0
  66. package/dist/src/infrastructure/services/HighlightBufferManager.d.ts.map +1 -0
  67. package/dist/src/infrastructure/services/HighlightBufferManager.js +203 -0
  68. package/dist/src/infrastructure/services/HighlightBufferManager.js.map +1 -0
  69. package/dist/src/infrastructure/services/HighlightBufferRegistry.d.ts +23 -0
  70. package/dist/src/infrastructure/services/HighlightBufferRegistry.d.ts.map +1 -0
  71. package/dist/src/infrastructure/services/HighlightBufferRegistry.js +44 -0
  72. package/dist/src/infrastructure/services/HighlightBufferRegistry.js.map +1 -0
  73. package/dist/src/infrastructure/services/HighlightExtractorService.d.ts +65 -0
  74. package/dist/src/infrastructure/services/HighlightExtractorService.d.ts.map +1 -0
  75. package/dist/src/infrastructure/services/HighlightExtractorService.js +264 -0
  76. package/dist/src/infrastructure/services/HighlightExtractorService.js.map +1 -0
  77. package/dist/src/infrastructure/services/HighlightRendererService.d.ts +38 -0
  78. package/dist/src/infrastructure/services/HighlightRendererService.d.ts.map +1 -0
  79. package/dist/src/infrastructure/services/HighlightRendererService.js +182 -0
  80. package/dist/src/infrastructure/services/HighlightRendererService.js.map +1 -0
  81. package/dist/src/infrastructure/services/NodeFFmpegService.d.ts +6 -4
  82. package/dist/src/infrastructure/services/NodeFFmpegService.d.ts.map +1 -1
  83. package/dist/src/infrastructure/services/NodeFFmpegService.js +154 -32
  84. package/dist/src/infrastructure/services/NodeFFmpegService.js.map +1 -1
  85. package/dist/src/infrastructure/services/NodeSSEService.d.ts +8 -0
  86. package/dist/src/infrastructure/services/NodeSSEService.d.ts.map +1 -1
  87. package/dist/src/infrastructure/services/NodeSSEService.js +71 -27
  88. package/dist/src/infrastructure/services/NodeSSEService.js.map +1 -1
  89. package/dist/src/infrastructure/services/NullBallReframer.d.ts +11 -0
  90. package/dist/src/infrastructure/services/NullBallReframer.d.ts.map +1 -0
  91. package/dist/src/infrastructure/services/NullBallReframer.js +16 -0
  92. package/dist/src/infrastructure/services/NullBallReframer.js.map +1 -0
  93. package/dist/src/infrastructure/services/PythonBallReframer.d.ts +35 -0
  94. package/dist/src/infrastructure/services/PythonBallReframer.d.ts.map +1 -0
  95. package/dist/src/infrastructure/services/PythonBallReframer.js +238 -0
  96. package/dist/src/infrastructure/services/PythonBallReframer.js.map +1 -0
  97. package/dist/src/infrastructure/services/SerialHighlightListener.d.ts +45 -0
  98. package/dist/src/infrastructure/services/SerialHighlightListener.d.ts.map +1 -0
  99. package/dist/src/infrastructure/services/SerialHighlightListener.js +440 -0
  100. package/dist/src/infrastructure/services/SerialHighlightListener.js.map +1 -0
  101. package/dist/src/infrastructure/services/YouTubeUploadService.d.ts +36 -0
  102. package/dist/src/infrastructure/services/YouTubeUploadService.d.ts.map +1 -0
  103. package/dist/src/infrastructure/services/YouTubeUploadService.js +402 -0
  104. package/dist/src/infrastructure/services/YouTubeUploadService.js.map +1 -0
  105. package/dist/src/infrastructure/utils/ResourceSampler.d.ts +47 -0
  106. package/dist/src/infrastructure/utils/ResourceSampler.d.ts.map +1 -0
  107. package/dist/src/infrastructure/utils/ResourceSampler.js +102 -0
  108. package/dist/src/infrastructure/utils/ResourceSampler.js.map +1 -0
  109. package/dist/src/infrastructure/utils/lowPriority.d.ts +29 -0
  110. package/dist/src/infrastructure/utils/lowPriority.d.ts.map +1 -0
  111. package/dist/src/infrastructure/utils/lowPriority.js +31 -0
  112. package/dist/src/infrastructure/utils/lowPriority.js.map +1 -0
  113. package/dist/src/infrastructure/utils/paths.d.ts +27 -0
  114. package/dist/src/infrastructure/utils/paths.d.ts.map +1 -0
  115. package/dist/src/infrastructure/utils/paths.js +69 -0
  116. package/dist/src/infrastructure/utils/paths.js.map +1 -0
  117. package/dist/src/infrastructure/utils/spawnToFile.d.ts +20 -0
  118. package/dist/src/infrastructure/utils/spawnToFile.d.ts.map +1 -0
  119. package/dist/src/infrastructure/utils/spawnToFile.js +98 -0
  120. package/dist/src/infrastructure/utils/spawnToFile.js.map +1 -0
  121. package/dist/src/main.js +228 -2
  122. package/dist/src/main.js.map +1 -1
  123. package/package.json +6 -2
@@ -0,0 +1,530 @@
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.AdDownloaderService = void 0;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const https = __importStar(require("https"));
40
+ const http = __importStar(require("http"));
41
+ const crypto = __importStar(require("crypto"));
42
+ const child_process_1 = require("child_process");
43
+ class AdDownloaderService {
44
+ constructor(logger) {
45
+ this.logger = logger;
46
+ this.downloadTimeoutMs = 10000;
47
+ this.normalizeTimeoutMs = 15000;
48
+ // Clips may re-encode up to maxDuration seconds of video; give them more room.
49
+ this.clipTimeoutMs = 30000;
50
+ // Cap concurrent ffmpeg encodes so building one court's ads never saturates
51
+ // every core and starves another court's live (main) encoder.
52
+ this.clipConcurrency = 3;
53
+ this.adDir = path.resolve("./ad");
54
+ fs.mkdirSync(this.adDir, { recursive: true });
55
+ }
56
+ /**
57
+ * True when the pool contains at least one animated ad (by URL extension).
58
+ * Drives the choice between the still file-swap rotation path and the native
59
+ * pre-composed concat path.
60
+ */
61
+ hasAnimatedAds(ads) {
62
+ return (ads ?? []).some((ad) => AdDownloaderService.VIDEO_EXTS.has(this.extractExtension(ad.url)));
63
+ }
64
+ /**
65
+ * Download a pool of ads and normalize each to a still PNG (poster frame for
66
+ * animated formats). Runs in parallel and is fail-soft: any ad that fails to
67
+ * download or normalize is dropped, so the returned pool only contains ads
68
+ * that are ready to be shown. Returns [] when there are no ads.
69
+ */
70
+ async downloadPool(ads, courtId) {
71
+ if (!ads || ads.length === 0)
72
+ return [];
73
+ const courtDir = this.getCourtDir(courtId);
74
+ fs.mkdirSync(courtDir, { recursive: true });
75
+ const results = await Promise.all(ads.map((ad) => this.prepareOne(ad, courtDir, courtId)));
76
+ return results.filter((ad) => ad !== null);
77
+ }
78
+ /** Absolute path to a court's ad working directory. */
79
+ getCourtDir(courtId) {
80
+ return path.join(this.adDir, this.safeSegment(courtId));
81
+ }
82
+ /** Fixed slot file paths the running ffmpeg reads; AdRotator overwrites them. */
83
+ getSlotPaths(courtId) {
84
+ const dir = this.getCourtDir(courtId);
85
+ return {
86
+ left: path.join(dir, "slot-left.png"),
87
+ right: path.join(dir, "slot-right.png"),
88
+ };
89
+ }
90
+ /** Looping concat-video slot paths (native-motion path). */
91
+ getSlotVideoPaths(courtId) {
92
+ const dir = this.getCourtDir(courtId);
93
+ return {
94
+ left: path.join(dir, "slot-left.mp4"),
95
+ right: path.join(dir, "slot-right.mp4"),
96
+ };
97
+ }
98
+ /**
99
+ * Native-motion path: download every ad in its original format, build a
100
+ * collision-free rotation schedule, materialize each round as a uniform clip,
101
+ * and concat each slot's clips into ONE looping MP4. The running ffmpeg reads
102
+ * each slot MP4 with `-stream_loop -1`, so the rotation is baked into the
103
+ * looping video and the main encoder never restarts.
104
+ *
105
+ * Fail-soft: ads that fail to download are dropped; if clip building or concat
106
+ * fails for a slot, that slot returns null and the stream runs without it.
107
+ * Returns { left: null, right: null } when there is nothing to show.
108
+ */
109
+ async buildSlotVideos(ads, courtId, options) {
110
+ const empty = { left: null, right: null };
111
+ if (!ads || ads.length === 0)
112
+ return empty;
113
+ try {
114
+ const courtDir = this.getCourtDir(courtId);
115
+ fs.mkdirSync(courtDir, { recursive: true });
116
+ // 1. Download all ads natively (parallel, fail-soft) and dedupe by content
117
+ // hash. Dedup makes the index-level collision guard a true content-level
118
+ // guard (duplicate URLs would otherwise show the same creative on both
119
+ // slots) and avoids encoding the same asset twice.
120
+ const downloaded = (await Promise.all(ads.map((ad) => this.downloadRaw(ad, courtDir, courtId)))).filter((d) => d !== null);
121
+ const seen = new Set();
122
+ let pool = downloaded.filter((d) => {
123
+ if (seen.has(d.hash))
124
+ return false;
125
+ seen.add(d.hash);
126
+ return true;
127
+ });
128
+ if (pool.length === 0)
129
+ return empty;
130
+ // 2. Schedule + materialize clips. Per-ad fail-soft: any ad whose clip
131
+ // fails to encode is dropped and the schedule is rebuilt from the
132
+ // survivors, so one bad creative can't blank the whole court. The pool
133
+ // strictly shrinks on failure, so this terminates; already-built clips
134
+ // are cached on disk, so reschedules are cheap.
135
+ let schedule = this.buildSchedule(pool, options);
136
+ while (pool.length > 0) {
137
+ const rounds = new Map();
138
+ for (const round of [...schedule.left, ...schedule.right]) {
139
+ rounds.set(this.clipPath(courtDir, round), round);
140
+ }
141
+ const failedHashes = new Set();
142
+ await this.mapWithConcurrency(Array.from(rounds), this.clipConcurrency, async ([clipPath, round]) => {
143
+ if (fs.existsSync(clipPath))
144
+ return;
145
+ try {
146
+ await this.normalizeClip(round.ad, round.lengthSec, clipPath, options.clipFps);
147
+ }
148
+ catch (error) {
149
+ failedHashes.add(round.ad.hash);
150
+ this.logger.warn("Failed to build ad clip, dropping ad", {
151
+ courtId,
152
+ url: round.ad.url,
153
+ error: error instanceof Error ? error.message : String(error),
154
+ });
155
+ }
156
+ });
157
+ if (failedHashes.size === 0)
158
+ break;
159
+ pool = pool.filter((d) => !failedHashes.has(d.hash));
160
+ if (pool.length === 0)
161
+ return empty;
162
+ schedule = this.buildSchedule(pool, options);
163
+ }
164
+ // 3. Concat each non-empty slot's clips into one looping MP4 (stream-copy).
165
+ const slots = this.getSlotVideoPaths(courtId);
166
+ const left = await this.concatSide(schedule.left, courtDir, slots.left, courtId);
167
+ const right = schedule.right.length > 0
168
+ ? await this.concatSide(schedule.right, courtDir, slots.right, courtId)
169
+ : null;
170
+ // 4. Drop files this run didn't use (cache churn + stale slot files of the
171
+ // other rotation path). Best-effort; never fatal.
172
+ this.pruneCourtDir(courtDir, schedule, courtId);
173
+ this.logger.info("Built native ad slot videos", {
174
+ courtId,
175
+ rounds: schedule.left.length,
176
+ left,
177
+ right,
178
+ });
179
+ return { left, right };
180
+ }
181
+ catch (error) {
182
+ // Ads must never break the stream — fail soft to no ads.
183
+ this.logger.warn("Failed to build ad slot videos, continuing without ads", {
184
+ courtId,
185
+ error: error instanceof Error ? error.message : String(error),
186
+ });
187
+ return empty;
188
+ }
189
+ }
190
+ // Download + normalize a single ad to a stable, cache-friendly PNG path.
191
+ // The filename is keyed by a hash of the URL so unchanged ads are reused.
192
+ async prepareOne(ad, courtDir, courtId) {
193
+ try {
194
+ const hash = crypto
195
+ .createHash("sha1")
196
+ .update(ad.url)
197
+ .digest("hex")
198
+ .slice(0, 12);
199
+ const normPath = path.join(courtDir, `norm-${hash}.png`);
200
+ if (fs.existsSync(normPath)) {
201
+ this.logger.info("Using cached normalized ad", { courtId, normPath });
202
+ return { pngPath: normPath, durationSec: ad.durationSec };
203
+ }
204
+ const ext = this.extractExtension(ad.url);
205
+ const rawPath = path.join(courtDir, `raw-${hash}.${ext}`);
206
+ await this.httpDownload(ad.url, rawPath);
207
+ await this.extractPosterFrame(rawPath, normPath);
208
+ fs.unlink(rawPath, () => { }); // raw no longer needed once normalized
209
+ this.logger.info("Prepared ad", { courtId, url: ad.url, normPath });
210
+ return { pngPath: normPath, durationSec: ad.durationSec };
211
+ }
212
+ catch (error) {
213
+ this.logger.warn("Failed to prepare ad, skipping", {
214
+ courtId,
215
+ url: ad.url,
216
+ error: error instanceof Error ? error.message : String(error),
217
+ });
218
+ return null;
219
+ }
220
+ }
221
+ // Extract a single still frame to PNG. Works for both still images (re-encode)
222
+ // and animated formats (first frame) via the same one-shot ffmpeg call.
223
+ extractPosterFrame(src, dest) {
224
+ return this.runFfmpegToFile(["-y", "-i", src, "-frames:v", "1", "-update", "1"], dest);
225
+ }
226
+ // Download a single ad in its native format, keyed by a hash of the URL so
227
+ // identical ads are reused across stream starts. Fail-soft: returns null on error.
228
+ async downloadRaw(ad, courtDir, courtId) {
229
+ try {
230
+ const hash = crypto
231
+ .createHash("sha1")
232
+ .update(ad.url)
233
+ .digest("hex")
234
+ .slice(0, 12);
235
+ const ext = this.extractExtension(ad.url);
236
+ const rawPath = path.join(courtDir, `raw-${hash}.${ext}`);
237
+ if (fs.existsSync(rawPath)) {
238
+ this.logger.info("Using cached raw ad", { courtId, rawPath });
239
+ }
240
+ else {
241
+ await this.httpDownload(ad.url, rawPath);
242
+ this.logger.info("Downloaded raw ad", { courtId, url: ad.url, rawPath });
243
+ }
244
+ return {
245
+ url: ad.url,
246
+ hash,
247
+ rawPath,
248
+ isVideo: AdDownloaderService.VIDEO_EXTS.has(ext),
249
+ durationSec: ad.durationSec,
250
+ };
251
+ }
252
+ catch (error) {
253
+ this.logger.warn("Failed to download ad, skipping", {
254
+ courtId,
255
+ url: ad.url,
256
+ error: error instanceof Error ? error.message : String(error),
257
+ });
258
+ return null;
259
+ }
260
+ }
261
+ // Build two equal-total, collision-free slot sequences from the pool.
262
+ //
263
+ // One round per ad: round k has length = the (clamped) duration of base[k] and
264
+ // shows base[k] on the left, base[(k+offset)] on the right. Both slots share
265
+ // the same ordered round lengths, so the two concatenated videos have an
266
+ // identical total length (they realign every loop) and at every instant the
267
+ // left and right ads differ (offset in [1, n-1] guarantees base[k] != right).
268
+ // Each ad's own duration is honored in its left round; on the right it is shown
269
+ // for that round's (left-driven) length.
270
+ buildSchedule(ads, options) {
271
+ const n = ads.length;
272
+ if (n === 0)
273
+ return { left: [], right: [] };
274
+ const round = (ad) => ({
275
+ ad,
276
+ lengthSec: this.clampDuration(ad.durationSec, options),
277
+ });
278
+ if (n === 1) {
279
+ // One ad → left slot only, looping its native clip; right stays empty.
280
+ return { left: [round(ads[0])], right: [] };
281
+ }
282
+ const base = this.shuffle(ads);
283
+ const offset = Math.floor(n / 2) || 1; // in [1, n-1] for n >= 2
284
+ const left = [];
285
+ const right = [];
286
+ for (let k = 0; k < n; k++) {
287
+ const lengthSec = this.clampDuration(base[k].durationSec, options);
288
+ left.push({ ad: base[k], lengthSec });
289
+ right.push({ ad: base[(k + offset) % n], lengthSec });
290
+ }
291
+ return { left, right };
292
+ }
293
+ // Concat one slot's rounds into a single looping MP4 (stream-copy, near-free).
294
+ // Fail-soft: returns null if the concat fails so the stream can run without
295
+ // that slot.
296
+ async concatSide(rounds, courtDir, dest, courtId) {
297
+ try {
298
+ const listPath = `${dest}.txt`;
299
+ const lines = rounds
300
+ .map((round) => `file '${this.clipPath(courtDir, round)}'`)
301
+ .join("\n");
302
+ fs.writeFileSync(listPath, `${lines}\n`);
303
+ await this.concatClips(listPath, dest);
304
+ return dest;
305
+ }
306
+ catch (error) {
307
+ this.logger.warn("Failed to concat ad slot, skipping slot", {
308
+ courtId,
309
+ dest,
310
+ error: error instanceof Error ? error.message : String(error),
311
+ });
312
+ return null;
313
+ }
314
+ }
315
+ // Cut a native ad to a uniform 220x500 clip of exactly `lengthSec` (still →
316
+ // held for the duration; video/gif → looped/trimmed). A forced keyframe every
317
+ // second makes the concat joins and the stream_loop wrap land cleanly. `-an`
318
+ // strips audio so ad sound can never reach the YouTube output.
319
+ normalizeClip(ad, lengthSec, dest, fps) {
320
+ const loopFlags = ad.isVideo ? ["-stream_loop", "-1"] : ["-loop", "1"];
321
+ const vf = "scale=220:500:force_original_aspect_ratio=decrease," +
322
+ "pad=220:500:(ow-iw)/2:(oh-ih)/2:color=black,setsar=1," +
323
+ `fps=${fps},format=yuv420p`;
324
+ return this.runFfmpegToFile([
325
+ "-y",
326
+ ...loopFlags,
327
+ "-i",
328
+ ad.rawPath,
329
+ "-t",
330
+ String(lengthSec),
331
+ "-vf",
332
+ vf,
333
+ "-c:v",
334
+ "libx264",
335
+ "-preset",
336
+ "veryfast",
337
+ "-g",
338
+ String(fps),
339
+ "-keyint_min",
340
+ String(fps),
341
+ "-sc_threshold",
342
+ "0",
343
+ "-video_track_timescale",
344
+ String(fps * 1000),
345
+ "-an",
346
+ ], dest, this.clipTimeoutMs);
347
+ }
348
+ // Stream-copy concat of a clip list into one MP4 (no re-encode).
349
+ concatClips(listPath, dest) {
350
+ return this.runFfmpegToFile(["-y", "-f", "concat", "-safe", "0", "-i", listPath, "-c", "copy"], dest);
351
+ }
352
+ // Per-(ad, length) clip path; cached so an ad shown at the same length twice
353
+ // (e.g. as both a left and a right round) is only encoded once.
354
+ clipPath(courtDir, round) {
355
+ return path.join(courtDir, `clip-${round.ad.hash}-${round.lengthSec}.mp4`);
356
+ }
357
+ // Resolve a payload duration to a clamped integer-second clip length. The
358
+ // final Math.max(1, …) floor guarantees a positive length even under a
359
+ // misconfigured (non-positive) min, so `-t` can never be 0 (which would emit a
360
+ // zero-frame clip and poison the concat).
361
+ clampDuration(durationSec, options) {
362
+ const sec = typeof durationSec === "number" && isFinite(durationSec) && durationSec > 0
363
+ ? durationSec
364
+ : options.defaultDurationSec;
365
+ return Math.max(1, Math.round(Math.max(options.minDurationSec, Math.min(options.maxDurationSec, sec))));
366
+ }
367
+ // Fisher–Yates shuffle into a new array.
368
+ shuffle(items) {
369
+ const arr = items.slice();
370
+ for (let i = arr.length - 1; i > 0; i--) {
371
+ const j = Math.floor(Math.random() * (i + 1));
372
+ [arr[i], arr[j]] = [arr[j], arr[i]];
373
+ }
374
+ return arr;
375
+ }
376
+ // Run `fn` over `items` with at most `limit` in flight at once. Rejects if any
377
+ // task rejects (mirrors Promise.all semantics); on rejection the surviving
378
+ // workers stop pulling new items so no further work is spawned after a failure.
379
+ async mapWithConcurrency(items, limit, fn) {
380
+ let cursor = 0;
381
+ let failed = false;
382
+ const worker = async () => {
383
+ while (!failed && cursor < items.length) {
384
+ const index = cursor++;
385
+ try {
386
+ await fn(items[index]);
387
+ }
388
+ catch (err) {
389
+ failed = true;
390
+ throw err;
391
+ }
392
+ }
393
+ };
394
+ const workers = Array.from({ length: Math.min(limit, items.length) }, () => worker());
395
+ await Promise.all(workers);
396
+ }
397
+ // Best-effort removal of files this run did not use: stale cache entries
398
+ // (raw-/clip-/norm-) from ad payloads no longer in rotation, and slot files of
399
+ // the OTHER rotation path (e.g. a leftover slot-*.png from a prior still run).
400
+ // Never throws — pruning must not break the stream.
401
+ pruneCourtDir(courtDir, schedule, courtId) {
402
+ try {
403
+ const keep = new Set([
404
+ "slot-left.mp4",
405
+ "slot-right.mp4",
406
+ "slot-left.mp4.txt",
407
+ "slot-right.mp4.txt",
408
+ ]);
409
+ for (const round of [...schedule.left, ...schedule.right]) {
410
+ keep.add(path.basename(this.clipPath(courtDir, round)));
411
+ keep.add(`raw-${round.ad.hash}.${this.extractExtension(round.ad.url)}`);
412
+ }
413
+ for (const file of fs.readdirSync(courtDir)) {
414
+ const isCache = file.startsWith("raw-") ||
415
+ file.startsWith("clip-") ||
416
+ file.startsWith("norm-");
417
+ const isStaleStill = file === "slot-left.png" || file === "slot-right.png";
418
+ if ((isCache || isStaleStill) && !keep.has(file)) {
419
+ fs.unlink(path.join(courtDir, file), () => { });
420
+ }
421
+ }
422
+ }
423
+ catch (error) {
424
+ this.logger.warn("Ad cache prune failed (non-fatal)", {
425
+ courtId,
426
+ error: error instanceof Error ? error.message : String(error),
427
+ });
428
+ }
429
+ }
430
+ // Run a one-shot ffmpeg whose output is `dest`. Writes to a temp file first and
431
+ // renames on success so a failed/killed run never leaves a corrupt file. The
432
+ // temp name PRESERVES dest's extension (e.g. clip-x.tmp.mp4, not clip-x.mp4.tmp)
433
+ // because ffmpeg selects the output muxer from the extension. The caller
434
+ // supplies all args EXCEPT the final output path.
435
+ runFfmpegToFile(args, dest, timeoutMs = this.normalizeTimeoutMs) {
436
+ const ext = path.extname(dest);
437
+ const tmp = ext ? `${dest.slice(0, -ext.length)}.tmp${ext}` : `${dest}.tmp`;
438
+ return new Promise((resolve, reject) => {
439
+ const proc = (0, child_process_1.spawn)("ffmpeg", [...args, tmp], {
440
+ stdio: ["ignore", "ignore", "pipe"],
441
+ });
442
+ let stderr = "";
443
+ proc.stderr?.on("data", (d) => {
444
+ stderr += d.toString();
445
+ });
446
+ const timeout = setTimeout(() => {
447
+ proc.kill("SIGKILL");
448
+ fs.unlink(tmp, () => { });
449
+ reject(new Error("ffmpeg timed out"));
450
+ }, timeoutMs);
451
+ proc.on("exit", (code) => {
452
+ clearTimeout(timeout);
453
+ if (code === 0 && fs.existsSync(tmp)) {
454
+ try {
455
+ fs.renameSync(tmp, dest);
456
+ resolve();
457
+ }
458
+ catch (err) {
459
+ fs.unlink(tmp, () => { });
460
+ reject(err);
461
+ }
462
+ }
463
+ else {
464
+ fs.unlink(tmp, () => { });
465
+ reject(new Error(`ffmpeg failed (code ${code}): ${stderr.slice(-200)}`));
466
+ }
467
+ });
468
+ proc.on("error", (err) => {
469
+ clearTimeout(timeout);
470
+ fs.unlink(tmp, () => { });
471
+ reject(err);
472
+ });
473
+ });
474
+ }
475
+ // Make a courtId safe to use as a single filesystem path segment.
476
+ safeSegment(value) {
477
+ return value.replace(/[^a-zA-Z0-9_-]/g, "_");
478
+ }
479
+ extractExtension(url) {
480
+ const cleaned = url.split("?")[0];
481
+ const match = cleaned.match(/\.([a-zA-Z0-9]{2,5})$/);
482
+ return match ? match[1].toLowerCase() : "png";
483
+ }
484
+ httpDownload(url, destPath) {
485
+ return new Promise((resolve, reject) => {
486
+ const client = url.startsWith("https") ? https : http;
487
+ const file = fs.createWriteStream(destPath);
488
+ const cleanup = () => {
489
+ file.close();
490
+ fs.unlink(destPath, () => { });
491
+ };
492
+ const request = client.get(url, (res) => {
493
+ if (res.statusCode !== 200) {
494
+ cleanup();
495
+ reject(new Error(`HTTP ${res.statusCode}`));
496
+ return;
497
+ }
498
+ res.pipe(file);
499
+ file.on("finish", () => {
500
+ file.close();
501
+ resolve();
502
+ });
503
+ file.on("error", (err) => {
504
+ cleanup();
505
+ reject(err);
506
+ });
507
+ });
508
+ request.on("error", (err) => {
509
+ cleanup();
510
+ reject(err);
511
+ });
512
+ request.setTimeout(this.downloadTimeoutMs, () => {
513
+ request.destroy();
514
+ cleanup();
515
+ reject(new Error("Download timeout"));
516
+ });
517
+ });
518
+ }
519
+ }
520
+ exports.AdDownloaderService = AdDownloaderService;
521
+ // Formats treated as animated (decoded as video); everything else is a still.
522
+ AdDownloaderService.VIDEO_EXTS = new Set([
523
+ "mp4",
524
+ "gif",
525
+ "webm",
526
+ "mov",
527
+ "avi",
528
+ "mkv",
529
+ ]);
530
+ //# sourceMappingURL=AdDownloaderService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AdDownloaderService.js","sourceRoot":"","sources":["../../../../src/infrastructure/services/AdDownloaderService.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ import { Logger } from "../../application/interfaces/Logger";
2
+ import { NormalizedAd } from "./AdDownloaderService";
3
+ export interface AdRotatorOptions {
4
+ defaultDurationSec: number;
5
+ minDurationSec: number;
6
+ maxDurationSec: number;
7
+ }
8
+ /**
9
+ * Rotates a pool of N normalized still ads through two on-screen slots
10
+ * (left/right) while the stream stays live. Each slot advances independently on
11
+ * its own timer; the two slots never show the same ad at once; the pool is
12
+ * reshuffled each time it is fully consumed.
13
+ *
14
+ * Mechanism: the running ffmpeg reads two fixed PNG files (the slot files) via
15
+ * `-f image2 -loop 1`, re-reading them on every loop. To change a slot we simply
16
+ * overwrite its file (atomically, via temp + rename). The ffmpeg process never
17
+ * restarts.
18
+ */
19
+ export declare class AdRotator {
20
+ private readonly courtId;
21
+ private readonly pool;
22
+ private readonly slotLeftPath;
23
+ private readonly slotRightPath;
24
+ private readonly options;
25
+ private readonly logger;
26
+ private queue;
27
+ private cursor;
28
+ private readonly left;
29
+ private readonly right;
30
+ private stopped;
31
+ constructor(courtId: string, pool: NormalizedAd[], slotLeftPath: string, slotRightPath: string, options: AdRotatorOptions, logger: Logger);
32
+ /**
33
+ * Seed the initial slot files. MUST run before ffmpeg starts so the slot
34
+ * inputs exist. Clears any stale slot files first, so an empty pool leaves no
35
+ * slot files behind (ffmpeg then adds no ad inputs).
36
+ */
37
+ prepare(): void;
38
+ /**
39
+ * Begin rotation. Call after ffmpeg has started. With fewer than two ads there
40
+ * is nothing to rotate, so the seeded slot(s) simply stay put.
41
+ */
42
+ start(): void;
43
+ /** Stop all timers. Idempotent. Safe to call from teardown/retry/shutdown. */
44
+ stop(): void;
45
+ private scheduleSlot;
46
+ private next;
47
+ private durationMs;
48
+ private writeSlot;
49
+ private removeSlotFiles;
50
+ private reshuffle;
51
+ }
52
+ /**
53
+ * Tracks the live AdRotator per court so its timers can be torn down when the
54
+ * stream stops, retries, fails, or the app shuts down. A leaked rotator would
55
+ * keep overwriting slot files for a dead stream.
56
+ */
57
+ export declare class AdRotationRegistry {
58
+ private readonly rotators;
59
+ set(courtId: string, rotator: AdRotator): void;
60
+ stop(courtId: string): void;
61
+ stopAll(): void;
62
+ }
63
+ //# sourceMappingURL=AdRotator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AdRotator.d.ts","sourceRoot":"","sources":["../../../../src/infrastructure/services/AdRotator.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,qCAAqC,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAErD,MAAM,WAAW,gBAAgB;IAC/B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;CACxB;AAOD;;;;;;;;;;GAUG;AACH,qBAAa,SAAS;IAQlB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAZzB,OAAO,CAAC,KAAK,CAAgB;IAC7B,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA6C;IAClE,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA6C;IACnE,OAAO,CAAC,OAAO,CAAS;gBAGL,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,YAAY,EAAE,EACpB,YAAY,EAAE,MAAM,EACpB,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,gBAAgB,EACzB,MAAM,EAAE,MAAM;IAGjC;;;;OAIG;IACI,OAAO,IAAI,IAAI;IAmBtB;;;OAGG;IACI,KAAK,IAAI,IAAI;IAqBpB,8EAA8E;IACvE,IAAI,IAAI,IAAI;IAUnB,OAAO,CAAC,YAAY;IAiBpB,OAAO,CAAC,IAAI;IAkBZ,OAAO,CAAC,UAAU;IAclB,OAAO,CAAC,SAAS;IAejB,OAAO,CAAC,eAAe;IAWvB,OAAO,CAAC,SAAS;CAQlB;AAED;;;;GAIG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgC;IAEzD,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,GAAG,IAAI;IAK9C,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAQ3B,OAAO,IAAI,IAAI;CAMhB"}