@overshoot/sdk 0.1.0-alpha.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.
package/dist/index.js ADDED
@@ -0,0 +1,668 @@
1
+ 'use strict';
2
+
3
+ // src/client/errors.ts
4
+ var ApiError = class extends Error {
5
+ constructor(message, statusCode, requestId, details) {
6
+ super(message);
7
+ this.name = "ApiError";
8
+ this.statusCode = statusCode;
9
+ this.requestId = requestId;
10
+ this.details = details;
11
+ }
12
+ };
13
+ var UnauthorizedError = class extends ApiError {
14
+ constructor(message, requestId) {
15
+ super(message, 401, requestId);
16
+ this.name = "UnauthorizedError";
17
+ }
18
+ };
19
+ var ValidationError = class extends ApiError {
20
+ constructor(message, requestId, details) {
21
+ super(message, 422, requestId, details);
22
+ this.name = "ValidationError";
23
+ }
24
+ };
25
+ var NotFoundError = class extends ApiError {
26
+ constructor(message, requestId) {
27
+ super(message, 404, requestId);
28
+ this.name = "NotFoundError";
29
+ }
30
+ };
31
+ var NetworkError = class extends ApiError {
32
+ constructor(message, cause) {
33
+ super(message);
34
+ this.name = "NetworkError";
35
+ this.cause = cause;
36
+ }
37
+ };
38
+ var ServerError = class extends ApiError {
39
+ constructor(message, requestId, details) {
40
+ super(message, 500, requestId, details);
41
+ this.name = "ServerError";
42
+ }
43
+ };
44
+
45
+ // src/client/client.ts
46
+ var StreamClient = class {
47
+ constructor(config) {
48
+ if (!config.apiKey || typeof config.apiKey !== "string") {
49
+ throw new Error("apiKey is required and must be a string");
50
+ }
51
+ this.baseUrl = config.baseUrl;
52
+ this.apiKey = config.apiKey;
53
+ }
54
+ async request(path, options = {}) {
55
+ const url = `${this.baseUrl}${path}`;
56
+ const controller = new AbortController();
57
+ try {
58
+ const response = await fetch(url, {
59
+ ...options,
60
+ signal: controller.signal,
61
+ headers: {
62
+ "Content-Type": "application/json",
63
+ Authorization: `Bearer ${this.apiKey}`,
64
+ ...options.headers
65
+ }
66
+ });
67
+ if (!response.ok) {
68
+ const errorData = await response.json().catch(() => ({
69
+ error: "unknown_error",
70
+ message: response.statusText
71
+ }));
72
+ const message = errorData.message || errorData.error;
73
+ if (response.status === 401) {
74
+ throw new UnauthorizedError(
75
+ message || "Invalid or revoked API key",
76
+ errorData.request_id
77
+ );
78
+ }
79
+ if (response.status === 422 || response.status === 400) {
80
+ throw new ValidationError(
81
+ message,
82
+ errorData.request_id,
83
+ errorData.details
84
+ );
85
+ }
86
+ if (response.status === 404) {
87
+ throw new NotFoundError(message, errorData.request_id);
88
+ }
89
+ if (response.status >= 500) {
90
+ throw new ServerError(
91
+ message,
92
+ errorData.request_id,
93
+ errorData.details
94
+ );
95
+ }
96
+ throw new ApiError(
97
+ message,
98
+ response.status,
99
+ errorData.request_id,
100
+ errorData.details
101
+ );
102
+ }
103
+ return await response.json();
104
+ } catch (error) {
105
+ if (error instanceof ApiError) {
106
+ throw error;
107
+ }
108
+ if (error instanceof Error) {
109
+ throw new NetworkError(`Network error: ${error.message}`, error);
110
+ }
111
+ throw new NetworkError("Unknown network error");
112
+ }
113
+ }
114
+ async createStream(request) {
115
+ return this.request("/streams", {
116
+ method: "POST",
117
+ body: JSON.stringify(request)
118
+ });
119
+ }
120
+ async renewLease(streamId) {
121
+ return this.request(`/streams/${streamId}/keepalive`, {
122
+ method: "POST"
123
+ });
124
+ }
125
+ async updatePrompt(streamId, prompt) {
126
+ return this.request(
127
+ `/streams/${streamId}/config/prompt`,
128
+ {
129
+ method: "PATCH",
130
+ body: JSON.stringify({ prompt })
131
+ }
132
+ );
133
+ }
134
+ async submitFeedback(streamId, feedback) {
135
+ return this.request(`/streams/${streamId}/feedback`, {
136
+ method: "POST",
137
+ body: JSON.stringify(feedback)
138
+ });
139
+ }
140
+ async getAllFeedback() {
141
+ return this.request("/streams/feedback", {
142
+ method: "GET"
143
+ });
144
+ }
145
+ connectWebSocket(streamId) {
146
+ const wsUrl = this.baseUrl.replace("http://", "ws://").replace("https://", "wss://");
147
+ return new WebSocket(`${wsUrl}/ws/streams/${streamId}`);
148
+ }
149
+ /**
150
+ * Health check endpoint (for testing, uses internal port if available)
151
+ * Note: This endpoint may not be available via the main API
152
+ */
153
+ async healthCheck() {
154
+ const url = `${this.baseUrl}/healthz`;
155
+ const response = await fetch(url);
156
+ return response.text();
157
+ }
158
+ };
159
+
160
+ // src/client/RealtimeVision.ts
161
+ var DEFAULTS = {
162
+ BACKEND: "overshoot",
163
+ MODEL: "Qwen/Qwen3-VL-30B-A3B-Instruct",
164
+ SOURCE: { type: "camera", cameraFacing: "environment" },
165
+ SAMPLING_RATIO: 0.1,
166
+ CLIP_LENGTH_SECONDS: 1,
167
+ DELAY_SECONDS: 1,
168
+ FALLBACK_FPS: 30,
169
+ ICE_SERVERS: [
170
+ {
171
+ urls: "turn:34.63.114.235:3478",
172
+ username: "1768325310:634a30f8-ae52-4a15-9d3f-a56b725dacd9",
173
+ credential: "H5IEJ1IJrOUzQkVG9lXr8Z4EJGU="
174
+ }
175
+ ]
176
+ };
177
+ var CONSTRAINTS = {
178
+ SAMPLING_RATIO: { min: 0, max: 1 },
179
+ FPS: { min: 1, max: 120 },
180
+ CLIP_LENGTH_SECONDS: { min: 0.1, max: 60 },
181
+ DELAY_SECONDS: { min: 0, max: 60 },
182
+ RATING: { min: 1, max: 5 }
183
+ };
184
+ var Logger = class {
185
+ constructor(debugEnabled = false) {
186
+ this.debugEnabled = debugEnabled;
187
+ }
188
+ debug(...args) {
189
+ if (this.debugEnabled) {
190
+ console.log("[RealtimeVision Debug]", ...args);
191
+ }
192
+ }
193
+ info(...args) {
194
+ console.log("[RealtimeVision]", ...args);
195
+ }
196
+ warn(...args) {
197
+ console.warn("[RealtimeVision]", ...args);
198
+ }
199
+ error(...args) {
200
+ console.error("[RealtimeVision]", ...args);
201
+ }
202
+ };
203
+ var ValidationError2 = class extends Error {
204
+ constructor(message) {
205
+ super(message);
206
+ this.name = "ValidationError";
207
+ }
208
+ };
209
+ var RealtimeVision = class {
210
+ constructor(config) {
211
+ this.mediaStream = null;
212
+ this.peerConnection = null;
213
+ this.webSocket = null;
214
+ this.streamId = null;
215
+ this.keepaliveInterval = null;
216
+ this.videoElement = null;
217
+ this.isRunning = false;
218
+ this.validateConfig(config);
219
+ this.config = config;
220
+ this.logger = new Logger(config.debug ?? false);
221
+ this.client = new StreamClient({
222
+ baseUrl: config.apiUrl,
223
+ apiKey: config.apiKey
224
+ });
225
+ }
226
+ /**
227
+ * Validate configuration values
228
+ */
229
+ validateConfig(config) {
230
+ if (!config.apiUrl || typeof config.apiUrl !== "string") {
231
+ throw new ValidationError2("apiUrl is required and must be a string");
232
+ }
233
+ if (!config.apiKey || typeof config.apiKey !== "string") {
234
+ throw new ValidationError2("apiKey is required and must be a string");
235
+ }
236
+ if (!config.prompt || typeof config.prompt !== "string") {
237
+ throw new ValidationError2("prompt is required and must be a string");
238
+ }
239
+ if (config.source) {
240
+ if (config.source.type === "camera") {
241
+ if (config.source.cameraFacing !== "user" && config.source.cameraFacing !== "environment") {
242
+ throw new ValidationError2(
243
+ 'cameraFacing must be "user" or "environment"'
244
+ );
245
+ }
246
+ } else if (config.source.type === "video") {
247
+ if (!(config.source.file instanceof File)) {
248
+ throw new ValidationError2("video source must provide a File object");
249
+ }
250
+ } else {
251
+ throw new ValidationError2('source.type must be "camera" or "video"');
252
+ }
253
+ }
254
+ if (config.processing?.sampling_ratio !== void 0) {
255
+ const ratio = config.processing.sampling_ratio;
256
+ if (ratio < CONSTRAINTS.SAMPLING_RATIO.min || ratio > CONSTRAINTS.SAMPLING_RATIO.max) {
257
+ throw new ValidationError2(
258
+ `sampling_ratio must be between ${CONSTRAINTS.SAMPLING_RATIO.min} and ${CONSTRAINTS.SAMPLING_RATIO.max}`
259
+ );
260
+ }
261
+ }
262
+ if (config.processing?.fps !== void 0) {
263
+ const fps = config.processing.fps;
264
+ if (fps < CONSTRAINTS.FPS.min || fps > CONSTRAINTS.FPS.max) {
265
+ throw new ValidationError2(
266
+ `fps must be between ${CONSTRAINTS.FPS.min} and ${CONSTRAINTS.FPS.max}`
267
+ );
268
+ }
269
+ }
270
+ if (config.processing?.clip_length_seconds !== void 0) {
271
+ const clip = config.processing.clip_length_seconds;
272
+ if (clip < CONSTRAINTS.CLIP_LENGTH_SECONDS.min || clip > CONSTRAINTS.CLIP_LENGTH_SECONDS.max) {
273
+ throw new ValidationError2(
274
+ `clip_length_seconds must be between ${CONSTRAINTS.CLIP_LENGTH_SECONDS.min} and ${CONSTRAINTS.CLIP_LENGTH_SECONDS.max}`
275
+ );
276
+ }
277
+ }
278
+ if (config.processing?.delay_seconds !== void 0) {
279
+ const delay = config.processing.delay_seconds;
280
+ if (delay < CONSTRAINTS.DELAY_SECONDS.min || delay > CONSTRAINTS.DELAY_SECONDS.max) {
281
+ throw new ValidationError2(
282
+ `delay_seconds must be between ${CONSTRAINTS.DELAY_SECONDS.min} and ${CONSTRAINTS.DELAY_SECONDS.max}`
283
+ );
284
+ }
285
+ }
286
+ }
287
+ /**
288
+ * Create media stream from the configured source
289
+ */
290
+ async createMediaStream(source) {
291
+ this.logger.debug("Creating media stream from source:", source.type);
292
+ switch (source.type) {
293
+ case "camera":
294
+ return await navigator.mediaDevices.getUserMedia({
295
+ video: { facingMode: { ideal: source.cameraFacing } },
296
+ audio: false
297
+ });
298
+ case "video":
299
+ const video = document.createElement("video");
300
+ video.src = URL.createObjectURL(source.file);
301
+ video.muted = true;
302
+ video.loop = true;
303
+ video.playsInline = true;
304
+ this.logger.debug("Loading video file:", source.file.name);
305
+ await new Promise((resolve, reject) => {
306
+ const timeout = setTimeout(() => {
307
+ reject(new Error("Video loading timeout after 10 seconds"));
308
+ }, 1e4);
309
+ video.onloadedmetadata = () => {
310
+ clearTimeout(timeout);
311
+ this.logger.debug("Video metadata loaded");
312
+ resolve();
313
+ };
314
+ video.onerror = (e) => {
315
+ clearTimeout(timeout);
316
+ this.logger.error("Video loading error:", e);
317
+ reject(new Error("Failed to load video file"));
318
+ };
319
+ if (video.readyState >= 1) {
320
+ clearTimeout(timeout);
321
+ resolve();
322
+ }
323
+ });
324
+ await video.play();
325
+ this.logger.debug("Video playback started");
326
+ const stream = video.captureStream();
327
+ if (!stream) {
328
+ throw new Error("Failed to capture video stream");
329
+ }
330
+ const videoTracks = stream.getVideoTracks();
331
+ if (videoTracks.length === 0) {
332
+ throw new Error("Video stream has no video tracks");
333
+ }
334
+ this.videoElement = video;
335
+ return stream;
336
+ default:
337
+ throw new Error(`Unknown source type: ${source.type}`);
338
+ }
339
+ }
340
+ /**
341
+ * Get FPS from media stream
342
+ */
343
+ async getStreamFps(stream, source) {
344
+ if (!stream) {
345
+ this.logger.warn("Stream is null, using fallback FPS");
346
+ return DEFAULTS.FALLBACK_FPS;
347
+ }
348
+ const videoTracks = stream.getVideoTracks();
349
+ if (!videoTracks || videoTracks.length === 0) {
350
+ this.logger.warn("No video tracks found, using fallback FPS");
351
+ return DEFAULTS.FALLBACK_FPS;
352
+ }
353
+ const videoTrack = videoTracks[0];
354
+ if (!videoTrack) {
355
+ this.logger.warn("First video track is null, using fallback FPS");
356
+ return DEFAULTS.FALLBACK_FPS;
357
+ }
358
+ if (source.type === "camera") {
359
+ const settings = videoTrack.getSettings();
360
+ const fps = settings.frameRate ?? DEFAULTS.FALLBACK_FPS;
361
+ this.logger.debug("Detected camera FPS:", fps);
362
+ return fps;
363
+ }
364
+ if (source.type === "video" && this.videoElement) {
365
+ await new Promise((resolve, reject) => {
366
+ if (this.videoElement.readyState >= 1) {
367
+ resolve();
368
+ } else {
369
+ this.videoElement.onloadedmetadata = () => resolve();
370
+ this.videoElement.onerror = () => reject(new Error("Failed to load video metadata"));
371
+ }
372
+ });
373
+ this.logger.debug("Using fallback FPS for video file");
374
+ return DEFAULTS.FALLBACK_FPS;
375
+ }
376
+ return DEFAULTS.FALLBACK_FPS;
377
+ }
378
+ /**
379
+ * Get processing configuration with defaults applied
380
+ */
381
+ getProcessingConfig(detectedFps) {
382
+ const userProcessing = this.config.processing || {};
383
+ return {
384
+ sampling_ratio: userProcessing.sampling_ratio ?? DEFAULTS.SAMPLING_RATIO,
385
+ fps: userProcessing.fps ?? detectedFps,
386
+ clip_length_seconds: userProcessing.clip_length_seconds ?? DEFAULTS.CLIP_LENGTH_SECONDS,
387
+ delay_seconds: userProcessing.delay_seconds ?? DEFAULTS.DELAY_SECONDS
388
+ };
389
+ }
390
+ /**
391
+ * Get the effective source configuration
392
+ */
393
+ getSource() {
394
+ return this.config.source ?? DEFAULTS.SOURCE;
395
+ }
396
+ /**
397
+ * Start the vision stream
398
+ */
399
+ async start() {
400
+ if (this.isRunning) {
401
+ throw new Error("Vision stream already running");
402
+ }
403
+ try {
404
+ const source = this.getSource();
405
+ this.logger.debug("Starting stream with source type:", source.type);
406
+ if (source.type === "video") {
407
+ this.logger.debug("Video file:", {
408
+ name: source.file.name,
409
+ size: source.file.size,
410
+ type: source.file.type
411
+ });
412
+ if (!source.file || !(source.file instanceof File)) {
413
+ throw new Error("Invalid video file");
414
+ }
415
+ }
416
+ this.mediaStream = await this.createMediaStream(source);
417
+ const videoTrack = this.mediaStream.getVideoTracks()[0];
418
+ if (!videoTrack) {
419
+ throw new Error("No video track available");
420
+ }
421
+ const detectedFps = await this.getStreamFps(this.mediaStream, source);
422
+ const iceServers = this.config.iceServers ?? DEFAULTS.ICE_SERVERS;
423
+ this.logger.debug("Creating peer connection with ICE servers");
424
+ this.peerConnection = new RTCPeerConnection({ iceServers });
425
+ this.peerConnection.onicecandidate = (event) => {
426
+ if (event.candidate) {
427
+ this.logger.debug("ICE candidate:", {
428
+ type: event.candidate.type,
429
+ protocol: event.candidate.protocol
430
+ });
431
+ } else {
432
+ this.logger.debug("ICE gathering complete");
433
+ }
434
+ };
435
+ this.peerConnection.oniceconnectionstatechange = () => {
436
+ this.logger.debug(
437
+ "ICE connection state:",
438
+ this.peerConnection?.iceConnectionState
439
+ );
440
+ };
441
+ this.peerConnection.addTrack(videoTrack, this.mediaStream);
442
+ const offer = await this.peerConnection.createOffer();
443
+ await this.peerConnection.setLocalDescription(offer);
444
+ if (!this.peerConnection.localDescription) {
445
+ throw new Error("Failed to create local description");
446
+ }
447
+ this.logger.debug("Creating stream on server");
448
+ const response = await this.client.createStream({
449
+ webrtc: {
450
+ type: "offer",
451
+ sdp: this.peerConnection.localDescription.sdp
452
+ },
453
+ processing: this.getProcessingConfig(detectedFps),
454
+ inference: {
455
+ prompt: this.config.prompt,
456
+ backend: this.config.backend ?? DEFAULTS.BACKEND,
457
+ model: this.config.model ?? DEFAULTS.MODEL,
458
+ output_schema_json: this.config.outputSchema
459
+ }
460
+ });
461
+ this.logger.debug("Backend response received:", {
462
+ stream_id: response.stream_id,
463
+ has_turn_servers: !!response.turn_servers
464
+ });
465
+ await this.peerConnection.setRemoteDescription(response.webrtc);
466
+ this.streamId = response.stream_id;
467
+ this.logger.info("Stream started:", this.streamId);
468
+ this.setupKeepalive(response.lease?.ttl_seconds);
469
+ this.setupWebSocket(response.stream_id);
470
+ this.isRunning = true;
471
+ } catch (error) {
472
+ await this.handleFatalError(error);
473
+ throw error;
474
+ }
475
+ }
476
+ /**
477
+ * Set up keepalive interval with error handling
478
+ */
479
+ setupKeepalive(ttlSeconds) {
480
+ if (!ttlSeconds) {
481
+ return;
482
+ }
483
+ const intervalMs = ttlSeconds / 2 * 1e3;
484
+ this.logger.debug("Setting up keepalive with interval:", intervalMs, "ms");
485
+ this.keepaliveInterval = window.setInterval(async () => {
486
+ try {
487
+ if (this.streamId) {
488
+ await this.client.renewLease(this.streamId);
489
+ this.logger.debug("Lease renewed");
490
+ }
491
+ } catch (error) {
492
+ this.logger.error("Keepalive failed:", error);
493
+ const keepaliveError = new Error(
494
+ `Keepalive failed: ${error instanceof Error ? error.message : String(error)}`
495
+ );
496
+ await this.handleFatalError(keepaliveError);
497
+ }
498
+ }, intervalMs);
499
+ }
500
+ /**
501
+ * Set up WebSocket connection with error handling
502
+ */
503
+ setupWebSocket(streamId) {
504
+ this.logger.debug("Connecting WebSocket for stream:", streamId);
505
+ this.webSocket = this.client.connectWebSocket(streamId);
506
+ this.webSocket.onopen = () => {
507
+ this.logger.debug("WebSocket connected");
508
+ if (this.webSocket) {
509
+ this.webSocket.send(JSON.stringify({ api_key: this.config.apiKey }));
510
+ }
511
+ };
512
+ this.webSocket.onmessage = (event) => {
513
+ try {
514
+ const result = JSON.parse(event.data);
515
+ this.config.onResult(result);
516
+ } catch (error) {
517
+ const parseError = new Error(
518
+ `Failed to parse WebSocket message: ${error instanceof Error ? error.message : String(error)}`
519
+ );
520
+ this.handleNonFatalError(parseError);
521
+ }
522
+ };
523
+ this.webSocket.onerror = () => {
524
+ this.logger.error("WebSocket error occurred");
525
+ const error = new Error("WebSocket error occurred");
526
+ this.handleFatalError(error);
527
+ };
528
+ this.webSocket.onclose = (event) => {
529
+ if (this.isRunning) {
530
+ if (event.code === 1008) {
531
+ this.logger.error("WebSocket authentication failed");
532
+ const error = new Error(
533
+ "WebSocket authentication failed: Invalid or revoked API key"
534
+ );
535
+ this.handleFatalError(error);
536
+ } else {
537
+ this.logger.warn("WebSocket closed unexpectedly:", event.code);
538
+ const error = new Error("WebSocket closed unexpectedly");
539
+ this.handleFatalError(error);
540
+ }
541
+ } else {
542
+ this.logger.debug("WebSocket closed");
543
+ }
544
+ };
545
+ }
546
+ /**
547
+ * Handle non-fatal errors (report but don't stop stream)
548
+ */
549
+ handleNonFatalError(error) {
550
+ this.logger.warn("Non-fatal error:", error.message);
551
+ if (this.config.onError) {
552
+ this.config.onError(error);
553
+ }
554
+ }
555
+ /**
556
+ * Handle fatal errors (stop stream and report)
557
+ */
558
+ async handleFatalError(error) {
559
+ this.logger.error("Fatal error:", error);
560
+ await this.cleanup();
561
+ this.isRunning = false;
562
+ const normalizedError = error instanceof Error ? error : new Error(String(error));
563
+ if (this.config.onError) {
564
+ this.config.onError(normalizedError);
565
+ }
566
+ }
567
+ /**
568
+ * Update the prompt/task while stream is running
569
+ */
570
+ async updatePrompt(prompt) {
571
+ if (!this.isRunning || !this.streamId) {
572
+ throw new Error("Vision stream not running");
573
+ }
574
+ if (!prompt || typeof prompt !== "string") {
575
+ throw new ValidationError2("prompt must be a non-empty string");
576
+ }
577
+ this.logger.debug("Updating prompt");
578
+ await this.client.updatePrompt(this.streamId, prompt);
579
+ this.logger.info("Prompt updated");
580
+ }
581
+ /**
582
+ * Stop the vision stream and clean up resources
583
+ */
584
+ async stop() {
585
+ this.logger.info("Stopping stream");
586
+ await this.cleanup();
587
+ this.isRunning = false;
588
+ }
589
+ /**
590
+ * Submit feedback for the stream
591
+ */
592
+ async submitFeedback(feedback) {
593
+ if (!this.streamId) {
594
+ throw new Error("No active stream");
595
+ }
596
+ if (feedback.rating < CONSTRAINTS.RATING.min || feedback.rating > CONSTRAINTS.RATING.max) {
597
+ throw new ValidationError2(
598
+ `rating must be between ${CONSTRAINTS.RATING.min} and ${CONSTRAINTS.RATING.max}`
599
+ );
600
+ }
601
+ if (!feedback.category || typeof feedback.category !== "string") {
602
+ throw new ValidationError2("category must be a non-empty string");
603
+ }
604
+ this.logger.debug("Submitting feedback");
605
+ await this.client.submitFeedback(this.streamId, {
606
+ rating: feedback.rating,
607
+ category: feedback.category,
608
+ feedback: feedback.feedback ?? ""
609
+ });
610
+ this.logger.info("Feedback submitted");
611
+ }
612
+ /**
613
+ * Get the current stream ID
614
+ */
615
+ getStreamId() {
616
+ return this.streamId;
617
+ }
618
+ /**
619
+ * Get the media stream (for displaying video preview)
620
+ */
621
+ getMediaStream() {
622
+ return this.mediaStream;
623
+ }
624
+ /**
625
+ * Check if the stream is running
626
+ */
627
+ isActive() {
628
+ return this.isRunning;
629
+ }
630
+ async cleanup() {
631
+ this.logger.debug("Cleaning up resources");
632
+ if (this.keepaliveInterval) {
633
+ window.clearInterval(this.keepaliveInterval);
634
+ this.keepaliveInterval = null;
635
+ }
636
+ if (this.webSocket) {
637
+ this.webSocket.close();
638
+ this.webSocket = null;
639
+ }
640
+ if (this.peerConnection) {
641
+ this.peerConnection.close();
642
+ this.peerConnection = null;
643
+ }
644
+ if (this.mediaStream) {
645
+ this.mediaStream.getTracks().forEach((track) => track.stop());
646
+ this.mediaStream = null;
647
+ }
648
+ if (this.videoElement) {
649
+ this.videoElement.pause();
650
+ URL.revokeObjectURL(this.videoElement.src);
651
+ this.videoElement.remove();
652
+ this.videoElement = null;
653
+ }
654
+ this.streamId = null;
655
+ this.logger.debug("Cleanup complete");
656
+ }
657
+ };
658
+
659
+ exports.ApiError = ApiError;
660
+ exports.NetworkError = NetworkError;
661
+ exports.NotFoundError = NotFoundError;
662
+ exports.RealtimeVision = RealtimeVision;
663
+ exports.ServerError = ServerError;
664
+ exports.StreamClient = StreamClient;
665
+ exports.UnauthorizedError = UnauthorizedError;
666
+ exports.ValidationError = ValidationError;
667
+ //# sourceMappingURL=index.js.map
668
+ //# sourceMappingURL=index.js.map