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,177 @@
1
+ // @ts-nocheck
2
+ import React, { useEffect, useRef } from 'react';
3
+ import type { CSSProperties } from 'react';
4
+ import type { VideoSource, SubtitleTrack, VideoMetadata, PlayerConfig } from '@unified-video/core';
5
+ import { WebPlayer } from '../WebPlayer';
6
+
7
+ export type WebPlayerViewProps = {
8
+ // Player config
9
+ autoPlay?: boolean;
10
+ muted?: boolean;
11
+ enableAdaptiveBitrate?: boolean;
12
+ debug?: boolean;
13
+ freeDuration?: number;
14
+ // Paywall
15
+ paywall?: import('@unified-video/core').PaywallConfig;
16
+ paywallConfigUrl?: string; // optional endpoint returning PaywallConfig JSON
17
+
18
+ // Source config
19
+ url: string;
20
+ type?: 'mp4' | 'hls' | 'dash' | 'webm' | 'auto';
21
+ subtitles?: SubtitleTrack[];
22
+ metadata?: VideoMetadata;
23
+
24
+ // Optional Google Cast sender SDK loader
25
+ cast?: boolean;
26
+
27
+ // Styling
28
+ className?: string;
29
+ style?: CSSProperties;
30
+ // Dynamic theming: pass a single accent color string or an object with fields
31
+ // { accent, accent2, iconColor, textPrimary, textSecondary }
32
+ playerTheme?: string | { accent?: string; accent2?: string; iconColor?: string; textPrimary?: string; textSecondary?: string };
33
+
34
+ // Callbacks
35
+ onReady?: (player: WebPlayer) => void;
36
+ onError?: (error: unknown) => void;
37
+ };
38
+
39
+ export const WebPlayerView: React.FC<WebPlayerViewProps> = (props) => {
40
+ const containerRef = useRef<HTMLDivElement>(null);
41
+ const playerRef = useRef<WebPlayer | null>(null);
42
+
43
+ // Checkout return bridge: forward rental=success/cancel with session_id/order_id back to opener and close
44
+ useEffect(() => {
45
+ try {
46
+ const params = new URLSearchParams(window.location.search);
47
+ const popup = (params.get('popup') || '').toLowerCase() === '1';
48
+ const status = (params.get('rental') || '').toLowerCase();
49
+ const orderId = params.get('order_id') || '';
50
+ const sessionId = params.get('session_id') || '';
51
+ if (popup && (status === 'success' || status === 'cancel')) {
52
+ try { window.opener?.postMessage({ type: 'uvfCheckout', status, orderId, sessionId }, '*'); } catch (_) {}
53
+ try { window.close(); } catch (_) {}
54
+ }
55
+ } catch (_) {}
56
+ }, []);
57
+
58
+ useEffect(() => {
59
+ let cancelled = false;
60
+
61
+ async function boot() {
62
+ if (!containerRef.current) return;
63
+
64
+ const player = new WebPlayer();
65
+ playerRef.current = player;
66
+
67
+ // Optionally load Google Cast sender SDK
68
+ if (props.cast) {
69
+ try {
70
+ const existing = document.querySelector('script[data-cast-sdk="1"]');
71
+ if (!existing) {
72
+ const s = document.createElement('script');
73
+ s.src = 'https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1';
74
+ s.async = true;
75
+ s.setAttribute('data-cast-sdk', '1');
76
+ document.head.appendChild(s);
77
+ }
78
+ } catch (_) {
79
+ // ignore load issues in SSR or restricted environments
80
+ }
81
+ }
82
+
83
+ // Resolve paywall config: inline or fetched
84
+ let paywallCfg = props.paywall as any;
85
+ if (!paywallCfg && props.paywallConfigUrl) {
86
+ try {
87
+ const resp = await fetch(props.paywallConfigUrl);
88
+ if (resp.ok) paywallCfg = await resp.json();
89
+ } catch(_) {}
90
+ }
91
+
92
+ const config: PlayerConfig = {
93
+ autoPlay: props.autoPlay ?? false,
94
+ muted: props.muted ?? false,
95
+ enableAdaptiveBitrate: props.enableAdaptiveBitrate ?? true,
96
+ debug: props.debug ?? false,
97
+ freeDuration: props.freeDuration,
98
+ paywall: paywallCfg
99
+ };
100
+
101
+ try {
102
+ await player.initialize(containerRef.current, config);
103
+
104
+ // Apply theme before loading source (so poster and UI show themed styles)
105
+ try {
106
+ if (props.playerTheme && (player as any).setTheme) {
107
+ (player as any).setTheme(props.playerTheme as any);
108
+ }
109
+ } catch (_) {}
110
+
111
+ const source: VideoSource = {
112
+ url: props.url,
113
+ type: props.type ?? 'auto',
114
+ subtitles: props.subtitles,
115
+ metadata: props.metadata,
116
+ };
117
+
118
+ await player.load(source);
119
+ if (!cancelled) props.onReady?.(player);
120
+ } catch (err) {
121
+ if (!cancelled) props.onError?.(err);
122
+ }
123
+ }
124
+
125
+ void boot();
126
+
127
+ return () => {
128
+ cancelled = true;
129
+ if (playerRef.current) {
130
+ playerRef.current.destroy().catch(() => {});
131
+ playerRef.current = null;
132
+ }
133
+ };
134
+ }, [
135
+ props.autoPlay,
136
+ props.muted,
137
+ props.enableAdaptiveBitrate,
138
+ props.debug,
139
+ props.url,
140
+ props.type,
141
+ JSON.stringify(props.subtitles),
142
+ JSON.stringify(props.metadata),
143
+ props.cast,
144
+ props.freeDuration,
145
+ ])
146
+
147
+ // Update free preview duration at runtime without full re-init
148
+ useEffect(() => {
149
+ const p = playerRef.current as any;
150
+ if (p && typeof p.setFreeDuration === 'function' && typeof props.freeDuration !== 'undefined') {
151
+ try { p.setFreeDuration(props.freeDuration as number); } catch(_) {}
152
+ }
153
+ }, [props.freeDuration]);
154
+
155
+ // Update paywall config at runtime if prop changes
156
+ useEffect(() => {
157
+ const p = playerRef.current as any;
158
+ if (p && typeof p.setPaywallConfig === 'function' && props.paywall) {
159
+ try { p.setPaywallConfig(props.paywall as any); } catch(_) {}
160
+ }
161
+ }, [JSON.stringify(props.paywall)]);
162
+
163
+ // Respond to theme updates without reinitializing the player
164
+ useEffect(() => {
165
+ const p = playerRef.current as any;
166
+ try {
167
+ if (p && typeof p.setTheme === 'function') {
168
+ p.setTheme(props.playerTheme as any);
169
+ }
170
+ } catch (_) {}
171
+ }, [JSON.stringify(props.playerTheme)]);
172
+
173
+ return <div ref={containerRef} className={props.className} style={props.style} />;
174
+ };
175
+
176
+ export default WebPlayerView;
177
+
@@ -0,0 +1,23 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "rootDir": "./src",
5
+ "outDir": "./dist",
6
+ "composite": true,
7
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
8
+ "noUnusedLocals": false,
9
+ "noUnusedParameters": false
10
+ },
11
+ "include": [
12
+ "src/**/*"
13
+ ],
14
+ "exclude": [
15
+ "node_modules",
16
+ "dist",
17
+ "**/*.test.ts",
18
+ "**/*.spec.ts"
19
+ ],
20
+ "references": [
21
+ { "path": "../core" }
22
+ ]
23
+ }
@@ -0,0 +1,45 @@
1
+ const path = require('path');
2
+
3
+ module.exports = {
4
+ mode: 'production',
5
+ entry: './src/index.ts',
6
+ output: {
7
+ path: path.resolve(__dirname, 'dist'),
8
+ filename: 'unified-video-web.js',
9
+ library: {
10
+ name: 'UnifiedVideoWeb',
11
+ type: 'umd'
12
+ },
13
+ globalObject: 'this'
14
+ },
15
+ module: {
16
+ rules: [
17
+ {
18
+ test: /\.tsx?$/,
19
+ use: 'ts-loader',
20
+ exclude: /node_modules/
21
+ }
22
+ ]
23
+ },
24
+ resolve: {
25
+ extensions: ['.tsx', '.ts', '.js'],
26
+ alias: {
27
+ '@unified-video/core': path.resolve(__dirname, '../core/src')
28
+ }
29
+ },
30
+ externals: {
31
+ 'hls.js': {
32
+ commonjs: 'hls.js',
33
+ commonjs2: 'hls.js',
34
+ amd: 'hls.js',
35
+ root: 'Hls'
36
+ },
37
+ 'dashjs': {
38
+ commonjs: 'dashjs',
39
+ commonjs2: 'dashjs',
40
+ amd: 'dashjs',
41
+ root: 'dashjs'
42
+ }
43
+ },
44
+ devtool: 'source-map'
45
+ };
package/server.js ADDED
@@ -0,0 +1,131 @@
1
+ // Simple HTTP server for local development
2
+ // This server properly handles CORS and MIME types for video streaming
3
+
4
+ const http = require('http');
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+ const url = require('url');
8
+
9
+ const PORT = 3000;
10
+ const HOST = 'localhost';
11
+
12
+ // MIME type mappings
13
+ const mimeTypes = {
14
+ '.html': 'text/html',
15
+ '.js': 'text/javascript',
16
+ '.css': 'text/css',
17
+ '.json': 'application/json',
18
+ '.png': 'image/png',
19
+ '.jpg': 'image/jpg',
20
+ '.gif': 'image/gif',
21
+ '.svg': 'image/svg+xml',
22
+ '.wav': 'audio/wav',
23
+ '.mp4': 'video/mp4',
24
+ '.webm': 'video/webm',
25
+ '.mp3': 'audio/mpeg',
26
+ '.m3u8': 'application/x-mpegURL',
27
+ '.ts': 'video/MP2T',
28
+ '.mpd': 'application/dash+xml',
29
+ '.woff': 'application/font-woff',
30
+ '.ttf': 'application/font-ttf',
31
+ '.eot': 'application/vnd.ms-fontobject',
32
+ '.otf': 'application/font-otf',
33
+ '.wasm': 'application/wasm'
34
+ };
35
+
36
+ const server = http.createServer((req, res) => {
37
+ console.log(`${req.method} ${req.url}`);
38
+
39
+ // Parse URL
40
+ const parsedUrl = url.parse(req.url);
41
+
42
+ // Extract pathname
43
+ let pathname = `.${parsedUrl.pathname}`;
44
+
45
+ // Default to index.html for root
46
+ if (pathname === './') {
47
+ pathname = './apps/demo/demo.html';
48
+ }
49
+
50
+ // Resolve full path
51
+ const filePath = path.resolve(pathname);
52
+
53
+ // Security check - ensure we're not serving files outside the project directory
54
+ const projectDir = path.resolve('./');
55
+ if (!filePath.startsWith(projectDir)) {
56
+ res.statusCode = 403;
57
+ res.end('Forbidden');
58
+ return;
59
+ }
60
+
61
+ // Check if file exists
62
+ fs.exists(filePath, (exist) => {
63
+ if (!exist) {
64
+ // File not found
65
+ res.statusCode = 404;
66
+ res.end(`File ${pathname} not found!`);
67
+ return;
68
+ }
69
+
70
+ // If directory, try to serve index.html
71
+ if (fs.statSync(filePath).isDirectory()) {
72
+ const indexPath = path.join(filePath, 'index.html');
73
+ if (fs.existsSync(indexPath)) {
74
+ serveFile(indexPath, res);
75
+ } else {
76
+ res.statusCode = 403;
77
+ res.end('Directory listing not allowed');
78
+ }
79
+ return;
80
+ }
81
+
82
+ // Serve the file
83
+ serveFile(filePath, res);
84
+ });
85
+ });
86
+
87
+ function serveFile(filePath, res) {
88
+ // Read file
89
+ fs.readFile(filePath, (err, data) => {
90
+ if (err) {
91
+ res.statusCode = 500;
92
+ res.end(`Error getting the file: ${err}.`);
93
+ } else {
94
+ // Set proper headers
95
+ const ext = path.parse(filePath).ext;
96
+ const mimeType = mimeTypes[ext] || 'application/octet-stream';
97
+
98
+ // Add CORS headers for development
99
+ res.setHeader('Access-Control-Allow-Origin', '*');
100
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
101
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Range');
102
+
103
+ // Set content type
104
+ res.setHeader('Content-Type', mimeType);
105
+
106
+ // Enable byte-range requests for video streaming
107
+ if (mimeType.startsWith('video/') || mimeType.startsWith('audio/')) {
108
+ res.setHeader('Accept-Ranges', 'bytes');
109
+ }
110
+
111
+ // Send the file
112
+ res.statusCode = 200;
113
+ res.end(data);
114
+ }
115
+ });
116
+ }
117
+
118
+ server.listen(PORT, HOST, () => {
119
+ console.log(`
120
+ ╔════════════════════════════════════════════════════════╗
121
+ ║ ║
122
+ ║ Unified Video Framework - Development Server ║
123
+ ║ ║
124
+ ╚════════════════════════════════════════════════════════╝
125
+
126
+ Server running at: http://${HOST}:${PORT}
127
+ Demo page: http://${HOST}:${PORT}/apps/demo/demo.html
128
+
129
+ Press Ctrl+C to stop the server
130
+ `);
131
+ });
package/server.py ADDED
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Simple HTTP server for local development of Unified Video Framework
4
+ Python 3 alternative to Node.js server
5
+ """
6
+
7
+ import http.server
8
+ import socketserver
9
+ import os
10
+ import sys
11
+ from urllib.parse import urlparse
12
+
13
+ PORT = 3000
14
+ HOST = "localhost"
15
+
16
+ class CORSRequestHandler(http.server.SimpleHTTPRequestHandler):
17
+ """HTTP request handler with CORS headers."""
18
+
19
+ def end_headers(self):
20
+ """Add CORS headers to all responses."""
21
+ self.send_header('Access-Control-Allow-Origin', '*')
22
+ self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
23
+ self.send_header('Access-Control-Allow-Headers', 'Content-Type, Range')
24
+
25
+ # Add proper MIME types for video files
26
+ if self.path.endswith('.m3u8'):
27
+ self.send_header('Content-Type', 'application/x-mpegURL')
28
+ elif self.path.endswith('.mpd'):
29
+ self.send_header('Content-Type', 'application/dash+xml')
30
+ elif self.path.endswith('.ts'):
31
+ self.send_header('Content-Type', 'video/MP2T')
32
+
33
+ super().end_headers()
34
+
35
+ def do_OPTIONS(self):
36
+ """Handle OPTIONS requests for CORS preflight."""
37
+ self.send_response(200)
38
+ self.end_headers()
39
+
40
+ def do_GET(self):
41
+ """Handle GET requests."""
42
+ # Redirect root to demo page
43
+ if self.path == '/':
44
+ self.path = '/apps/demo/demo.html'
45
+
46
+ # Log the request
47
+ print(f"GET {self.path}")
48
+
49
+ # Serve the file
50
+ super().do_GET()
51
+
52
+ def run_server():
53
+ """Start the development server."""
54
+ try:
55
+ # Change to the project directory
56
+ os.chdir(os.path.dirname(os.path.abspath(__file__)))
57
+
58
+ # Create server
59
+ with socketserver.TCPServer((HOST, PORT), CORSRequestHandler) as httpd:
60
+ print(f"""
61
+ ╔════════════════════════════════════════════════════════╗
62
+ ║ ║
63
+ ║ Unified Video Framework - Development Server ║
64
+ ║ (Python Version) ║
65
+ ║ ║
66
+ ╚════════════════════════════════════════════════════════╝
67
+
68
+ Server running at: http://{HOST}:{PORT}
69
+ Demo page: http://{HOST}:{PORT}/apps/demo/demo.html
70
+
71
+ Press Ctrl+C to stop the server
72
+ """)
73
+ # Start serving
74
+ httpd.serve_forever()
75
+
76
+ except KeyboardInterrupt:
77
+ print("\nServer stopped.")
78
+ sys.exit(0)
79
+ except Exception as e:
80
+ print(f"Error starting server: {e}")
81
+ sys.exit(1)
82
+
83
+ if __name__ == "__main__":
84
+ run_server()
package/test-urls.ps1 ADDED
@@ -0,0 +1,97 @@
1
+ # Test all video URLs in the framework to ensure they're accessible
2
+
3
+ Write-Host "Testing Video URLs in Unified Video Framework" -ForegroundColor Cyan
4
+ Write-Host "=" * 50
5
+
6
+ # Define all URLs to test
7
+ $urls = @(
8
+ @{Name="Mux HLS Stream"; URL="https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8"; Type="HLS"},
9
+ @{Name="Unified Streaming HLS"; URL="https://demo.unified-streaming.com/k8s/features/stable/video/tears-of-steel/tears-of-steel.ism/.m3u8"; Type="HLS"},
10
+ @{Name="Akamai DASH BBB"; URL="https://dash.akamaized.net/akamai/bbb_30fps/bbb_30fps.mpd"; Type="DASH"},
11
+ @{Name="Akamai DASH Envivio"; URL="https://dash.akamaized.net/envivio/EnvivioDash3/manifest.mpd"; Type="DASH"},
12
+ @{Name="Google Shaka DASH"; URL="https://storage.googleapis.com/shaka-demo-assets/angel-one/dash.mpd"; Type="DASH"},
13
+ @{Name="Big Buck Bunny MP4"; URL="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"; Type="MP4"},
14
+ @{Name="Elephants Dream MP4"; URL="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4"; Type="MP4"},
15
+ @{Name="Sintel MP4"; URL="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/Sintel.mp4"; Type="MP4"}
16
+ )
17
+
18
+ $results = @()
19
+
20
+ foreach ($item in $urls) {
21
+ Write-Host "`nTesting: $($item.Name) [$($item.Type)]" -ForegroundColor Yellow
22
+ Write-Host "URL: $($item.URL)" -ForegroundColor Gray
23
+
24
+ try {
25
+ $response = Invoke-WebRequest -Uri $item.URL -Method Head -TimeoutSec 10 -UseBasicParsing -ErrorAction Stop
26
+ $statusCode = $response.StatusCode
27
+
28
+ if ($statusCode -eq 200) {
29
+ Write-Host "✓ SUCCESS: Status $statusCode" -ForegroundColor Green
30
+ $status = "Working"
31
+ } else {
32
+ Write-Host "⚠ WARNING: Status $statusCode" -ForegroundColor Yellow
33
+ $status = "Warning - Status $statusCode"
34
+ }
35
+ }
36
+ catch {
37
+ $errorMessage = $_.Exception.Message
38
+
39
+ if ($errorMessage -like "*403*") {
40
+ Write-Host "✗ FORBIDDEN: Access denied (403)" -ForegroundColor Red
41
+ $status = "Broken - 403 Forbidden"
42
+ }
43
+ elseif ($errorMessage -like "*404*") {
44
+ Write-Host "✗ NOT FOUND: URL not found (404)" -ForegroundColor Red
45
+ $status = "Broken - 404 Not Found"
46
+ }
47
+ else {
48
+ # Some servers don't support HEAD requests, try GET with limited range
49
+ try {
50
+ $response2 = Invoke-WebRequest -Uri $item.URL -Method Get -Headers @{"Range"="bytes=0-1"} -TimeoutSec 10 -UseBasicParsing -ErrorAction Stop
51
+ Write-Host "✓ SUCCESS: Server responded (GET request)" -ForegroundColor Green
52
+ $status = "Working"
53
+ }
54
+ catch {
55
+ Write-Host "✗ ERROR: $($_.Exception.Message)" -ForegroundColor Red
56
+ $status = "Error - $($_.Exception.Message)"
57
+ }
58
+ }
59
+ }
60
+
61
+ $results += [PSCustomObject]@{
62
+ Name = $item.Name
63
+ Type = $item.Type
64
+ URL = $item.URL
65
+ Status = $status
66
+ }
67
+ }
68
+
69
+ Write-Host "`n" + ("=" * 50) -ForegroundColor Cyan
70
+ Write-Host "SUMMARY REPORT" -ForegroundColor Cyan
71
+ Write-Host ("=" * 50) -ForegroundColor Cyan
72
+
73
+ $working = $results | Where-Object { $_.Status -eq "Working" }
74
+ $broken = $results | Where-Object { $_.Status -like "Broken*" }
75
+ $errors = $results | Where-Object { $_.Status -like "Error*" -or $_.Status -like "Warning*" }
76
+
77
+ Write-Host "`nWorking URLs: $($working.Count)/$($results.Count)" -ForegroundColor Green
78
+ if ($broken.Count -gt 0) {
79
+ Write-Host "Broken URLs: $($broken.Count)" -ForegroundColor Red
80
+ $broken | ForEach-Object {
81
+ Write-Host " - $($_.Name): $($_.Status)" -ForegroundColor Red
82
+ }
83
+ }
84
+
85
+ if ($errors.Count -gt 0) {
86
+ Write-Host "`nURLs with issues: $($errors.Count)" -ForegroundColor Yellow
87
+ $errors | ForEach-Object {
88
+ Write-Host " - $($_.Name): $($_.Status)" -ForegroundColor Yellow
89
+ }
90
+ }
91
+
92
+ Write-Host "`nDetailed Results:" -ForegroundColor Cyan
93
+ $results | Format-Table -AutoSize
94
+
95
+ # Export results to JSON for reference
96
+ $results | ConvertTo-Json -Depth 3 | Out-File "url-test-results.json"
97
+ Write-Host "`nResults saved to url-test-results.json" -ForegroundColor Gray
@@ -0,0 +1,87 @@
1
+ # Test all video URLs in the framework to ensure they're accessible
2
+
3
+ Write-Host "`nTesting Video URLs in Unified Video Framework`n" -ForegroundColor Cyan
4
+ Write-Host ("=" * 60) -ForegroundColor Cyan
5
+
6
+ # Define all URLs to test
7
+ $urls = @(
8
+ @{Name="Mux HLS Stream"; URL="https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8"; Type="HLS"},
9
+ @{Name="Unified Streaming HLS"; URL="https://demo.unified-streaming.com/k8s/features/stable/video/tears-of-steel/tears-of-steel.ism/.m3u8"; Type="HLS"},
10
+ @{Name="Akamai DASH BBB"; URL="https://dash.akamaized.net/akamai/bbb_30fps/bbb_30fps.mpd"; Type="DASH"},
11
+ @{Name="Akamai DASH Envivio"; URL="https://dash.akamaized.net/envivio/EnvivioDash3/manifest.mpd"; Type="DASH"},
12
+ @{Name="Google Shaka DASH"; URL="https://storage.googleapis.com/shaka-demo-assets/angel-one/dash.mpd"; Type="DASH"},
13
+ @{Name="Big Buck Bunny MP4"; URL="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"; Type="MP4"},
14
+ @{Name="Elephants Dream MP4"; URL="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4"; Type="MP4"},
15
+ @{Name="Sintel MP4"; URL="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/Sintel.mp4"; Type="MP4"}
16
+ )
17
+
18
+ $results = @()
19
+ $workingCount = 0
20
+ $brokenCount = 0
21
+
22
+ foreach ($item in $urls) {
23
+ Write-Host "`nTesting: $($item.Name) [$($item.Type)]" -ForegroundColor Yellow
24
+ Write-Host "URL: $($item.URL)" -ForegroundColor Gray
25
+
26
+ $status = "Unknown"
27
+
28
+ try {
29
+ # Try a simple web request with a timeout
30
+ $response = Invoke-WebRequest -Uri $item.URL -Method Get -Headers @{"Range"="bytes=0-1"} -TimeoutSec 5 -UseBasicParsing -ErrorAction Stop
31
+ Write-Host "✓ SUCCESS: URL is accessible" -ForegroundColor Green
32
+ $status = "Working"
33
+ $workingCount++
34
+ }
35
+ catch {
36
+ $errorMsg = $_.Exception.Message
37
+
38
+ if ($errorMsg -match "403") {
39
+ Write-Host "✗ FORBIDDEN: Access denied (403)" -ForegroundColor Red
40
+ $status = "403 Forbidden"
41
+ $brokenCount++
42
+ }
43
+ elseif ($errorMsg -match "404") {
44
+ Write-Host "✗ NOT FOUND: URL not found (404)" -ForegroundColor Red
45
+ $status = "404 Not Found"
46
+ $brokenCount++
47
+ }
48
+ else {
49
+ Write-Host "⚠ WARNING: Could not verify (may still work in browser)" -ForegroundColor Yellow
50
+ $status = "Uncertain"
51
+ }
52
+ }
53
+
54
+ $results += [PSCustomObject]@{
55
+ Name = $item.Name
56
+ Type = $item.Type
57
+ Status = $status
58
+ }
59
+ }
60
+
61
+ Write-Host "`n" -NoNewline
62
+ Write-Host ("=" * 60) -ForegroundColor Cyan
63
+ Write-Host "SUMMARY REPORT" -ForegroundColor Cyan
64
+ Write-Host ("=" * 60) -ForegroundColor Cyan
65
+
66
+ Write-Host "`nTotal URLs tested: $($urls.Count)" -ForegroundColor White
67
+ Write-Host "✓ Working: $workingCount" -ForegroundColor Green
68
+ Write-Host "✗ Broken: $brokenCount" -ForegroundColor Red
69
+ Write-Host "⚠ Uncertain: $($urls.Count - $workingCount - $brokenCount)" -ForegroundColor Yellow
70
+
71
+ Write-Host "`nDetailed Results:" -ForegroundColor Cyan
72
+ $results | Format-Table Name, Type, Status -AutoSize
73
+
74
+ # Show which files use broken URLs if any found
75
+ if ($brokenCount -gt 0) {
76
+ Write-Host "`n⚠ Action Required:" -ForegroundColor Red
77
+ Write-Host "Found $brokenCount broken URL(s) that need to be replaced in the framework." -ForegroundColor Red
78
+
79
+ $brokenUrls = $results | Where-Object { $_.Status -match "403|404" }
80
+ foreach ($broken in $brokenUrls) {
81
+ Write-Host "`n • $($broken.Name) ($($broken.Status))" -ForegroundColor Yellow
82
+ Write-Host " Type: $($broken.Type)" -ForegroundColor Gray
83
+ }
84
+ }
85
+ else {
86
+ Write-Host "`n✓ All URLs are working correctly!" -ForegroundColor Green
87
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "commonjs",
5
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
6
+ "declaration": true,
7
+ "declarationMap": true,
8
+ "sourceMap": true,
9
+ "outDir": "./dist",
10
+ "rootDir": "./",
11
+ "composite": true,
12
+ "removeComments": true,
13
+ "strict": true,
14
+ "noUnusedLocals": true,
15
+ "noUnusedParameters": true,
16
+ "noImplicitReturns": true,
17
+ "noFallthroughCasesInSwitch": true,
18
+ "esModuleInterop": true,
19
+ "skipLibCheck": true,
20
+ "allowSyntheticDefaultImports": true,
21
+ "forceConsistentCasingInFileNames": true,
22
+ "moduleResolution": "node",
23
+ "resolveJsonModule": true,
24
+ "isolatedModules": true,
25
+ "jsx": "react"
26
+ },
27
+ "exclude": [
28
+ "node_modules",
29
+ "dist",
30
+ "build",
31
+ "*.config.js",
32
+ "*.config.ts"
33
+ ],
34
+ "references": [
35
+ { "path": "./packages/core" },
36
+ { "path": "./packages/web" },
37
+ { "path": "./packages/react-native" }
38
+ ]
39
+ }