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,73 @@
1
+ import { Router, type Request, type Response } from 'express';
2
+ import Stripe from 'stripe';
3
+ import { config } from '../config.js';
4
+ import { upsertPayment } from '../services/payments.js';
5
+ import { issueRentalEntitlement, expireEntitlementsForPayment } from '../services/entitlements.js';
6
+ import { db } from '../db.js';
7
+
8
+ export const stripeWebhookRouter = Router();
9
+ const stripe = new Stripe(config.stripeSecretKey, { apiVersion: '2024-06-20' });
10
+
11
+ // Stripe requires raw body at server.ts level
12
+ stripeWebhookRouter.post('/stripe', async (req: Request, res: Response) => {
13
+ const sig = req.headers['stripe-signature'];
14
+ if (!sig) return res.status(400).send('Missing signature');
15
+
16
+ try {
17
+ const event = stripe.webhooks.constructEvent(req.body, sig as string, config.stripeWebhookSecret);
18
+
19
+ if (event.type === 'checkout.session.completed') {
20
+ const session = event.data.object as Stripe.Checkout.Session;
21
+
22
+ const amountCents = session.amount_total ?? 0;
23
+ const currency = (session.currency || 'USD').toUpperCase();
24
+ const userId = session.metadata?.userId || '';
25
+ const videoId = session.metadata?.videoId || '';
26
+ const rentalDurationHours = Number(session.metadata?.rentalDurationHours || 48);
27
+
28
+ const payment = await upsertPayment({
29
+ gateway: 'stripe',
30
+ gatewayRef: session.id,
31
+ amountCents,
32
+ currency,
33
+ status: 'succeeded',
34
+ rawPayload: session,
35
+ userId,
36
+ videoId
37
+ });
38
+
39
+ // Store payment_intent as subref for refunds correlation
40
+ const pi = session.payment_intent ? String(session.payment_intent) : null;
41
+ if (pi) {
42
+ await db.query(
43
+ `UPDATE payments SET gateway_subref=$1 WHERE id=$2`,
44
+ [pi, payment.id]
45
+ );
46
+ }
47
+
48
+ await issueRentalEntitlement({
49
+ userId, videoId, paymentId: payment.id, rentalDurationHours
50
+ });
51
+ }
52
+
53
+ // Handle refunds: charge.refunded
54
+ if (event.type === 'charge.refunded') {
55
+ const charge = event.data.object as Stripe.Charge;
56
+ const pi = charge.payment_intent ? String(charge.payment_intent) : null;
57
+ if (pi) {
58
+ const { rows } = await db.query(
59
+ `SELECT id FROM payments WHERE gateway='stripe' AND gateway_subref=$1 LIMIT 1`,
60
+ [pi]
61
+ );
62
+ if (rows[0]?.id) {
63
+ await expireEntitlementsForPayment(rows[0].id);
64
+ }
65
+ }
66
+ }
67
+
68
+ res.status(200).send('ok');
69
+ } catch (err) {
70
+ return res.status(400).send('Webhook signature verification failed');
71
+ }
72
+ });
73
+
@@ -0,0 +1,41 @@
1
+ import express from 'express';
2
+ import bodyParser from 'body-parser';
3
+ import cors from 'cors';
4
+ import { rentalsRouter } from './routes/rentals.js';
5
+ import { stripeWebhookRouter } from './routes/webhooks.js';
6
+ // import { pesapalRouter } from './routes/pesapal.js';
7
+ import { cashfreeRouter } from './routes/cashfree.js';
8
+ import { initDb } from './db.js';
9
+ import { config } from './config.js';
10
+
11
+ const app = express();
12
+
13
+ // Enable CORS for browser calls from the demo
14
+ app.use(cors());
15
+
16
+ // Stripe webhook must be raw body
17
+ app.use('/api/webhooks/stripe', bodyParser.raw({ type: 'application/json' }));
18
+
19
+ // JSON body for other routes
20
+ app.use(bodyParser.json());
21
+
22
+ app.use('/api/rentals', rentalsRouter);
23
+ app.use('/api/webhooks', stripeWebhookRouter);
24
+ app.use('/api/rentals', cashfreeRouter);
25
+
26
+ const PORT = process.env.PORT ? Number(process.env.PORT) : 3000;
27
+
28
+ (async () => {
29
+ try {
30
+ if (process.env.DISABLE_DB === '1' || !config.dbUrl) {
31
+ console.warn('[rental-api] DB disabled or no DATABASE_URL set - starting without database. Some endpoints may be unavailable.');
32
+ } else {
33
+ await initDb();
34
+ }
35
+ app.listen(PORT, () => console.log(`[rental-api] listening on :${PORT}`));
36
+ } catch (err) {
37
+ console.error('Failed to start server', err);
38
+ process.exit(1);
39
+ }
40
+ })();
41
+
@@ -0,0 +1,45 @@
1
+ import { db } from '../db.js';
2
+
3
+ export async function getEntitlement(userId: string, videoId: string) {
4
+ const { rows } = await db.query(
5
+ `SELECT * FROM entitlements
6
+ WHERE user_id=$1 AND video_id=$2
7
+ ORDER BY created_at DESC LIMIT 1`,
8
+ [userId, videoId]
9
+ );
10
+ if (!rows[0]) return { entitled: false as const };
11
+ const e = rows[0];
12
+ const now = new Date();
13
+ const entitled = new Date(e.expires_at) > now && e.status === 'active';
14
+ return { entitled, expiresAt: e.expires_at };
15
+ }
16
+
17
+ export async function issueRentalEntitlement(args: {
18
+ userId: string; videoId: string; paymentId: string; rentalDurationHours: number;
19
+ }) {
20
+ const startsAt = new Date();
21
+ const expiresAt = new Date(startsAt.getTime() + args.rentalDurationHours * 3600 * 1000);
22
+
23
+ await db.query(
24
+ `UPDATE entitlements SET status='expired'
25
+ WHERE user_id=$1 AND video_id=$2 AND status='active' AND expires_at <= NOW()`,
26
+ [args.userId, args.videoId]
27
+ );
28
+
29
+ const { rows } = await db.query(
30
+ `INSERT INTO entitlements (user_id, video_id, type, starts_at, expires_at, status, source_payment_id)
31
+ VALUES ($1,$2,'rental',$3,$4,'active',$5)
32
+ RETURNING *`,
33
+ [args.userId, args.videoId, startsAt.toISOString(), expiresAt.toISOString(), args.paymentId]
34
+ );
35
+ return rows[0];
36
+ }
37
+
38
+ export async function expireEntitlementsForPayment(paymentId: string) {
39
+ await db.query(
40
+ `UPDATE entitlements SET status='expired', expires_at=LEAST(expires_at, NOW())
41
+ WHERE source_payment_id=$1 AND status='active'`,
42
+ [paymentId]
43
+ );
44
+ }
45
+
@@ -0,0 +1,22 @@
1
+ import { db } from '../db.js';
2
+
3
+ export async function upsertPayment(args: {
4
+ gateway: 'stripe'|'pesapal'|'google_pay'|'cashfree',
5
+ gatewayRef: string,
6
+ amountCents: number,
7
+ currency: string,
8
+ status: 'succeeded'|'pending'|'failed'|'refunded',
9
+ rawPayload: any,
10
+ userId: string,
11
+ videoId: string
12
+ }) {
13
+ const { rows } = await db.query(
14
+ `INSERT INTO payments (gateway,gateway_ref,amount_cents,currency,status,raw_payload,user_id,video_id)
15
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8)
16
+ ON CONFLICT (gateway_ref) DO UPDATE SET status=EXCLUDED.status, raw_payload=EXCLUDED.raw_payload
17
+ RETURNING *`,
18
+ [args.gateway, args.gatewayRef, args.amountCents, args.currency, args.status, args.rawPayload, args.userId, args.videoId]
19
+ );
20
+ return rows[0];
21
+ }
22
+
@@ -0,0 +1,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "ES2020",
5
+ "moduleResolution": "Node",
6
+ "outDir": "dist",
7
+ "rootDir": "src",
8
+ "esModuleInterop": true,
9
+ "forceConsistentCasingInFileNames": true,
10
+ "skipLibCheck": true,
11
+ "strict": true,
12
+ "resolveJsonModule": true
13
+ },
14
+ "include": ["src/**/*"],
15
+ "exclude": ["node_modules", "dist"]
16
+ }
17
+
package/check-urls.ps1 ADDED
@@ -0,0 +1,74 @@
1
+ # Test video URLs in the framework
2
+
3
+ Write-Host ""
4
+ Write-Host "Testing Video URLs in Unified Video Framework" -ForegroundColor Cyan
5
+ Write-Host "============================================================" -ForegroundColor Cyan
6
+
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
+ $workingCount = 0
19
+ $brokenCount = 0
20
+ $uncertainCount = 0
21
+
22
+ foreach ($item in $urls) {
23
+ Write-Host ""
24
+ Write-Host "Testing: $($item.Name) [$($item.Type)]" -ForegroundColor Yellow
25
+
26
+ try {
27
+ $response = Invoke-WebRequest -Uri $item.URL -Method Head -TimeoutSec 5 -UseBasicParsing -ErrorAction Stop
28
+ Write-Host "[OK] URL is accessible" -ForegroundColor Green
29
+ $workingCount++
30
+ }
31
+ catch {
32
+ $errorMsg = $_.Exception.Message
33
+
34
+ if ($errorMsg -match "403") {
35
+ Write-Host "[ERROR] 403 Forbidden - Access denied" -ForegroundColor Red
36
+ $brokenCount++
37
+ }
38
+ elseif ($errorMsg -match "404") {
39
+ Write-Host "[ERROR] 404 Not Found" -ForegroundColor Red
40
+ $brokenCount++
41
+ }
42
+ else {
43
+ # Try GET request with byte range
44
+ try {
45
+ $response2 = Invoke-WebRequest -Uri $item.URL -Method Get -Headers @{"Range"="bytes=0-1"} -TimeoutSec 5 -UseBasicParsing -ErrorAction Stop
46
+ Write-Host "[OK] URL is accessible (via GET)" -ForegroundColor Green
47
+ $workingCount++
48
+ }
49
+ catch {
50
+ Write-Host "[WARNING] Could not verify - may still work in browser" -ForegroundColor Yellow
51
+ $uncertainCount++
52
+ }
53
+ }
54
+ }
55
+ }
56
+
57
+ Write-Host ""
58
+ Write-Host "============================================================" -ForegroundColor Cyan
59
+ Write-Host "SUMMARY" -ForegroundColor Cyan
60
+ Write-Host "============================================================" -ForegroundColor Cyan
61
+ Write-Host ""
62
+ Write-Host "Total URLs tested: $($urls.Count)"
63
+ Write-Host "Working: $workingCount" -ForegroundColor Green
64
+ Write-Host "Broken: $brokenCount" -ForegroundColor Red
65
+ Write-Host "Uncertain: $uncertainCount" -ForegroundColor Yellow
66
+
67
+ if ($brokenCount -gt 0) {
68
+ Write-Host ""
69
+ Write-Host "ACTION REQUIRED: Found broken URLs that need replacement" -ForegroundColor Red
70
+ }
71
+ else {
72
+ Write-Host ""
73
+ Write-Host "All URLs are working or accessible!" -ForegroundColor Green
74
+ }
@@ -0,0 +1,181 @@
1
+ # Comparison Report: Enhanced-Player.html vs WebPlayer.ts
2
+
3
+ ## Overview
4
+ This document compares the `enhanced-player.html` standalone implementation with the `WebPlayer.ts` class implementation to ensure feature parity.
5
+
6
+ ## βœ… MATCHING FEATURES
7
+
8
+ ### 1. **UI Structure**
9
+ Both implementations have identical UI components:
10
+ - Player wrapper with gradient backgrounds
11
+ - Video container with aspect ratio 16:9
12
+ - Watermark canvas overlay
13
+ - Top and bottom gradient overlays
14
+ - Loading spinner
15
+ - Center play button
16
+ - Controls bar with all buttons
17
+
18
+ ### 2. **CSS Styles**
19
+ Both use the same styling:
20
+ - Identical gradient colors (#ff0080, #8b00ff)
21
+ - Same backdrop-filter blur effects
22
+ - Matching button sizes and animations
23
+ - Same hover effects and transitions
24
+ - Identical opacity and transform values
25
+
26
+ ### 3. **Control Elements**
27
+ Both have the same controls:
28
+ - Play/pause button (center and in controls)
29
+ - Skip back/forward buttons (10 seconds)
30
+ - Volume control with hover panel
31
+ - Time display (current/duration)
32
+ - Progress bar with buffered indicator
33
+ - Settings button
34
+ - Fullscreen button
35
+ - All use same SVG icons
36
+
37
+ ### 4. **Auto-Hide Controls**
38
+ Both implement identical behavior:
39
+ - Show controls on mouse movement
40
+ - Hide after 3 seconds of inactivity when playing
41
+ - Immediate hide on mouse leave when playing
42
+ - Keep visible when hovering over controls
43
+ - Cursor hiding with controls
44
+
45
+ ### 5. **Volume Control**
46
+ Both have matching implementation:
47
+ - Volume button with mute toggle
48
+ - Hover panel that appears with delay
49
+ - Draggable volume slider
50
+ - Volume percentage display
51
+ - Smart panel persistence on hover/drag
52
+
53
+ ### 6. **Progress Bar**
54
+ Both feature:
55
+ - Click to seek
56
+ - Drag to scrub
57
+ - Buffered progress display
58
+ - Progress handle that appears on hover
59
+ - Percentage-based positioning
60
+
61
+ ### 7. **Watermark System**
62
+ Both implement:
63
+ - Canvas-based watermark
64
+ - Gradient text effect
65
+ - Random positioning every 5 seconds
66
+ - Same "PREMIUM" text with timestamp
67
+
68
+ ### 8. **Event Handling**
69
+ Both handle:
70
+ - Context menu disabled
71
+ - Play/pause events
72
+ - Time updates
73
+ - Volume changes
74
+ - Loading states
75
+ - Fullscreen changes
76
+
77
+ ## ⚠️ DIFFERENCES
78
+
79
+ ### 1. **Additional Features in enhanced-player.html**
80
+ The HTML version includes extra features NOT in WebPlayer.ts:
81
+
82
+ #### Top Bar Elements:
83
+ - **Title Bar** - Video title and subtitle display
84
+ - **Cast Button** - Chromecast support
85
+ - **Playlist Button** - Add to playlist functionality
86
+ - **Share Button** - Share video functionality
87
+ - **Quality Badge** - Visual quality indicator
88
+
89
+ #### Advanced Features:
90
+ - **Keyboard Shortcuts** - Full keyboard control (Space, arrows, numbers, etc.)
91
+ - **Shortcut Indicator** - Visual feedback for keyboard actions
92
+ - **Time Tooltip** - Hover preview on progress bar
93
+ - **Settings Menu** - Playback speed and quality options
94
+ - **Picture-in-Picture Button** - PiP mode support
95
+
96
+ ### 2. **Implementation Differences**
97
+
98
+ | Feature | enhanced-player.html | WebPlayer.ts |
99
+ |---------|---------------------|--------------|
100
+ | Architecture | Standalone class | Extends BasePlayer |
101
+ | Video Loading | Direct URL | HLS/DASH support |
102
+ | Quality Control | Manual settings | Adaptive bitrate |
103
+ | Subtitles | Not implemented | Full support |
104
+ | Error Handling | Basic | Comprehensive |
105
+ | Events | Internal only | Event emitter system |
106
+
107
+ ### 3. **Missing in WebPlayer.ts**
108
+ To achieve complete parity, WebPlayer.ts needs:
109
+
110
+ 1. **Top Controls Section**
111
+ - Title bar with video metadata
112
+ - Cast, playlist, share buttons
113
+ - Quality badge display
114
+
115
+ 2. **Keyboard Shortcuts**
116
+ - Space/K for play/pause
117
+ - Arrow keys for seek/volume
118
+ - Number keys for percentage seek
119
+ - M for mute, F for fullscreen, P for PiP
120
+
121
+ 3. **Visual Feedback**
122
+ - Shortcut indicator overlay
123
+ - Time tooltip on progress hover
124
+ - Settings menu with speed/quality options
125
+
126
+ 4. **PiP Button**
127
+ - Picture-in-Picture toggle in controls
128
+
129
+ 5. **Advanced Volume Features**
130
+ - Keyboard volume control
131
+ - More refined hover timing
132
+
133
+ ## πŸ“Š Feature Comparison Table
134
+
135
+ | Feature | enhanced-player.html | WebPlayer.ts | Status |
136
+ |---------|---------------------|--------------|--------|
137
+ | Custom Controls | βœ… | βœ… | βœ… Matching |
138
+ | Auto-hide Controls | βœ… | βœ… | βœ… Matching |
139
+ | Volume Panel | βœ… | βœ… | βœ… Matching |
140
+ | Progress Bar | βœ… | βœ… | βœ… Matching |
141
+ | Watermark | βœ… | βœ… | βœ… Matching |
142
+ | Loading Spinner | βœ… | βœ… | βœ… Matching |
143
+ | Fullscreen | βœ… | βœ… | βœ… Matching |
144
+ | Context Menu Disabled | βœ… | βœ… | βœ… Matching |
145
+ | Keyboard Shortcuts | βœ… | ❌ | ⚠️ Missing |
146
+ | Time Tooltip | βœ… | ❌ | ⚠️ Missing |
147
+ | Settings Menu | βœ… | ❌ | ⚠️ Missing |
148
+ | PiP Support | βœ… | βœ…* | ⚠️ No UI button |
149
+ | Title Bar | βœ… | ❌ | ⚠️ Missing |
150
+ | Cast Support | βœ… | ❌ | ⚠️ Missing |
151
+ | Share Feature | βœ… | ❌ | ⚠️ Missing |
152
+ | Quality Badge | βœ… | ❌ | ⚠️ Missing |
153
+ | Shortcut Indicator | βœ… | ❌ | ⚠️ Missing |
154
+ | HLS/DASH Support | ❌ | βœ… | 🎯 WebPlayer only |
155
+ | Subtitle Support | ❌ | βœ… | 🎯 WebPlayer only |
156
+ | Event System | ❌ | βœ… | 🎯 WebPlayer only |
157
+
158
+ *PiP methods exist but no UI button
159
+
160
+ ## πŸ”§ Recommendations
161
+
162
+ To make WebPlayer.ts match enhanced-player.html completely:
163
+
164
+ 1. **Add keyboard event listener** in `setupControlsEventListeners()`
165
+ 2. **Create settings menu** with speed and quality options
166
+ 3. **Add time tooltip** to progress bar hover
167
+ 4. **Include PiP button** in controls bar
168
+ 5. **Add title bar** section with metadata display
169
+ 6. **Implement cast support** (optional, browser-specific)
170
+ 7. **Add share functionality** using Web Share API
171
+ 8. **Include quality badge** in controls
172
+ 9. **Create shortcut indicator** overlay
173
+
174
+ ## Conclusion
175
+
176
+ The core functionality and visual design are **mostly matching** between both implementations. WebPlayer.ts has the essential premium UI features but lacks some of the advanced interactive features present in enhanced-player.html. The main differences are:
177
+
178
+ - WebPlayer.ts focuses on **video format compatibility** (HLS/DASH)
179
+ - enhanced-player.html focuses on **user interaction features** (keyboard, sharing, etc.)
180
+
181
+ Both serve their purposes well, with WebPlayer.ts being more suitable for production use with streaming protocols, while enhanced-player.html provides a richer interactive experience for demonstration purposes.
@@ -0,0 +1,95 @@
1
+ # Paywall Integration (Stripe + Cashfree)
2
+
3
+ This document describes how to enable a dynamic paywall for rental flows across the Web player. It covers free preview gating, in-player paywall overlay (80% modal), popup checkout windows, entitlement polling/verification, and gateway configuration.
4
+
5
+ ## Features
6
+ - Free preview limit (freeDuration) enforced locally and during casting
7
+ - 80% in-player overlay on preview end showing metadata and a Rent Now CTA
8
+ - Gateway selection list rendered dynamically from configuration
9
+ - Popup checkout window (not a tab) for Stripe and Cashfree
10
+ - Auto-close popup and resume playback on payment success
11
+ - Cancel handling: popup closes and overlay returns to gateway list
12
+
13
+ ## Backend Requirements
14
+ - Rental API exposes:
15
+ - Stripe: `POST /api/rentals/stripe/checkout-session` (returns { url })
16
+ - Cashfree: `POST /api/rentals/cashfree/order` (returns { paymentLink, orderId })
17
+ - Cashfree verify: `GET /api/rentals/cashfree/verify?orderId=&userId=&videoId=`
18
+ - Environment variables:
19
+ - STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET
20
+ - CASHFREE_APP_ID, CASHFREE_SECRET_KEY, CASHFREE_BASE_URL (sandbox/live)
21
+
22
+ ## Core Types
23
+ ```
24
+ export interface PaywallConfig {
25
+ enabled: boolean;
26
+ apiBase: string;
27
+ userId: string;
28
+ videoId: string;
29
+ gateways: Array<'stripe' | 'cashfree'>;
30
+ branding?: { title?: string; description?: string; logoUrl?: string; theme?: any };
31
+ popup?: { width?: number; height?: number };
32
+ }
33
+
34
+ export interface PlayerConfig {
35
+ ...
36
+ freeDuration?: number;
37
+ paywall?: PaywallConfig;
38
+ }
39
+ ```
40
+
41
+ ## Web Player Usage
42
+ ```
43
+ import { WebPlayerView } from '@unified-video/web/react';
44
+
45
+ <WebPlayerView
46
+ url="https://.../movie.m3u8"
47
+ type="hls"
48
+ freeDuration={60}
49
+ paywall={{
50
+ enabled: true,
51
+ apiBase: 'http://localhost:3100',
52
+ userId: 'u1',
53
+ videoId: 'v1',
54
+ gateways: ['stripe','cashfree'],
55
+ branding: { title: 'Continue watching', description: 'Rent to continue watching this video.' },
56
+ popup: { width: 1000, height: 800 }
57
+ }}
58
+ />
59
+ ```
60
+
61
+ Or fetch paywall config dynamically per tenant:
62
+ ```
63
+ <WebPlayerView
64
+ url="..."
65
+ freeDuration={60}
66
+ paywallConfigUrl="/api/rentals/config?tenant=acme"
67
+ />
68
+ ```
69
+ `paywallConfigUrl` should return PaywallConfig JSON.
70
+
71
+ ## Stripe
72
+ - We open the exact Checkout URL returned from `stripe/checkout-session`.
73
+ - successUrl: `.../enhanced-player.html?rental=success&popup=1`
74
+ - cancelUrl: `.../enhanced-player.html?rental=cancel&popup=1`
75
+ - The popup page posts `{ type:'uvfCheckout', status:'success|cancel' }` to the opener and closes.
76
+ - Entitlement is granted by webhook; the player polls entitlement and resumes on success.
77
+
78
+ ## Cashfree
79
+ - We call `cashfree/order` to get `paymentLink` and `orderId`.
80
+ - Open `paymentLink` in a popup; on success the return URL includes `?rental=success&popup=1&order_id=...`.
81
+ - The popup posts a message with `{ type:'uvfCheckout', status:'success', orderId }` and closes.
82
+ - The player verifies via `cashfree/verify` and grants entitlement immediately.
83
+
84
+ ## Styling & Branding
85
+ - The overlay uses minimal inline styles for portability. You can pass `branding` and `popup` in `PaywallConfig` for per-tenant customization.
86
+
87
+ ## Removing Pesapal
88
+ - Pesapal code is disabled and not wired in the server.
89
+ - Demo and shared paywall UI list Stripe and Cashfree only.
90
+
91
+ ## Troubleshooting
92
+ - Popup blocked: allow popups for your domain (localhost in dev).
93
+ - Stripe β€œSomething went wrong”: never append query params to the Checkout URL; only add `popup=1` to your own return/cancel URLs.
94
+ - Entitlement doesn’t resume: ensure webhooks (Stripe) or `cashfree/verify` are reachable; check Rental API logs.
95
+