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,1636 @@
|
|
|
1
|
+
# ๐ Unified Video Framework - Complete Platform Setup Guide
|
|
2
|
+
|
|
3
|
+
This guide provides step-by-step instructions for setting up the Unified Video Framework on all supported platforms.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
1. [Web Platform](#1-web-platform-setup)
|
|
7
|
+
2. [iOS Platform](#2-ios-platform-setup)
|
|
8
|
+
3. [Android Platform](#3-android-platform-setup)
|
|
9
|
+
4. [Samsung Tizen TV](#4-samsung-tizen-tv-setup)
|
|
10
|
+
5. [LG webOS TV](#5-lg-webos-tv-setup)
|
|
11
|
+
6. [Roku Platform](#6-roku-platform-setup)
|
|
12
|
+
7. [Android TV](#7-android-tv-setup)
|
|
13
|
+
8. [React Native (Mobile)](#8-react-native-mobile-setup)
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 1. Web Platform Setup
|
|
18
|
+
|
|
19
|
+
### Prerequisites
|
|
20
|
+
- Node.js 16+ and npm 8+
|
|
21
|
+
- Modern web browser (Chrome, Firefox, Safari, Edge)
|
|
22
|
+
|
|
23
|
+
### Step-by-Step Installation
|
|
24
|
+
|
|
25
|
+
#### Step 1: Install Dependencies
|
|
26
|
+
```bash
|
|
27
|
+
npm install @unified-video/core @unified-video/web
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
#### Step 2: Create HTML Structure
|
|
31
|
+
```html
|
|
32
|
+
<!DOCTYPE html>
|
|
33
|
+
<html>
|
|
34
|
+
<head>
|
|
35
|
+
<title>Video Player</title>
|
|
36
|
+
<style>
|
|
37
|
+
#video-container {
|
|
38
|
+
width: 100%;
|
|
39
|
+
max-width: 800px;
|
|
40
|
+
margin: 0 auto;
|
|
41
|
+
}
|
|
42
|
+
</style>
|
|
43
|
+
</head>
|
|
44
|
+
<body>
|
|
45
|
+
<div id="video-container"></div>
|
|
46
|
+
<script type="module" src="app.js"></script>
|
|
47
|
+
</body>
|
|
48
|
+
</html>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
#### Step 3: Initialize Player (app.js)
|
|
52
|
+
```javascript
|
|
53
|
+
import { WebPlayer } from '@unified-video/web';
|
|
54
|
+
|
|
55
|
+
async function initializePlayer() {
|
|
56
|
+
// Create player instance
|
|
57
|
+
const player = new WebPlayer();
|
|
58
|
+
|
|
59
|
+
// Initialize with container
|
|
60
|
+
await player.initialize('#video-container', {
|
|
61
|
+
controls: true,
|
|
62
|
+
autoPlay: false,
|
|
63
|
+
muted: false,
|
|
64
|
+
debug: true
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Load video source
|
|
68
|
+
await player.load({
|
|
69
|
+
url: 'https://example.com/video.mp4',
|
|
70
|
+
type: 'mp4',
|
|
71
|
+
subtitles: [{
|
|
72
|
+
url: 'https://example.com/subs.vtt',
|
|
73
|
+
language: 'en',
|
|
74
|
+
label: 'English',
|
|
75
|
+
kind: 'subtitles'
|
|
76
|
+
}]
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Event handling
|
|
80
|
+
player.on('onReady', () => console.log('Player ready'));
|
|
81
|
+
player.on('onPlay', () => console.log('Playback started'));
|
|
82
|
+
player.on('onError', (error) => console.error('Player error:', error));
|
|
83
|
+
|
|
84
|
+
// Start playback
|
|
85
|
+
await player.play();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Initialize when DOM is ready
|
|
89
|
+
document.addEventListener('DOMContentLoaded', initializePlayer);
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
#### Step 4: Build for Production
|
|
93
|
+
```bash
|
|
94
|
+
# Using Webpack
|
|
95
|
+
npm install --save-dev webpack webpack-cli
|
|
96
|
+
npx webpack app.js --output bundle.js
|
|
97
|
+
|
|
98
|
+
# Or using Vite
|
|
99
|
+
npm install --save-dev vite
|
|
100
|
+
npx vite build
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
#### Step 5: Deploy
|
|
104
|
+
```bash
|
|
105
|
+
# Test locally
|
|
106
|
+
npx http-server
|
|
107
|
+
|
|
108
|
+
# Deploy to production (examples)
|
|
109
|
+
# - Upload to CDN
|
|
110
|
+
# - Deploy to Netlify/Vercel
|
|
111
|
+
# - Integrate with existing web app
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## 2. iOS Platform Setup
|
|
117
|
+
|
|
118
|
+
### Prerequisites
|
|
119
|
+
- macOS with Xcode 14+
|
|
120
|
+
- iOS 13.0+ deployment target
|
|
121
|
+
- Swift 5.0+ or Objective-C
|
|
122
|
+
- SwiftUI 2.0+ (for SwiftUI implementation)
|
|
123
|
+
|
|
124
|
+
### โ ๏ธ Architecture Build Fix
|
|
125
|
+
If you encounter "UnifiedVideoPlayer.swiftmodule is not built for arm64" error:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
# Quick Fix - Use Swift Package Manager (Recommended)
|
|
129
|
+
1. In Xcode: File > Add Package Dependencies
|
|
130
|
+
2. Click "Add Local..." and navigate to packages/ios/
|
|
131
|
+
3. Add the package to your target
|
|
132
|
+
|
|
133
|
+
# Alternative - Build Universal Framework
|
|
134
|
+
cd packages/ios
|
|
135
|
+
chmod +x build_framework.sh
|
|
136
|
+
./build_framework.sh
|
|
137
|
+
# This creates Output/UnifiedVideoPlayer.xcframework
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
For detailed architecture troubleshooting, see [BUILD_INSTRUCTIONS.md](packages/ios/BUILD_INSTRUCTIONS.md)
|
|
141
|
+
|
|
142
|
+
### Choose Your Integration Method:
|
|
143
|
+
|
|
144
|
+
## Option A: Native iOS (Swift/SwiftUI) - For Modern iOS Apps โญ RECOMMENDED
|
|
145
|
+
|
|
146
|
+
### Step 1: Add Framework to Your Project
|
|
147
|
+
|
|
148
|
+
#### Using Swift Package Manager (Auto-builds for correct architecture):
|
|
149
|
+
```swift
|
|
150
|
+
// In Xcode: File > Add Package Dependencies
|
|
151
|
+
// Add Local Package: packages/ios/
|
|
152
|
+
// Or Remote: https://github.com/yourcompany/unified-video-ios.git
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
#### Using CocoaPods:
|
|
156
|
+
```ruby
|
|
157
|
+
# Podfile
|
|
158
|
+
pod 'UnifiedVideoPlayer', '~> 1.0'
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
#### Using XCFramework (Universal Binary):
|
|
162
|
+
```bash
|
|
163
|
+
cd packages/ios
|
|
164
|
+
./build_framework.sh
|
|
165
|
+
# Drag Output/UnifiedVideoPlayer.xcframework into your project
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Quick Start - iOS (Just 3 lines!):
|
|
169
|
+
```swift
|
|
170
|
+
// UIKit
|
|
171
|
+
let player = UnifiedVideoPlayer()
|
|
172
|
+
player.initialize(container: yourView)
|
|
173
|
+
player.load(url: "video.mp4")
|
|
174
|
+
|
|
175
|
+
// SwiftUI
|
|
176
|
+
UnifiedVideoPlayerView(url: URL(string: "video.mp4")!)
|
|
177
|
+
.frame(height: 200)
|
|
178
|
+
.onAppear { /* auto-plays */ }
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Step 2: Configure Info.plist
|
|
182
|
+
```xml
|
|
183
|
+
<key>NSAppTransportSecurity</key>
|
|
184
|
+
<dict>
|
|
185
|
+
<key>NSAllowsArbitraryLoads</key>
|
|
186
|
+
<true/>
|
|
187
|
+
</dict>
|
|
188
|
+
|
|
189
|
+
<!-- For background playback -->
|
|
190
|
+
<key>UIBackgroundModes</key>
|
|
191
|
+
<array>
|
|
192
|
+
<string>audio</string>
|
|
193
|
+
</array>
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Step 3A: SwiftUI Implementation (Modern Approach) ๐
|
|
197
|
+
```swift
|
|
198
|
+
import SwiftUI
|
|
199
|
+
import UnifiedVideoPlayer
|
|
200
|
+
|
|
201
|
+
struct VideoPlayerView: View {
|
|
202
|
+
@StateObject private var playerViewModel = VideoPlayerViewModel()
|
|
203
|
+
@State private var isPlaying = false
|
|
204
|
+
@State private var showControls = true
|
|
205
|
+
|
|
206
|
+
var body: some View {
|
|
207
|
+
VStack {
|
|
208
|
+
// Video Player
|
|
209
|
+
UnifiedVideoPlayerView(
|
|
210
|
+
url: URL(string: "https://example.com/video.m3u8")!,
|
|
211
|
+
configuration: .init(
|
|
212
|
+
autoPlay: true,
|
|
213
|
+
controls: false, // Custom controls
|
|
214
|
+
muted: false
|
|
215
|
+
)
|
|
216
|
+
)
|
|
217
|
+
.frame(height: 250)
|
|
218
|
+
.overlay(
|
|
219
|
+
Group {
|
|
220
|
+
if showControls {
|
|
221
|
+
PlayerControlsOverlay(
|
|
222
|
+
isPlaying: $isPlaying,
|
|
223
|
+
onPlayPause: { playerViewModel.togglePlayPause() },
|
|
224
|
+
onSeek: { playerViewModel.seek(to: $0) }
|
|
225
|
+
)
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
)
|
|
229
|
+
.onTapGesture {
|
|
230
|
+
withAnimation { showControls.toggle() }
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Custom Controls
|
|
234
|
+
HStack {
|
|
235
|
+
Button(action: { playerViewModel.skipBackward(10) }) {
|
|
236
|
+
Image(systemName: "gobackward.10")
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
Button(action: { playerViewModel.togglePlayPause() }) {
|
|
240
|
+
Image(systemName: isPlaying ? "pause.fill" : "play.fill")
|
|
241
|
+
.font(.title)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
Button(action: { playerViewModel.skipForward(10) }) {
|
|
245
|
+
Image(systemName: "goforward.10")
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
.padding()
|
|
249
|
+
|
|
250
|
+
// Progress Bar
|
|
251
|
+
ProgressView(value: playerViewModel.currentTime,
|
|
252
|
+
total: playerViewModel.duration)
|
|
253
|
+
.padding(.horizontal)
|
|
254
|
+
}
|
|
255
|
+
.onAppear {
|
|
256
|
+
playerViewModel.load(url: "https://example.com/video.m3u8")
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// View Model for SwiftUI
|
|
262
|
+
class VideoPlayerViewModel: ObservableObject {
|
|
263
|
+
@Published var currentTime: Double = 0
|
|
264
|
+
@Published var duration: Double = 100
|
|
265
|
+
@Published var isPlaying = false
|
|
266
|
+
|
|
267
|
+
private var player: UnifiedVideoPlayer?
|
|
268
|
+
|
|
269
|
+
func load(url: String) {
|
|
270
|
+
player = UnifiedVideoPlayer()
|
|
271
|
+
player?.load(url: url)
|
|
272
|
+
player?.onTimeUpdate = { [weak self] time in
|
|
273
|
+
self?.currentTime = time
|
|
274
|
+
}
|
|
275
|
+
player?.onDurationChange = { [weak self] duration in
|
|
276
|
+
self?.duration = duration
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
func togglePlayPause() {
|
|
281
|
+
isPlaying.toggle()
|
|
282
|
+
isPlaying ? player?.play() : player?.pause()
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
func seek(to time: Double) {
|
|
286
|
+
player?.seek(to: time)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
func skipForward(_ seconds: Double) {
|
|
290
|
+
player?.seek(to: currentTime + seconds)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
func skipBackward(_ seconds: Double) {
|
|
294
|
+
player?.seek(to: max(0, currentTime - seconds))
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### Step 3B: UIKit Implementation (Traditional Approach)
|
|
300
|
+
```swift
|
|
301
|
+
import UIKit
|
|
302
|
+
import AVFoundation
|
|
303
|
+
import UnifiedVideoPlayer
|
|
304
|
+
|
|
305
|
+
class VideoViewController: UIViewController {
|
|
306
|
+
|
|
307
|
+
private var videoPlayer: UnifiedVideoPlayer?
|
|
308
|
+
@IBOutlet weak var playerContainer: UIView!
|
|
309
|
+
|
|
310
|
+
override func viewDidLoad() {
|
|
311
|
+
super.viewDidLoad()
|
|
312
|
+
setupVideoPlayer()
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
private func setupVideoPlayer() {
|
|
316
|
+
// Initialize player
|
|
317
|
+
videoPlayer = UnifiedVideoPlayer()
|
|
318
|
+
|
|
319
|
+
// Configure
|
|
320
|
+
let config = PlayerConfiguration(
|
|
321
|
+
autoPlay: true,
|
|
322
|
+
controls: true,
|
|
323
|
+
muted: false
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
// Add to your view
|
|
327
|
+
videoPlayer?.initialize(
|
|
328
|
+
container: playerContainer,
|
|
329
|
+
configuration: config
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
// Load video
|
|
333
|
+
videoPlayer?.load(url: "https://example.com/video.m3u8")
|
|
334
|
+
|
|
335
|
+
// Handle events
|
|
336
|
+
videoPlayer?.onReady = { [weak self] in
|
|
337
|
+
print("Player ready")
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
videoPlayer?.onError = { [weak self] error in
|
|
341
|
+
self?.showError(error)
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Control methods
|
|
346
|
+
@IBAction func playButtonTapped() {
|
|
347
|
+
videoPlayer?.play()
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
@IBAction func pauseButtonTapped() {
|
|
351
|
+
videoPlayer?.pause()
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### Step 4: Objective-C Implementation (Alternative)
|
|
357
|
+
```objc
|
|
358
|
+
// VideoViewController.m
|
|
359
|
+
#import "UnifiedVideoPlayer.h"
|
|
360
|
+
|
|
361
|
+
@interface VideoViewController ()
|
|
362
|
+
@property (nonatomic, strong) UnifiedVideoPlayer *videoPlayer;
|
|
363
|
+
@property (weak, nonatomic) IBOutlet UIView *playerContainer;
|
|
364
|
+
@end
|
|
365
|
+
|
|
366
|
+
@implementation VideoViewController
|
|
367
|
+
|
|
368
|
+
- (void)viewDidLoad {
|
|
369
|
+
[super viewDidLoad];
|
|
370
|
+
[self setupVideoPlayer];
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
- (void)setupVideoPlayer {
|
|
374
|
+
// Initialize player
|
|
375
|
+
self.videoPlayer = [[UnifiedVideoPlayer alloc] init];
|
|
376
|
+
|
|
377
|
+
// Configure
|
|
378
|
+
NSDictionary *config = @{
|
|
379
|
+
@"autoPlay": @YES,
|
|
380
|
+
@"controls": @YES,
|
|
381
|
+
@"muted": @NO
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
// Add to view
|
|
385
|
+
[self.videoPlayer initializeWithContainer:self.playerContainer
|
|
386
|
+
configuration:config];
|
|
387
|
+
|
|
388
|
+
// Load video
|
|
389
|
+
[self.videoPlayer loadWithUrl:@"https://example.com/video.m3u8"];
|
|
390
|
+
|
|
391
|
+
// Handle events
|
|
392
|
+
self.videoPlayer.onReady = ^{
|
|
393
|
+
NSLog(@"Player ready");
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
- (IBAction)playButtonTapped:(id)sender {
|
|
398
|
+
[self.videoPlayer play];
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
@end
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
## Option B: React Native - For Cross-Platform Apps
|
|
405
|
+
|
|
406
|
+
### Step 1: Install Dependencies
|
|
407
|
+
```bash
|
|
408
|
+
npx react-native init MyVideoApp
|
|
409
|
+
cd MyVideoApp
|
|
410
|
+
npm install @unified-video/core @unified-video/react-native
|
|
411
|
+
npm install react-native-video
|
|
412
|
+
cd ios && pod install
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### Step 2: React Native Implementation
|
|
416
|
+
```jsx
|
|
417
|
+
import React, { useRef } from 'react';
|
|
418
|
+
import { View, Button } from 'react-native';
|
|
419
|
+
import { ReactNativePlayer } from '@unified-video/react-native';
|
|
420
|
+
|
|
421
|
+
export default function VideoScreen() {
|
|
422
|
+
const playerRef = useRef(null);
|
|
423
|
+
|
|
424
|
+
return (
|
|
425
|
+
<View style={{ flex: 1 }}>
|
|
426
|
+
<ReactNativePlayer
|
|
427
|
+
ref={playerRef}
|
|
428
|
+
style={{ flex: 1 }}
|
|
429
|
+
config={{ autoPlay: true }}
|
|
430
|
+
/>
|
|
431
|
+
</View>
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
## Option C: Flutter - For Dart-based Cross-Platform
|
|
437
|
+
|
|
438
|
+
### Step 1: Add Dependencies
|
|
439
|
+
```yaml
|
|
440
|
+
# pubspec.yaml
|
|
441
|
+
dependencies:
|
|
442
|
+
unified_video_player: ^1.0.0
|
|
443
|
+
video_player: ^2.7.0
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
### Step 2: Flutter Implementation
|
|
447
|
+
```dart
|
|
448
|
+
import 'package:flutter/material.dart';
|
|
449
|
+
import 'package:unified_video_player/unified_video_player.dart';
|
|
450
|
+
|
|
451
|
+
class VideoScreen extends StatefulWidget {
|
|
452
|
+
@override
|
|
453
|
+
_VideoScreenState createState() => _VideoScreenState();
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
class _VideoScreenState extends State<VideoScreen> {
|
|
457
|
+
UnifiedVideoPlayer? _player;
|
|
458
|
+
|
|
459
|
+
@override
|
|
460
|
+
void initState() {
|
|
461
|
+
super.initState();
|
|
462
|
+
_player = UnifiedVideoPlayer();
|
|
463
|
+
_player!.initialize();
|
|
464
|
+
_player!.load('https://example.com/video.m3u8');
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
@override
|
|
468
|
+
Widget build(BuildContext context) {
|
|
469
|
+
return Scaffold(
|
|
470
|
+
body: UnifiedVideoPlayerView(
|
|
471
|
+
player: _player!,
|
|
472
|
+
),
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
### Build and Run
|
|
479
|
+
```bash
|
|
480
|
+
# Native iOS (UIKit/SwiftUI)
|
|
481
|
+
open MyApp.xcodeproj
|
|
482
|
+
# Or: xcodebuild -scheme MyApp -destination 'platform=iOS Simulator,name=iPhone 14'
|
|
483
|
+
|
|
484
|
+
# Fix Architecture Issues Before Building
|
|
485
|
+
# In Xcode: Product > Clean Build Folder (โงโK)
|
|
486
|
+
# Then: Product > Build (โB)
|
|
487
|
+
|
|
488
|
+
# React Native
|
|
489
|
+
npx react-native run-ios
|
|
490
|
+
# Or specific simulator: npx react-native run-ios --simulator="iPhone 14 Pro"
|
|
491
|
+
|
|
492
|
+
# Flutter
|
|
493
|
+
flutter run -d ios
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
### Troubleshooting iOS Build Issues
|
|
497
|
+
|
|
498
|
+
#### Architecture Error Fix:
|
|
499
|
+
```bash
|
|
500
|
+
# If you see: "not built for arm64"
|
|
501
|
+
# Solution 1: Clean and rebuild
|
|
502
|
+
xcodebuild clean -workspace MyApp.xcworkspace -scheme MyApp
|
|
503
|
+
xcodebuild build -workspace MyApp.xcworkspace -scheme MyApp -destination 'generic/platform=iOS'
|
|
504
|
+
|
|
505
|
+
# Solution 2: Reset Swift Package Manager cache
|
|
506
|
+
rm -rf ~/Library/Caches/org.swift.swiftpm
|
|
507
|
+
rm -rf ~/Library/Developer/Xcode/DerivedData
|
|
508
|
+
|
|
509
|
+
# Solution 3: Build universal framework
|
|
510
|
+
cd packages/ios
|
|
511
|
+
./build_framework.sh
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
---
|
|
515
|
+
|
|
516
|
+
## 3. Android Platform Setup
|
|
517
|
+
|
|
518
|
+
### Prerequisites
|
|
519
|
+
- Android Studio Arctic Fox+
|
|
520
|
+
- Android SDK 21+ (Lollipop)
|
|
521
|
+
- Java 11 / Kotlin 1.6+
|
|
522
|
+
|
|
523
|
+
### Choose Your Integration Method:
|
|
524
|
+
|
|
525
|
+
## Option A: Native Android (Kotlin/Java) - For Existing Android Apps โญ RECOMMENDED
|
|
526
|
+
|
|
527
|
+
### Step 1: Add Library to Your Project
|
|
528
|
+
|
|
529
|
+
cd packages/android
|
|
530
|
+
./gradlew assembleRelease
|
|
531
|
+
# This creates an AAR file in build/outputs/aar/
|
|
532
|
+
|
|
533
|
+
Android (Just 3 lines!):
|
|
534
|
+
val player = UnifiedVideoPlayer(context)
|
|
535
|
+
player.initialize(container)
|
|
536
|
+
player.load("video.mp4")
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
#### Using Gradle:
|
|
540
|
+
```gradle
|
|
541
|
+
// app/build.gradle
|
|
542
|
+
dependencies {
|
|
543
|
+
implementation 'com.unifiedvideo:player:1.0.0'
|
|
544
|
+
|
|
545
|
+
// Or local AAR
|
|
546
|
+
implementation files('libs/unified-video-player.aar')
|
|
547
|
+
|
|
548
|
+
// Required dependencies
|
|
549
|
+
implementation 'com.google.android.exoplayer:exoplayer:2.18.5'
|
|
550
|
+
implementation 'com.google.android.exoplayer:exoplayer-hls:2.18.5'
|
|
551
|
+
}
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
### Step 2: Configure AndroidManifest.xml
|
|
555
|
+
```xml
|
|
556
|
+
<uses-permission android:name="android.permission.INTERNET" />
|
|
557
|
+
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
|
558
|
+
|
|
559
|
+
<application
|
|
560
|
+
android:usesCleartextTraffic="true"
|
|
561
|
+
android:largeHeap="true">
|
|
562
|
+
|
|
563
|
+
<!-- For background playback -->
|
|
564
|
+
<service android:name="com.unifiedvideo.PlaybackService" />
|
|
565
|
+
</application>
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
### Step 3: Implement in Your Activity (Kotlin)
|
|
569
|
+
```kotlin
|
|
570
|
+
// VideoActivity.kt
|
|
571
|
+
import android.os.Bundle
|
|
572
|
+
import androidx.appcompat.app.AppCompatActivity
|
|
573
|
+
import com.unifiedvideo.player.UnifiedVideoPlayer
|
|
574
|
+
import com.unifiedvideo.player.PlayerConfiguration
|
|
575
|
+
|
|
576
|
+
class VideoActivity : AppCompatActivity() {
|
|
577
|
+
|
|
578
|
+
private lateinit var videoPlayer: UnifiedVideoPlayer
|
|
579
|
+
|
|
580
|
+
override fun onCreate(savedInstanceState: Bundle?) {
|
|
581
|
+
super.onCreate(savedInstanceState)
|
|
582
|
+
setContentView(R.layout.activity_video)
|
|
583
|
+
|
|
584
|
+
setupVideoPlayer()
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
private fun setupVideoPlayer() {
|
|
588
|
+
// Find your container view
|
|
589
|
+
val playerContainer = findViewById<FrameLayout>(R.id.player_container)
|
|
590
|
+
|
|
591
|
+
// Create and initialize player
|
|
592
|
+
videoPlayer = UnifiedVideoPlayer(this).apply {
|
|
593
|
+
initialize(
|
|
594
|
+
container = playerContainer,
|
|
595
|
+
configuration = PlayerConfiguration(
|
|
596
|
+
autoPlay = true,
|
|
597
|
+
controls = true,
|
|
598
|
+
muted = false
|
|
599
|
+
)
|
|
600
|
+
)
|
|
601
|
+
|
|
602
|
+
// Set event listeners
|
|
603
|
+
onReady = {
|
|
604
|
+
Log.d("Player", "Ready")
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
onError = { error ->
|
|
608
|
+
showError(error)
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// Load video
|
|
612
|
+
load("https://example.com/video.m3u8")
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// Control methods
|
|
617
|
+
fun onPlayButtonClick() {
|
|
618
|
+
videoPlayer.play()
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
fun onPauseButtonClick() {
|
|
622
|
+
videoPlayer.pause()
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
override fun onPause() {
|
|
626
|
+
super.onPause()
|
|
627
|
+
videoPlayer.pause()
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
override fun onDestroy() {
|
|
631
|
+
super.onDestroy()
|
|
632
|
+
videoPlayer.release()
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
### Step 4: Java Implementation (Alternative)
|
|
638
|
+
```java
|
|
639
|
+
// VideoActivity.java
|
|
640
|
+
import android.os.Bundle;
|
|
641
|
+
import androidx.appcompat.app.AppCompatActivity;
|
|
642
|
+
import com.unifiedvideo.player.UnifiedVideoPlayer;
|
|
643
|
+
import com.unifiedvideo.player.PlayerConfiguration;
|
|
644
|
+
|
|
645
|
+
public class VideoActivity extends AppCompatActivity {
|
|
646
|
+
|
|
647
|
+
private UnifiedVideoPlayer videoPlayer;
|
|
648
|
+
|
|
649
|
+
@Override
|
|
650
|
+
protected void onCreate(Bundle savedInstanceState) {
|
|
651
|
+
super.onCreate(savedInstanceState);
|
|
652
|
+
setContentView(R.layout.activity_video);
|
|
653
|
+
|
|
654
|
+
setupVideoPlayer();
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
private void setupVideoPlayer() {
|
|
658
|
+
// Find container
|
|
659
|
+
FrameLayout playerContainer = findViewById(R.id.player_container);
|
|
660
|
+
|
|
661
|
+
// Create player
|
|
662
|
+
videoPlayer = new UnifiedVideoPlayer(this);
|
|
663
|
+
|
|
664
|
+
// Configure
|
|
665
|
+
PlayerConfiguration config = new PlayerConfiguration.Builder()
|
|
666
|
+
.setAutoPlay(true)
|
|
667
|
+
.setControls(true)
|
|
668
|
+
.build();
|
|
669
|
+
|
|
670
|
+
// Initialize
|
|
671
|
+
videoPlayer.initialize(playerContainer, config);
|
|
672
|
+
|
|
673
|
+
// Set listeners
|
|
674
|
+
videoPlayer.setOnReadyListener(() -> {
|
|
675
|
+
Log.d("Player", "Ready");
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
// Load video
|
|
679
|
+
videoPlayer.load("https://example.com/video.mp4");
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
@Override
|
|
683
|
+
protected void onDestroy() {
|
|
684
|
+
super.onDestroy();
|
|
685
|
+
if (videoPlayer != null) {
|
|
686
|
+
videoPlayer.release();
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
## Option B: React Native - For Cross-Platform Apps
|
|
693
|
+
|
|
694
|
+
### Step 1: Install Dependencies
|
|
695
|
+
```bash
|
|
696
|
+
npx react-native init MyVideoApp
|
|
697
|
+
cd MyVideoApp
|
|
698
|
+
npm install @unified-video/core @unified-video/react-native
|
|
699
|
+
npm install react-native-video
|
|
700
|
+
```
|
|
701
|
+
|
|
702
|
+
### Step 2: React Native Implementation
|
|
703
|
+
```jsx
|
|
704
|
+
import React from 'react';
|
|
705
|
+
import { View } from 'react-native';
|
|
706
|
+
import { ReactNativePlayer } from '@unified-video/react-native';
|
|
707
|
+
|
|
708
|
+
export default function VideoScreen() {
|
|
709
|
+
return (
|
|
710
|
+
<View style={{ flex: 1 }}>
|
|
711
|
+
<ReactNativePlayer
|
|
712
|
+
style={{ flex: 1 }}
|
|
713
|
+
config={{ autoPlay: true }}
|
|
714
|
+
/>
|
|
715
|
+
</View>
|
|
716
|
+
);
|
|
717
|
+
}
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
## Option C: Flutter - For Dart-based Cross-Platform
|
|
721
|
+
|
|
722
|
+
### Step 1: Add Dependencies
|
|
723
|
+
```yaml
|
|
724
|
+
# pubspec.yaml
|
|
725
|
+
dependencies:
|
|
726
|
+
unified_video_player: ^1.0.0
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
### Step 2: Flutter Implementation
|
|
730
|
+
```dart
|
|
731
|
+
import 'package:unified_video_player/unified_video_player.dart';
|
|
732
|
+
|
|
733
|
+
class VideoScreen extends StatelessWidget {
|
|
734
|
+
@override
|
|
735
|
+
Widget build(BuildContext context) {
|
|
736
|
+
return UnifiedVideoPlayer(
|
|
737
|
+
url: 'https://example.com/video.mp4',
|
|
738
|
+
autoPlay: true,
|
|
739
|
+
);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
```
|
|
743
|
+
|
|
744
|
+
### Build and Run
|
|
745
|
+
```bash
|
|
746
|
+
# Native Android
|
|
747
|
+
# Open in Android Studio and run
|
|
748
|
+
|
|
749
|
+
# React Native
|
|
750
|
+
npx react-native run-android
|
|
751
|
+
|
|
752
|
+
# Flutter
|
|
753
|
+
flutter run -d android
|
|
754
|
+
```
|
|
755
|
+
|
|
756
|
+
---
|
|
757
|
+
|
|
758
|
+
## 4. Samsung Tizen TV Setup
|
|
759
|
+
|
|
760
|
+
### Prerequisites
|
|
761
|
+
- Tizen Studio 5.0+
|
|
762
|
+
- Samsung TV (2017+ model) or Emulator
|
|
763
|
+
- Developer Certificate
|
|
764
|
+
|
|
765
|
+
### Step-by-Step Installation
|
|
766
|
+
|
|
767
|
+
#### Step 1: Create Tizen Project
|
|
768
|
+
```bash
|
|
769
|
+
# Install Tizen CLI
|
|
770
|
+
npm install -g @tizen/cli
|
|
771
|
+
|
|
772
|
+
# Create new project
|
|
773
|
+
tizen create web-project -n VideoPlayerApp -t BasicProject
|
|
774
|
+
cd VideoPlayerApp
|
|
775
|
+
```
|
|
776
|
+
|
|
777
|
+
#### Step 2: Install Framework
|
|
778
|
+
```bash
|
|
779
|
+
npm install @unified-video/core @unified-video/web
|
|
780
|
+
```
|
|
781
|
+
|
|
782
|
+
#### Step 3: Configure config.xml
|
|
783
|
+
```xml
|
|
784
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
785
|
+
<widget xmlns="http://www.w3.org/ns/widgets"
|
|
786
|
+
xmlns:tizen="http://tizen.org/ns/widgets"
|
|
787
|
+
id="http://yourdomain/VideoPlayerApp"
|
|
788
|
+
version="1.0.0">
|
|
789
|
+
<name>VideoPlayerApp</name>
|
|
790
|
+
<tizen:application id="ABC123.VideoPlayerApp"
|
|
791
|
+
package="ABC123"
|
|
792
|
+
required_version="5.0"/>
|
|
793
|
+
<content src="index.html"/>
|
|
794
|
+
<feature name="http://tizen.org/feature/screen.size.all"/>
|
|
795
|
+
<access origin="*" subdomains="true"/>
|
|
796
|
+
<tizen:privilege name="http://tizen.org/privilege/internet"/>
|
|
797
|
+
<tizen:privilege name="http://tizen.org/privilege/tv.inputdevice"/>
|
|
798
|
+
<tizen:setting screen-orientation="landscape"/>
|
|
799
|
+
</widget>
|
|
800
|
+
```
|
|
801
|
+
|
|
802
|
+
#### Step 4: Implement TV Controls
|
|
803
|
+
```javascript
|
|
804
|
+
// js/app.js
|
|
805
|
+
import { WebPlayer } from '@unified-video/web';
|
|
806
|
+
|
|
807
|
+
let player;
|
|
808
|
+
|
|
809
|
+
async function initTizenPlayer() {
|
|
810
|
+
player = new WebPlayer();
|
|
811
|
+
|
|
812
|
+
await player.initialize('#player-container', {
|
|
813
|
+
controls: false, // Use custom TV controls
|
|
814
|
+
autoPlay: true
|
|
815
|
+
});
|
|
816
|
+
|
|
817
|
+
// Register TV remote control events
|
|
818
|
+
registerKeyHandler();
|
|
819
|
+
|
|
820
|
+
// Load video
|
|
821
|
+
await player.load({
|
|
822
|
+
url: 'https://example.com/video.m3u8',
|
|
823
|
+
type: 'hls'
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
function registerKeyHandler() {
|
|
828
|
+
document.addEventListener('keydown', function(e) {
|
|
829
|
+
switch(e.keyCode) {
|
|
830
|
+
case 13: // Enter
|
|
831
|
+
player.isPlaying() ? player.pause() : player.play();
|
|
832
|
+
break;
|
|
833
|
+
case 37: // Left
|
|
834
|
+
player.seek(player.getCurrentTime() - 10);
|
|
835
|
+
break;
|
|
836
|
+
case 39: // Right
|
|
837
|
+
player.seek(player.getCurrentTime() + 10);
|
|
838
|
+
break;
|
|
839
|
+
case 38: // Up
|
|
840
|
+
player.setVolume(player.getState().volume + 0.1);
|
|
841
|
+
break;
|
|
842
|
+
case 40: // Down
|
|
843
|
+
player.setVolume(player.getState().volume - 0.1);
|
|
844
|
+
break;
|
|
845
|
+
case 10009: // Back button
|
|
846
|
+
tizen.application.getCurrentApplication().exit();
|
|
847
|
+
break;
|
|
848
|
+
}
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
// Initialize on load
|
|
853
|
+
window.onload = initTizenPlayer;
|
|
854
|
+
```
|
|
855
|
+
|
|
856
|
+
#### Step 5: Build and Deploy
|
|
857
|
+
```bash
|
|
858
|
+
# Build package
|
|
859
|
+
tizen build-web
|
|
860
|
+
|
|
861
|
+
# Package as WGT
|
|
862
|
+
tizen package -t wgt -s yourCertificate -- .buildResult
|
|
863
|
+
|
|
864
|
+
# Install on TV
|
|
865
|
+
tizen install -n VideoPlayerApp.wgt -t <TV_IP>
|
|
866
|
+
|
|
867
|
+
# Run on TV
|
|
868
|
+
tizen run -p ABC123.VideoPlayerApp -t <TV_IP>
|
|
869
|
+
```
|
|
870
|
+
|
|
871
|
+
---
|
|
872
|
+
|
|
873
|
+
## 5. LG webOS TV Setup
|
|
874
|
+
|
|
875
|
+
### Prerequisites
|
|
876
|
+
- webOS TV SDK 6.0+
|
|
877
|
+
- LG TV (2018+ model) or Emulator
|
|
878
|
+
- Developer Account
|
|
879
|
+
|
|
880
|
+
### Step-by-Step Installation
|
|
881
|
+
|
|
882
|
+
#### Step 1: Create webOS Project
|
|
883
|
+
```bash
|
|
884
|
+
# Install webOS CLI
|
|
885
|
+
npm install -g @webos-tools/cli
|
|
886
|
+
|
|
887
|
+
# Create new project
|
|
888
|
+
ares-generate -t webapp VideoPlayerApp
|
|
889
|
+
cd VideoPlayerApp
|
|
890
|
+
```
|
|
891
|
+
|
|
892
|
+
#### Step 2: Configure appinfo.json
|
|
893
|
+
```json
|
|
894
|
+
{
|
|
895
|
+
"id": "com.yourdomain.videoplayerapp",
|
|
896
|
+
"version": "1.0.0",
|
|
897
|
+
"vendor": "Your Company",
|
|
898
|
+
"type": "web",
|
|
899
|
+
"main": "index.html",
|
|
900
|
+
"title": "Video Player App",
|
|
901
|
+
"icon": "icon.png",
|
|
902
|
+
"largeIcon": "largeIcon.png",
|
|
903
|
+
"requiredPermissions": ["internet", "media.playback"]
|
|
904
|
+
}
|
|
905
|
+
```
|
|
906
|
+
|
|
907
|
+
#### Step 3: Implement webOS Player
|
|
908
|
+
```javascript
|
|
909
|
+
// js/app.js
|
|
910
|
+
import { WebPlayer } from '@unified-video/web';
|
|
911
|
+
|
|
912
|
+
class WebOSVideoPlayer {
|
|
913
|
+
constructor() {
|
|
914
|
+
this.player = new WebPlayer();
|
|
915
|
+
this.initializePlayer();
|
|
916
|
+
this.registerRemoteControl();
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
async initializePlayer() {
|
|
920
|
+
await this.player.initialize('#video-container', {
|
|
921
|
+
controls: false,
|
|
922
|
+
autoPlay: true
|
|
923
|
+
});
|
|
924
|
+
|
|
925
|
+
// Load video
|
|
926
|
+
await this.player.load({
|
|
927
|
+
url: 'https://example.com/video.m3u8',
|
|
928
|
+
type: 'hls',
|
|
929
|
+
drm: {
|
|
930
|
+
type: 'playready',
|
|
931
|
+
licenseUrl: 'https://license.server.com'
|
|
932
|
+
}
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
registerRemoteControl() {
|
|
937
|
+
// Register webOS specific remote control
|
|
938
|
+
webOS.registerKeys(['red', 'green', 'yellow', 'blue', 'back']);
|
|
939
|
+
|
|
940
|
+
document.addEventListener('webOSRelaunch', (e) => {
|
|
941
|
+
if (e.detail.parameters.contentId) {
|
|
942
|
+
this.loadContent(e.detail.parameters.contentId);
|
|
943
|
+
}
|
|
944
|
+
});
|
|
945
|
+
|
|
946
|
+
document.addEventListener('keydown', (e) => {
|
|
947
|
+
switch(e.key) {
|
|
948
|
+
case 'Enter':
|
|
949
|
+
this.togglePlayPause();
|
|
950
|
+
break;
|
|
951
|
+
case 'ArrowLeft':
|
|
952
|
+
this.seek(-10);
|
|
953
|
+
break;
|
|
954
|
+
case 'ArrowRight':
|
|
955
|
+
this.seek(10);
|
|
956
|
+
break;
|
|
957
|
+
case 'Back':
|
|
958
|
+
webOS.platformBack();
|
|
959
|
+
break;
|
|
960
|
+
}
|
|
961
|
+
});
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
togglePlayPause() {
|
|
965
|
+
this.player.isPlaying() ?
|
|
966
|
+
this.player.pause() :
|
|
967
|
+
this.player.play();
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
seek(seconds) {
|
|
971
|
+
const currentTime = this.player.getCurrentTime();
|
|
972
|
+
this.player.seek(currentTime + seconds);
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
// Initialize on app launch
|
|
977
|
+
window.addEventListener('load', () => {
|
|
978
|
+
new WebOSVideoPlayer();
|
|
979
|
+
});
|
|
980
|
+
```
|
|
981
|
+
|
|
982
|
+
#### Step 4: Build and Deploy
|
|
983
|
+
```bash
|
|
984
|
+
# Package app
|
|
985
|
+
ares-package . -o dist/
|
|
986
|
+
|
|
987
|
+
# Install on TV
|
|
988
|
+
ares-install dist/com.yourdomain.videoplayerapp_1.0.0_all.ipk -t <TV_NAME>
|
|
989
|
+
|
|
990
|
+
# Launch app
|
|
991
|
+
ares-launch com.yourdomain.videoplayerapp -t <TV_NAME>
|
|
992
|
+
|
|
993
|
+
# Debug app
|
|
994
|
+
ares-inspect com.yourdomain.videoplayerapp -t <TV_NAME>
|
|
995
|
+
```
|
|
996
|
+
|
|
997
|
+
---
|
|
998
|
+
|
|
999
|
+
## 6. Roku Platform Setup
|
|
1000
|
+
|
|
1001
|
+
### Prerequisites
|
|
1002
|
+
- Roku Device (or Emulator)
|
|
1003
|
+
- Roku Developer Account
|
|
1004
|
+
- Developer Mode enabled on device
|
|
1005
|
+
|
|
1006
|
+
### Step-by-Step Installation
|
|
1007
|
+
|
|
1008
|
+
#### Step 1: Project Structure
|
|
1009
|
+
```
|
|
1010
|
+
VideoPlayerApp/
|
|
1011
|
+
โโโ manifest
|
|
1012
|
+
โโโ source/
|
|
1013
|
+
โ โโโ main.brs
|
|
1014
|
+
โโโ components/
|
|
1015
|
+
โ โโโ UnifiedVideoPlayer.xml
|
|
1016
|
+
โ โโโ UnifiedVideoPlayer.brs
|
|
1017
|
+
โโโ images/
|
|
1018
|
+
โโโ splash_hd.png
|
|
1019
|
+
โโโ splash_sd.png
|
|
1020
|
+
```
|
|
1021
|
+
|
|
1022
|
+
#### Step 2: Configure Manifest
|
|
1023
|
+
```
|
|
1024
|
+
title=Video Player App
|
|
1025
|
+
major_version=1
|
|
1026
|
+
minor_version=0
|
|
1027
|
+
build_version=0
|
|
1028
|
+
mm_icon_focus_hd=pkg:/images/icon_hd.png
|
|
1029
|
+
mm_icon_focus_sd=pkg:/images/icon_sd.png
|
|
1030
|
+
splash_screen_hd=pkg:/images/splash_hd.png
|
|
1031
|
+
splash_screen_sd=pkg:/images/splash_sd.png
|
|
1032
|
+
ui_resolutions=fhd
|
|
1033
|
+
bs_libs_required=roku_ads_lib
|
|
1034
|
+
```
|
|
1035
|
+
|
|
1036
|
+
#### Step 3: Main Scene Component
|
|
1037
|
+
Create `components/MainScene.xml`:
|
|
1038
|
+
```xml
|
|
1039
|
+
<?xml version="1.0" encoding="utf-8" ?>
|
|
1040
|
+
<component name="MainScene" extends="Scene">
|
|
1041
|
+
<interface>
|
|
1042
|
+
<field id="videoUrl" type="string" />
|
|
1043
|
+
</interface>
|
|
1044
|
+
|
|
1045
|
+
<script type="text/brightscript" uri="MainScene.brs" />
|
|
1046
|
+
|
|
1047
|
+
<children>
|
|
1048
|
+
<UnifiedVideoPlayer
|
|
1049
|
+
id="videoPlayer"
|
|
1050
|
+
width="1920"
|
|
1051
|
+
height="1080" />
|
|
1052
|
+
</children>
|
|
1053
|
+
</component>
|
|
1054
|
+
```
|
|
1055
|
+
|
|
1056
|
+
Create `components/MainScene.brs`:
|
|
1057
|
+
```brightscript
|
|
1058
|
+
sub init()
|
|
1059
|
+
m.videoPlayer = m.top.findNode("videoPlayer")
|
|
1060
|
+
m.top.setFocus(true)
|
|
1061
|
+
|
|
1062
|
+
' Initialize with sample content
|
|
1063
|
+
loadContent()
|
|
1064
|
+
end sub
|
|
1065
|
+
|
|
1066
|
+
sub loadContent()
|
|
1067
|
+
content = CreateObject("roSGNode", "ContentNode")
|
|
1068
|
+
content.url = "https://example.com/video.m3u8"
|
|
1069
|
+
content.streamFormat = "hls"
|
|
1070
|
+
content.title = "Sample Video"
|
|
1071
|
+
|
|
1072
|
+
' DRM configuration
|
|
1073
|
+
content.drmParams = {
|
|
1074
|
+
type: "widevine",
|
|
1075
|
+
licenseUrl: "https://license.server.com"
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
' Load into player
|
|
1079
|
+
m.videoPlayer.callFunc("loadContent", content)
|
|
1080
|
+
end sub
|
|
1081
|
+
|
|
1082
|
+
function onKeyEvent(key as String, press as Boolean) as Boolean
|
|
1083
|
+
if press then
|
|
1084
|
+
if key = "play"
|
|
1085
|
+
m.videoPlayer.callFunc("play")
|
|
1086
|
+
return true
|
|
1087
|
+
else if key = "pause"
|
|
1088
|
+
m.videoPlayer.callFunc("pause")
|
|
1089
|
+
return true
|
|
1090
|
+
else if key = "back"
|
|
1091
|
+
return true
|
|
1092
|
+
end if
|
|
1093
|
+
end if
|
|
1094
|
+
return false
|
|
1095
|
+
end function
|
|
1096
|
+
```
|
|
1097
|
+
|
|
1098
|
+
#### Step 4: Build and Deploy
|
|
1099
|
+
```bash
|
|
1100
|
+
# Create ZIP package
|
|
1101
|
+
zip -r VideoPlayerApp.zip manifest source components images
|
|
1102
|
+
|
|
1103
|
+
# Enable developer mode on Roku (Settings > System > Advanced > Developer Mode)
|
|
1104
|
+
|
|
1105
|
+
# Upload via browser
|
|
1106
|
+
# Navigate to http://<ROKU_IP> and upload the ZIP
|
|
1107
|
+
|
|
1108
|
+
# Or use Roku Deploy CLI
|
|
1109
|
+
npm install -g roku-deploy
|
|
1110
|
+
roku-deploy --host <ROKU_IP> --password <DEV_PASSWORD>
|
|
1111
|
+
```
|
|
1112
|
+
|
|
1113
|
+
---
|
|
1114
|
+
|
|
1115
|
+
## 7. Android TV Setup
|
|
1116
|
+
|
|
1117
|
+
### Prerequisites
|
|
1118
|
+
- Android Studio with Android TV SDK
|
|
1119
|
+
- Android TV device or emulator
|
|
1120
|
+
- Leanback library
|
|
1121
|
+
|
|
1122
|
+
### Step-by-Step Installation
|
|
1123
|
+
|
|
1124
|
+
#### Step 1: Create Android TV Project
|
|
1125
|
+
```bash
|
|
1126
|
+
# In Android Studio: New Project > TV > Empty Activity
|
|
1127
|
+
```
|
|
1128
|
+
|
|
1129
|
+
#### Step 2: Configure build.gradle
|
|
1130
|
+
```gradle
|
|
1131
|
+
// app/build.gradle
|
|
1132
|
+
android {
|
|
1133
|
+
compileSdk 33
|
|
1134
|
+
|
|
1135
|
+
defaultConfig {
|
|
1136
|
+
applicationId "com.yourdomain.videoplayer"
|
|
1137
|
+
minSdk 21
|
|
1138
|
+
targetSdk 33
|
|
1139
|
+
versionCode 1
|
|
1140
|
+
versionName "1.0"
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
dependencies {
|
|
1145
|
+
implementation 'androidx.leanback:leanback:1.2.0'
|
|
1146
|
+
implementation 'com.google.android.exoplayer:exoplayer:2.18.5'
|
|
1147
|
+
implementation 'com.google.android.exoplayer:exoplayer-hls:2.18.5'
|
|
1148
|
+
implementation 'com.google.android.exoplayer:extension-leanback:2.18.5'
|
|
1149
|
+
}
|
|
1150
|
+
```
|
|
1151
|
+
|
|
1152
|
+
#### Step 3: TV Manifest Configuration
|
|
1153
|
+
```xml
|
|
1154
|
+
<!-- AndroidManifest.xml -->
|
|
1155
|
+
<uses-feature
|
|
1156
|
+
android:name="android.software.leanback"
|
|
1157
|
+
android:required="true" />
|
|
1158
|
+
|
|
1159
|
+
<uses-feature
|
|
1160
|
+
android:name="android.hardware.touchscreen"
|
|
1161
|
+
android:required="false" />
|
|
1162
|
+
|
|
1163
|
+
<application
|
|
1164
|
+
android:banner="@drawable/tv_banner"
|
|
1165
|
+
android:theme="@style/Theme.Leanback">
|
|
1166
|
+
|
|
1167
|
+
<activity
|
|
1168
|
+
android:name=".MainActivity"
|
|
1169
|
+
android:label="@string/app_name"
|
|
1170
|
+
android:screenOrientation="landscape">
|
|
1171
|
+
<intent-filter>
|
|
1172
|
+
<action android:name="android.intent.action.MAIN" />
|
|
1173
|
+
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
|
1174
|
+
</intent-filter>
|
|
1175
|
+
</activity>
|
|
1176
|
+
|
|
1177
|
+
<activity
|
|
1178
|
+
android:name=".PlayerActivity"
|
|
1179
|
+
android:configChanges="orientation|screenSize" />
|
|
1180
|
+
</application>
|
|
1181
|
+
```
|
|
1182
|
+
|
|
1183
|
+
#### Step 4: Implement Player Activity
|
|
1184
|
+
```kotlin
|
|
1185
|
+
// PlayerActivity.kt
|
|
1186
|
+
import android.os.Bundle
|
|
1187
|
+
import androidx.fragment.app.FragmentActivity
|
|
1188
|
+
import com.google.android.exoplayer2.ExoPlayer
|
|
1189
|
+
import com.google.android.exoplayer2.MediaItem
|
|
1190
|
+
import com.google.android.exoplayer2.ext.leanback.LeanbackPlayerAdapter
|
|
1191
|
+
import androidx.leanback.app.VideoSupportFragment
|
|
1192
|
+
import androidx.leanback.app.VideoSupportFragmentGlueHost
|
|
1193
|
+
import androidx.leanback.media.PlaybackTransportControlGlue
|
|
1194
|
+
|
|
1195
|
+
class PlayerActivity : FragmentActivity() {
|
|
1196
|
+
private lateinit var exoPlayer: ExoPlayer
|
|
1197
|
+
private lateinit var playerGlue: PlaybackTransportControlGlue<LeanbackPlayerAdapter>
|
|
1198
|
+
|
|
1199
|
+
override fun onCreate(savedInstanceState: Bundle?) {
|
|
1200
|
+
super.onCreate(savedInstanceState)
|
|
1201
|
+
setContentView(R.layout.activity_player)
|
|
1202
|
+
|
|
1203
|
+
val videoFragment = supportFragmentManager
|
|
1204
|
+
.findFragmentById(R.id.video_fragment) as VideoSupportFragment
|
|
1205
|
+
|
|
1206
|
+
// Initialize ExoPlayer
|
|
1207
|
+
exoPlayer = ExoPlayer.Builder(this).build()
|
|
1208
|
+
|
|
1209
|
+
// Create Leanback adapter
|
|
1210
|
+
val playerAdapter = LeanbackPlayerAdapter(this, exoPlayer, 16)
|
|
1211
|
+
|
|
1212
|
+
// Create playback control glue
|
|
1213
|
+
playerGlue = PlaybackTransportControlGlue(this, playerAdapter)
|
|
1214
|
+
playerGlue.host = VideoSupportFragmentGlueHost(videoFragment)
|
|
1215
|
+
playerGlue.title = "Video Title"
|
|
1216
|
+
playerGlue.subtitle = "Video Subtitle"
|
|
1217
|
+
|
|
1218
|
+
// Load video
|
|
1219
|
+
val videoUrl = intent.getStringExtra("video_url")
|
|
1220
|
+
val mediaItem = MediaItem.fromUri(videoUrl!!)
|
|
1221
|
+
exoPlayer.setMediaItem(mediaItem)
|
|
1222
|
+
exoPlayer.prepare()
|
|
1223
|
+
exoPlayer.play()
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
override fun onStop() {
|
|
1227
|
+
super.onStop()
|
|
1228
|
+
playerGlue.pause()
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
override fun onDestroy() {
|
|
1232
|
+
super.onDestroy()
|
|
1233
|
+
exoPlayer.release()
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
```
|
|
1237
|
+
|
|
1238
|
+
#### Step 5: Build and Deploy
|
|
1239
|
+
```bash
|
|
1240
|
+
# Build APK
|
|
1241
|
+
./gradlew assembleDebug
|
|
1242
|
+
|
|
1243
|
+
# Install on Android TV
|
|
1244
|
+
adb connect <TV_IP>
|
|
1245
|
+
adb install app/build/outputs/apk/debug/app-debug.apk
|
|
1246
|
+
|
|
1247
|
+
# Launch app
|
|
1248
|
+
adb shell am start -n com.yourdomain.videoplayer/.MainActivity
|
|
1249
|
+
```
|
|
1250
|
+
|
|
1251
|
+
---
|
|
1252
|
+
|
|
1253
|
+
## 8. React Native Mobile Setup (iOS + Android Combined)
|
|
1254
|
+
|
|
1255
|
+
### Prerequisites
|
|
1256
|
+
- Node.js 16+
|
|
1257
|
+
- React Native CLI
|
|
1258
|
+
- Xcode (for iOS)
|
|
1259
|
+
- Android Studio (for Android)
|
|
1260
|
+
|
|
1261
|
+
### Step-by-Step Installation
|
|
1262
|
+
|
|
1263
|
+
#### Step 1: Create React Native Project
|
|
1264
|
+
```bash
|
|
1265
|
+
npx react-native init VideoPlayerApp --template react-native-template-typescript
|
|
1266
|
+
cd VideoPlayerApp
|
|
1267
|
+
```
|
|
1268
|
+
|
|
1269
|
+
#### Step 2: Install Dependencies
|
|
1270
|
+
```bash
|
|
1271
|
+
npm install @unified-video/core @unified-video/react-native
|
|
1272
|
+
npm install react-native-video react-native-orientation-locker react-native-slider
|
|
1273
|
+
npm install --save-dev @types/react-native-video
|
|
1274
|
+
```
|
|
1275
|
+
|
|
1276
|
+
#### Step 3: Platform-specific Setup
|
|
1277
|
+
```bash
|
|
1278
|
+
# iOS
|
|
1279
|
+
cd ios && pod install && cd ..
|
|
1280
|
+
|
|
1281
|
+
# Android - Already configured via autolinking
|
|
1282
|
+
```
|
|
1283
|
+
|
|
1284
|
+
#### Step 4: Create Video Player Screen
|
|
1285
|
+
```typescript
|
|
1286
|
+
// src/screens/VideoPlayerScreen.tsx
|
|
1287
|
+
import React, { useRef, useState, useEffect } from 'react';
|
|
1288
|
+
import {
|
|
1289
|
+
View,
|
|
1290
|
+
StyleSheet,
|
|
1291
|
+
TouchableOpacity,
|
|
1292
|
+
Text,
|
|
1293
|
+
StatusBar,
|
|
1294
|
+
Platform,
|
|
1295
|
+
SafeAreaView
|
|
1296
|
+
} from 'react-native';
|
|
1297
|
+
import { ReactNativePlayer, ReactNativePlayerRef } from '@unified-video/react-native';
|
|
1298
|
+
import Orientation from 'react-native-orientation-locker';
|
|
1299
|
+
import Slider from '@react-native-community/slider';
|
|
1300
|
+
|
|
1301
|
+
interface VideoPlayerScreenProps {
|
|
1302
|
+
route: {
|
|
1303
|
+
params: {
|
|
1304
|
+
videoUrl: string;
|
|
1305
|
+
title?: string;
|
|
1306
|
+
};
|
|
1307
|
+
};
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
export const VideoPlayerScreen: React.FC<VideoPlayerScreenProps> = ({ route }) => {
|
|
1311
|
+
const playerRef = useRef<ReactNativePlayerRef>(null);
|
|
1312
|
+
const [isPlaying, setIsPlaying] = useState(false);
|
|
1313
|
+
const [currentTime, setCurrentTime] = useState(0);
|
|
1314
|
+
const [duration, setDuration] = useState(0);
|
|
1315
|
+
const [showControls, setShowControls] = useState(true);
|
|
1316
|
+
|
|
1317
|
+
useEffect(() => {
|
|
1318
|
+
// Lock to landscape
|
|
1319
|
+
Orientation.lockToLandscape();
|
|
1320
|
+
|
|
1321
|
+
// Hide status bar
|
|
1322
|
+
StatusBar.setHidden(true);
|
|
1323
|
+
|
|
1324
|
+
// Load video
|
|
1325
|
+
loadVideo();
|
|
1326
|
+
|
|
1327
|
+
return () => {
|
|
1328
|
+
Orientation.unlockAllOrientations();
|
|
1329
|
+
StatusBar.setHidden(false);
|
|
1330
|
+
};
|
|
1331
|
+
}, []);
|
|
1332
|
+
|
|
1333
|
+
const loadVideo = async () => {
|
|
1334
|
+
const { videoUrl } = route.params;
|
|
1335
|
+
|
|
1336
|
+
await playerRef.current?.load({
|
|
1337
|
+
url: videoUrl,
|
|
1338
|
+
type: videoUrl.includes('.m3u8') ? 'hls' : 'mp4',
|
|
1339
|
+
metadata: {
|
|
1340
|
+
title: route.params.title || 'Video'
|
|
1341
|
+
}
|
|
1342
|
+
});
|
|
1343
|
+
|
|
1344
|
+
// Start playback
|
|
1345
|
+
await playerRef.current?.play();
|
|
1346
|
+
setIsPlaying(true);
|
|
1347
|
+
};
|
|
1348
|
+
|
|
1349
|
+
const togglePlayPause = () => {
|
|
1350
|
+
if (isPlaying) {
|
|
1351
|
+
playerRef.current?.pause();
|
|
1352
|
+
} else {
|
|
1353
|
+
playerRef.current?.play();
|
|
1354
|
+
}
|
|
1355
|
+
setIsPlaying(!isPlaying);
|
|
1356
|
+
};
|
|
1357
|
+
|
|
1358
|
+
const onSliderValueChange = (value: number) => {
|
|
1359
|
+
playerRef.current?.seek(value);
|
|
1360
|
+
};
|
|
1361
|
+
|
|
1362
|
+
const formatTime = (seconds: number): string => {
|
|
1363
|
+
const mins = Math.floor(seconds / 60);
|
|
1364
|
+
const secs = Math.floor(seconds % 60);
|
|
1365
|
+
return `${mins}:${secs < 10 ? '0' : ''}${secs}`;
|
|
1366
|
+
};
|
|
1367
|
+
|
|
1368
|
+
return (
|
|
1369
|
+
<SafeAreaView style={styles.container}>
|
|
1370
|
+
<TouchableOpacity
|
|
1371
|
+
activeOpacity={1}
|
|
1372
|
+
style={styles.videoContainer}
|
|
1373
|
+
onPress={() => setShowControls(!showControls)}
|
|
1374
|
+
>
|
|
1375
|
+
<ReactNativePlayer
|
|
1376
|
+
ref={playerRef}
|
|
1377
|
+
style={styles.video}
|
|
1378
|
+
config={{
|
|
1379
|
+
autoPlay: false,
|
|
1380
|
+
controls: false, // Use custom controls
|
|
1381
|
+
muted: false
|
|
1382
|
+
}}
|
|
1383
|
+
onReady={() => console.log('Player ready')}
|
|
1384
|
+
onPlay={() => setIsPlaying(true)}
|
|
1385
|
+
onPause={() => setIsPlaying(false)}
|
|
1386
|
+
onTimeUpdate={(time) => setCurrentTime(time)}
|
|
1387
|
+
onLoadedMetadata={(metadata) => {
|
|
1388
|
+
setDuration(metadata.duration || 0);
|
|
1389
|
+
}}
|
|
1390
|
+
onError={(error) => console.error('Player error:', error)}
|
|
1391
|
+
/>
|
|
1392
|
+
|
|
1393
|
+
{showControls && (
|
|
1394
|
+
<View style={styles.controls}>
|
|
1395
|
+
<View style={styles.topControls}>
|
|
1396
|
+
<Text style={styles.title}>{route.params.title}</Text>
|
|
1397
|
+
</View>
|
|
1398
|
+
|
|
1399
|
+
<View style={styles.centerControls}>
|
|
1400
|
+
<TouchableOpacity onPress={togglePlayPause}>
|
|
1401
|
+
<Text style={styles.playButton}>
|
|
1402
|
+
{isPlaying ? 'โธ' : 'โถ'}
|
|
1403
|
+
</Text>
|
|
1404
|
+
</TouchableOpacity>
|
|
1405
|
+
</View>
|
|
1406
|
+
|
|
1407
|
+
<View style={styles.bottomControls}>
|
|
1408
|
+
<Text style={styles.time}>{formatTime(currentTime)}</Text>
|
|
1409
|
+
<Slider
|
|
1410
|
+
style={styles.slider}
|
|
1411
|
+
minimumValue={0}
|
|
1412
|
+
maximumValue={duration}
|
|
1413
|
+
value={currentTime}
|
|
1414
|
+
onSlidingComplete={onSliderValueChange}
|
|
1415
|
+
minimumTrackTintColor="#FFFFFF"
|
|
1416
|
+
maximumTrackTintColor="rgba(255,255,255,0.3)"
|
|
1417
|
+
/>
|
|
1418
|
+
<Text style={styles.time}>{formatTime(duration)}</Text>
|
|
1419
|
+
</View>
|
|
1420
|
+
</View>
|
|
1421
|
+
)}
|
|
1422
|
+
</TouchableOpacity>
|
|
1423
|
+
</SafeAreaView>
|
|
1424
|
+
);
|
|
1425
|
+
};
|
|
1426
|
+
|
|
1427
|
+
const styles = StyleSheet.create({
|
|
1428
|
+
container: {
|
|
1429
|
+
flex: 1,
|
|
1430
|
+
backgroundColor: '#000',
|
|
1431
|
+
},
|
|
1432
|
+
videoContainer: {
|
|
1433
|
+
flex: 1,
|
|
1434
|
+
},
|
|
1435
|
+
video: {
|
|
1436
|
+
position: 'absolute',
|
|
1437
|
+
top: 0,
|
|
1438
|
+
left: 0,
|
|
1439
|
+
right: 0,
|
|
1440
|
+
bottom: 0,
|
|
1441
|
+
},
|
|
1442
|
+
controls: {
|
|
1443
|
+
position: 'absolute',
|
|
1444
|
+
top: 0,
|
|
1445
|
+
left: 0,
|
|
1446
|
+
right: 0,
|
|
1447
|
+
bottom: 0,
|
|
1448
|
+
backgroundColor: 'rgba(0,0,0,0.5)',
|
|
1449
|
+
},
|
|
1450
|
+
topControls: {
|
|
1451
|
+
position: 'absolute',
|
|
1452
|
+
top: 20,
|
|
1453
|
+
left: 20,
|
|
1454
|
+
right: 20,
|
|
1455
|
+
},
|
|
1456
|
+
title: {
|
|
1457
|
+
color: '#FFF',
|
|
1458
|
+
fontSize: 18,
|
|
1459
|
+
fontWeight: 'bold',
|
|
1460
|
+
},
|
|
1461
|
+
centerControls: {
|
|
1462
|
+
flex: 1,
|
|
1463
|
+
justifyContent: 'center',
|
|
1464
|
+
alignItems: 'center',
|
|
1465
|
+
},
|
|
1466
|
+
playButton: {
|
|
1467
|
+
fontSize: 60,
|
|
1468
|
+
color: '#FFF',
|
|
1469
|
+
},
|
|
1470
|
+
bottomControls: {
|
|
1471
|
+
position: 'absolute',
|
|
1472
|
+
bottom: 20,
|
|
1473
|
+
left: 20,
|
|
1474
|
+
right: 20,
|
|
1475
|
+
flexDirection: 'row',
|
|
1476
|
+
alignItems: 'center',
|
|
1477
|
+
},
|
|
1478
|
+
time: {
|
|
1479
|
+
color: '#FFF',
|
|
1480
|
+
fontSize: 14,
|
|
1481
|
+
},
|
|
1482
|
+
slider: {
|
|
1483
|
+
flex: 1,
|
|
1484
|
+
height: 40,
|
|
1485
|
+
marginHorizontal: 10,
|
|
1486
|
+
},
|
|
1487
|
+
});
|
|
1488
|
+
```
|
|
1489
|
+
|
|
1490
|
+
#### Step 5: Build and Deploy
|
|
1491
|
+
|
|
1492
|
+
**iOS:**
|
|
1493
|
+
```bash
|
|
1494
|
+
# Development
|
|
1495
|
+
npx react-native run-ios
|
|
1496
|
+
|
|
1497
|
+
# Production
|
|
1498
|
+
cd ios
|
|
1499
|
+
xcodebuild archive \
|
|
1500
|
+
-workspace VideoPlayerApp.xcworkspace \
|
|
1501
|
+
-scheme VideoPlayerApp \
|
|
1502
|
+
-archivePath ~/Desktop/VideoPlayerApp.xcarchive
|
|
1503
|
+
|
|
1504
|
+
# Upload to App Store
|
|
1505
|
+
xcrun altool --upload-app \
|
|
1506
|
+
-f ~/Desktop/VideoPlayerApp.ipa \
|
|
1507
|
+
-u your@email.com \
|
|
1508
|
+
-p app-specific-password
|
|
1509
|
+
```
|
|
1510
|
+
|
|
1511
|
+
**Android:**
|
|
1512
|
+
```bash
|
|
1513
|
+
# Development
|
|
1514
|
+
npx react-native run-android
|
|
1515
|
+
|
|
1516
|
+
# Production
|
|
1517
|
+
cd android
|
|
1518
|
+
./gradlew bundleRelease
|
|
1519
|
+
|
|
1520
|
+
# Sign and upload to Google Play Console
|
|
1521
|
+
```
|
|
1522
|
+
|
|
1523
|
+
---
|
|
1524
|
+
|
|
1525
|
+
## ๐ Quick Testing Commands
|
|
1526
|
+
|
|
1527
|
+
### Web
|
|
1528
|
+
```bash
|
|
1529
|
+
npm run serve:demo
|
|
1530
|
+
open http://localhost:3000/apps/demo/demo.html
|
|
1531
|
+
```
|
|
1532
|
+
|
|
1533
|
+
### React Native
|
|
1534
|
+
```bash
|
|
1535
|
+
# iOS Simulator
|
|
1536
|
+
npx react-native run-ios --simulator="iPhone 14 Pro"
|
|
1537
|
+
|
|
1538
|
+
# Android Emulator
|
|
1539
|
+
npx react-native run-android --deviceId=emulator-5554
|
|
1540
|
+
```
|
|
1541
|
+
|
|
1542
|
+
### Smart TV
|
|
1543
|
+
```bash
|
|
1544
|
+
# Samsung Tizen
|
|
1545
|
+
tizen emulator --name TV-5.0
|
|
1546
|
+
|
|
1547
|
+
# LG webOS
|
|
1548
|
+
ares-launch-simulator TV_23
|
|
1549
|
+
```
|
|
1550
|
+
|
|
1551
|
+
### Roku
|
|
1552
|
+
```bash
|
|
1553
|
+
# Use browser
|
|
1554
|
+
open http://<ROKU_IP>
|
|
1555
|
+
```
|
|
1556
|
+
|
|
1557
|
+
---
|
|
1558
|
+
|
|
1559
|
+
## ๐ Common Issues and Solutions
|
|
1560
|
+
|
|
1561
|
+
### Issue: iOS - UnifiedVideoPlayer.swiftmodule not built for arm64
|
|
1562
|
+
**Solution:** The framework needs to be built for the correct architecture:
|
|
1563
|
+
```bash
|
|
1564
|
+
# Option 1: Use Swift Package Manager (auto-builds)
|
|
1565
|
+
File > Add Package Dependencies > Add Local Package
|
|
1566
|
+
|
|
1567
|
+
# Option 2: Build universal framework
|
|
1568
|
+
cd packages/ios
|
|
1569
|
+
./build_framework.sh
|
|
1570
|
+
|
|
1571
|
+
# Option 3: Clean Xcode build
|
|
1572
|
+
Product > Clean Build Folder (โงโK)
|
|
1573
|
+
File > Packages > Reset Package Caches
|
|
1574
|
+
```
|
|
1575
|
+
|
|
1576
|
+
### Issue: iOS - Module 'UnifiedVideoPlayer' not found
|
|
1577
|
+
**Solution:** Ensure the framework is properly added:
|
|
1578
|
+
```bash
|
|
1579
|
+
# Check target membership in Xcode
|
|
1580
|
+
1. Select UnifiedVideoPlayer.framework
|
|
1581
|
+
2. File Inspector > Target Membership
|
|
1582
|
+
3. Check your app target
|
|
1583
|
+
|
|
1584
|
+
# Verify Build Phases
|
|
1585
|
+
1. Target > Build Phases
|
|
1586
|
+
2. Link Binary With Libraries should include UnifiedVideoPlayer
|
|
1587
|
+
3. Embed Frameworks should include it with "Embed & Sign"
|
|
1588
|
+
```
|
|
1589
|
+
|
|
1590
|
+
### Issue: DRM Content Not Playing
|
|
1591
|
+
**Solution:** Ensure proper DRM configuration and certificates are in place for each platform.
|
|
1592
|
+
|
|
1593
|
+
### Issue: CORS Errors in Web
|
|
1594
|
+
**Solution:** Configure proper CORS headers on your video server or use a proxy.
|
|
1595
|
+
|
|
1596
|
+
### Issue: React Native Build Fails
|
|
1597
|
+
**Solution:** Clean and rebuild:
|
|
1598
|
+
```bash
|
|
1599
|
+
cd ios && pod deintegrate && pod install
|
|
1600
|
+
cd android && ./gradlew clean
|
|
1601
|
+
npx react-native start --reset-cache
|
|
1602
|
+
```
|
|
1603
|
+
|
|
1604
|
+
### Issue: TV Remote Not Working
|
|
1605
|
+
**Solution:** Register proper key codes for each TV platform and test in actual device.
|
|
1606
|
+
|
|
1607
|
+
---
|
|
1608
|
+
|
|
1609
|
+
## ๐ฏ Production Checklist
|
|
1610
|
+
|
|
1611
|
+
- [ ] Configure proper DRM licenses for protected content
|
|
1612
|
+
- [ ] Set up CDN for video delivery
|
|
1613
|
+
- [ ] Implement analytics tracking
|
|
1614
|
+
- [ ] Add error reporting (Sentry, Crashlytics)
|
|
1615
|
+
- [ ] Configure app signing certificates
|
|
1616
|
+
- [ ] Test on actual devices
|
|
1617
|
+
- [ ] Implement adaptive bitrate streaming
|
|
1618
|
+
- [ ] Add offline download capability (mobile)
|
|
1619
|
+
- [ ] Configure background playback (mobile)
|
|
1620
|
+
- [ ] Implement Chromecast support
|
|
1621
|
+
|
|
1622
|
+
---
|
|
1623
|
+
|
|
1624
|
+
## ๐ Additional Resources
|
|
1625
|
+
|
|
1626
|
+
- [Web Platform Docs](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement)
|
|
1627
|
+
- [iOS AVPlayer Docs](https://developer.apple.com/documentation/avfoundation/avplayer)
|
|
1628
|
+
- [Android ExoPlayer Docs](https://exoplayer.dev/)
|
|
1629
|
+
- [Samsung Tizen Docs](https://developer.samsung.com/smarttv)
|
|
1630
|
+
- [LG webOS Docs](https://webostv.developer.lge.com/)
|
|
1631
|
+
- [Roku SDK Docs](https://developer.roku.com/docs/developer-program/getting-started/roku-dev-prog.md)
|
|
1632
|
+
- [Android TV Docs](https://developer.android.com/tv)
|
|
1633
|
+
|
|
1634
|
+
---
|
|
1635
|
+
|
|
1636
|
+
This guide provides everything needed to implement the Unified Video Framework on any supported platform. Choose your target platform and follow the step-by-step instructions to get started!
|