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,159 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Unified Video Framework - Web Demo</title>
7
+ <style>
8
+ html, body { height: 100%; margin: 0; background: #111; color: #eee; font-family: Arial, sans-serif; }
9
+ .app { display: flex; height: 100%; }
10
+ .sidebar { width: 320px; background: #1b1b1b; padding: 16px; box-sizing: border-box; border-right: 1px solid #2a2a2a; }
11
+ .content { flex: 1; display: flex; flex-direction: column; }
12
+ .player-container { position: relative; flex: 1; padding: 12px; box-sizing: border-box; }
13
+ .player { width: 100%; height: 100%; border-radius: 6px; overflow: hidden; background: #000; }
14
+ h1 { font-size: 18px; margin: 0 0 12px; }
15
+ label { display: block; margin: 10px 0 6px; font-size: 12px; color: #bbb; }
16
+ input, select { width: 100%; padding: 8px; border-radius: 4px; border: 1px solid #333; background: #222; color: #eee; }
17
+ button { padding: 8px 12px; background: #2e7d32; color: #fff; border: none; border-radius: 4px; cursor: pointer; margin-right: 8px; }
18
+ button.secondary { background: #1565c0; }
19
+ .controls { padding: 12px; border-top: 1px solid #2a2a2a; background: #161616; }
20
+ .row { margin-bottom: 12px; }
21
+ .small { font-size: 12px; color: #aaa; }
22
+ .examples { margin-top: 16px; }
23
+ .examples button { display: block; width: 100%; margin: 6px 0; background: #333; }
24
+ </style>
25
+ </head>
26
+ <body>
27
+ <div class="app">
28
+ <div class="sidebar">
29
+ <h1>Web Demo</h1>
30
+ <div class="row">
31
+ <label for="sourceUrl">Video URL</label>
32
+ <input id="sourceUrl" type="text" placeholder="https://... .mp4 | .m3u8 | .mpd" />
33
+ </div>
34
+ <div class="row">
35
+ <label for="subtitleUrl">Subtitle (VTT) URL</label>
36
+ <input id="subtitleUrl" type="text" placeholder="https://... .vtt (optional)" />
37
+ </div>
38
+ <div class="row">
39
+ <button id="loadBtn">Load</button>
40
+ <button id="playBtn" class="secondary">Play</button>
41
+ <button id="pauseBtn" class="secondary">Pause</button>
42
+ </div>
43
+ <div class="examples">
44
+ <div class="small">Examples</div>
45
+ <button data-url="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4">Big Buck Bunny (MP4)</button>
46
+ <button data-url="https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8">HLS Sample (m3u8)</button>
47
+ <button data-url="https://dash.akamaized.net/envivio/EnvivioDash3/manifest.mpd">DASH Sample (mpd)</button>
48
+ </div>
49
+ </div>
50
+ <div class="content">
51
+ <div class="player-container">
52
+ <div id="player" class="player"></div>
53
+ </div>
54
+ <div class="controls">
55
+ <div class="row">
56
+ <button id="seekBack">⏪ 10s</button>
57
+ <button id="seekFwd">10s ⏩</button>
58
+ <button id="muteBtn">Mute</button>
59
+ <button id="unmuteBtn">Unmute</button>
60
+ <button id="pipBtn">PiP</button>
61
+ <button id="fsBtn">Fullscreen</button>
62
+ </div>
63
+ <div class="row small" id="status">Idle</div>
64
+ </div>
65
+ </div>
66
+ </div>
67
+
68
+ <script type="module">
69
+ // If this page is opened in a popup (checkout return), notify the opener and close
70
+ try {
71
+ const params = new URLSearchParams(window.location.search);
72
+ if (params.get('popup') === '1') {
73
+ const status = params.get('rental') || 'cancel';
74
+ const orderId = params.get('order_id') || null;
75
+ window.opener?.postMessage({ type: 'uvfCheckout', status, orderId }, '*');
76
+ window.close();
77
+ }
78
+ } catch (_) {}
79
+
80
+ import { WebPlayer } from '../../packages/web/dist/WebPlayer.js';
81
+
82
+ const container = document.getElementById('player');
83
+ const player = new WebPlayer();
84
+
85
+ const urlInput = document.getElementById('sourceUrl');
86
+ const subInput = document.getElementById('subtitleUrl');
87
+ const status = document.getElementById('status');
88
+
89
+ function setStatus(text) {
90
+ status.textContent = text;
91
+ }
92
+
93
+ async function initPlayer() {
94
+ // Fetch a dynamic paywall config (mock) for demo tenant
95
+ let paywallCfg = null;
96
+ try {
97
+ const r = await fetch('http://localhost:3100/api/rentals/config?tenant=demo&userId=u1&videoId=v1');
98
+ if (r.ok) paywallCfg = await r.json();
99
+ } catch(_) {}
100
+
101
+ await player.initialize(container, {
102
+ autoPlay: false,
103
+ playsInline: true,
104
+ freeDuration: 60,
105
+ paywall: paywallCfg
106
+ });
107
+ }
108
+
109
+ async function loadFromInputs() {
110
+ const url = urlInput.value.trim();
111
+ const sub = subInput.value.trim();
112
+ if (!url) {
113
+ alert('Please enter a video URL');
114
+ return;
115
+ }
116
+ const source = sub ? { url, subtitles: [{ url: sub, label: 'English', language: 'en', kind: 'subtitles' }] } : { url };
117
+ await player.load(source);
118
+ setStatus('Loaded: ' + url);
119
+ }
120
+
121
+ document.getElementById('loadBtn').addEventListener('click', loadFromInputs);
122
+ document.getElementById('playBtn').addEventListener('click', () => player.play());
123
+ document.getElementById('pauseBtn').addEventListener('click', () => player.pause());
124
+ document.getElementById('seekBack').addEventListener('click', () => player.seek(Math.max(0, player.getCurrentTime() - 10)));
125
+ document.getElementById('seekFwd').addEventListener('click', () => player.seek(player.getCurrentTime() + 10));
126
+ document.getElementById('muteBtn').addEventListener('click', () => player.mute());
127
+ document.getElementById('unmuteBtn').addEventListener('click', () => player.unmute());
128
+ document.getElementById('pipBtn').addEventListener('click', () => player.enterPictureInPicture());
129
+ document.getElementById('fsBtn').addEventListener('click', () => player.enterFullscreen());
130
+
131
+ // Example buttons
132
+ document.querySelectorAll('.examples button').forEach(btn => {
133
+ btn.addEventListener('click', async () => {
134
+ urlInput.value = btn.dataset.url;
135
+ subInput.value = '';
136
+ await loadFromInputs();
137
+ await player.play();
138
+ });
139
+ });
140
+
141
+ // Initialize and set default content
142
+ await initPlayer();
143
+
144
+ // Update status on core events emitted by WebPlayer
145
+ player.on('onReady', () => setStatus('ready'));
146
+ player.on('onPlay', () => setStatus('play'));
147
+ player.on('onPause', () => setStatus('pause'));
148
+ player.on('onEnded', () => setStatus('ended'));
149
+ player.on('onTimeUpdate', (t) => setStatus('timeupdate | ' + t));
150
+ player.on('onProgress', (b) => setStatus('progress | ' + b));
151
+ player.on('onBuffering', (is) => setStatus('buffering | ' + is));
152
+
153
+ // Load an example by default
154
+ urlInput.value = document.querySelector('.examples button')?.dataset.url || '';
155
+ await loadFromInputs();
156
+ </script>
157
+ </body>
158
+ </html>
159
+
@@ -0,0 +1,24 @@
1
+ # Environment variables for Rental API
2
+
3
+ # Server
4
+ APP_BASE_URL=http://localhost:3100
5
+
6
+ # Database
7
+
8
+ # Stripe
9
+ STRIPE_SECRET_KEY=sk_test_51Ng98aG6SInHNoTWaOwzKVMVh4TrBoAph0udCI7jx53Ho2aaVMyAqWi2jyqg2tu4MYRXunb9Gcok0FTn0LDKtYWy00aHmPk4u7
10
+ STRIPE_WEBHOOK_SECRET=whsec_...
11
+
12
+ # Cashfree (use sandbox creds for testing)
13
+ CASHFREE_APP_ID=TEST10707044630f12610890ea0a28ed44070701
14
+ CASHFREE_SECRET_KEY=cfsk_ma_test_6d20ed37e28873306663b9615051e96d_9a698b9d
15
+ CASHFREE_BASE_URL=https://sandbox.cashfree.com
16
+ # Cashfree (live creds)
17
+ # CASHFREE_APP_ID=1015351787aa22366940646e8bc1535101
18
+ # CASHFREE_SECRET_KEY=cfsk_ma_prod_8fa46de39384ffcf86de01fb50aacdcc_0f3197a9
19
+ # https://api.cashfree.com - live
20
+ # https://sandbox.cashfree.com - sandbox
21
+
22
+ DATABASE_URL=postgres://postgres:CzfWq3PUksRKnw50O6vlacxF@localhost:5432/uvf
23
+ PORT=3100
24
+ ENABLE_DEV_MOCKS=1
@@ -0,0 +1,23 @@
1
+ # Rental API (Stripe + Pesapal)
2
+
3
+ Implements the per-video rental paywall flow. Keep the player payment-agnostic and unlock playback after entitlement is true.
4
+
5
+ ## Commands
6
+
7
+ - Install deps: (from repo root)
8
+ - npm install
9
+ - Build: (from repo root)
10
+ - npm run build:rental-api
11
+ - Dev server (hot reload):
12
+ - cd apps/rental-api && npm run dev
13
+ - Start (compiled):
14
+ - cd apps/rental-api && npm start
15
+
16
+ ## Env
17
+
18
+ Copy .env.example to .env and set values.
19
+
20
+ ## Migrations
21
+
22
+ Run SQL in migrations/001_init.sql on your Postgres DB (or use your migration tool). A simple script scaffolding is included in scripts/run-migration.js.
23
+
@@ -0,0 +1,35 @@
1
+ -- Enable pgcrypto for gen_random_uuid()
2
+ CREATE EXTENSION IF NOT EXISTS pgcrypto;
3
+
4
+ -- Payments
5
+ CREATE TABLE IF NOT EXISTS payments (
6
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
7
+ gateway TEXT NOT NULL CHECK (gateway IN ('stripe','pesapal','google_pay')),
8
+ gateway_ref TEXT NOT NULL UNIQUE,
9
+ amount_cents INT NOT NULL,
10
+ currency TEXT NOT NULL,
11
+ status TEXT NOT NULL CHECK (status IN ('succeeded','pending','failed','refunded')),
12
+ raw_payload JSONB NOT NULL,
13
+ user_id TEXT NOT NULL,
14
+ video_id TEXT NOT NULL,
15
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
16
+ );
17
+
18
+ CREATE INDEX IF NOT EXISTS idx_payments_user_video ON payments(user_id, video_id);
19
+
20
+ -- Entitlements (rentals only)
21
+ CREATE TABLE IF NOT EXISTS entitlements (
22
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
23
+ user_id TEXT NOT NULL,
24
+ video_id TEXT NOT NULL,
25
+ type TEXT NOT NULL CHECK (type = 'rental'),
26
+ starts_at TIMESTAMPTZ NOT NULL,
27
+ expires_at TIMESTAMPTZ NOT NULL,
28
+ status TEXT NOT NULL CHECK (status IN ('active','expired')),
29
+ source_payment_id UUID NOT NULL REFERENCES payments(id) ON DELETE RESTRICT,
30
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
31
+ UNIQUE (user_id, video_id, status)
32
+ );
33
+
34
+ CREATE INDEX IF NOT EXISTS idx_entitlements_user_video ON entitlements(user_id, video_id);
35
+
@@ -0,0 +1,10 @@
1
+ -- Videos catalog used by the rental API
2
+ CREATE TABLE IF NOT EXISTS videos (
3
+ video_id TEXT PRIMARY KEY,
4
+ title TEXT,
5
+ price_cents INT NOT NULL,
6
+ currency TEXT NOT NULL,
7
+ rental_duration_hours INT NOT NULL DEFAULT 48,
8
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
9
+ );
10
+
@@ -0,0 +1,4 @@
1
+ -- Add gateway_subref to correlate refunds with payments (e.g., Stripe payment_intent)
2
+ ALTER TABLE payments ADD COLUMN IF NOT EXISTS gateway_subref TEXT;
3
+ CREATE INDEX IF NOT EXISTS idx_payments_gateway_subref ON payments(gateway_subref);
4
+
@@ -0,0 +1,4 @@
1
+ -- Update payments.gateway check constraint to include 'cashfree' and drop 'pesapal'
2
+ ALTER TABLE payments DROP CONSTRAINT IF EXISTS payments_gateway_check;
3
+ ALTER TABLE payments ADD CONSTRAINT payments_gateway_check CHECK (gateway IN ('stripe','cashfree','google_pay'));
4
+
@@ -0,0 +1,5 @@
1
+ -- Seed a demo video so checkout endpoints can resolve price and duration
2
+ INSERT INTO videos (video_id, title, price_cents, currency, rental_duration_hours)
3
+ VALUES ('v1', 'Demo Video', 199, 'USD', 48)
4
+ ON CONFLICT (video_id) DO NOTHING;
5
+