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,423 @@
1
+ /**
2
+ * React Native implementation of the video player
3
+ */
4
+
5
+ import React, { useRef, useImperativeHandle, forwardRef, useCallback, useEffect, useState } from 'react';
6
+ import { View, StyleSheet, Platform, ViewStyle } from 'react-native';
7
+ import Video, {
8
+ OnLoadData,
9
+ OnProgressData,
10
+ OnSeekData,
11
+ LoadError,
12
+ OnBufferData,
13
+ OnBandwidthUpdateData,
14
+ VideoProperties,
15
+ TextTrackType,
16
+ SelectedTrackType
17
+ } from 'react-native-video';
18
+ import {
19
+ IVideoPlayer,
20
+ VideoSource,
21
+ PlayerConfig,
22
+ PlayerState,
23
+ Quality,
24
+ SubtitleTrack,
25
+ PlayerError,
26
+ PlayerEvents
27
+ } from '@unified-video/core';
28
+ import { EventEmitter } from './utils/EventEmitter';
29
+
30
+ interface ReactNativePlayerProps {
31
+ style?: ViewStyle;
32
+ config?: PlayerConfig;
33
+ onReady?: () => void;
34
+ onError?: (error: PlayerError) => void;
35
+ }
36
+
37
+ export interface ReactNativePlayerRef extends IVideoPlayer {
38
+ getVideoRef: () => Video | null;
39
+ }
40
+
41
+ export const ReactNativePlayer = forwardRef<ReactNativePlayerRef, ReactNativePlayerProps>(
42
+ ({ style, config = {}, onReady, onError }, ref) => {
43
+ const videoRef = useRef<Video>(null);
44
+ const events = useRef(new EventEmitter()).current;
45
+
46
+ const [source, setSource] = useState<VideoSource | null>(null);
47
+ const [paused, setPaused] = useState(!config.autoPlay);
48
+ const [volume, setVolume] = useState(config.volume ?? 1.0);
49
+ const [muted, setMuted] = useState(config.muted ?? false);
50
+ const [rate, setRate] = useState(1.0);
51
+ const [currentTime, setCurrentTime] = useState(0);
52
+ const [duration, setDuration] = useState(0);
53
+ const [buffering, setBuffering] = useState(false);
54
+ const [qualities, setQualities] = useState<Quality[]>([]);
55
+ const [selectedQuality, setSelectedQuality] = useState<Quality | null>(null);
56
+ const [selectedTextTrack, setSelectedTextTrack] = useState<number>(-1);
57
+
58
+ // State getter
59
+ const getState = useCallback((): PlayerState => ({
60
+ isPlaying: !paused,
61
+ isPaused: paused,
62
+ isBuffering: buffering,
63
+ isEnded: false,
64
+ isError: false,
65
+ currentTime,
66
+ duration,
67
+ bufferedPercentage: 0,
68
+ volume,
69
+ isMuted: muted,
70
+ playbackRate: rate,
71
+ currentQuality: selectedQuality,
72
+ availableQualities: qualities
73
+ }), [paused, buffering, currentTime, duration, volume, muted, rate, selectedQuality, qualities]);
74
+
75
+ // Implement IVideoPlayer interface
76
+ const playerMethods: ReactNativePlayerRef = {
77
+ async initialize(container: any, cfg?: PlayerConfig): Promise<void> {
78
+ // In React Native, initialization is handled by component mount
79
+ if (onReady) onReady();
80
+ events.emit('onReady');
81
+ },
82
+
83
+ async destroy(): Promise<void> {
84
+ setSource(null);
85
+ events.removeAllListeners();
86
+ },
87
+
88
+ async load(videoSource: VideoSource): Promise<void> {
89
+ const videoSrc: any = {
90
+ uri: videoSource.url,
91
+ type: videoSource.type
92
+ };
93
+
94
+ // Add DRM if provided
95
+ if (videoSource.drm) {
96
+ videoSrc.drm = {
97
+ type: Platform.select({
98
+ ios: videoSource.drm.type === 'widevine' ? 'fairplay' : videoSource.drm.type,
99
+ android: videoSource.drm.type
100
+ }),
101
+ licenseServer: videoSource.drm.licenseUrl,
102
+ headers: videoSource.drm.headers
103
+ };
104
+
105
+ if (videoSource.drm.certificateUrl) {
106
+ videoSrc.drm.certificateUrl = videoSource.drm.certificateUrl;
107
+ }
108
+ }
109
+
110
+ setSource(videoSource);
111
+ events.emit('onLoad', videoSource);
112
+ },
113
+
114
+ async play(): Promise<void> {
115
+ setPaused(false);
116
+ events.emit('onPlay');
117
+ },
118
+
119
+ pause(): void {
120
+ setPaused(true);
121
+ events.emit('onPause');
122
+ },
123
+
124
+ stop(): void {
125
+ setPaused(true);
126
+ setCurrentTime(0);
127
+ if (videoRef.current) {
128
+ videoRef.current.seek(0);
129
+ }
130
+ },
131
+
132
+ seek(time: number): void {
133
+ if (videoRef.current) {
134
+ videoRef.current.seek(time);
135
+ setCurrentTime(time);
136
+ events.emit('onSeeking');
137
+ }
138
+ },
139
+
140
+ setVolume(level: number): void {
141
+ const vol = Math.max(0, Math.min(1, level));
142
+ setVolume(vol);
143
+ events.emit('onVolumeChanged', vol);
144
+ },
145
+
146
+ mute(): void {
147
+ setMuted(true);
148
+ events.emit('onVolumeChanged', 0);
149
+ },
150
+
151
+ unmute(): void {
152
+ setMuted(false);
153
+ events.emit('onVolumeChanged', volume);
154
+ },
155
+
156
+ toggleMute(): void {
157
+ setMuted(!muted);
158
+ events.emit('onVolumeChanged', muted ? volume : 0);
159
+ },
160
+
161
+ getQualities(): Quality[] {
162
+ return qualities;
163
+ },
164
+
165
+ getCurrentQuality(): Quality | null {
166
+ return selectedQuality;
167
+ },
168
+
169
+ setQuality(index: number): void {
170
+ if (qualities[index]) {
171
+ setSelectedQuality(qualities[index]);
172
+ // Platform-specific quality switching would go here
173
+ events.emit('onQualityChanged', qualities[index]);
174
+ }
175
+ },
176
+
177
+ setAutoQuality(enabled: boolean): void {
178
+ // Auto quality selection logic
179
+ if (enabled && qualities.length > 0) {
180
+ // Select best quality based on bandwidth
181
+ const bestQuality = qualities[qualities.length - 1];
182
+ setSelectedQuality(bestQuality);
183
+ }
184
+ },
185
+
186
+ setPlaybackRate(rate: number): void {
187
+ setRate(rate);
188
+ },
189
+
190
+ getPlaybackRate(): number {
191
+ return rate;
192
+ },
193
+
194
+ getCurrentTime(): number {
195
+ return currentTime;
196
+ },
197
+
198
+ getDuration(): number {
199
+ return duration;
200
+ },
201
+
202
+ getBufferedPercentage(): number {
203
+ return 0; // Would need to calculate from buffer data
204
+ },
205
+
206
+ getState,
207
+
208
+ isPlaying(): boolean {
209
+ return !paused;
210
+ },
211
+
212
+ isPaused(): boolean {
213
+ return paused;
214
+ },
215
+
216
+ isEnded(): boolean {
217
+ return currentTime >= duration && duration > 0;
218
+ },
219
+
220
+ async enterFullscreen(): Promise<void> {
221
+ if (videoRef.current) {
222
+ (videoRef.current as any).presentFullscreenPlayer?.();
223
+ events.emit('onFullscreenChanged', true);
224
+ }
225
+ },
226
+
227
+ async exitFullscreen(): Promise<void> {
228
+ if (videoRef.current) {
229
+ (videoRef.current as any).dismissFullscreenPlayer?.();
230
+ events.emit('onFullscreenChanged', false);
231
+ }
232
+ },
233
+
234
+ async toggleFullscreen(): Promise<void> {
235
+ // Toggle implementation would check current state
236
+ await this.enterFullscreen();
237
+ },
238
+
239
+ async enterPictureInPicture(): Promise<void> {
240
+ if (Platform.OS === 'ios' && videoRef.current) {
241
+ (videoRef.current as any).restoreUserInterfaceForPictureInPictureStop?.();
242
+ }
243
+ },
244
+
245
+ async exitPictureInPicture(): Promise<void> {
246
+ // PiP exit implementation
247
+ },
248
+
249
+ on(event: keyof PlayerEvents, handler: Function): void {
250
+ events.on(event as string, handler);
251
+ },
252
+
253
+ off(event: keyof PlayerEvents, handler?: Function): void {
254
+ events.off(event as string, handler);
255
+ },
256
+
257
+ once(event: keyof PlayerEvents, handler: Function): void {
258
+ events.once(event as string, handler);
259
+ },
260
+
261
+ getSubtitles(): SubtitleTrack[] {
262
+ return source?.subtitles || [];
263
+ },
264
+
265
+ setSubtitleTrack(index: number): void {
266
+ setSelectedTextTrack(index);
267
+ },
268
+
269
+ disableSubtitles(): void {
270
+ setSelectedTextTrack(-1);
271
+ },
272
+
273
+ getVideoRef: () => videoRef.current
274
+ };
275
+
276
+ useImperativeHandle(ref, () => playerMethods, [
277
+ paused, volume, muted, rate, currentTime, duration,
278
+ qualities, selectedQuality, source, buffering
279
+ ]);
280
+
281
+ // Video event handlers
282
+ const handleLoad = useCallback((data: OnLoadData) => {
283
+ setDuration(data.duration);
284
+
285
+ // Extract quality levels if available
286
+ if (data.videoTracks && data.videoTracks.length > 0) {
287
+ const qualityLevels = data.videoTracks.map((track, index) => ({
288
+ height: track.height || 0,
289
+ width: track.width || 0,
290
+ bitrate: track.bitrate || 0,
291
+ label: `${track.height}p`,
292
+ index
293
+ }));
294
+ setQualities(qualityLevels);
295
+ }
296
+
297
+ events.emit('onLoadedMetadata', {
298
+ duration: data.duration,
299
+ width: data.naturalSize?.width,
300
+ height: data.naturalSize?.height
301
+ });
302
+ }, [events]);
303
+
304
+ const handleProgress = useCallback((data: OnProgressData) => {
305
+ setCurrentTime(data.currentTime);
306
+ events.emit('onTimeUpdate', data.currentTime);
307
+
308
+ if (data.playableDuration && duration > 0) {
309
+ const bufferedPercentage = (data.playableDuration / duration) * 100;
310
+ events.emit('onProgress', bufferedPercentage);
311
+ }
312
+ }, [duration, events]);
313
+
314
+ const handleBuffer = useCallback((data: OnBufferData) => {
315
+ setBuffering(data.isBuffering);
316
+ events.emit('onBuffering', data.isBuffering);
317
+ }, [events]);
318
+
319
+ const handleError = useCallback((error: LoadError) => {
320
+ const playerError: PlayerError = {
321
+ code: error.error?.code || 'UNKNOWN',
322
+ message: error.error?.localizedDescription || 'Unknown error',
323
+ type: 'media',
324
+ fatal: true,
325
+ details: error.error
326
+ };
327
+
328
+ if (onError) onError(playerError);
329
+ events.emit('onError', playerError);
330
+ }, [events, onError]);
331
+
332
+ const handleEnd = useCallback(() => {
333
+ events.emit('onEnded');
334
+ }, [events]);
335
+
336
+ const handleSeek = useCallback((data: OnSeekData) => {
337
+ setCurrentTime(data.currentTime);
338
+ events.emit('onSeeked');
339
+ }, [events]);
340
+
341
+ const handleBandwidthUpdate = useCallback((data: OnBandwidthUpdateData) => {
342
+ // Could use this for adaptive bitrate switching
343
+ console.log('Bandwidth update:', data.bitrate);
344
+ }, []);
345
+
346
+ if (!source) {
347
+ return <View style={[styles.container, style]} />;
348
+ }
349
+
350
+ // Convert source to react-native-video format
351
+ const videoSource: any = {
352
+ uri: source.url
353
+ };
354
+
355
+ if (source.drm) {
356
+ videoSource.drm = {
357
+ type: Platform.select({
358
+ ios: source.drm.type === 'widevine' ? 'fairplay' : source.drm.type,
359
+ android: source.drm.type
360
+ }),
361
+ licenseServer: source.drm.licenseUrl,
362
+ headers: source.drm.headers
363
+ };
364
+ }
365
+
366
+ // Convert subtitles to text tracks
367
+ const textTracks = source.subtitles?.map(subtitle => ({
368
+ type: 'text/vtt' as TextTrackType,
369
+ language: subtitle.language,
370
+ title: subtitle.label,
371
+ uri: subtitle.url
372
+ }));
373
+
374
+ return (
375
+ <View style={[styles.container, style]}>
376
+ <Video
377
+ ref={videoRef}
378
+ source={videoSource}
379
+ style={styles.video}
380
+ paused={paused}
381
+ volume={volume}
382
+ muted={muted}
383
+ rate={rate}
384
+ resizeMode="contain"
385
+ repeat={config.loop || false}
386
+ controls={config.controls !== false}
387
+ playInBackground={false}
388
+ playWhenInactive={false}
389
+ ignoreSilentSwitch="ignore"
390
+ progressUpdateInterval={250}
391
+ textTracks={textTracks}
392
+ selectedTextTrack={
393
+ selectedTextTrack >= 0
394
+ ? { type: SelectedTrackType.INDEX, value: selectedTextTrack }
395
+ : { type: SelectedTrackType.DISABLED }
396
+ }
397
+ onLoad={handleLoad}
398
+ onProgress={handleProgress}
399
+ onBuffer={handleBuffer}
400
+ onError={handleError}
401
+ onEnd={handleEnd}
402
+ onSeek={handleSeek}
403
+ onBandwidthUpdate={handleBandwidthUpdate}
404
+ onTimedMetadata={(metadata) => console.log('Metadata:', metadata)}
405
+ />
406
+ </View>
407
+ );
408
+ }
409
+ );
410
+
411
+ const styles = StyleSheet.create({
412
+ container: {
413
+ flex: 1,
414
+ backgroundColor: '#000'
415
+ },
416
+ video: {
417
+ position: 'absolute',
418
+ top: 0,
419
+ left: 0,
420
+ right: 0,
421
+ bottom: 0
422
+ }
423
+ });
@@ -0,0 +1,224 @@
1
+ /**
2
+ * React Native Video Player Implementation
3
+ * This would wrap react-native-video or similar native video libraries
4
+ */
5
+
6
+ import React, { useRef, useEffect, useCallback } from 'react';
7
+ import { View, StyleSheet, Platform } from 'react-native';
8
+ // Would import: import Video from 'react-native-video';
9
+ import type {
10
+ VideoSource,
11
+ VideoPlayerConfig,
12
+ VideoPlayerInterface,
13
+ VideoPlayerState
14
+ } from '../../core/src/interfaces';
15
+
16
+ export class ReactNativeVideoPlayer implements VideoPlayerInterface {
17
+ private videoRef: any;
18
+ private config: VideoPlayerConfig;
19
+ private state: VideoPlayerState = 'idle';
20
+ private listeners: Map<string, Function[]> = new Map();
21
+
22
+ constructor(container: any, config: VideoPlayerConfig) {
23
+ this.config = config;
24
+ // In real implementation, would initialize react-native-video here
25
+ console.log('ReactNativeVideoPlayer initialized for', Platform.OS);
26
+ }
27
+
28
+ async load(source: VideoSource): Promise<void> {
29
+ // Implementation would load video into react-native-video
30
+ this.state = 'loading';
31
+
32
+ // Would set source on native player
33
+ // this.videoRef.source = {
34
+ // uri: source.url,
35
+ // type: source.type,
36
+ // headers: source.headers
37
+ // };
38
+
39
+ return Promise.resolve();
40
+ }
41
+
42
+ async play(): Promise<void> {
43
+ // Would call native play method
44
+ // this.videoRef.play();
45
+ this.state = 'playing';
46
+ this.emit('play');
47
+ return Promise.resolve();
48
+ }
49
+
50
+ pause(): void {
51
+ // Would call native pause method
52
+ // this.videoRef.pause();
53
+ this.state = 'paused';
54
+ this.emit('pause');
55
+ }
56
+
57
+ seek(position: number): void {
58
+ // Would seek in native player
59
+ // this.videoRef.seek(position);
60
+ this.emit('seeking', { position });
61
+ }
62
+
63
+ setVolume(volume: number): void {
64
+ // Would set volume on native player
65
+ // this.videoRef.volume = volume;
66
+ this.emit('volumechange', { volume });
67
+ }
68
+
69
+ getCurrentTime(): number {
70
+ // Would get from native player
71
+ // return this.videoRef.currentTime;
72
+ return 0;
73
+ }
74
+
75
+ getDuration(): number {
76
+ // Would get from native player
77
+ // return this.videoRef.duration;
78
+ return 0;
79
+ }
80
+
81
+ getVolume(): number {
82
+ // Would get from native player
83
+ // return this.videoRef.volume;
84
+ return 1;
85
+ }
86
+
87
+ isMuted(): boolean {
88
+ // Would get from native player
89
+ // return this.videoRef.muted;
90
+ return false;
91
+ }
92
+
93
+ mute(): void {
94
+ // Would mute native player
95
+ // this.videoRef.muted = true;
96
+ this.emit('volumechange', { muted: true });
97
+ }
98
+
99
+ unmute(): void {
100
+ // Would unmute native player
101
+ // this.videoRef.muted = false;
102
+ this.emit('volumechange', { muted: false });
103
+ }
104
+
105
+ setPlaybackRate(rate: number): void {
106
+ // Would set rate on native player
107
+ // this.videoRef.rate = rate;
108
+ }
109
+
110
+ getPlaybackRate(): number {
111
+ // Would get from native player
112
+ // return this.videoRef.rate;
113
+ return 1;
114
+ }
115
+
116
+ enterFullscreen(): void {
117
+ // Would use native fullscreen APIs
118
+ if (Platform.OS === 'ios') {
119
+ // iOS specific fullscreen
120
+ } else if (Platform.OS === 'android') {
121
+ // Android specific fullscreen
122
+ }
123
+ }
124
+
125
+ exitFullscreen(): void {
126
+ // Would exit native fullscreen
127
+ }
128
+
129
+ enterPictureInPicture(): void {
130
+ // Would use native PiP APIs if available
131
+ if (Platform.OS === 'ios' && Platform.Version >= 14) {
132
+ // iOS PiP implementation
133
+ } else if (Platform.OS === 'android' && Platform.Version >= 26) {
134
+ // Android PiP implementation
135
+ }
136
+ }
137
+
138
+ exitPictureInPicture(): void {
139
+ // Would exit native PiP
140
+ }
141
+
142
+ on(event: string, handler: Function): void {
143
+ if (!this.listeners.has(event)) {
144
+ this.listeners.set(event, []);
145
+ }
146
+ this.listeners.get(event)?.push(handler);
147
+ }
148
+
149
+ off(event: string, handler: Function): void {
150
+ const handlers = this.listeners.get(event);
151
+ if (handlers) {
152
+ const index = handlers.indexOf(handler);
153
+ if (index > -1) {
154
+ handlers.splice(index, 1);
155
+ }
156
+ }
157
+ }
158
+
159
+ private emit(event: string, data?: any): void {
160
+ const handlers = this.listeners.get(event);
161
+ if (handlers) {
162
+ handlers.forEach(handler => handler(data));
163
+ }
164
+ }
165
+
166
+ destroy(): void {
167
+ // Clean up native resources
168
+ this.listeners.clear();
169
+ this.state = 'idle';
170
+ }
171
+
172
+ getState(): VideoPlayerState {
173
+ return this.state;
174
+ }
175
+ }
176
+
177
+ // React Component Wrapper
178
+ export const VideoPlayer: React.FC<{
179
+ source: VideoSource;
180
+ config?: VideoPlayerConfig;
181
+ style?: any;
182
+ onReady?: () => void;
183
+ onPlay?: () => void;
184
+ onPause?: () => void;
185
+ onEnd?: () => void;
186
+ onError?: (error: any) => void;
187
+ }> = ({ source, config, style, ...callbacks }) => {
188
+ const videoRef = useRef(null);
189
+
190
+ useEffect(() => {
191
+ // Initialize player when component mounts
192
+ // Set up native video callbacks
193
+ }, []);
194
+
195
+ return (
196
+ <View style={[styles.container, style]}>
197
+ {/* In real implementation, would render react-native-video here */}
198
+ {/* <Video
199
+ ref={videoRef}
200
+ source={{ uri: source.url }}
201
+ style={styles.video}
202
+ controls={config?.controls}
203
+ paused={!config?.autoPlay}
204
+ {...callbacks}
205
+ /> */}
206
+ </View>
207
+ );
208
+ };
209
+
210
+ const styles = StyleSheet.create({
211
+ container: {
212
+ flex: 1,
213
+ backgroundColor: 'black',
214
+ },
215
+ video: {
216
+ position: 'absolute',
217
+ top: 0,
218
+ left: 0,
219
+ bottom: 0,
220
+ right: 0,
221
+ },
222
+ });
223
+
224
+ export default ReactNativeVideoPlayer;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @unified-video/react-native
3
+ * React Native implementation for iOS and Android
4
+ */
5
+
6
+ // Export main video player
7
+ export { ReactNativeVideoPlayer, VideoPlayer } from './VideoPlayer';
8
+
9
+ // Re-export core types for convenience
10
+ export type {
11
+ VideoSource,
12
+ VideoPlayerConfig,
13
+ VideoPlayerInterface,
14
+ DRMConfig,
15
+ SubtitleTrack,
16
+ AudioTrack,
17
+ Quality,
18
+ PlayerState,
19
+ PlayerEvent,
20
+ PlayerError,
21
+ PlayerMetrics
22
+ } from '@unified-video/core';
23
+
24
+ // Export version
25
+ export const VERSION = '1.0.0';
26
+
27
+ // Export platform identifier
28
+ export const PLATFORM = 'react-native';