@unboundcx/video-sdk-client 1.1.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/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@unboundcx/video-sdk-client",
3
+ "version": "1.1.0",
4
+ "description": "Framework-agnostic WebRTC video meeting SDK powered by mediasoup",
5
+ "type": "module",
6
+ "main": "index.js",
7
+ "exports": {
8
+ ".": "./index.js",
9
+ "./client": "./VideoMeetingClient.js",
10
+ "./managers/*": "./managers/*.js",
11
+ "./utils/*": "./utils/*.js"
12
+ },
13
+ "scripts": {
14
+ "test": "echo \"Error: no test specified\" && exit 1"
15
+ },
16
+ "keywords": [
17
+ "webrtc",
18
+ "video",
19
+ "meeting",
20
+ "mediasoup",
21
+ "conferencing",
22
+ "real-time",
23
+ "streaming",
24
+ "sfu"
25
+ ],
26
+ "author": "UnboundCX",
27
+ "license": "MIT",
28
+ "peerDependencies": {
29
+ "mediasoup-client": "^3.x",
30
+ "socket.io-client": "^4.x"
31
+ },
32
+ "dependencies": {
33
+ "@mediapipe/selfie_segmentation": "^0.1.1675465747"
34
+ },
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "https://github.com/unboundcx/video-sdk"
38
+ },
39
+ "bugs": {
40
+ "url": "https://github.com/unboundcx/video-sdk/issues"
41
+ },
42
+ "homepage": "https://github.com/unboundcx/video-sdk#readme",
43
+ "files": [
44
+ "index.js",
45
+ "VideoMeetingClient.js",
46
+ "VideoProcessor.js",
47
+ "AudioMixer.js",
48
+ "managers/",
49
+ "utils/",
50
+ "README.md",
51
+ "LICENSE"
52
+ ],
53
+ "engines": {
54
+ "node": ">=14.0.0"
55
+ }
56
+ }
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Simple EventEmitter implementation for the SDK
3
+ * Allows subscribing to and emitting events
4
+ */
5
+ export class EventEmitter {
6
+ constructor() {
7
+ this._events = new Map();
8
+ }
9
+
10
+ /**
11
+ * Subscribe to an event
12
+ * @param {string} event - Event name
13
+ * @param {Function} handler - Event handler function
14
+ * @returns {Function} Unsubscribe function
15
+ */
16
+ on(event, handler) {
17
+ if (!this._events.has(event)) {
18
+ this._events.set(event, []);
19
+ }
20
+
21
+ const handlers = this._events.get(event);
22
+ handlers.push(handler);
23
+
24
+ // Return unsubscribe function
25
+ return () => {
26
+ const index = handlers.indexOf(handler);
27
+ if (index > -1) {
28
+ handlers.splice(index, 1);
29
+ }
30
+ };
31
+ }
32
+
33
+ /**
34
+ * Subscribe to an event that only fires once
35
+ * @param {string} event - Event name
36
+ * @param {Function} handler - Event handler function
37
+ * @returns {Function} Unsubscribe function
38
+ */
39
+ once(event, handler) {
40
+ const wrappedHandler = (...args) => {
41
+ handler(...args);
42
+ this.off(event, wrappedHandler);
43
+ };
44
+
45
+ return this.on(event, wrappedHandler);
46
+ }
47
+
48
+ /**
49
+ * Unsubscribe from an event
50
+ * @param {string} event - Event name
51
+ * @param {Function} handler - Event handler function to remove
52
+ */
53
+ off(event, handler) {
54
+ const handlers = this._events.get(event);
55
+ if (!handlers) return;
56
+
57
+ const index = handlers.indexOf(handler);
58
+ if (index > -1) {
59
+ handlers.splice(index, 1);
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Emit an event to all subscribers
65
+ * @param {string} event - Event name
66
+ * @param {*} data - Event data
67
+ */
68
+ emit(event, data) {
69
+ const handlers = this._events.get(event);
70
+ if (!handlers || handlers.length === 0) return;
71
+
72
+ // Call all handlers with the event data
73
+ for (const handler of handlers) {
74
+ try {
75
+ handler(data);
76
+ } catch (error) {
77
+ console.error(`Error in event handler for "${event}":`, error);
78
+ }
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Remove all event listeners
84
+ * @param {string?} event - Optional event name to clear only that event
85
+ */
86
+ removeAllListeners(event = null) {
87
+ if (event) {
88
+ this._events.delete(event);
89
+ } else {
90
+ this._events.clear();
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Get count of listeners for an event
96
+ * @param {string} event - Event name
97
+ * @returns {number} Number of listeners
98
+ */
99
+ listenerCount(event) {
100
+ const handlers = this._events.get(event);
101
+ return handlers ? handlers.length : 0;
102
+ }
103
+ }
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Logger utility with namespaced prefixes and debug mode
3
+ */
4
+
5
+ // Global filters for verbose logs (stats, keepalive, etc.)
6
+ const globalFilters = {
7
+ stats: false,
8
+ keepalive: false,
9
+ performance: false,
10
+ };
11
+
12
+ export class Logger {
13
+ /**
14
+ * @param {string} namespace - Logger namespace (e.g., 'SDK:ConnectionManager')
15
+ * @param {boolean} debug - Enable debug logging
16
+ */
17
+ constructor(namespace, debug = false) {
18
+ this.namespace = namespace;
19
+ this.debug = debug;
20
+ }
21
+
22
+ /**
23
+ * Set global filter for verbose log categories
24
+ * @param {string} category - Category name (stats, keepalive, performance)
25
+ * @param {boolean} enabled - Enable or disable this category
26
+ */
27
+ static setFilter(category, enabled) {
28
+ if (category in globalFilters) {
29
+ globalFilters[category] = enabled;
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Enable/disable all verbose logging categories
35
+ * @param {boolean} enabled - Enable or disable verbose logs
36
+ */
37
+ static setVerbose(enabled) {
38
+ Object.keys(globalFilters).forEach(key => {
39
+ globalFilters[key] = enabled;
40
+ });
41
+ }
42
+
43
+ /**
44
+ * Check if a message should be filtered based on content
45
+ * @private
46
+ */
47
+ _shouldFilter(args) {
48
+ const message = args.join(' ').toLowerCase();
49
+
50
+ if (!globalFilters.stats && (message.includes('stats') || message.includes('mos'))) {
51
+ return true;
52
+ }
53
+ if (!globalFilters.keepalive && message.includes('keepalive')) {
54
+ return true;
55
+ }
56
+ if (!globalFilters.performance && message.includes('performance')) {
57
+ return true;
58
+ }
59
+
60
+ return false;
61
+ }
62
+
63
+ /**
64
+ * Format message with namespace prefix
65
+ * @private
66
+ */
67
+ _format(level, ...args) {
68
+ const prefix = `[${this.namespace}]`;
69
+ return [prefix, ...args];
70
+ }
71
+
72
+ /**
73
+ * Log info message
74
+ */
75
+ info(...args) {
76
+ if (this.debug && !this._shouldFilter(args)) {
77
+ console.log(...this._format('INFO', ...args));
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Log warning message
83
+ */
84
+ warn(...args) {
85
+ if (!this._shouldFilter(args)) {
86
+ console.warn(...this._format('WARN', ...args));
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Log error message (never filtered)
92
+ */
93
+ error(...args) {
94
+ console.error(...this._format('ERROR', ...args));
95
+ }
96
+
97
+ /**
98
+ * Log debug message (only in debug mode)
99
+ */
100
+ log(...args) {
101
+ if (this.debug && !this._shouldFilter(args)) {
102
+ console.log(...this._format('DEBUG', ...args));
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Create a child logger with additional namespace
108
+ * @param {string} childNamespace - Additional namespace to append
109
+ * @returns {Logger} New logger instance
110
+ */
111
+ child(childNamespace) {
112
+ return new Logger(`${this.namespace}:${childNamespace}`, this.debug);
113
+ }
114
+ }
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Custom error classes for the SDK
3
+ * These provide better error handling and debugging
4
+ */
5
+
6
+ /**
7
+ * Base SDK Error class
8
+ */
9
+ export class SDKError extends Error {
10
+ constructor(message, code = 'SDK_ERROR') {
11
+ super(message);
12
+ this.name = 'SDKError';
13
+ this.code = code;
14
+ this.timestamp = new Date().toISOString();
15
+ }
16
+ }
17
+
18
+ /**
19
+ * Connection-related errors
20
+ */
21
+ export class ConnectionError extends SDKError {
22
+ constructor(message, details = {}) {
23
+ super(message, 'CONNECTION_ERROR');
24
+ this.name = 'ConnectionError';
25
+ this.details = details;
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Device/media permission errors
31
+ */
32
+ export class PermissionDeniedError extends SDKError {
33
+ constructor(deviceType, originalError = null) {
34
+ super(
35
+ `Permission denied for ${deviceType}. Please allow access in your browser settings.`,
36
+ 'PERMISSION_DENIED'
37
+ );
38
+ this.name = 'PermissionDeniedError';
39
+ this.deviceType = deviceType;
40
+ this.originalError = originalError;
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Device not found errors
46
+ */
47
+ export class DeviceNotFoundError extends SDKError {
48
+ constructor(deviceType, deviceId = null) {
49
+ super(
50
+ `${deviceType} device not found${deviceId ? ` (ID: ${deviceId})` : ''}`,
51
+ 'DEVICE_NOT_FOUND'
52
+ );
53
+ this.name = 'DeviceNotFoundError';
54
+ this.deviceType = deviceType;
55
+ this.deviceId = deviceId;
56
+ }
57
+ }
58
+
59
+ /**
60
+ * mediasoup-related errors
61
+ */
62
+ export class MediasoupError extends SDKError {
63
+ constructor(message, operation = null, originalError = null) {
64
+ super(message, 'MEDIASOUP_ERROR');
65
+ this.name = 'MediasoupError';
66
+ this.operation = operation;
67
+ this.originalError = originalError;
68
+ }
69
+ }
70
+
71
+ /**
72
+ * State-related errors (invalid state transitions)
73
+ */
74
+ export class StateError extends SDKError {
75
+ constructor(message, currentState = null, expectedState = null) {
76
+ super(message, 'STATE_ERROR');
77
+ this.name = 'StateError';
78
+ this.currentState = currentState;
79
+ this.expectedState = expectedState;
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Room/meeting errors
85
+ */
86
+ export class RoomError extends SDKError {
87
+ constructor(message, roomId = null) {
88
+ super(message, 'ROOM_ERROR');
89
+ this.name = 'RoomError';
90
+ this.roomId = roomId;
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Timeout errors
96
+ */
97
+ export class TimeoutError extends SDKError {
98
+ constructor(operation, timeoutMs) {
99
+ super(
100
+ `Operation "${operation}" timed out after ${timeoutMs}ms`,
101
+ 'TIMEOUT_ERROR'
102
+ );
103
+ this.name = 'TimeoutError';
104
+ this.operation = operation;
105
+ this.timeoutMs = timeoutMs;
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Helper function to wrap unknown errors
111
+ * @param {Error} error - Original error
112
+ * @param {string} context - Context where error occurred
113
+ * @returns {SDKError} Wrapped error
114
+ */
115
+ export function wrapError(error, context) {
116
+ if (error instanceof SDKError) {
117
+ return error;
118
+ }
119
+
120
+ // Check for common browser errors
121
+ if (error.name === 'NotAllowedError' || error.name === 'PermissionDeniedError') {
122
+ return new PermissionDeniedError(context, error);
123
+ }
124
+
125
+ if (error.name === 'NotFoundError' || error.name === 'DevicesNotFoundError') {
126
+ return new DeviceNotFoundError(context, null);
127
+ }
128
+
129
+ // Wrap as generic SDK error
130
+ const sdkError = new SDKError(
131
+ `${context}: ${error.message}`,
132
+ 'UNKNOWN_ERROR'
133
+ );
134
+ sdkError.originalError = error;
135
+ return sdkError;
136
+ }