streamer-node 1.0.0

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 (100) hide show
  1. package/README.md +11 -0
  2. package/dist/public/client.png +0 -0
  3. package/dist/public/ds.png +0 -0
  4. package/dist/src/application/interfaces/Logger.d.ts +7 -0
  5. package/dist/src/application/interfaces/Logger.d.ts.map +1 -0
  6. package/dist/src/application/interfaces/Logger.js +3 -0
  7. package/dist/src/application/interfaces/Logger.js.map +1 -0
  8. package/dist/src/application/interfaces/StartStreamUseCase.types.d.ts +36 -0
  9. package/dist/src/application/interfaces/StartStreamUseCase.types.d.ts.map +1 -0
  10. package/dist/src/application/interfaces/StartStreamUseCase.types.js +12 -0
  11. package/dist/src/application/interfaces/StartStreamUseCase.types.js.map +1 -0
  12. package/dist/src/application/services/HttpClient.d.ts +5 -0
  13. package/dist/src/application/services/HttpClient.d.ts.map +1 -0
  14. package/dist/src/application/services/HttpClient.js +30 -0
  15. package/dist/src/application/services/HttpClient.js.map +1 -0
  16. package/dist/src/application/services/StreamManagerService.d.ts +38 -0
  17. package/dist/src/application/services/StreamManagerService.d.ts.map +1 -0
  18. package/dist/src/application/services/StreamManagerService.js +298 -0
  19. package/dist/src/application/services/StreamManagerService.js.map +1 -0
  20. package/dist/src/application/use-cases/StartStreamUseCase.d.ts +27 -0
  21. package/dist/src/application/use-cases/StartStreamUseCase.d.ts.map +1 -0
  22. package/dist/src/application/use-cases/StartStreamUseCase.js +195 -0
  23. package/dist/src/application/use-cases/StartStreamUseCase.js.map +1 -0
  24. package/dist/src/application/use-cases/StopStreamUseCase.d.ts +18 -0
  25. package/dist/src/application/use-cases/StopStreamUseCase.d.ts.map +1 -0
  26. package/dist/src/application/use-cases/StopStreamUseCase.js +96 -0
  27. package/dist/src/application/use-cases/StopStreamUseCase.js.map +1 -0
  28. package/dist/src/domain/entities/Stream.d.ts +40 -0
  29. package/dist/src/domain/entities/Stream.d.ts.map +1 -0
  30. package/dist/src/domain/entities/Stream.js +115 -0
  31. package/dist/src/domain/entities/Stream.js.map +1 -0
  32. package/dist/src/domain/events/StreamEvent.d.ts +48 -0
  33. package/dist/src/domain/events/StreamEvent.d.ts.map +1 -0
  34. package/dist/src/domain/events/StreamEvent.js +3 -0
  35. package/dist/src/domain/events/StreamEvent.js.map +1 -0
  36. package/dist/src/domain/repositories/StreamRepository.d.ts +41 -0
  37. package/dist/src/domain/repositories/StreamRepository.d.ts.map +1 -0
  38. package/dist/src/domain/repositories/StreamRepository.js +3 -0
  39. package/dist/src/domain/repositories/StreamRepository.js.map +1 -0
  40. package/dist/src/domain/services/FFmpegService.d.ts +42 -0
  41. package/dist/src/domain/services/FFmpegService.d.ts.map +1 -0
  42. package/dist/src/domain/services/FFmpegService.js +3 -0
  43. package/dist/src/domain/services/FFmpegService.js.map +1 -0
  44. package/dist/src/domain/services/SSEService.d.ts +42 -0
  45. package/dist/src/domain/services/SSEService.d.ts.map +1 -0
  46. package/dist/src/domain/services/SSEService.js +3 -0
  47. package/dist/src/domain/services/SSEService.js.map +1 -0
  48. package/dist/src/domain/value-objects/StreamId.d.ts +10 -0
  49. package/dist/src/domain/value-objects/StreamId.d.ts.map +1 -0
  50. package/dist/src/domain/value-objects/StreamId.js +31 -0
  51. package/dist/src/domain/value-objects/StreamId.js.map +1 -0
  52. package/dist/src/domain/value-objects/StreamState.d.ts +12 -0
  53. package/dist/src/domain/value-objects/StreamState.d.ts.map +1 -0
  54. package/dist/src/domain/value-objects/StreamState.js +35 -0
  55. package/dist/src/domain/value-objects/StreamState.js.map +1 -0
  56. package/dist/src/domain/value-objects/StreamUrl.d.ts +12 -0
  57. package/dist/src/domain/value-objects/StreamUrl.d.ts.map +1 -0
  58. package/dist/src/domain/value-objects/StreamUrl.js +48 -0
  59. package/dist/src/domain/value-objects/StreamUrl.js.map +1 -0
  60. package/dist/src/infrastructure/config/Config.d.ts +42 -0
  61. package/dist/src/infrastructure/config/Config.d.ts.map +1 -0
  62. package/dist/src/infrastructure/config/Config.js +113 -0
  63. package/dist/src/infrastructure/config/Config.js.map +1 -0
  64. package/dist/src/infrastructure/logging/ConsoleLogger.d.ts +12 -0
  65. package/dist/src/infrastructure/logging/ConsoleLogger.d.ts.map +1 -0
  66. package/dist/src/infrastructure/logging/ConsoleLogger.js +41 -0
  67. package/dist/src/infrastructure/logging/ConsoleLogger.js.map +1 -0
  68. package/dist/src/infrastructure/logging/LogBuffer.d.ts +20 -0
  69. package/dist/src/infrastructure/logging/LogBuffer.d.ts.map +1 -0
  70. package/dist/src/infrastructure/logging/LogBuffer.js +95 -0
  71. package/dist/src/infrastructure/logging/LogBuffer.js.map +1 -0
  72. package/dist/src/infrastructure/logging/LogShippingService.d.ts +23 -0
  73. package/dist/src/infrastructure/logging/LogShippingService.d.ts.map +1 -0
  74. package/dist/src/infrastructure/logging/LogShippingService.js +128 -0
  75. package/dist/src/infrastructure/logging/LogShippingService.js.map +1 -0
  76. package/dist/src/infrastructure/logging/RemoteLogger.d.ts +37 -0
  77. package/dist/src/infrastructure/logging/RemoteLogger.d.ts.map +1 -0
  78. package/dist/src/infrastructure/logging/RemoteLogger.js +120 -0
  79. package/dist/src/infrastructure/logging/RemoteLogger.js.map +1 -0
  80. package/dist/src/infrastructure/logging/types/LogTypes.d.ts +27 -0
  81. package/dist/src/infrastructure/logging/types/LogTypes.d.ts.map +1 -0
  82. package/dist/src/infrastructure/logging/types/LogTypes.js +3 -0
  83. package/dist/src/infrastructure/logging/types/LogTypes.js.map +1 -0
  84. package/dist/src/infrastructure/repositories/FileSystemStreamRepository.d.ts +22 -0
  85. package/dist/src/infrastructure/repositories/FileSystemStreamRepository.d.ts.map +1 -0
  86. package/dist/src/infrastructure/repositories/FileSystemStreamRepository.js +212 -0
  87. package/dist/src/infrastructure/repositories/FileSystemStreamRepository.js.map +1 -0
  88. package/dist/src/infrastructure/services/NodeFFmpegService.d.ts +17 -0
  89. package/dist/src/infrastructure/services/NodeFFmpegService.d.ts.map +1 -0
  90. package/dist/src/infrastructure/services/NodeFFmpegService.js +306 -0
  91. package/dist/src/infrastructure/services/NodeFFmpegService.js.map +1 -0
  92. package/dist/src/infrastructure/services/NodeSSEService.d.ts +30 -0
  93. package/dist/src/infrastructure/services/NodeSSEService.d.ts.map +1 -0
  94. package/dist/src/infrastructure/services/NodeSSEService.js +268 -0
  95. package/dist/src/infrastructure/services/NodeSSEService.js.map +1 -0
  96. package/dist/src/main.d.ts +3 -0
  97. package/dist/src/main.d.ts.map +1 -0
  98. package/dist/src/main.js +87 -0
  99. package/dist/src/main.js.map +1 -0
  100. package/package.json +52 -0
@@ -0,0 +1,195 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.StartStreamUseCase = void 0;
4
+ const Stream_1 = require("../../domain/entities/Stream");
5
+ const StreamId_1 = require("../../domain/value-objects/StreamId");
6
+ const StreamUrl_1 = require("../../domain/value-objects/StreamUrl");
7
+ const Config_1 = require("../../infrastructure/config/Config");
8
+ const StreamState_1 = require("../../domain/value-objects/StreamState");
9
+ const StartStreamUseCase_types_1 = require("../interfaces/StartStreamUseCase.types");
10
+ class StartStreamUseCase {
11
+ constructor(streamRepository, ffmpegService, logger, httpClient) {
12
+ this.streamRepository = streamRepository;
13
+ this.ffmpegService = ffmpegService;
14
+ this.logger = logger;
15
+ this.httpClient = httpClient;
16
+ this.config = Config_1.Config.getInstance().get();
17
+ }
18
+ async shouldStartNewStream(event, stopStream) {
19
+ // Find stream by camera URL and stream key
20
+ const streams = await this.streamRepository.findAll();
21
+ const runningStreams = streams.filter((stream) => stream.courtId === event.courtId && stream.state === StreamState_1.StreamState.RUNNING);
22
+ const action = await this.validateStreamEvent(runningStreams, event);
23
+ if (action.isValid)
24
+ return { isValid: true }; // return true for happy case
25
+ // handle other event types
26
+ const streamEvent = action.data;
27
+ switch (streamEvent.action) {
28
+ // stop all running streams, and run against the only one event received
29
+ case StartStreamUseCase_types_1.StreamAction.MULTIPLE_STREAMS_RUNNING:
30
+ await Promise.all(streamEvent.streamList.map((stream) => stopStream({
31
+ streamId: stream.id.toString(),
32
+ })));
33
+ return { isValid: true };
34
+ // update the file with the failed state against process
35
+ case StartStreamUseCase_types_1.StreamAction.STREAM_RUNNING_WITHOUT_PID:
36
+ case StartStreamUseCase_types_1.StreamAction.DEAD_PROCESS_DETECTED:
37
+ streamEvent.stream.markAsFailed();
38
+ await this.streamRepository.save(streamEvent.stream);
39
+ return { isValid: true };
40
+ // ignore the event is received for a stream that is already running
41
+ case StartStreamUseCase_types_1.StreamAction.DUPLICATE_EVENT:
42
+ return {
43
+ isValid: false,
44
+ data: {
45
+ streamId: streamEvent.stream.id.toString(),
46
+ hasAudio: streamEvent.stream.hasAudio,
47
+ processId: streamEvent.stream.processId,
48
+ },
49
+ };
50
+ // restart the stream since the youtube stream key is no longer valid
51
+ case StartStreamUseCase_types_1.StreamAction.INVALID_YOUTUBE_STREAM_KEY:
52
+ await stopStream({
53
+ streamId: streamEvent.stream.id.toString(),
54
+ });
55
+ return { isValid: true };
56
+ }
57
+ }
58
+ /**
59
+ * Validates if a new stream can be started by checking:
60
+ * 1. No existing streams (valid to start)
61
+ * 2. Multiple streams on same court (invalid)
62
+ * 3. Stream exists but missing process ID (invalid)
63
+ * 4. Different stream key than existing (invalid)
64
+ * 5. Process already running (duplicate event)
65
+ * 6. Process dead but stream marked as running (stale state)
66
+ */
67
+ async validateStreamEvent(runningStreams, event) {
68
+ const targetStream = runningStreams.length > 0 ? runningStreams[0] : null;
69
+ if (!targetStream) {
70
+ // there is no running stream. Safe to say stream can be started
71
+ return {
72
+ isValid: true,
73
+ };
74
+ }
75
+ // check if there are multiple streams on a single court - ideally this should never be the case
76
+ if (runningStreams.length > 1) {
77
+ return {
78
+ isValid: false,
79
+ data: {
80
+ action: StartStreamUseCase_types_1.StreamAction.MULTIPLE_STREAMS_RUNNING,
81
+ streamList: runningStreams,
82
+ },
83
+ };
84
+ }
85
+ // stream file info update issue - stream was saved without pid - ideally this should never be the case
86
+ if (!targetStream.processId) {
87
+ return {
88
+ isValid: false,
89
+ data: {
90
+ action: StartStreamUseCase_types_1.StreamAction.STREAM_RUNNING_WITHOUT_PID,
91
+ stream: targetStream,
92
+ },
93
+ };
94
+ }
95
+ // check if the incoming event is not a duplicate
96
+ if (targetStream.streamKey !== event.streamKey) {
97
+ return {
98
+ isValid: false,
99
+ data: {
100
+ action: StartStreamUseCase_types_1.StreamAction.INVALID_YOUTUBE_STREAM_KEY,
101
+ stream: targetStream,
102
+ },
103
+ };
104
+ }
105
+ const ffmpegProcess = await this.ffmpegService.isProcessRunning(targetStream.processId);
106
+ // check if the process is already running - this means the event is duplicate
107
+ if (ffmpegProcess) {
108
+ return {
109
+ isValid: false,
110
+ data: {
111
+ action: StartStreamUseCase_types_1.StreamAction.DUPLICATE_EVENT,
112
+ stream: targetStream,
113
+ },
114
+ };
115
+ }
116
+ return {
117
+ isValid: false,
118
+ data: {
119
+ action: StartStreamUseCase_types_1.StreamAction.DEAD_PROCESS_DETECTED,
120
+ stream: targetStream,
121
+ },
122
+ };
123
+ }
124
+ async execute(request, stopProcess) {
125
+ // check if the stream should be started
126
+ const handleResponse = await this.shouldStartNewStream(request, stopProcess);
127
+ if (!handleResponse.isValid)
128
+ return handleResponse.data;
129
+ this.logger.info("Starting stream", {
130
+ cameraUrl: request.cameraUrl,
131
+ streamKey: request.streamKey,
132
+ courtId: request.courtId,
133
+ });
134
+ try {
135
+ // Create value objects
136
+ const streamId = StreamId_1.StreamId.create();
137
+ const cameraUrl = StreamUrl_1.StreamUrl.create(request.cameraUrl);
138
+ // Detect audio if requested
139
+ let hasAudio = false;
140
+ if (request.detectAudio) {
141
+ this.logger.info("Detecting audio for stream", {
142
+ streamId: streamId.value,
143
+ });
144
+ hasAudio = await this.ffmpegService.detectAudio(cameraUrl);
145
+ this.logger.info("Audio detection result", {
146
+ streamId: streamId.value,
147
+ hasAudio,
148
+ });
149
+ }
150
+ // Create stream entity
151
+ const stream = Stream_1.Stream.create(streamId, cameraUrl, request.streamKey, request.courtId, hasAudio);
152
+ // Start FFmpeg process
153
+ this.logger.info("Starting FFmpeg process", { streamId: streamId.value });
154
+ // Create PID update callback for retries
155
+ const onPidUpdate = async (newPid) => {
156
+ this.logger.info("Updating stream PID after retry", {
157
+ streamId: streamId.value,
158
+ oldPid: stream.processId,
159
+ newPid,
160
+ });
161
+ stream.updateProcessId(newPid);
162
+ await this.streamRepository.save(stream);
163
+ };
164
+ const ffmpegProcess = await this.ffmpegService.startStream(cameraUrl, request.streamKey, hasAudio, 5, // maxRetries
165
+ 5000, // retryDelayMs
166
+ onPidUpdate);
167
+ // Update stream with process ID
168
+ stream.start(ffmpegProcess.pid);
169
+ // Save stream state
170
+ await this.streamRepository.save(stream);
171
+ this.logger.info("Stream started successfully", {
172
+ streamId: streamId.value,
173
+ processId: ffmpegProcess.pid,
174
+ hasAudio,
175
+ });
176
+ // notify server that the stream has started to go live on YouTube
177
+ await this.httpClient.goLiveYouTube(this.config.groundInfo.groundId, stream.courtId, stream.streamKey);
178
+ return {
179
+ streamId: streamId.value,
180
+ processId: ffmpegProcess.pid,
181
+ hasAudio,
182
+ };
183
+ }
184
+ catch (error) {
185
+ this.logger.error("Failed to start stream", {
186
+ error: error instanceof Error ? error.message : String(error),
187
+ cameraUrl: request.cameraUrl,
188
+ streamKey: request.streamKey,
189
+ });
190
+ throw error;
191
+ }
192
+ }
193
+ }
194
+ exports.StartStreamUseCase = StartStreamUseCase;
195
+ //# sourceMappingURL=StartStreamUseCase.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StartStreamUseCase.js","sourceRoot":"","sources":["../../../../src/application/use-cases/StartStreamUseCase.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import { StreamRepository } from "../../domain/repositories/StreamRepository";
2
+ import { FFmpegService } from "../../domain/services/FFmpegService";
3
+ import { Logger } from "../interfaces/Logger";
4
+ export interface StopStreamRequest {
5
+ streamId: string;
6
+ }
7
+ export interface StopStreamResponse {
8
+ streamId: string;
9
+ stopped: boolean;
10
+ }
11
+ export declare class StopStreamUseCase {
12
+ private readonly streamRepository;
13
+ private readonly ffmpegService;
14
+ private readonly logger;
15
+ constructor(streamRepository: StreamRepository, ffmpegService: FFmpegService, logger: Logger);
16
+ execute(request: StopStreamRequest): Promise<StopStreamResponse>;
17
+ }
18
+ //# sourceMappingURL=StopStreamUseCase.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StopStreamUseCase.d.ts","sourceRoot":"","sources":["../../../../src/application/use-cases/StopStreamUseCase.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,4CAA4C,CAAC;AAC9E,OAAO,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAC;AACpE,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAE9C,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,qBAAa,iBAAiB;IAE1B,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAFN,gBAAgB,EAAE,gBAAgB,EAClC,aAAa,EAAE,aAAa,EAC5B,MAAM,EAAE,MAAM;IAGpB,OAAO,CAClB,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,kBAAkB,CAAC;CAuG/B"}
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.StopStreamUseCase = void 0;
4
+ const StreamId_1 = require("../../domain/value-objects/StreamId");
5
+ class StopStreamUseCase {
6
+ constructor(streamRepository, ffmpegService, logger) {
7
+ this.streamRepository = streamRepository;
8
+ this.ffmpegService = ffmpegService;
9
+ this.logger = logger;
10
+ }
11
+ async execute(request) {
12
+ this.logger.info("Stopping stream", { streamId: request.streamId });
13
+ try {
14
+ const streamId = StreamId_1.StreamId.fromString(request.streamId);
15
+ // Find the stream
16
+ const stream = await this.streamRepository.findById(streamId);
17
+ if (!stream) {
18
+ this.logger.warn("Stream not found", { streamId: request.streamId });
19
+ return { streamId: request.streamId, stopped: false };
20
+ }
21
+ // Stop FFmpeg process if it has a PID and the process is actually running
22
+ if (stream.processId) {
23
+ const isProcessRunning = await this.ffmpegService.isProcessRunning(stream.processId);
24
+ this.logger.info("Checking FFmpeg process status", {
25
+ streamId: request.streamId,
26
+ processId: stream.processId,
27
+ isProcessRunning,
28
+ streamState: stream.state,
29
+ });
30
+ if (isProcessRunning) {
31
+ this.logger.info("Stopping running FFmpeg process", {
32
+ streamId: request.streamId,
33
+ processId: stream.processId,
34
+ });
35
+ await this.ffmpegService.stopStream(stream.processId);
36
+ // Clear the process ID after successfully stopping the process
37
+ stream.clearProcessId();
38
+ this.logger.info("FFmpeg process stopped and process ID cleared", {
39
+ streamId: request.streamId,
40
+ processId: stream.processId,
41
+ });
42
+ }
43
+ else {
44
+ this.logger.info("FFmpeg process not running, skipping process termination", {
45
+ streamId: request.streamId,
46
+ processId: stream.processId,
47
+ });
48
+ // Clear the process ID since the process is not running anyway
49
+ stream.clearProcessId();
50
+ }
51
+ }
52
+ else {
53
+ this.logger.info("No process ID found for stream, skipping process termination", {
54
+ streamId: request.streamId,
55
+ });
56
+ }
57
+ // Update stream state - handle different states gracefully
58
+ if (stream.isRunning()) {
59
+ stream.stop();
60
+ }
61
+ else if (stream.isFailed()) {
62
+ // For failed streams, we've already cleaned up the process above
63
+ // The stream remains in failed state, but the process is properly terminated
64
+ this.logger.info("Stream was in failed state, process cleanup completed", {
65
+ streamId: request.streamId,
66
+ currentState: stream.state,
67
+ });
68
+ // Don't change the state - failed streams should remain failed until explicitly restarted
69
+ // The important part is that we've cleaned up any running processes
70
+ }
71
+ else {
72
+ this.logger.info("Stream already in stopped/pending state", {
73
+ streamId: request.streamId,
74
+ currentState: stream.state,
75
+ });
76
+ }
77
+ await this.streamRepository.save(stream);
78
+ this.logger.info("Stream stopped successfully", {
79
+ streamId: request.streamId,
80
+ });
81
+ return {
82
+ streamId: request.streamId,
83
+ stopped: true,
84
+ };
85
+ }
86
+ catch (error) {
87
+ this.logger.error("Failed to stop stream", {
88
+ error: error instanceof Error ? error.message : String(error),
89
+ streamId: request.streamId,
90
+ });
91
+ throw error;
92
+ }
93
+ }
94
+ }
95
+ exports.StopStreamUseCase = StopStreamUseCase;
96
+ //# sourceMappingURL=StopStreamUseCase.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StopStreamUseCase.js","sourceRoot":"","sources":["../../../../src/application/use-cases/StopStreamUseCase.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ import { StreamId } from "../value-objects/StreamId";
2
+ import { StreamUrl } from "../value-objects/StreamUrl";
3
+ import { StreamState } from "../value-objects/StreamState";
4
+ export interface StreamProps {
5
+ id: StreamId;
6
+ cameraUrl: StreamUrl;
7
+ streamKey: string;
8
+ courtId: string;
9
+ state: StreamState;
10
+ hasAudio: boolean;
11
+ processId?: number;
12
+ createdAt: Date;
13
+ updatedAt: Date;
14
+ }
15
+ export declare class Stream {
16
+ private props;
17
+ private constructor();
18
+ static create(id: StreamId, cameraUrl: StreamUrl, streamKey: string, courtId: string, hasAudio?: boolean): Stream;
19
+ static fromPersistence(props: StreamProps): Stream;
20
+ get id(): StreamId;
21
+ get cameraUrl(): StreamUrl;
22
+ get streamKey(): string;
23
+ get courtId(): string;
24
+ get state(): StreamState;
25
+ get hasAudio(): boolean;
26
+ get processId(): number | undefined;
27
+ get createdAt(): Date;
28
+ get updatedAt(): Date;
29
+ start(processId: number): void;
30
+ stop(): void;
31
+ markAsFailed(error?: string): void;
32
+ updateAudioDetection(hasAudio: boolean): void;
33
+ clearProcessId(): void;
34
+ updateProcessId(processId: number): void;
35
+ isRunning(): boolean;
36
+ isStopped(): boolean;
37
+ isFailed(): boolean;
38
+ toJSON(): any;
39
+ }
40
+ //# sourceMappingURL=Stream.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Stream.d.ts","sourceRoot":"","sources":["../../../../src/domain/entities/Stream.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAE3D,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,QAAQ,CAAC;IACb,SAAS,EAAE,SAAS,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,WAAW,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,qBAAa,MAAM;IACG,OAAO,CAAC,KAAK;IAAjC,OAAO;WAEO,MAAM,CAClB,EAAE,EAAE,QAAQ,EACZ,SAAS,EAAE,SAAS,EACpB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,QAAQ,GAAE,OAAe,GACxB,MAAM;WAcK,eAAe,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM;IAKzD,IAAW,EAAE,IAAI,QAAQ,CAExB;IAED,IAAW,SAAS,IAAI,SAAS,CAEhC;IAED,IAAW,SAAS,IAAI,MAAM,CAE7B;IAED,IAAW,OAAO,IAAI,MAAM,CAE3B;IAED,IAAW,KAAK,IAAI,WAAW,CAE9B;IAED,IAAW,QAAQ,IAAI,OAAO,CAE7B;IAED,IAAW,SAAS,IAAI,MAAM,GAAG,SAAS,CAEzC;IAED,IAAW,SAAS,IAAI,IAAI,CAE3B;IAED,IAAW,SAAS,IAAI,IAAI,CAE3B;IAGM,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAa9B,IAAI,IAAI,IAAI;IAUZ,YAAY,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI;IAMlC,oBAAoB,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI;IAK7C,cAAc,IAAI,IAAI;IAKtB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IASxC,SAAS,IAAI,OAAO;IAIpB,SAAS,IAAI,OAAO;IAIpB,QAAQ,IAAI,OAAO;IAInB,MAAM,IAAI,GAAG;CAarB"}
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Stream = void 0;
4
+ const StreamState_1 = require("../value-objects/StreamState");
5
+ class Stream {
6
+ constructor(props) {
7
+ this.props = props;
8
+ }
9
+ static create(id, cameraUrl, streamKey, courtId, hasAudio = false) {
10
+ const now = new Date();
11
+ return new Stream({
12
+ id,
13
+ cameraUrl,
14
+ streamKey,
15
+ courtId,
16
+ state: StreamState_1.StreamState.PENDING,
17
+ hasAudio,
18
+ createdAt: now,
19
+ updatedAt: now,
20
+ });
21
+ }
22
+ static fromPersistence(props) {
23
+ return new Stream(props);
24
+ }
25
+ // Getters
26
+ get id() {
27
+ return this.props.id;
28
+ }
29
+ get cameraUrl() {
30
+ return this.props.cameraUrl;
31
+ }
32
+ get streamKey() {
33
+ return this.props.streamKey;
34
+ }
35
+ get courtId() {
36
+ return this.props.courtId;
37
+ }
38
+ get state() {
39
+ return this.props.state;
40
+ }
41
+ get hasAudio() {
42
+ return this.props.hasAudio;
43
+ }
44
+ get processId() {
45
+ return this.props.processId;
46
+ }
47
+ get createdAt() {
48
+ return this.props.createdAt;
49
+ }
50
+ get updatedAt() {
51
+ return this.props.updatedAt;
52
+ }
53
+ // Business methods
54
+ start(processId) {
55
+ if (this.props.state !== StreamState_1.StreamState.PENDING &&
56
+ this.props.state !== StreamState_1.StreamState.STOPPED) {
57
+ throw new Error(`Cannot start stream in ${this.props.state} state`);
58
+ }
59
+ this.props.state = StreamState_1.StreamState.RUNNING;
60
+ this.props.processId = processId;
61
+ this.props.updatedAt = new Date();
62
+ }
63
+ stop() {
64
+ if (this.props.state !== StreamState_1.StreamState.RUNNING) {
65
+ throw new Error(`Cannot stop stream in ${this.props.state} state`);
66
+ }
67
+ this.props.state = StreamState_1.StreamState.STOPPED;
68
+ this.props.processId = undefined;
69
+ this.props.updatedAt = new Date();
70
+ }
71
+ markAsFailed(error) {
72
+ this.props.state = StreamState_1.StreamState.FAILED;
73
+ // Keep processId so we can still terminate the process later
74
+ this.props.updatedAt = new Date();
75
+ }
76
+ updateAudioDetection(hasAudio) {
77
+ this.props.hasAudio = hasAudio;
78
+ this.props.updatedAt = new Date();
79
+ }
80
+ clearProcessId() {
81
+ this.props.processId = undefined;
82
+ this.props.updatedAt = new Date();
83
+ }
84
+ updateProcessId(processId) {
85
+ if (this.props.state !== StreamState_1.StreamState.RUNNING) {
86
+ throw new Error(`Cannot update processId for stream in ${this.props.state} state`);
87
+ }
88
+ this.props.processId = processId;
89
+ this.props.updatedAt = new Date();
90
+ }
91
+ isRunning() {
92
+ return this.props.state === StreamState_1.StreamState.RUNNING;
93
+ }
94
+ isStopped() {
95
+ return this.props.state === StreamState_1.StreamState.STOPPED;
96
+ }
97
+ isFailed() {
98
+ return this.props.state === StreamState_1.StreamState.FAILED;
99
+ }
100
+ toJSON() {
101
+ return {
102
+ id: this.props.id.value,
103
+ cameraUrl: this.props.cameraUrl.value,
104
+ streamKey: this.props.streamKey,
105
+ courtId: this.props.courtId,
106
+ state: this.props.state,
107
+ hasAudio: this.props.hasAudio,
108
+ processId: this.props.processId,
109
+ createdAt: this.props.createdAt.toISOString(),
110
+ updatedAt: this.props.updatedAt.toISOString(),
111
+ };
112
+ }
113
+ }
114
+ exports.Stream = Stream;
115
+ //# sourceMappingURL=Stream.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Stream.js","sourceRoot":"","sources":["../../../../src/domain/entities/Stream.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ import { StreamId } from "../value-objects/StreamId";
2
+ export interface DomainEvent {
3
+ readonly eventId: string;
4
+ readonly occurredOn: Date;
5
+ readonly eventType: string;
6
+ }
7
+ export interface StreamStartedEvent extends DomainEvent {
8
+ readonly eventType: "StreamStarted";
9
+ readonly streamId: StreamId;
10
+ readonly processId: number;
11
+ readonly cameraUrl: string;
12
+ readonly streamKey: string;
13
+ readonly courtId: string;
14
+ }
15
+ export interface StreamStoppedEvent extends DomainEvent {
16
+ readonly eventType: "StreamStopped";
17
+ readonly streamId: StreamId;
18
+ readonly courtId: string;
19
+ readonly reason?: string;
20
+ }
21
+ export interface StreamFailedEvent extends DomainEvent {
22
+ readonly eventType: "StreamFailed";
23
+ readonly streamId: StreamId;
24
+ readonly courtId: string;
25
+ readonly error: string;
26
+ readonly processId?: number;
27
+ }
28
+ export interface AudioDetectedEvent extends DomainEvent {
29
+ readonly eventType: "AudioDetected";
30
+ readonly streamId: StreamId;
31
+ readonly courtId: string;
32
+ readonly hasAudio: boolean;
33
+ }
34
+ export interface SSEConnectionEvent extends DomainEvent {
35
+ readonly eventType: "SSEConnectionEvent";
36
+ readonly status: "connected" | "disconnected" | "reconnecting";
37
+ readonly retryCount?: number;
38
+ }
39
+ export interface SSEStreamEvent extends DomainEvent {
40
+ readonly eventType: "SSEStreamEvent";
41
+ readonly courtId: string;
42
+ readonly action: "start" | "stop";
43
+ readonly cameraUrl: string;
44
+ readonly streamKey: string;
45
+ readonly reconciliationMode?: boolean;
46
+ }
47
+ export type StreamDomainEvent = StreamStartedEvent | StreamStoppedEvent | StreamFailedEvent | AudioDetectedEvent | SSEConnectionEvent | SSEStreamEvent;
48
+ //# sourceMappingURL=StreamEvent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StreamEvent.d.ts","sourceRoot":"","sources":["../../../../src/domain/events/StreamEvent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAErD,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,IAAI,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,kBAAmB,SAAQ,WAAW;IACrD,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;IACpC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,kBAAmB,SAAQ,WAAW;IACrD,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;IACpC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,iBAAkB,SAAQ,WAAW;IACpD,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC;IACnC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,kBAAmB,SAAQ,WAAW;IACrD,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;IACpC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,kBAAmB,SAAQ,WAAW;IACrD,QAAQ,CAAC,SAAS,EAAE,oBAAoB,CAAC;IACzC,QAAQ,CAAC,MAAM,EAAE,WAAW,GAAG,cAAc,GAAG,cAAc,CAAC;IAC/D,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,cAAe,SAAQ,WAAW;IACjD,QAAQ,CAAC,SAAS,EAAE,gBAAgB,CAAC;IACrC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,CAAC;IAClC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC;CACvC;AAED,MAAM,MAAM,iBAAiB,GACzB,kBAAkB,GAClB,kBAAkB,GAClB,iBAAiB,GACjB,kBAAkB,GAClB,kBAAkB,GAClB,cAAc,CAAC"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=StreamEvent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StreamEvent.js","sourceRoot":"","sources":["../../../../src/domain/events/StreamEvent.ts"],"names":[],"mappings":""}
@@ -0,0 +1,41 @@
1
+ import { Stream } from '../entities/Stream';
2
+ import { StreamId } from '../value-objects/StreamId';
3
+ export interface StreamRepository {
4
+ /**
5
+ * Save a stream to persistent storage
6
+ */
7
+ save(stream: Stream): Promise<void>;
8
+ /**
9
+ * Find a stream by its ID
10
+ */
11
+ findById(id: StreamId): Promise<Stream | null>;
12
+ /**
13
+ * Find all streams
14
+ */
15
+ findAll(): Promise<Stream[]>;
16
+ /**
17
+ * Find all running streams
18
+ */
19
+ findRunning(): Promise<Stream[]>;
20
+ /**
21
+ * Find streams by state
22
+ */
23
+ findByState(state: string): Promise<Stream[]>;
24
+ /**
25
+ * Delete a stream
26
+ */
27
+ delete(id: StreamId): Promise<void>;
28
+ /**
29
+ * Check if a stream exists
30
+ */
31
+ exists(id: StreamId): Promise<boolean>;
32
+ /**
33
+ * Get all stream IDs
34
+ */
35
+ getAllIds(): Promise<StreamId[]>;
36
+ /**
37
+ * Clear all streams (for testing/cleanup)
38
+ */
39
+ clear(): Promise<void>;
40
+ }
41
+ //# sourceMappingURL=StreamRepository.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StreamRepository.d.ts","sourceRoot":"","sources":["../../../../src/domain/repositories/StreamRepository.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAErD,MAAM,WAAW,gBAAgB;IAC/B;;OAEG;IACH,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEpC;;OAEG;IACH,QAAQ,CAAC,EAAE,EAAE,QAAQ,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAE/C;;OAEG;IACH,OAAO,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAE7B;;OAEG;IACH,WAAW,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAEjC;;OAEG;IACH,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAE9C;;OAEG;IACH,MAAM,CAAC,EAAE,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEpC;;OAEG;IACH,MAAM,CAAC,EAAE,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAEvC;;OAEG;IACH,SAAS,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;IAEjC;;OAEG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=StreamRepository.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StreamRepository.js","sourceRoot":"","sources":["../../../../src/domain/repositories/StreamRepository.ts"],"names":[],"mappings":""}