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,250 @@
1
+ /**
2
+ * Abstract base player class that provides common functionality
3
+ */
4
+
5
+ import {
6
+ IVideoPlayer,
7
+ VideoSource,
8
+ PlayerConfig,
9
+ PlayerState,
10
+ PlayerEvents,
11
+ PlayerError,
12
+ Quality,
13
+ SubtitleTrack
14
+ } from './interfaces/IVideoPlayer';
15
+ import { EventEmitter } from './utils/EventEmitter';
16
+
17
+ export abstract class BasePlayer implements IVideoPlayer {
18
+ protected container: HTMLElement | null = null;
19
+ protected config: PlayerConfig;
20
+ protected events: EventEmitter;
21
+ protected state: PlayerState;
22
+ protected source: VideoSource | null = null;
23
+ protected subtitles: SubtitleTrack[] = [];
24
+ protected currentSubtitleIndex: number = -1;
25
+
26
+ constructor() {
27
+ this.config = this.getDefaultConfig();
28
+ this.events = new EventEmitter();
29
+ this.state = this.getDefaultState();
30
+ }
31
+
32
+ protected getDefaultConfig(): PlayerConfig {
33
+ return {
34
+ autoPlay: false,
35
+ muted: false,
36
+ volume: 1.0,
37
+ controls: true,
38
+ loop: false,
39
+ preload: 'metadata',
40
+ playsInline: true,
41
+ enableAdaptiveBitrate: true,
42
+ debug: false,
43
+ freeDuration: 0
44
+ };
45
+ }
46
+
47
+ protected getDefaultState(): PlayerState {
48
+ return {
49
+ isPlaying: false,
50
+ isPaused: true,
51
+ isBuffering: false,
52
+ isEnded: false,
53
+ isError: false,
54
+ currentTime: 0,
55
+ duration: 0,
56
+ bufferedPercentage: 0,
57
+ volume: 1.0,
58
+ isMuted: false,
59
+ playbackRate: 1.0,
60
+ availableQualities: []
61
+ };
62
+ }
63
+
64
+ async initialize(container: HTMLElement | string, config?: PlayerConfig): Promise<void> {
65
+ if (typeof container === 'string') {
66
+ const element = document.querySelector(container) as HTMLElement;
67
+ if (!element) {
68
+ throw new Error(`Container element not found: ${container}`);
69
+ }
70
+ this.container = element;
71
+ } else {
72
+ this.container = container;
73
+ }
74
+
75
+ this.config = { ...this.getDefaultConfig(), ...config };
76
+ this.state.volume = this.config.volume || 1.0;
77
+ this.state.isMuted = this.config.muted || false;
78
+
79
+ await this.setupPlayer();
80
+ }
81
+
82
+ protected abstract setupPlayer(): Promise<void>;
83
+
84
+ abstract destroy(): Promise<void>;
85
+
86
+ abstract load(source: VideoSource): Promise<void>;
87
+
88
+ async play(): Promise<void> {
89
+ this.state.isPlaying = true;
90
+ this.state.isPaused = false;
91
+ this.emit('onPlay');
92
+ }
93
+
94
+ pause(): void {
95
+ this.state.isPlaying = false;
96
+ this.state.isPaused = true;
97
+ this.emit('onPause');
98
+ }
99
+
100
+ stop(): void {
101
+ this.pause();
102
+ this.seek(0);
103
+ this.state.isEnded = true;
104
+ }
105
+
106
+ abstract seek(time: number): void;
107
+
108
+ setVolume(level: number): void {
109
+ const volume = Math.max(0, Math.min(1, level));
110
+ this.state.volume = volume;
111
+ this.emit('onVolumeChanged', volume);
112
+ }
113
+
114
+ mute(): void {
115
+ this.state.isMuted = true;
116
+ this.emit('onVolumeChanged', 0);
117
+ }
118
+
119
+ unmute(): void {
120
+ this.state.isMuted = false;
121
+ this.emit('onVolumeChanged', this.state.volume);
122
+ }
123
+
124
+ toggleMute(): void {
125
+ if (this.state.isMuted) {
126
+ this.unmute();
127
+ } else {
128
+ this.mute();
129
+ }
130
+ }
131
+
132
+ abstract getQualities(): Quality[];
133
+ abstract getCurrentQuality(): Quality | null;
134
+ abstract setQuality(index: number): void;
135
+ abstract setAutoQuality(enabled: boolean): void;
136
+
137
+ setPlaybackRate(rate: number): void {
138
+ this.state.playbackRate = rate;
139
+ }
140
+
141
+ getPlaybackRate(): number {
142
+ return this.state.playbackRate;
143
+ }
144
+
145
+ getCurrentTime(): number {
146
+ return this.state.currentTime;
147
+ }
148
+
149
+ getDuration(): number {
150
+ return this.state.duration;
151
+ }
152
+
153
+ getBufferedPercentage(): number {
154
+ return this.state.bufferedPercentage;
155
+ }
156
+
157
+ getState(): PlayerState {
158
+ return { ...this.state };
159
+ }
160
+
161
+ isPlaying(): boolean {
162
+ return this.state.isPlaying;
163
+ }
164
+
165
+ isPaused(): boolean {
166
+ return this.state.isPaused;
167
+ }
168
+
169
+ isEnded(): boolean {
170
+ return this.state.isEnded;
171
+ }
172
+
173
+ abstract enterFullscreen(): Promise<void>;
174
+ abstract exitFullscreen(): Promise<void>;
175
+
176
+ async toggleFullscreen(): Promise<void> {
177
+ if (document.fullscreenElement) {
178
+ await this.exitFullscreen();
179
+ } else {
180
+ await this.enterFullscreen();
181
+ }
182
+ }
183
+
184
+ abstract enterPictureInPicture(): Promise<void>;
185
+ abstract exitPictureInPicture(): Promise<void>;
186
+
187
+ on(event: keyof PlayerEvents, handler: Function): void {
188
+ this.events.on(event, handler as any);
189
+ }
190
+
191
+ off(event: keyof PlayerEvents, handler?: Function): void {
192
+ this.events.off(event, handler as any);
193
+ }
194
+
195
+ once(event: keyof PlayerEvents, handler: Function): void {
196
+ this.events.once(event, handler as any);
197
+ }
198
+
199
+ protected emit(event: keyof PlayerEvents, ...args: any[]): void {
200
+ this.events.emit(event, ...args);
201
+ }
202
+
203
+ // Optional runtime free preview controls (no-op base; platform implementations may override)
204
+ setFreeDuration?(seconds: number): void;
205
+ resetFreePreviewGate?(): void;
206
+
207
+ getSubtitles(): SubtitleTrack[] {
208
+ return this.subtitles;
209
+ }
210
+
211
+ setSubtitleTrack(index: number): void {
212
+ if (index >= 0 && index < this.subtitles.length) {
213
+ this.currentSubtitleIndex = index;
214
+ this.applySubtitleTrack(this.subtitles[index]);
215
+ }
216
+ }
217
+
218
+ disableSubtitles(): void {
219
+ this.currentSubtitleIndex = -1;
220
+ this.removeSubtitles();
221
+ }
222
+
223
+ protected abstract applySubtitleTrack(track: SubtitleTrack): void;
224
+ protected abstract removeSubtitles(): void;
225
+
226
+ protected handleError(error: PlayerError): void {
227
+ this.state.isError = true;
228
+ this.state.isPlaying = false;
229
+ this.emit('onError', error);
230
+
231
+ if (this.config.debug) {
232
+ console.error('[VideoPlayer Error]', error);
233
+ }
234
+ }
235
+
236
+ protected updateTime(time: number): void {
237
+ this.state.currentTime = time;
238
+ this.emit('onTimeUpdate', time);
239
+ }
240
+
241
+ protected updateBuffered(percentage: number): void {
242
+ this.state.bufferedPercentage = percentage;
243
+ this.emit('onProgress', percentage);
244
+ }
245
+
246
+ protected setBuffering(isBuffering: boolean): void {
247
+ this.state.isBuffering = isBuffering;
248
+ this.emit('onBuffering', isBuffering);
249
+ }
250
+ }
@@ -0,0 +1,237 @@
1
+ import { EventEmitter } from 'events';
2
+ import {
3
+ VideoPlayerConfig,
4
+ VideoSource,
5
+ PlayerState,
6
+ PlayerEvent,
7
+ EventHandler,
8
+ Quality,
9
+ SubtitleTrack,
10
+ AudioTrack,
11
+ PlayerMetrics,
12
+ PlayerError,
13
+ DRMConfig
14
+ } from './interfaces';
15
+
16
+ export abstract class VideoPlayer {
17
+ protected config: VideoPlayerConfig;
18
+ protected eventEmitter: EventEmitter;
19
+ protected state: PlayerState;
20
+ protected currentSource?: VideoSource;
21
+ protected metrics: PlayerMetrics;
22
+ protected errors: PlayerError[] = [];
23
+
24
+ constructor(config: VideoPlayerConfig = {}) {
25
+ this.config = {
26
+ autoPlay: false,
27
+ muted: false,
28
+ controls: true,
29
+ loop: false,
30
+ preload: 'metadata',
31
+ playsInline: true,
32
+ ...config
33
+ };
34
+
35
+ this.eventEmitter = new EventEmitter();
36
+ this.state = PlayerState.IDLE;
37
+ this.metrics = this.initializeMetrics();
38
+ }
39
+
40
+ // Core playback methods
41
+ abstract load(source: VideoSource): Promise<void>;
42
+ abstract play(): Promise<void>;
43
+ abstract pause(): void;
44
+ abstract stop(): void;
45
+ abstract seek(position: number): void;
46
+ abstract setVolume(volume: number): void;
47
+ abstract setPlaybackRate(rate: number): void;
48
+ abstract getCurrentTime(): number;
49
+ abstract getDuration(): number;
50
+ abstract getVolume(): number;
51
+ abstract getPlaybackRate(): number;
52
+ abstract isMuted(): boolean;
53
+ abstract setMuted(muted: boolean): void;
54
+
55
+ // Quality management
56
+ abstract getAvailableQualities(): Quality[];
57
+ abstract getCurrentQuality(): Quality | null;
58
+ abstract setQuality(quality: Quality): void;
59
+ abstract enableAutoQuality(enabled: boolean): void;
60
+
61
+ // Subtitle/Audio tracks
62
+ abstract getSubtitleTracks(): SubtitleTrack[];
63
+ abstract getCurrentSubtitleTrack(): SubtitleTrack | null;
64
+ abstract setSubtitleTrack(track: SubtitleTrack | null): void;
65
+ abstract getAudioTracks(): AudioTrack[];
66
+ abstract getCurrentAudioTrack(): AudioTrack | null;
67
+ abstract setAudioTrack(track: AudioTrack): void;
68
+
69
+ // Platform-specific features
70
+ abstract enterFullscreen(): void;
71
+ abstract exitFullscreen(): void;
72
+ abstract isFullscreen(): boolean;
73
+ abstract enterPictureInPicture(): void;
74
+ abstract exitPictureInPicture(): void;
75
+ abstract isPictureInPicture(): boolean;
76
+
77
+ // Events
78
+ on(event: PlayerEvent, handler: EventHandler): void {
79
+ this.eventEmitter.on(event, handler);
80
+ }
81
+
82
+ off(event: PlayerEvent, handler: EventHandler): void {
83
+ this.eventEmitter.off(event, handler);
84
+ }
85
+
86
+ once(event: PlayerEvent, handler: EventHandler): void {
87
+ this.eventEmitter.once(event, handler);
88
+ }
89
+
90
+ removeAllListeners(event?: PlayerEvent): void {
91
+ if (event) {
92
+ this.eventEmitter.removeAllListeners(event);
93
+ } else {
94
+ this.eventEmitter.removeAllListeners();
95
+ }
96
+ }
97
+
98
+ protected emit(event: PlayerEvent, data?: any): void {
99
+ this.eventEmitter.emit(event, data);
100
+
101
+ // Track analytics events
102
+ if (this.config.analytics?.enabled) {
103
+ this.trackAnalytics(event, data);
104
+ }
105
+ }
106
+
107
+ // State management
108
+ getState(): PlayerState {
109
+ return this.state;
110
+ }
111
+
112
+ protected setState(newState: PlayerState): void {
113
+ const oldState = this.state;
114
+ this.state = newState;
115
+
116
+ if (oldState !== newState) {
117
+ this.emit('statechange' as PlayerEvent, { oldState, newState });
118
+ }
119
+ }
120
+
121
+ // Configuration
122
+ getConfig(): VideoPlayerConfig {
123
+ return { ...this.config };
124
+ }
125
+
126
+ updateConfig(config: Partial<VideoPlayerConfig>): void {
127
+ this.config = { ...this.config, ...config };
128
+ this.applyConfig();
129
+ }
130
+
131
+ protected abstract applyConfig(): void;
132
+
133
+ // Metrics
134
+ getMetrics(): PlayerMetrics {
135
+ return {
136
+ ...this.metrics,
137
+ errors: [...this.errors]
138
+ };
139
+ }
140
+
141
+ protected initializeMetrics(): PlayerMetrics {
142
+ return {
143
+ sessionId: this.generateSessionId(),
144
+ totalPlayTime: 0,
145
+ bufferingCount: 0,
146
+ bufferingDuration: 0,
147
+ averageBitrate: 0,
148
+ qualityChanges: 0,
149
+ errors: []
150
+ };
151
+ }
152
+
153
+ protected generateSessionId(): string {
154
+ return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
155
+ }
156
+
157
+ // Error handling
158
+ protected handleError(error: PlayerError): void {
159
+ this.errors.push(error);
160
+ this.emit('error', error);
161
+
162
+ if (error.fatal) {
163
+ this.setState(PlayerState.ERROR);
164
+ }
165
+ }
166
+
167
+ // Analytics
168
+ protected trackAnalytics(event: string, data?: any): void {
169
+ if (!this.config.analytics?.providers) return;
170
+
171
+ const analyticsData = {
172
+ event,
173
+ timestamp: Date.now(),
174
+ sessionId: this.metrics.sessionId,
175
+ currentTime: this.getCurrentTime(),
176
+ duration: this.getDuration(),
177
+ state: this.state,
178
+ ...data
179
+ };
180
+
181
+ this.config.analytics.providers.forEach(provider => {
182
+ try {
183
+ provider.track(event, analyticsData);
184
+ } catch (error) {
185
+ console.error(`Analytics provider ${provider.name} failed:`, error);
186
+ }
187
+ });
188
+ }
189
+
190
+ // DRM
191
+ protected abstract configureDRM(drmConfig: DRMConfig): Promise<void>;
192
+
193
+ // Cleanup
194
+ abstract destroy(): void;
195
+
196
+ protected cleanup(): void {
197
+ this.removeAllListeners();
198
+ this.state = PlayerState.IDLE;
199
+ this.currentSource = undefined;
200
+ }
201
+
202
+ // Utility methods
203
+ protected formatTime(seconds: number): string {
204
+ if (!isFinite(seconds)) return '00:00';
205
+
206
+ const hours = Math.floor(seconds / 3600);
207
+ const minutes = Math.floor((seconds % 3600) / 60);
208
+ const secs = Math.floor(seconds % 60);
209
+
210
+ if (hours > 0) {
211
+ return `${hours}:${this.pad(minutes)}:${this.pad(secs)}`;
212
+ }
213
+ return `${minutes}:${this.pad(secs)}`;
214
+ }
215
+
216
+ private pad(num: number): string {
217
+ return num.toString().padStart(2, '0');
218
+ }
219
+
220
+ // Buffer management
221
+ abstract getBufferedRanges(): TimeRanges;
222
+ abstract getSeekableRanges(): TimeRanges;
223
+
224
+ protected isBuffering(): boolean {
225
+ return this.state === PlayerState.BUFFERING;
226
+ }
227
+
228
+ // Network
229
+ abstract getBandwidth(): number;
230
+ abstract getNetworkState(): number;
231
+
232
+ // Video properties
233
+ abstract getVideoWidth(): number;
234
+ abstract getVideoHeight(): number;
235
+ abstract getDroppedFrames(): number;
236
+ abstract getDecodedFrames(): number;
237
+ }
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Factory for creating video player instances based on platform
3
+ */
4
+
5
+ import { IVideoPlayer, PlayerConfig } from './interfaces/IVideoPlayer';
6
+
7
+ export type Platform =
8
+ | 'web'
9
+ | 'ios'
10
+ | 'android'
11
+ | 'tizen'
12
+ | 'webos'
13
+ | 'roku'
14
+ | 'androidtv'
15
+ | 'appletv'
16
+ | 'windows';
17
+
18
+ export class VideoPlayerFactory {
19
+ /**
20
+ * Create a video player instance for the specified platform
21
+ */
22
+ static async create(
23
+ platform: Platform,
24
+ container: HTMLElement | string | any,
25
+ config?: PlayerConfig
26
+ ): Promise<IVideoPlayer> {
27
+ // Dynamic imports will be resolved at runtime
28
+ // This allows the factory to work even if not all platform packages are installed
29
+
30
+ switch (platform) {
31
+ case 'web':
32
+ // Dynamic imports will be resolved at runtime when the package is available
33
+ try {
34
+ const WebModule = await (eval('import("@unified-video/web")') as Promise<any>);
35
+ if (WebModule?.WebPlayer) {
36
+ const player = new WebModule.WebPlayer();
37
+ await player.initialize(container, config);
38
+ return player;
39
+ }
40
+ } catch (e) {
41
+ // Package not installed or not available
42
+ }
43
+ break;
44
+
45
+ case 'ios':
46
+ case 'android':
47
+ try {
48
+ const RNModule = await (eval('import("@unified-video/react-native")') as Promise<any>);
49
+ if (RNModule?.ReactNativePlayer) {
50
+ // React Native player is a component, handle differently
51
+ return RNModule.ReactNativePlayer;
52
+ }
53
+ } catch (e) {
54
+ // Package not installed or not available
55
+ }
56
+ break;
57
+
58
+ case 'tizen':
59
+ case 'webos':
60
+ try {
61
+ const EnactModule = await (eval('import("@unified-video/enact")') as Promise<any>);
62
+ if (EnactModule?.EnactPlayer) {
63
+ const player = new EnactModule.EnactPlayer();
64
+ await player.initialize(container, config);
65
+ return player;
66
+ }
67
+ } catch (e) {
68
+ // Package not installed or not available
69
+ }
70
+ break;
71
+
72
+ case 'roku':
73
+ try {
74
+ const RokuModule = await (eval('import("@unified-video/roku")') as Promise<any>);
75
+ if (RokuModule?.RokuPlayer) {
76
+ const player = new RokuModule.RokuPlayer();
77
+ await player.initialize(container, config);
78
+ return player;
79
+ }
80
+ } catch (e) {
81
+ // Package not installed or not available
82
+ }
83
+ break;
84
+
85
+ default:
86
+ throw new Error(`Platform '${platform}' is not supported`);
87
+ }
88
+
89
+ throw new Error(`Failed to load player for platform '${platform}'`);
90
+ }
91
+
92
+ /**
93
+ * Detect the current platform
94
+ */
95
+ static detectPlatform(): Platform {
96
+ // Check if running in React Native
97
+ if (typeof global !== 'undefined' && (global as any).nativeCallSyncHook) {
98
+ // React Native environment
99
+ const { Platform } = require('react-native');
100
+ return Platform.OS as Platform;
101
+ }
102
+
103
+ // Check if running in browser
104
+ if (typeof window !== 'undefined') {
105
+ const userAgent = window.navigator.userAgent.toLowerCase();
106
+
107
+ // Check for Smart TV platforms
108
+ if (userAgent.includes('tizen')) return 'tizen';
109
+ if (userAgent.includes('webos')) return 'webos';
110
+ if (userAgent.includes('roku')) return 'roku';
111
+
112
+ // Check for mobile browsers (might be Android TV)
113
+ if (userAgent.includes('android') && userAgent.includes('tv')) {
114
+ return 'androidtv';
115
+ }
116
+
117
+ // Check for Apple TV
118
+ if (userAgent.includes('appletv')) return 'appletv';
119
+
120
+ // Check for Windows
121
+ if (userAgent.includes('windows')) return 'windows';
122
+
123
+ // Default to web
124
+ return 'web';
125
+ }
126
+
127
+ // Check if running in Node.js (server-side)
128
+ if (typeof process !== 'undefined' && process.versions && process.versions.node) {
129
+ return 'web'; // Default to web for SSR
130
+ }
131
+
132
+ throw new Error('Unable to detect platform');
133
+ }
134
+
135
+ /**
136
+ * Create a video player for the current platform
137
+ */
138
+ static async createForCurrentPlatform(
139
+ container: HTMLElement | string | any,
140
+ config?: PlayerConfig
141
+ ): Promise<IVideoPlayer> {
142
+ const platform = this.detectPlatform();
143
+ return this.create(platform, container, config);
144
+ }
145
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @unified-video/core
3
+ * Core interfaces and implementations for the Unified Video Framework
4
+ */
5
+
6
+ // Export all interfaces
7
+ export * from './interfaces/IVideoPlayer';
8
+
9
+ // Export base classes
10
+ export { BasePlayer } from './BasePlayer';
11
+
12
+ // Export factory
13
+ export { VideoPlayerFactory } from './VideoPlayerFactory';
14
+ export type { Platform } from './VideoPlayerFactory';
15
+
16
+ // Export utilities
17
+ export { EventEmitter } from './utils/EventEmitter';
18
+
19
+ // Export version
20
+ export const VERSION = '1.0.0';