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.
- package/.github/workflows/ci.yml +253 -0
- package/ANDROID_TV_IMPLEMENTATION.md +313 -0
- package/COMPLETION_STATUS.md +165 -0
- package/CONTRIBUTING.md +376 -0
- package/FINAL_STATUS_REPORT.md +170 -0
- package/FRAMEWORK_REVIEW.md +247 -0
- package/IMPROVEMENTS_SUMMARY.md +168 -0
- package/LICENSE +21 -0
- package/NATIVE_APP_INTEGRATION_GUIDE.md +903 -0
- package/PAYWALL_RENTAL_FLOW.md +499 -0
- package/PLATFORM_SETUP_GUIDE.md +1636 -0
- package/README.md +315 -0
- package/RUN_LOCALLY.md +151 -0
- package/apps/demo/cast-sender-min.html +173 -0
- package/apps/demo/custom-player.html +883 -0
- package/apps/demo/demo.html +990 -0
- package/apps/demo/enhanced-player.html +3556 -0
- package/apps/demo/index.html +159 -0
- package/apps/rental-api/.env.example +24 -0
- package/apps/rental-api/README.md +23 -0
- package/apps/rental-api/migrations/001_init.sql +35 -0
- package/apps/rental-api/migrations/002_videos.sql +10 -0
- package/apps/rental-api/migrations/003_add_gateway_subref.sql +4 -0
- package/apps/rental-api/migrations/004_update_gateways.sql +4 -0
- package/apps/rental-api/migrations/005_seed_demo_video.sql +5 -0
- package/apps/rental-api/package-lock.json +2045 -0
- package/apps/rental-api/package.json +33 -0
- package/apps/rental-api/scripts/run-migration.js +42 -0
- package/apps/rental-api/scripts/update-video-currency.js +21 -0
- package/apps/rental-api/scripts/update-video-price.js +19 -0
- package/apps/rental-api/src/config.ts +14 -0
- package/apps/rental-api/src/db.ts +10 -0
- package/apps/rental-api/src/routes/cashfree.ts +167 -0
- package/apps/rental-api/src/routes/pesapal.ts +92 -0
- package/apps/rental-api/src/routes/rentals.ts +242 -0
- package/apps/rental-api/src/routes/webhooks.ts +73 -0
- package/apps/rental-api/src/server.ts +41 -0
- package/apps/rental-api/src/services/entitlements.ts +45 -0
- package/apps/rental-api/src/services/payments.ts +22 -0
- package/apps/rental-api/tsconfig.json +17 -0
- package/check-urls.ps1 +74 -0
- package/comparison-report.md +181 -0
- package/docs/PAYWALL.md +95 -0
- package/docs/PLAYER_UI_VISIBILITY.md +431 -0
- package/docs/README.md +7 -0
- package/docs/SYSTEM_ARCHITECTURE.md +612 -0
- package/docs/VDOCIPHER_CLONE_REQUIREMENTS.md +403 -0
- package/examples/android/JavaSampleApp/MainActivity.java +641 -0
- package/examples/android/JavaSampleApp/activity_main.xml +226 -0
- package/examples/android/SampleApp/MainActivity.kt +430 -0
- package/examples/ios/SampleApp/ViewController.swift +337 -0
- package/examples/ios/SwiftUISampleApp/ContentView.swift +304 -0
- package/iOS_IMPLEMENTATION_OPTIONS.md +470 -0
- package/ios/UnifiedVideoPlayer/UnifiedVideoPlayer.podspec +33 -0
- package/jest.config.js +33 -0
- package/jitpack.yml +5 -0
- package/lerna.json +35 -0
- package/package.json +69 -0
- package/packages/PLATFORM_STATUS.md +163 -0
- package/packages/android/build.gradle +135 -0
- package/packages/android/src/main/AndroidManifest.xml +36 -0
- package/packages/android/src/main/java/com/unifiedvideo/player/PlayerConfiguration.java +221 -0
- package/packages/android/src/main/java/com/unifiedvideo/player/UnifiedVideoPlayer.java +1037 -0
- package/packages/android/src/main/java/com/unifiedvideo/player/UnifiedVideoPlayer.kt +707 -0
- package/packages/android/src/main/java/com/unifiedvideo/player/analytics/AnalyticsProvider.java +9 -0
- package/packages/android/src/main/java/com/unifiedvideo/player/cast/CastManager.java +141 -0
- package/packages/android/src/main/java/com/unifiedvideo/player/cast/CastOptionsProvider.java +29 -0
- package/packages/android/src/main/java/com/unifiedvideo/player/overlay/WatermarkOverlayView.java +88 -0
- package/packages/android/src/main/java/com/unifiedvideo/player/pip/PipActionReceiver.java +33 -0
- package/packages/android/src/main/java/com/unifiedvideo/player/services/PlaybackService.java +110 -0
- package/packages/android/src/main/java/com/unifiedvideo/player/services/PlayerHolder.java +19 -0
- package/packages/core/package.json +34 -0
- package/packages/core/src/BasePlayer.ts +250 -0
- package/packages/core/src/VideoPlayer.ts +237 -0
- package/packages/core/src/VideoPlayerFactory.ts +145 -0
- package/packages/core/src/index.ts +20 -0
- package/packages/core/src/interfaces/IVideoPlayer.ts +184 -0
- package/packages/core/src/interfaces.ts +240 -0
- package/packages/core/src/utils/EventEmitter.ts +66 -0
- package/packages/core/src/utils/PlatformDetector.ts +300 -0
- package/packages/core/tsconfig.json +20 -0
- package/packages/enact/package.json +51 -0
- package/packages/enact/src/VideoPlayer.js +365 -0
- package/packages/enact/src/adapters/TizenAdapter.js +354 -0
- package/packages/enact/src/index.js +82 -0
- package/packages/ios/BUILD_INSTRUCTIONS.md +108 -0
- package/packages/ios/FIX_EMBED_ISSUE.md +142 -0
- package/packages/ios/GETTING_STARTED.md +100 -0
- package/packages/ios/Package.swift +35 -0
- package/packages/ios/README.md +84 -0
- package/packages/ios/Sources/UnifiedVideoPlayer/Analytics/AnalyticsEmitter.swift +26 -0
- package/packages/ios/Sources/UnifiedVideoPlayer/DRM/FairPlayDRMManager.swift +102 -0
- package/packages/ios/Sources/UnifiedVideoPlayer/Info.plist +24 -0
- package/packages/ios/Sources/UnifiedVideoPlayer/Remote/RemoteCommandCenter.swift +109 -0
- package/packages/ios/Sources/UnifiedVideoPlayer/UnifiedVideoPlayer.swift +811 -0
- package/packages/ios/Sources/UnifiedVideoPlayer/UnifiedVideoPlayerView.swift +640 -0
- package/packages/ios/Sources/UnifiedVideoPlayer/Utilities/Color+Hex.swift +36 -0
- package/packages/ios/UnifiedVideoPlayer.podspec +27 -0
- package/packages/ios/UnifiedVideoPlayer.xcodeproj/project.pbxproj +385 -0
- package/packages/ios/build_framework.sh +55 -0
- package/packages/react-native/android/src/main/java/com/unifiedvideo/UnifiedVideoPlayerModule.kt +482 -0
- package/packages/react-native/ios/UnifiedVideoPlayer.swift +436 -0
- package/packages/react-native/package.json +51 -0
- package/packages/react-native/src/ReactNativePlayer.tsx +423 -0
- package/packages/react-native/src/VideoPlayer.tsx +224 -0
- package/packages/react-native/src/index.ts +28 -0
- package/packages/react-native/src/utils/EventEmitter.ts +66 -0
- package/packages/react-native/tsconfig.json +31 -0
- package/packages/roku/components/UnifiedVideoPlayer.brs +400 -0
- package/packages/roku/package.json +44 -0
- package/packages/roku/source/VideoPlayer.brs +231 -0
- package/packages/roku/source/main.brs +28 -0
- package/packages/web/GETTING_STARTED.md +292 -0
- package/packages/web/jest.config.js +28 -0
- package/packages/web/jest.setup.ts +110 -0
- package/packages/web/package.json +50 -0
- package/packages/web/src/SecureVideoPlayer.ts +1164 -0
- package/packages/web/src/WebPlayer.ts +3110 -0
- package/packages/web/src/__tests__/WebPlayer.test.ts +314 -0
- package/packages/web/src/index.ts +14 -0
- package/packages/web/src/paywall/PaywallController.ts +215 -0
- package/packages/web/src/react/WebPlayerView.tsx +177 -0
- package/packages/web/tsconfig.json +23 -0
- package/packages/web/webpack.config.js +45 -0
- package/server.js +131 -0
- package/server.py +84 -0
- package/test-urls.ps1 +97 -0
- package/test-video-urls.ps1 +87 -0
- 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
|
+
-- 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
|
+
|