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,117 @@
1
+ #!/bin/bash
2
+
3
+ set -e
4
+
5
+ echo "=================================="
6
+ echo "Streamer Node Installation"
7
+ echo "=================================="
8
+ echo ""
9
+
10
+ # Check if running as root
11
+ if [ "$EUID" -eq 0 ]; then
12
+ echo "Please run as regular user (with sudo privileges), not as root"
13
+ exit 1
14
+ fi
15
+
16
+ # Prompt for configuration
17
+ read -p "Enter GROUND_ID: " GROUND_ID
18
+ if [ -z "$GROUND_ID" ]; then
19
+ echo "Error: GROUND_ID is required"
20
+ exit 1
21
+ fi
22
+
23
+ read -p "Enter installation path [/opt/streamer-node]: " INSTALL_PATH
24
+ INSTALL_PATH=${INSTALL_PATH:-/opt/streamer-node}
25
+
26
+ # Install Node.js if not present
27
+ if ! command -v node &> /dev/null; then
28
+ echo "Installing Node.js..."
29
+ curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
30
+ sudo apt-get install -y nodejs
31
+ fi
32
+
33
+ # Install FFmpeg if not present
34
+ if ! command -v ffmpeg &> /dev/null; then
35
+ echo "Installing FFmpeg..."
36
+ sudo apt-get update
37
+ sudo apt-get install -y ffmpeg
38
+ fi
39
+
40
+ # Create directories
41
+ echo "Creating directories..."
42
+ sudo mkdir -p $INSTALL_PATH/public
43
+ sudo mkdir -p /var/tmp/stream_registry
44
+ sudo mkdir -p /tmp/stream_registry
45
+ sudo chown -R $USER:$USER $INSTALL_PATH
46
+ sudo chown -R $USER:$USER /var/tmp/stream_registry
47
+
48
+ # Set global environment variables
49
+ echo "Setting up environment variables..."
50
+ if ! grep -q "GROUND_ID" ~/.bashrc; then
51
+ cat >> ~/.bashrc << EOF
52
+
53
+ # Streamer Node Configuration
54
+ export GROUND_ID="$GROUND_ID"
55
+ export CLIENT_IMAGES_PATH="./public/client.png"
56
+ EOF
57
+ source ~/.bashrc
58
+ fi
59
+
60
+ # Download setup script
61
+ echo "Downloading setup script..."
62
+ curl -sL https://raw.githubusercontent.com/MHA10/drop-shot-streaming-scripts-ubuntu/master/streamer-node-env-setup.sh -o /tmp/streamer-node-env-setup.sh
63
+ sudo mv /tmp/streamer-node-env-setup.sh /usr/local/bin/
64
+ sudo chmod +x /usr/local/bin/streamer-node-env-setup.sh
65
+
66
+ # Create systemd service
67
+ echo "Creating systemd service..."
68
+ sudo tee /etc/systemd/system/streamer-node.service > /dev/null << EOF
69
+ [Unit]
70
+ Description=Streamer Node Service
71
+ After=network.target network-online.target
72
+ Wants=network-online.target
73
+
74
+ [Service]
75
+ Type=simple
76
+ User=$USER
77
+ Group=$USER
78
+ Environment="GROUND_ID=$GROUND_ID"
79
+ Environment="CLIENT_IMAGES_PATH=./public/client.png"
80
+ ExecStart=/usr/local/bin/streamer-node-env-setup.sh --path $INSTALL_PATH
81
+ Restart=always
82
+ RestartSec=10
83
+ StandardOutput=journal
84
+ StandardError=journal
85
+ SyslogIdentifier=streamer-node
86
+
87
+ [Install]
88
+ WantedBy=multi-user.target
89
+ EOF
90
+
91
+ # Reload systemd
92
+ echo "Configuring systemd..."
93
+ sudo systemctl daemon-reload
94
+ sudo systemctl enable streamer-node
95
+
96
+ echo ""
97
+ echo "=================================="
98
+ echo "Installation Complete!"
99
+ echo "=================================="
100
+ echo ""
101
+ echo "Configuration:"
102
+ echo " GROUND_ID: $GROUND_ID"
103
+ echo " Installation Path: $INSTALL_PATH"
104
+ echo " Environment Variables: ~/.bashrc"
105
+ echo ""
106
+ echo "Commands:"
107
+ echo " Start: sudo systemctl start streamer-node"
108
+ echo " Stop: sudo systemctl stop streamer-node"
109
+ echo " Status: sudo systemctl status streamer-node"
110
+ echo " Logs: sudo journalctl -u streamer-node -f"
111
+ echo ""
112
+ echo "Manual run:"
113
+ echo " streamer-node-env-setup.sh --path $INSTALL_PATH"
114
+ echo ""
115
+ echo "To apply environment variables in current session:"
116
+ echo " source ~/.bashrc"
117
+ echo ""
@@ -0,0 +1,354 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ reframe_ball.py — crop a padel highlight clip to a reel aspect that follows the
4
+ play, using classical CV (no ML model).
5
+
6
+ MODES (--mode)
7
+ action (default) — follow the PLAYERS. Background subtraction (MOG2) isolates
8
+ the moving people; the crop centre is their area-weighted horizontal
9
+ centre, so it sits on wherever the rally is. Robust: players are big,
10
+ stable targets (unlike the tiny ball, which color/motion can't separate
11
+ from yellow-green plants and orange paddles on this footage).
12
+ ball — follow the BALL via an HSV colour gate ∩ motion ∩ small-round shape.
13
+ Kept for footage where the ball is cleanly separable; unreliable on dim,
14
+ cluttered courts (documented limitation).
15
+
16
+ SMOOTH PANNING (two passes)
17
+ Detect a target x for every frame, then build ONE smoothed pan path
18
+ (interpolate gaps → median filter → wide moving average → hard max-velocity
19
+ clamp) and render the crop along it. Smoothness is prioritized.
20
+
21
+ DEBUG / DEMO
22
+ --debug-overlay <path> writes a full-frame annotated video: detected boxes
23
+ (green), the chosen crop-centre (red dot), and the crop window (yellow).
24
+
25
+ Off by default in the streamer; falls back to full frame on any failure.
26
+ """
27
+
28
+ import argparse
29
+ import sys
30
+
31
+ try:
32
+ import cv2
33
+ import numpy as np
34
+ except Exception as exc: # noqa: BLE001
35
+ sys.stderr.write(f"reframe_ball: dependencies unavailable: {exc}\n")
36
+ sys.exit(2)
37
+
38
+
39
+ def parse_aspect(aspect):
40
+ try:
41
+ w, h = aspect.split(":")
42
+ r = float(w) / float(h)
43
+ return r if r > 0 else 9 / 16
44
+ except Exception: # noqa: BLE001
45
+ return 9 / 16
46
+
47
+
48
+ def parse_hsv(s, default):
49
+ try:
50
+ p = [int(v) for v in s.split(",")]
51
+ return np.array(p[:3], np.uint8) if len(p) >= 3 else np.array(default, np.uint8)
52
+ except Exception: # noqa: BLE001
53
+ return np.array(default, np.uint8)
54
+
55
+
56
+ def moving_average(xs, win):
57
+ if win < 2:
58
+ return xs
59
+ win = int(win) | 1
60
+ pad = win // 2
61
+ return np.convolve(np.pad(xs, pad, mode="edge"), np.ones(win) / win, mode="valid")
62
+
63
+
64
+ def median_filter(xs, win):
65
+ if win < 3:
66
+ return xs
67
+ win = int(win) | 1
68
+ pad = win // 2
69
+ padded = np.pad(xs, pad, mode="edge")
70
+ out = np.empty_like(xs)
71
+ for i in range(len(xs)):
72
+ out[i] = np.median(padded[i:i + win])
73
+ return out
74
+
75
+
76
+ def main():
77
+ ap = argparse.ArgumentParser()
78
+ ap.add_argument("--input", required=True)
79
+ ap.add_argument("--output", required=True)
80
+ ap.add_argument("--aspect", default="4:5")
81
+ ap.add_argument("--mode", choices=["action", "ball"], default="action")
82
+ # action-mode (players)
83
+ ap.add_argument("--min-player-frac", type=float, default=0.0015)
84
+ ap.add_argument("--max-player-frac", type=float, default=0.20)
85
+ # ball-mode
86
+ ap.add_argument("--hsv-lower", default="20,70,120")
87
+ ap.add_argument("--hsv-upper", default="45,255,255")
88
+ ap.add_argument("--diff-thresh", type=int, default=18)
89
+ ap.add_argument("--min-area-frac", type=float, default=0.000008)
90
+ ap.add_argument("--max-area-frac", type=float, default=0.0006)
91
+ ap.add_argument("--min-aspect", type=float, default=0.5)
92
+ ap.add_argument("--max-jump-frac", type=float, default=0.30)
93
+ # smoothing
94
+ ap.add_argument("--median-sec", type=float, default=0.5)
95
+ ap.add_argument("--smooth-sec", type=float, default=2.0)
96
+ ap.add_argument("--max-vel-frac", type=float, default=0.004)
97
+ # performance: detection runs on a downscaled, frame-skipped copy so the
98
+ # reframe finishes well under timeout on low-power streamer boxes.
99
+ ap.add_argument("--proc-width", type=int, default=640)
100
+ ap.add_argument("--detect-fps", type=float, default=12.0)
101
+ ap.add_argument("--debug-overlay", default="")
102
+ ap.add_argument("--debug", action="store_true")
103
+ args = ap.parse_args()
104
+
105
+ cap = cv2.VideoCapture(args.input)
106
+ if not cap.isOpened():
107
+ sys.stderr.write("reframe_ball: cannot open input\n")
108
+ return 3
109
+ fps = cap.get(cv2.CAP_PROP_FPS) or 30.0
110
+ src_w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
111
+ src_h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
112
+ if src_w <= 0 or src_h <= 0:
113
+ sys.stderr.write("reframe_ball: bad source dimensions\n")
114
+ cap.release()
115
+ return 3
116
+
117
+ ratio = parse_aspect(args.aspect)
118
+ crop_w = min(src_w, int(round(src_h * ratio)))
119
+ crop_w -= crop_w % 2
120
+ crop_h = src_h - (src_h % 2)
121
+ half = crop_w // 2
122
+
123
+ # ── DETECTION runs downscaled + frame-skipped (perf) ──
124
+ # MOG2 + contours on every full-res 1080p frame is far too slow on
125
+ # low-power streamer boxes — it blew the reframe timeout and starved the
126
+ # live encoder. We only need approximate player positions (the pan path is
127
+ # heavily smoothed afterwards), so detect on a ~proc_width-wide frame, only
128
+ # every `step` frames (~detect_fps), and scale results back to source
129
+ # coordinates. Output resolution/quality is unchanged — only detection is
130
+ # cheapened.
131
+ proc_w = min(src_w, max(160, args.proc_width))
132
+ det_scale = proc_w / float(src_w) # <= 1.0
133
+ det_w = proc_w
134
+ det_h = max(2, int(round(src_h * det_scale)))
135
+ det_area = float(det_w * det_h)
136
+ inv_scale = src_w / float(det_w) # det coords → source coords
137
+ step = max(1, int(round(fps / max(1.0, args.detect_fps))))
138
+
139
+ # detector state (all area/jump thresholds measured on the DOWNSCALED frame)
140
+ lower = parse_hsv(args.hsv_lower, [20, 70, 120])
141
+ upper = parse_hsv(args.hsv_upper, [45, 255, 255])
142
+ min_area = det_area * args.min_area_frac
143
+ max_area = det_area * args.max_area_frac
144
+ max_jump = det_w * args.max_jump_frac
145
+ min_player = det_area * args.min_player_frac
146
+ max_player = det_area * args.max_player_frac
147
+ bg = cv2.createBackgroundSubtractorMOG2(history=300, varThreshold=40, detectShadows=False) \
148
+ if args.mode == "action" else None
149
+
150
+ xs_raw, boxes_per_frame = [], []
151
+ prev_gray = None
152
+ last_x = det_w / 2.0
153
+ idx = 0
154
+ last_pos_msec = 0.0
155
+
156
+ while True:
157
+ # grab() advances the decoder without fully decoding; only retrieve()
158
+ # (decode) the frames we actually analyse, so skipped frames are cheap.
159
+ if not cap.grab():
160
+ break
161
+ # Track the real presentation time of the last frame. The DECLARED fps
162
+ # cannot be trusted: RTSP sub-streams routinely announce 25fps while
163
+ # delivering ~8, and writing N frames at the declared rate then produces
164
+ # a clip several times shorter than the footage (a 30s window came out
165
+ # as 10.28s). We rebuild the true rate from these timestamps below.
166
+ pos = cap.get(cv2.CAP_PROP_POS_MSEC)
167
+ if pos and pos > last_pos_msec:
168
+ last_pos_msec = pos
169
+ found_x = np.nan
170
+ frame_boxes = []
171
+
172
+ if idx % step == 0:
173
+ ok, frame = cap.retrieve()
174
+ if not ok:
175
+ break
176
+ frame_s = cv2.resize(frame, (det_w, det_h), interpolation=cv2.INTER_AREA) \
177
+ if det_scale < 1.0 else frame
178
+ gray = cv2.cvtColor(frame_s, cv2.COLOR_BGR2GRAY)
179
+
180
+ if args.mode == "action":
181
+ fg = bg.apply(frame_s)
182
+ _, fg = cv2.threshold(fg, 200, 255, cv2.THRESH_BINARY) # drop shadows/soft
183
+ fg = cv2.morphologyEx(fg, cv2.MORPH_OPEN, np.ones((3, 3), np.uint8))
184
+ # kernels sized for the downscaled frame (were 9x9 at full res)
185
+ fg = cv2.dilate(fg, np.ones((5, 5), np.uint8), iterations=1)
186
+ contours, _ = cv2.findContours(fg, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
187
+ wsum = 0.0
188
+ xsum = 0.0
189
+ for c in contours:
190
+ area = cv2.contourArea(c)
191
+ if area < min_player or area > max_player:
192
+ continue # too small (noise/plants) or too big (light flash)
193
+ x, y, w, h = cv2.boundingRect(c)
194
+ # Reject only extreme horizontal streaks (reflections / banner
195
+ # glints). Keep near-square and wide blobs — two clustered
196
+ # players often merge into a wide box at the key rally moments,
197
+ # and dropping those was hurting tracking accuracy.
198
+ if w > h * 3:
199
+ continue
200
+ cx = x + w / 2.0
201
+ wsum += area
202
+ xsum += area * cx
203
+ frame_boxes.append((x, y, w, h))
204
+ if wsum > 0:
205
+ found_x = xsum / wsum
206
+ else: # ball mode
207
+ hsv = cv2.cvtColor(frame_s, cv2.COLOR_BGR2HSV)
208
+ color = cv2.inRange(hsv, lower, upper)
209
+ if prev_gray is not None:
210
+ diff = cv2.absdiff(prev_gray, gray)
211
+ _, motion = cv2.threshold(diff, args.diff_thresh, 255, cv2.THRESH_BINARY)
212
+ motion = cv2.dilate(motion, np.ones((7, 7), np.uint8), iterations=1)
213
+ mask = cv2.bitwise_and(color, motion)
214
+ contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
215
+ best = -1.0
216
+ for c in contours:
217
+ area = cv2.contourArea(c)
218
+ if area < min_area or area > max_area:
219
+ continue
220
+ x, y, w, h = cv2.boundingRect(c)
221
+ if w == 0 or h == 0 or min(w, h) / max(w, h) < args.min_aspect:
222
+ continue
223
+ cx = x + w / 2.0
224
+ dist = abs(cx - last_x)
225
+ if dist > max_jump:
226
+ continue
227
+ score = (min(w, h) / max(w, h)) - dist / det_w
228
+ if score > best:
229
+ best = score
230
+ found_x = cx
231
+ frame_boxes = [(x, y, w, h)]
232
+ if not np.isnan(found_x):
233
+ last_x = found_x
234
+ prev_gray = gray
235
+
236
+ # Map detection-space results up to source coordinates so all the
237
+ # downstream path math stays in real (source) pixels.
238
+ if not np.isnan(found_x):
239
+ found_x *= inv_scale
240
+ if inv_scale != 1.0 and frame_boxes:
241
+ frame_boxes = [
242
+ (int(x * inv_scale), int(y * inv_scale),
243
+ int(w * inv_scale), int(h * inv_scale))
244
+ for (x, y, w, h) in frame_boxes
245
+ ]
246
+
247
+ xs_raw.append(found_x)
248
+ boxes_per_frame.append(frame_boxes)
249
+ idx += 1
250
+ cap.release()
251
+
252
+ n = len(xs_raw)
253
+ if n == 0:
254
+ sys.stderr.write("reframe_ball: no frames read\n")
255
+ return 4
256
+
257
+ # ── True output frame rate ──
258
+ # Write N frames at the rate the footage ACTUALLY ran at, not the rate the
259
+ # container claims. A camera sub-stream that declares 25fps but delivers ~8
260
+ # would otherwise yield a clip ~3x too short and playing ~3x too fast.
261
+ # Derived from the last frame's presentation time; falls back to the
262
+ # declared fps when timestamps are unavailable or implausible.
263
+ out_fps = fps
264
+ if last_pos_msec > 0 and n > 1:
265
+ measured = n / (last_pos_msec / 1000.0)
266
+ if 1.0 <= measured <= 120.0:
267
+ out_fps = measured
268
+ else:
269
+ sys.stderr.write(
270
+ f"reframe_ball: measured fps {measured:.2f} implausible; "
271
+ f"keeping declared {fps:.2f}\n"
272
+ )
273
+ xs = np.array(xs_raw, dtype=float)
274
+ valid = ~np.isnan(xs)
275
+ # hit_rate is over the frames we actually analysed (not skipped ones)
276
+ processed = max(1, (n + step - 1) // step)
277
+ hit_rate = valid.sum() / processed
278
+
279
+ if valid.sum() < max(5, n * 0.03):
280
+ path = np.full(n, src_w / 2.0)
281
+ else:
282
+ idx = np.arange(n)
283
+ xs[~valid] = np.interp(idx[~valid], idx[valid], xs[valid])
284
+ xs = median_filter(xs, round(args.median_sec * fps))
285
+ xs = moving_average(xs, round(args.smooth_sec * fps))
286
+ max_vel = max(1.0, src_w * args.max_vel_frac)
287
+ path = np.empty(n)
288
+ path[0] = xs[0]
289
+ for i in range(1, n):
290
+ dx = np.clip(xs[i] - path[i - 1], -max_vel, max_vel)
291
+ path[i] = path[i - 1] + dx
292
+ path = np.clip(np.round(path), half, src_w - half).astype(int)
293
+
294
+ if args.debug:
295
+ vel = np.abs(np.diff(path)) if n > 1 else np.array([0])
296
+ sys.stderr.write(
297
+ f"reframe_ball: mode={args.mode} frames={n} hit_rate={hit_rate:.2f} "
298
+ f"fps_declared={fps:.2f} fps_out={out_fps:.2f} dur={n/out_fps:.1f}s "
299
+ f"max_step_px={vel.max():.0f} mean_step_px={vel.mean():.2f}\n"
300
+ )
301
+
302
+ cap = cv2.VideoCapture(args.input)
303
+ fourcc = cv2.VideoWriter_fourcc(*"mp4v")
304
+ writer = cv2.VideoWriter(args.output, fourcc, out_fps, (crop_w, crop_h))
305
+ if not writer.isOpened():
306
+ sys.stderr.write("reframe_ball: cannot open output writer\n")
307
+ cap.release()
308
+ return 3
309
+ ov_writer = cv2.VideoWriter(args.debug_overlay, fourcc, out_fps, (src_w, src_h)) \
310
+ if args.debug_overlay else None
311
+
312
+ i = written = 0
313
+ try:
314
+ while True:
315
+ ok, frame = cap.read()
316
+ if not ok:
317
+ break
318
+ cx = int(path[min(i, n - 1)])
319
+ x0 = cx - half
320
+ crop = frame[0:crop_h, x0:x0 + crop_w]
321
+ if crop.shape[1] != crop_w or crop.shape[0] != crop_h:
322
+ crop = cv2.resize(crop, (crop_w, crop_h))
323
+ writer.write(crop)
324
+ written += 1
325
+ if ov_writer is not None:
326
+ ov = frame.copy()
327
+ cv2.rectangle(ov, (x0, 0), (x0 + crop_w, crop_h), (0, 255, 255), 3)
328
+ for (bx, by, bw, bh) in boxes_per_frame[min(i, n - 1)]:
329
+ cv2.rectangle(ov, (bx, by), (bx + bw, by + bh), (0, 255, 0), 2)
330
+ cv2.circle(ov, (cx, crop_h // 2), 8, (0, 0, 255), -1)
331
+ cv2.putText(ov, f"{args.mode} center x={cx}", (x0 + 8, 30),
332
+ cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
333
+ ov_writer.write(ov)
334
+ i += 1
335
+ finally:
336
+ # Always release so an error mid-render never leaks handles or leaves a
337
+ # locked/partial file (the caller ffprobe-validates and falls back).
338
+ cap.release()
339
+ writer.release()
340
+ if ov_writer is not None:
341
+ ov_writer.release()
342
+
343
+ if written == 0:
344
+ sys.stderr.write("reframe_ball: no frames written\n")
345
+ return 4
346
+ return 0
347
+
348
+
349
+ if __name__ == "__main__":
350
+ try:
351
+ sys.exit(main())
352
+ except Exception as exc: # noqa: BLE001
353
+ sys.stderr.write(f"reframe_ball: unexpected error: {exc}\n")
354
+ sys.exit(1)
@@ -1,10 +1,13 @@
1
1
  import { Stream } from "../../domain/entities/Stream";
2
+ import { AdSpec } from "../../domain/events/StreamEvent";
3
+ export { AdSpec };
2
4
  export interface StartStreamRequest {
3
5
  cameraUrl: string;
4
6
  streamKey: string;
5
7
  courtId: string;
6
8
  detectAudio?: boolean;
7
9
  isScorecardActivated?: boolean;
10
+ ads?: AdSpec[];
8
11
  }
9
12
  export interface StartStreamResponse {
10
13
  streamId: string;
@@ -33,5 +36,4 @@ type TrueOrFalse<T> = {
33
36
  };
34
37
  export type ValidationEvent = TrueOrFalse<StartStreamEvent>;
35
38
  export type ShouldStartStream = TrueOrFalse<StartStreamResponse>;
36
- export {};
37
39
  //# sourceMappingURL=StartStreamUseCase.types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"StartStreamUseCase.types.d.ts","sourceRoot":"","sources":["../../../../src/application/interfaces/StartStreamUseCase.types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAC;AAEtD,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,oBAAY,YAAY;IACtB,wBAAwB,6BAA6B;IACrD,0BAA0B,+BAA+B;IACzD,eAAe,oBAAoB;IACnC,0BAA0B,+BAA+B;IACzD,qBAAqB,0BAA0B;CAChD;AAED,MAAM,MAAM,gBAAgB,GACxB;IACE,MAAM,EAAE,YAAY,CAAC,wBAAwB,CAAC;IAC9C,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB,GACD;IACE,MAAM,EACF,YAAY,CAAC,0BAA0B,GACvC,YAAY,CAAC,eAAe,GAC5B,YAAY,CAAC,0BAA0B,GACvC,YAAY,CAAC,qBAAqB,CAAC;IACvC,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEN,KAAK,WAAW,CAAC,CAAC,IAAI;IAAE,OAAO,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,CAAC,CAAA;CAAE,CAAC;AAEtE,MAAM,MAAM,eAAe,GAAG,WAAW,CAAC,gBAAgB,CAAC,CAAC;AAE5D,MAAM,MAAM,iBAAiB,GAAG,WAAW,CAAC,mBAAmB,CAAC,CAAC"}
1
+ {"version":3,"file":"StartStreamUseCase.types.d.ts","sourceRoot":"","sources":["../../../../src/application/interfaces/StartStreamUseCase.types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAC;AACtD,OAAO,EAAE,MAAM,EAAE,MAAM,iCAAiC,CAAC;AACzD,OAAO,EAAE,MAAM,EAAE,CAAC;AAElB,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,oBAAY,YAAY;IACtB,wBAAwB,6BAA6B;IACrD,0BAA0B,+BAA+B;IACzD,eAAe,oBAAoB;IACnC,0BAA0B,+BAA+B;IACzD,qBAAqB,0BAA0B;CAChD;AAED,MAAM,MAAM,gBAAgB,GACxB;IACE,MAAM,EAAE,YAAY,CAAC,wBAAwB,CAAC;IAC9C,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB,GACD;IACE,MAAM,EACF,YAAY,CAAC,0BAA0B,GACvC,YAAY,CAAC,eAAe,GAC5B,YAAY,CAAC,0BAA0B,GACvC,YAAY,CAAC,qBAAqB,CAAC;IACvC,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEN,KAAK,WAAW,CAAC,CAAC,IAAI;IAAE,OAAO,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,CAAC,CAAA;CAAE,CAAC;AAEtE,MAAM,MAAM,eAAe,GAAG,WAAW,CAAC,gBAAgB,CAAC,CAAC;AAE5D,MAAM,MAAM,iBAAiB,GAAG,WAAW,CAAC,mBAAmB,CAAC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"StartStreamUseCase.types.js","sourceRoot":"","sources":["../../../../src/application/interfaces/StartStreamUseCase.types.ts"],"names":[],"mappings":";;;AAgBA,IAAY,YAMX;AAND,WAAY,YAAY;IACtB,qEAAqD,CAAA;IACrD,yEAAyD,CAAA;IACzD,mDAAmC,CAAA;IACnC,yEAAyD,CAAA;IACzD,+DAA+C,CAAA;AACjD,CAAC,EANW,YAAY,4BAAZ,YAAY,QAMvB"}
1
+ {"version":3,"file":"StartStreamUseCase.types.js","sourceRoot":"","sources":["../../../../src/application/interfaces/StartStreamUseCase.types.ts"],"names":[],"mappings":";;;AAmBA,IAAY,YAMX;AAND,WAAY,YAAY;IACtB,qEAAqD,CAAA;IACrD,yEAAyD,CAAA;IACzD,mDAAmC,CAAA;IACnC,yEAAyD,CAAA;IACzD,+DAA+C,CAAA;AACjD,CAAC,EANW,YAAY,4BAAZ,YAAY,QAMvB"}
@@ -1,6 +1,27 @@
1
1
  export declare class HttpClient {
2
2
  private readonly config;
3
+ private backendHeaders;
3
4
  goLiveYouTube(groundId: string, courtId: string, streamKey: string): Promise<Response>;
5
+ /**
6
+ * Forward a court's current scoreboard state to the backend, which writes it
7
+ * to Supabase (the single source of truth the overlay already reads).
8
+ *
9
+ * ── WHY VIA THE BACKEND AND NOT SUPABASE DIRECTLY ──
10
+ * Writing Supabase from the box would need write credentials on every court
11
+ * machine. This reuses the x-streaming-api-key every box already has, and the
12
+ * backend validates the court actually belongs to this ground — a check the
13
+ * shared device key cannot make on its own.
14
+ *
15
+ * The state is ABSOLUTE, never a delta: mesh delivery is best-effort with no
16
+ * retry, so a dropped packet must be self-healing on the next update.
17
+ */
18
+ postScoreboard(groundId: string, courtId: string, body: {
19
+ mode?: string;
20
+ redScore: string;
21
+ blueScore: string;
22
+ redGames?: number;
23
+ blueGames?: number;
24
+ }): Promise<Response>;
4
25
  sendHeartbeat(groundId: string): Promise<Response>;
5
26
  }
6
27
  //# sourceMappingURL=HttpClient.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"HttpClient.d.ts","sourceRoot":"","sources":["../../../../src/application/services/HttpClient.ts"],"names":[],"mappings":"AAEA,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA8B;IAE/C,aAAa,CACjB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,QAAQ,CAAC;IA0Dd,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;CAyBzD"}
1
+ {"version":3,"file":"HttpClient.d.ts","sourceRoot":"","sources":["../../../../src/application/services/HttpClient.ts"],"names":[],"mappings":"AAEA,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA8B;IAKrD,OAAO,CAAC,cAAc;IAOhB,aAAa,CACjB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,QAAQ,CAAC;IAwDpB;;;;;;;;;;;;OAYG;IACG,cAAc,CAClB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE;QACJ,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,GACA,OAAO,CAAC,QAAQ,CAAC;IAYd,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;CAyBzD"}
@@ -6,6 +6,16 @@ class HttpClient {
6
6
  constructor() {
7
7
  this.config = Config_1.Config.getInstance().get();
8
8
  }
9
+ // Base headers for DropShot backend calls, including the server-to-server
10
+ // auth key when configured. Sent only when STREAMING_API_KEY is set, so this
11
+ // stays backward-compatible with a backend that doesn't yet require it.
12
+ backendHeaders(extra = {}) {
13
+ const headers = { ...extra };
14
+ const key = this.config.server.streamingApiKey;
15
+ if (key)
16
+ headers["x-streaming-api-key"] = key;
17
+ return headers;
18
+ }
9
19
  async goLiveYouTube(groundId, courtId, streamKey) {
10
20
  // GET request to notify YouTube to go live
11
21
  const url = `${this.config.server.baseUrl}/api/v1/padel-grounds/${groundId}/courts/${courtId}/go-live/${streamKey}`;
@@ -17,9 +27,7 @@ class HttpClient {
17
27
  try {
18
28
  const response = await fetch(url, {
19
29
  method: "GET",
20
- headers: {
21
- "Content-Type": "application/json",
22
- },
30
+ headers: this.backendHeaders({ "Content-Type": "application/json" }),
23
31
  });
24
32
  // If we get a non-5xx response, return it (success or non-retryable error)
25
33
  if (response.status < 500) {
@@ -55,16 +63,40 @@ class HttpClient {
55
63
  }
56
64
  }
57
65
  }
66
+ /**
67
+ * Forward a court's current scoreboard state to the backend, which writes it
68
+ * to Supabase (the single source of truth the overlay already reads).
69
+ *
70
+ * ── WHY VIA THE BACKEND AND NOT SUPABASE DIRECTLY ──
71
+ * Writing Supabase from the box would need write credentials on every court
72
+ * machine. This reuses the x-streaming-api-key every box already has, and the
73
+ * backend validates the court actually belongs to this ground — a check the
74
+ * shared device key cannot make on its own.
75
+ *
76
+ * The state is ABSOLUTE, never a delta: mesh delivery is best-effort with no
77
+ * retry, so a dropped packet must be self-healing on the next update.
78
+ */
79
+ async postScoreboard(groundId, courtId, body) {
80
+ const url = `${this.config.server.baseUrl}/api/v1/padel-grounds/${groundId}/courts/${courtId}/scoreboard`;
81
+ return fetch(url, {
82
+ method: "POST",
83
+ headers: this.backendHeaders({
84
+ accept: "application/json",
85
+ "Content-Type": "application/json",
86
+ }),
87
+ body: JSON.stringify(body),
88
+ });
89
+ }
58
90
  async sendHeartbeat(groundId) {
59
91
  // POST request to send heartbeat
60
92
  const url = `${this.config.server.baseUrl}/api/v1/padel-grounds/heartbeat`;
61
93
  try {
62
94
  const response = await fetch(url, {
63
95
  method: "POST",
64
- headers: {
96
+ headers: this.backendHeaders({
65
97
  "accept": "application/json",
66
98
  "Content-Type": "application/json",
67
- },
99
+ }),
68
100
  body: JSON.stringify({
69
101
  groundId: groundId
70
102
  }),
@@ -1 +1 @@
1
- {"version":3,"file":"HttpClient.js","sourceRoot":"","sources":["../../../../src/application/services/HttpClient.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"HttpClient.js","sourceRoot":"","sources":["../../../../src/application/services/HttpClient.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"StreamManagerService.d.ts","sourceRoot":"","sources":["../../../../src/application/services/StreamManagerService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,4CAA4C,CAAC;AAC9E,OAAO,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAC;AACpE,OAAO,EAAE,UAAU,EAAE,MAAM,kCAAkC,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AAEnE,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC9C,OAAO,EAAE,MAAM,EAAE,MAAM,oCAAoC,CAAC;AAE5D,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAI1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,iDAAiD,CAAC;AAGnF,qBAAa,oBAAoB;IAK7B,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,kBAAkB;IACnC,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IAClC,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAC;IAZpC,OAAO,CAAC,mBAAmB,CAAC,CAAiB;IAC7C,OAAO,CAAC,SAAS,CAAS;gBAGP,gBAAgB,EAAE,gBAAgB,EAClC,aAAa,EAAE,aAAa,EAC5B,UAAU,EAAE,UAAU,EACtB,kBAAkB,EAAE,kBAAkB,EACtC,iBAAiB,EAAE,iBAAiB,EACpC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,UAAU,EACtB,gBAAgB,CAAC,EAAE,gBAAgB,YAAA;IAGzC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAsCtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YA8BpB,gBAAgB;YAsBhB,cAAc;YA2Dd,wBAAwB;IA8BtC,OAAO,CAAC,qBAAqB;YAQf,iBAAiB;YAsBjB,gBAAgB;YAsBhB,eAAe;IA8B7B,OAAO,CAAC,gBAAgB;YAsBV,kBAAkB;YAuDlB,cAAc;YAiCd,sBAAsB;YAwFtB,YAAY;IAiCb,SAAS,IAAI,OAAO,CAAC;QAChC,SAAS,EAAE,OAAO,CAAC;QACnB,YAAY,EAAE,OAAO,CAAC;QACtB,cAAc,EAAE,MAAM,CAAC;QACvB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;CAWH"}
1
+ {"version":3,"file":"StreamManagerService.d.ts","sourceRoot":"","sources":["../../../../src/application/services/StreamManagerService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,4CAA4C,CAAC;AAC9E,OAAO,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAC;AACpE,OAAO,EAAE,UAAU,EAAE,MAAM,kCAAkC,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AAEnE,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC9C,OAAO,EAAE,MAAM,EAAE,MAAM,oCAAoC,CAAC;AAE5D,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAI1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,iDAAiD,CAAC;AAGnF,qBAAa,oBAAoB;IAK7B,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,kBAAkB;IACnC,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IAClC,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAC;IAZpC,OAAO,CAAC,mBAAmB,CAAC,CAAiB;IAC7C,OAAO,CAAC,SAAS,CAAS;gBAGP,gBAAgB,EAAE,gBAAgB,EAClC,aAAa,EAAE,aAAa,EAC5B,UAAU,EAAE,UAAU,EACtB,kBAAkB,EAAE,kBAAkB,EACtC,iBAAiB,EAAE,iBAAiB,EACpC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,UAAU,EACtB,gBAAgB,CAAC,EAAE,gBAAgB,YAAA;IAGzC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAuCtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YA8BpB,gBAAgB;YAsBhB,cAAc;YA2Dd,wBAAwB;IA8BtC,OAAO,CAAC,qBAAqB;YAQf,iBAAiB;YAsBjB,gBAAgB;YAuBhB,eAAe;IA8B7B,OAAO,CAAC,gBAAgB;YAsBV,kBAAkB;YAuDlB,cAAc;YAiCd,sBAAsB;YAwFtB,YAAY;IAiCb,SAAS,IAAI,OAAO,CAAC;QAChC,SAAS,EAAE,OAAO,CAAC;QACnB,YAAY,EAAE,OAAO,CAAC;QACtB,cAAc,EAAE,MAAM,CAAC;QACvB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;CAWH"}
@@ -73,6 +73,7 @@ class StreamManagerService {
73
73
  ...sseConfig,
74
74
  groundId: this.config.get().groundInfo.groundId,
75
75
  baseUrl: this.config.get().server.baseUrl,
76
+ streamingApiKey: this.config.get().server.streamingApiKey,
76
77
  });
77
78
  this.logger.info("Stream Manager Service started successfully");
78
79
  }
@@ -236,6 +237,7 @@ class StreamManagerService {
236
237
  courtId: event.courtId,
237
238
  detectAudio: true,
238
239
  isScorecardActivated: event.isScorecardActivated,
240
+ ads: event.ads,
239
241
  }, this.stopStreamUseCase);
240
242
  // If stream started successfully (has a streamId), subscribe to updates
241
243
  if (result && result.streamId && this.supabaseListener && event.isScorecardActivated) {