unified-video-framework 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 (129) hide show
  1. package/.github/workflows/ci.yml +253 -0
  2. package/ANDROID_TV_IMPLEMENTATION.md +313 -0
  3. package/COMPLETION_STATUS.md +165 -0
  4. package/CONTRIBUTING.md +376 -0
  5. package/FINAL_STATUS_REPORT.md +170 -0
  6. package/FRAMEWORK_REVIEW.md +247 -0
  7. package/IMPROVEMENTS_SUMMARY.md +168 -0
  8. package/LICENSE +21 -0
  9. package/NATIVE_APP_INTEGRATION_GUIDE.md +903 -0
  10. package/PAYWALL_RENTAL_FLOW.md +499 -0
  11. package/PLATFORM_SETUP_GUIDE.md +1636 -0
  12. package/README.md +315 -0
  13. package/RUN_LOCALLY.md +151 -0
  14. package/apps/demo/cast-sender-min.html +173 -0
  15. package/apps/demo/custom-player.html +883 -0
  16. package/apps/demo/demo.html +990 -0
  17. package/apps/demo/enhanced-player.html +3556 -0
  18. package/apps/demo/index.html +159 -0
  19. package/apps/rental-api/.env.example +24 -0
  20. package/apps/rental-api/README.md +23 -0
  21. package/apps/rental-api/migrations/001_init.sql +35 -0
  22. package/apps/rental-api/migrations/002_videos.sql +10 -0
  23. package/apps/rental-api/migrations/003_add_gateway_subref.sql +4 -0
  24. package/apps/rental-api/migrations/004_update_gateways.sql +4 -0
  25. package/apps/rental-api/migrations/005_seed_demo_video.sql +5 -0
  26. package/apps/rental-api/package-lock.json +2045 -0
  27. package/apps/rental-api/package.json +33 -0
  28. package/apps/rental-api/scripts/run-migration.js +42 -0
  29. package/apps/rental-api/scripts/update-video-currency.js +21 -0
  30. package/apps/rental-api/scripts/update-video-price.js +19 -0
  31. package/apps/rental-api/src/config.ts +14 -0
  32. package/apps/rental-api/src/db.ts +10 -0
  33. package/apps/rental-api/src/routes/cashfree.ts +167 -0
  34. package/apps/rental-api/src/routes/pesapal.ts +92 -0
  35. package/apps/rental-api/src/routes/rentals.ts +242 -0
  36. package/apps/rental-api/src/routes/webhooks.ts +73 -0
  37. package/apps/rental-api/src/server.ts +41 -0
  38. package/apps/rental-api/src/services/entitlements.ts +45 -0
  39. package/apps/rental-api/src/services/payments.ts +22 -0
  40. package/apps/rental-api/tsconfig.json +17 -0
  41. package/check-urls.ps1 +74 -0
  42. package/comparison-report.md +181 -0
  43. package/docs/PAYWALL.md +95 -0
  44. package/docs/PLAYER_UI_VISIBILITY.md +431 -0
  45. package/docs/README.md +7 -0
  46. package/docs/SYSTEM_ARCHITECTURE.md +612 -0
  47. package/docs/VDOCIPHER_CLONE_REQUIREMENTS.md +403 -0
  48. package/examples/android/JavaSampleApp/MainActivity.java +641 -0
  49. package/examples/android/JavaSampleApp/activity_main.xml +226 -0
  50. package/examples/android/SampleApp/MainActivity.kt +430 -0
  51. package/examples/ios/SampleApp/ViewController.swift +337 -0
  52. package/examples/ios/SwiftUISampleApp/ContentView.swift +304 -0
  53. package/iOS_IMPLEMENTATION_OPTIONS.md +470 -0
  54. package/ios/UnifiedVideoPlayer/UnifiedVideoPlayer.podspec +33 -0
  55. package/jest.config.js +33 -0
  56. package/jitpack.yml +5 -0
  57. package/lerna.json +35 -0
  58. package/package.json +69 -0
  59. package/packages/PLATFORM_STATUS.md +163 -0
  60. package/packages/android/build.gradle +135 -0
  61. package/packages/android/src/main/AndroidManifest.xml +36 -0
  62. package/packages/android/src/main/java/com/unifiedvideo/player/PlayerConfiguration.java +221 -0
  63. package/packages/android/src/main/java/com/unifiedvideo/player/UnifiedVideoPlayer.java +1037 -0
  64. package/packages/android/src/main/java/com/unifiedvideo/player/UnifiedVideoPlayer.kt +707 -0
  65. package/packages/android/src/main/java/com/unifiedvideo/player/analytics/AnalyticsProvider.java +9 -0
  66. package/packages/android/src/main/java/com/unifiedvideo/player/cast/CastManager.java +141 -0
  67. package/packages/android/src/main/java/com/unifiedvideo/player/cast/CastOptionsProvider.java +29 -0
  68. package/packages/android/src/main/java/com/unifiedvideo/player/overlay/WatermarkOverlayView.java +88 -0
  69. package/packages/android/src/main/java/com/unifiedvideo/player/pip/PipActionReceiver.java +33 -0
  70. package/packages/android/src/main/java/com/unifiedvideo/player/services/PlaybackService.java +110 -0
  71. package/packages/android/src/main/java/com/unifiedvideo/player/services/PlayerHolder.java +19 -0
  72. package/packages/core/package.json +34 -0
  73. package/packages/core/src/BasePlayer.ts +250 -0
  74. package/packages/core/src/VideoPlayer.ts +237 -0
  75. package/packages/core/src/VideoPlayerFactory.ts +145 -0
  76. package/packages/core/src/index.ts +20 -0
  77. package/packages/core/src/interfaces/IVideoPlayer.ts +184 -0
  78. package/packages/core/src/interfaces.ts +240 -0
  79. package/packages/core/src/utils/EventEmitter.ts +66 -0
  80. package/packages/core/src/utils/PlatformDetector.ts +300 -0
  81. package/packages/core/tsconfig.json +20 -0
  82. package/packages/enact/package.json +51 -0
  83. package/packages/enact/src/VideoPlayer.js +365 -0
  84. package/packages/enact/src/adapters/TizenAdapter.js +354 -0
  85. package/packages/enact/src/index.js +82 -0
  86. package/packages/ios/BUILD_INSTRUCTIONS.md +108 -0
  87. package/packages/ios/FIX_EMBED_ISSUE.md +142 -0
  88. package/packages/ios/GETTING_STARTED.md +100 -0
  89. package/packages/ios/Package.swift +35 -0
  90. package/packages/ios/README.md +84 -0
  91. package/packages/ios/Sources/UnifiedVideoPlayer/Analytics/AnalyticsEmitter.swift +26 -0
  92. package/packages/ios/Sources/UnifiedVideoPlayer/DRM/FairPlayDRMManager.swift +102 -0
  93. package/packages/ios/Sources/UnifiedVideoPlayer/Info.plist +24 -0
  94. package/packages/ios/Sources/UnifiedVideoPlayer/Remote/RemoteCommandCenter.swift +109 -0
  95. package/packages/ios/Sources/UnifiedVideoPlayer/UnifiedVideoPlayer.swift +811 -0
  96. package/packages/ios/Sources/UnifiedVideoPlayer/UnifiedVideoPlayerView.swift +640 -0
  97. package/packages/ios/Sources/UnifiedVideoPlayer/Utilities/Color+Hex.swift +36 -0
  98. package/packages/ios/UnifiedVideoPlayer.podspec +27 -0
  99. package/packages/ios/UnifiedVideoPlayer.xcodeproj/project.pbxproj +385 -0
  100. package/packages/ios/build_framework.sh +55 -0
  101. package/packages/react-native/android/src/main/java/com/unifiedvideo/UnifiedVideoPlayerModule.kt +482 -0
  102. package/packages/react-native/ios/UnifiedVideoPlayer.swift +436 -0
  103. package/packages/react-native/package.json +51 -0
  104. package/packages/react-native/src/ReactNativePlayer.tsx +423 -0
  105. package/packages/react-native/src/VideoPlayer.tsx +224 -0
  106. package/packages/react-native/src/index.ts +28 -0
  107. package/packages/react-native/src/utils/EventEmitter.ts +66 -0
  108. package/packages/react-native/tsconfig.json +31 -0
  109. package/packages/roku/components/UnifiedVideoPlayer.brs +400 -0
  110. package/packages/roku/package.json +44 -0
  111. package/packages/roku/source/VideoPlayer.brs +231 -0
  112. package/packages/roku/source/main.brs +28 -0
  113. package/packages/web/GETTING_STARTED.md +292 -0
  114. package/packages/web/jest.config.js +28 -0
  115. package/packages/web/jest.setup.ts +110 -0
  116. package/packages/web/package.json +50 -0
  117. package/packages/web/src/SecureVideoPlayer.ts +1164 -0
  118. package/packages/web/src/WebPlayer.ts +3110 -0
  119. package/packages/web/src/__tests__/WebPlayer.test.ts +314 -0
  120. package/packages/web/src/index.ts +14 -0
  121. package/packages/web/src/paywall/PaywallController.ts +215 -0
  122. package/packages/web/src/react/WebPlayerView.tsx +177 -0
  123. package/packages/web/tsconfig.json +23 -0
  124. package/packages/web/webpack.config.js +45 -0
  125. package/server.js +131 -0
  126. package/server.py +84 -0
  127. package/test-urls.ps1 +97 -0
  128. package/test-video-urls.ps1 +87 -0
  129. package/tsconfig.json +39 -0
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Core video player interface that all platform implementations must follow
3
+ */
4
+
5
+ export interface VideoSource {
6
+ url: string;
7
+ type?: 'mp4' | 'hls' | 'dash' | 'webm' | 'auto';
8
+ drm?: DRMConfig;
9
+ subtitles?: SubtitleTrack[];
10
+ metadata?: VideoMetadata;
11
+ }
12
+
13
+ export interface DRMConfig {
14
+ licenseUrl: string;
15
+ certificateUrl?: string;
16
+ headers?: Record<string, string>;
17
+ type: 'widevine' | 'playready' | 'fairplay' | 'clearkey';
18
+ }
19
+
20
+ export interface SubtitleTrack {
21
+ url: string;
22
+ language: string;
23
+ label: string;
24
+ kind: 'subtitles' | 'captions' | 'descriptions';
25
+ default?: boolean;
26
+ }
27
+
28
+ export interface VideoMetadata {
29
+ id?: string;
30
+ title?: string;
31
+ description?: string;
32
+ duration?: number;
33
+ thumbnailUrl?: string;
34
+ posterUrl?: string;
35
+ }
36
+
37
+ export interface Quality {
38
+ height: number;
39
+ width: number;
40
+ bitrate: number;
41
+ label: string;
42
+ index: number;
43
+ }
44
+
45
+ export interface PlayerState {
46
+ isPlaying: boolean;
47
+ isPaused: boolean;
48
+ isBuffering: boolean;
49
+ isEnded: boolean;
50
+ isError: boolean;
51
+ currentTime: number;
52
+ duration: number;
53
+ bufferedPercentage: number;
54
+ volume: number;
55
+ isMuted: boolean;
56
+ playbackRate: number;
57
+ currentQuality?: Quality;
58
+ availableQualities: Quality[];
59
+ }
60
+
61
+ export interface PlayerEvents {
62
+ onReady?: () => void;
63
+ onPlay?: () => void;
64
+ onPause?: () => void;
65
+ onEnded?: () => void;
66
+ onTimeUpdate?: (time: number) => void;
67
+ onBuffering?: (isBuffering: boolean) => void;
68
+ onError?: (error: PlayerError) => void;
69
+ onQualityChanged?: (quality: Quality) => void;
70
+ onVolumeChanged?: (volume: number) => void;
71
+ onFullscreenChanged?: (isFullscreen: boolean) => void;
72
+ onProgress?: (buffered: number) => void;
73
+ onSeeking?: () => void;
74
+ onSeeked?: () => void;
75
+ onLoadedMetadata?: (metadata: VideoMetadata) => void;
76
+ // Fired exactly once when free preview duration is reached and playback is blocked
77
+ onFreePreviewEnded?: () => void;
78
+ }
79
+
80
+ export interface PlayerError {
81
+ code: string;
82
+ message: string;
83
+ type: 'network' | 'media' | 'drm' | 'unknown';
84
+ fatal: boolean;
85
+ details?: any;
86
+ }
87
+
88
+ export interface PaywallConfig {
89
+ enabled: boolean;
90
+ apiBase: string; // e.g., http://localhost:3100
91
+ userId: string;
92
+ videoId: string;
93
+ gateways: Array<'stripe' | 'cashfree'>;
94
+ branding?: { title?: string; description?: string; logoUrl?: string; theme?: any };
95
+ popup?: { width?: number; height?: number };
96
+ }
97
+
98
+ export interface PlayerConfig {
99
+ autoPlay?: boolean;
100
+ muted?: boolean;
101
+ volume?: number;
102
+ controls?: boolean;
103
+ loop?: boolean;
104
+ preload?: 'none' | 'metadata' | 'auto';
105
+ crossOrigin?: 'anonymous' | 'use-credentials';
106
+ playsInline?: boolean;
107
+ defaultQuality?: number;
108
+ enableAdaptiveBitrate?: boolean;
109
+ debug?: boolean;
110
+
111
+ // Free preview
112
+ freeDuration?: number; // seconds of free playback before paywall
113
+
114
+ // Optional paywall for dynamic rental flow
115
+ paywall?: PaywallConfig;
116
+ }
117
+
118
+ /**
119
+ * Main video player interface
120
+ */
121
+ export interface IVideoPlayer {
122
+ // Lifecycle methods
123
+ initialize(container: HTMLElement | string, config?: PlayerConfig): Promise<void>;
124
+ destroy(): Promise<void>;
125
+
126
+ // Media control
127
+ load(source: VideoSource): Promise<void>;
128
+ play(): Promise<void>;
129
+ pause(): void;
130
+ stop(): void;
131
+ seek(time: number): void;
132
+
133
+ // Volume control
134
+ setVolume(level: number): void;
135
+ mute(): void;
136
+ unmute(): void;
137
+ toggleMute(): void;
138
+
139
+ // Quality control
140
+ getQualities(): Quality[];
141
+ getCurrentQuality(): Quality | null;
142
+ setQuality(index: number): void;
143
+ setAutoQuality(enabled: boolean): void;
144
+
145
+ // Playback control
146
+ setPlaybackRate(rate: number): void;
147
+ getPlaybackRate(): number;
148
+
149
+ // State queries
150
+ getCurrentTime(): number;
151
+ getDuration(): number;
152
+ getBufferedPercentage(): number;
153
+ getState(): PlayerState;
154
+ isPlaying(): boolean;
155
+ isPaused(): boolean;
156
+ isEnded(): boolean;
157
+
158
+ // Display control
159
+ enterFullscreen(): Promise<void>;
160
+ exitFullscreen(): Promise<void>;
161
+ toggleFullscreen(): Promise<void>;
162
+ enterPictureInPicture(): Promise<void>;
163
+ exitPictureInPicture(): Promise<void>;
164
+
165
+ // Event handling
166
+ on(event: keyof PlayerEvents, handler: Function): void;
167
+ off(event: keyof PlayerEvents, handler?: Function): void;
168
+ once(event: keyof PlayerEvents, handler: Function): void;
169
+
170
+ // Subtitle control
171
+ getSubtitles(): SubtitleTrack[];
172
+ setSubtitleTrack(index: number): void;
173
+ disableSubtitles(): void;
174
+
175
+ // Advanced features
176
+ setAudioTrack?(index: number): void;
177
+ getAudioTracks?(): any[];
178
+ getThumbnail?(time: number): string;
179
+ getStats?(): any;
180
+
181
+ // Free preview runtime controls (optional)
182
+ setFreeDuration?(seconds: number): void;
183
+ resetFreePreviewGate?(): void;
184
+ }
@@ -0,0 +1,240 @@
1
+ // Core TypeScript interfaces for the unified video framework
2
+
3
+ export enum PlatformType {
4
+ IOS = 'ios',
5
+ ANDROID = 'android',
6
+ TIZEN = 'tizen',
7
+ WEBOS = 'webos',
8
+ ROKU = 'roku',
9
+ ANDROID_TV = 'androidtv',
10
+ APPLE_TV = 'appletv',
11
+ WEB = 'web',
12
+ WINDOWS = 'windows'
13
+ }
14
+
15
+ export enum PlayerState {
16
+ IDLE = 'idle',
17
+ LOADING = 'loading',
18
+ READY = 'ready',
19
+ PLAYING = 'playing',
20
+ PAUSED = 'paused',
21
+ BUFFERING = 'buffering',
22
+ ENDED = 'ended',
23
+ ERROR = 'error'
24
+ }
25
+
26
+ export enum DRMType {
27
+ FAIRPLAY = 'fairplay',
28
+ WIDEVINE = 'widevine',
29
+ PLAYREADY = 'playready',
30
+ CLEARKEY = 'clearkey'
31
+ }
32
+
33
+ export interface VideoSource {
34
+ url: string;
35
+ type?: string;
36
+ title?: string;
37
+ description?: string;
38
+ thumbnail?: string;
39
+ duration?: number;
40
+ drm?: DRMConfig;
41
+ subtitles?: SubtitleTrack[];
42
+ metadata?: Record<string, any>;
43
+ }
44
+
45
+ export interface DRMConfig {
46
+ type: DRMType;
47
+ licenseUrl: string;
48
+ certificateUrl?: string;
49
+ headers?: Record<string, string>;
50
+ customData?: string;
51
+ fairplayOptions?: {
52
+ certificateUrl: string;
53
+ licenseUrl: string;
54
+ };
55
+ widevineOptions?: {
56
+ licenseUrl: string;
57
+ serverCertificate?: ArrayBuffer;
58
+ };
59
+ playreadyOptions?: {
60
+ licenseUrl: string;
61
+ customData?: string;
62
+ };
63
+ }
64
+
65
+ export interface SubtitleTrack {
66
+ id: string;
67
+ label: string;
68
+ language: string;
69
+ url?: string;
70
+ kind?: 'subtitles' | 'captions' | 'descriptions';
71
+ default?: boolean;
72
+ }
73
+
74
+ export interface AudioTrack {
75
+ id: string;
76
+ label: string;
77
+ language: string;
78
+ channels?: number;
79
+ bitrate?: number;
80
+ codec?: string;
81
+ }
82
+
83
+ export interface Quality {
84
+ id: string;
85
+ label: string;
86
+ height: number;
87
+ width: number;
88
+ bitrate: number;
89
+ frameRate?: number;
90
+ codec?: string;
91
+ }
92
+
93
+ export interface AdaptiveBitrateConfig {
94
+ minBitrate?: number;
95
+ maxBitrate?: number;
96
+ startBitrate?: number;
97
+ autoLevelEnabled?: boolean;
98
+ startLevel?: number;
99
+ }
100
+
101
+ export interface VideoPlayerConfig {
102
+ autoPlay?: boolean;
103
+ muted?: boolean;
104
+ controls?: boolean;
105
+ loop?: boolean;
106
+ preload?: 'none' | 'metadata' | 'auto';
107
+ crossOrigin?: 'anonymous' | 'use-credentials';
108
+ playsInline?: boolean;
109
+ pictureInPicture?: boolean;
110
+ adaptiveBitrate?: AdaptiveBitrateConfig;
111
+ drm?: DRMConfig;
112
+ analytics?: AnalyticsConfig;
113
+ ads?: AdsConfig;
114
+ cast?: CastConfig;
115
+ offline?: OfflineConfig;
116
+ }
117
+
118
+ export interface AnalyticsConfig {
119
+ enabled: boolean;
120
+ providers: AnalyticsProvider[];
121
+ trackingInterval?: number;
122
+ customDimensions?: Record<string, any>;
123
+ }
124
+
125
+ export interface AnalyticsProvider {
126
+ name: string;
127
+ config: Record<string, any>;
128
+ track: (event: string, data: any) => void;
129
+ }
130
+
131
+ export interface AdsConfig {
132
+ enabled: boolean;
133
+ adTagUrl?: string;
134
+ adsManager?: any;
135
+ midrollPositions?: number[];
136
+ companionAds?: {
137
+ width: number;
138
+ height: number;
139
+ container: HTMLElement;
140
+ };
141
+ }
142
+
143
+ export interface CastConfig {
144
+ enabled: boolean;
145
+ receiverApplicationId?: string;
146
+ autoJoinPolicy?: 'ORIGIN_SCOPED' | 'TAB_AND_ORIGIN_SCOPED' | 'PAGE_SCOPED';
147
+ }
148
+
149
+ export interface OfflineConfig {
150
+ enabled: boolean;
151
+ storageLimit?: number;
152
+ downloadQuality?: 'auto' | 'high' | 'medium' | 'low';
153
+ }
154
+
155
+ export interface PlayerMetrics {
156
+ sessionId: string;
157
+ playbackStarted?: number;
158
+ totalPlayTime: number;
159
+ bufferingCount: number;
160
+ bufferingDuration: number;
161
+ averageBitrate: number;
162
+ qualityChanges: number;
163
+ errors: PlayerError[];
164
+ bandwidth?: number;
165
+ droppedFrames?: number;
166
+ decodedFrames?: number;
167
+ }
168
+
169
+ export interface PlayerError {
170
+ code: string;
171
+ message: string;
172
+ timestamp: number;
173
+ fatal: boolean;
174
+ data?: any;
175
+ }
176
+
177
+ export interface ProgressEvent {
178
+ currentTime: number;
179
+ duration: number;
180
+ buffered: TimeRanges;
181
+ seekable: TimeRanges;
182
+ played: TimeRanges;
183
+ }
184
+
185
+ export interface TimeRanges {
186
+ length: number;
187
+ start(index: number): number;
188
+ end(index: number): number;
189
+ }
190
+
191
+ export interface PlatformInfo {
192
+ type: PlatformType;
193
+ os: string;
194
+ version: string;
195
+ isTV: boolean;
196
+ isMobile: boolean;
197
+ isDesktop?: boolean;
198
+ hasTouch?: boolean;
199
+ screenSize?: {
200
+ width: number;
201
+ height: number;
202
+ };
203
+ }
204
+
205
+ export type PlayerEvent =
206
+ | 'ready'
207
+ | 'play'
208
+ | 'pause'
209
+ | 'ended'
210
+ | 'error'
211
+ | 'loadstart'
212
+ | 'loadedmetadata'
213
+ | 'timeupdate'
214
+ | 'progress'
215
+ | 'seeking'
216
+ | 'seeked'
217
+ | 'waiting'
218
+ | 'canplay'
219
+ | 'canplaythrough'
220
+ | 'volumechange'
221
+ | 'ratechange'
222
+ | 'qualitychange'
223
+ | 'subtitlechange'
224
+ | 'audiotrackchange'
225
+ | 'fullscreenchange'
226
+ | 'pictureInPicturechange'
227
+ | 'castStateChanged'
228
+ | 'adstart'
229
+ | 'adend'
230
+ | 'aderror';
231
+
232
+ export type EventHandler = (data?: any) => void;
233
+
234
+ export interface EventEmitter {
235
+ on(event: PlayerEvent, handler: EventHandler): void;
236
+ off(event: PlayerEvent, handler: EventHandler): void;
237
+ once(event: PlayerEvent, handler: EventHandler): void;
238
+ emit(event: PlayerEvent, data?: any): void;
239
+ removeAllListeners(event?: PlayerEvent): void;
240
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Simple event emitter for handling player events
3
+ */
4
+
5
+ type EventHandler = (...args: any[]) => void;
6
+
7
+ export class EventEmitter {
8
+ private events: Map<string, Set<EventHandler>>;
9
+
10
+ constructor() {
11
+ this.events = new Map();
12
+ }
13
+
14
+ on(event: string, handler: EventHandler): void {
15
+ if (!this.events.has(event)) {
16
+ this.events.set(event, new Set());
17
+ }
18
+ this.events.get(event)!.add(handler);
19
+ }
20
+
21
+ off(event: string, handler?: EventHandler): void {
22
+ if (!this.events.has(event)) return;
23
+
24
+ if (handler) {
25
+ this.events.get(event)!.delete(handler);
26
+ } else {
27
+ this.events.delete(event);
28
+ }
29
+ }
30
+
31
+ once(event: string, handler: EventHandler): void {
32
+ const onceWrapper = (...args: any[]) => {
33
+ handler(...args);
34
+ this.off(event, onceWrapper);
35
+ };
36
+ this.on(event, onceWrapper);
37
+ }
38
+
39
+ emit(event: string, ...args: any[]): void {
40
+ if (!this.events.has(event)) return;
41
+
42
+ this.events.get(event)!.forEach(handler => {
43
+ try {
44
+ handler(...args);
45
+ } catch (error) {
46
+ console.error(`Error in event handler for ${event}:`, error);
47
+ }
48
+ });
49
+ }
50
+
51
+ removeAllListeners(event?: string): void {
52
+ if (event) {
53
+ this.events.delete(event);
54
+ } else {
55
+ this.events.clear();
56
+ }
57
+ }
58
+
59
+ listenerCount(event: string): number {
60
+ return this.events.has(event) ? this.events.get(event)!.size : 0;
61
+ }
62
+
63
+ eventNames(): string[] {
64
+ return Array.from(this.events.keys());
65
+ }
66
+ }