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,903 @@
|
|
|
1
|
+
# 📱 Integrating Unified Video Framework into Existing Native Apps
|
|
2
|
+
|
|
3
|
+
This guide shows how to add the Unified Video Framework to your **existing iOS and Android apps** as a native SDK/library.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 🎯 Integration Overview
|
|
8
|
+
|
|
9
|
+
Since you have existing native apps, you'll integrate the video player as:
|
|
10
|
+
- **iOS**: Swift/Objective-C Framework or CocoaPod
|
|
11
|
+
- **Android**: AAR library or Gradle dependency
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Part 1: iOS Integration (Existing iOS App)
|
|
16
|
+
|
|
17
|
+
## Option A: Swift Package Manager (Recommended)
|
|
18
|
+
|
|
19
|
+
### Step 1: Create iOS Framework
|
|
20
|
+
Create `UnifiedVideoPlayer.xcframework` from the framework:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# Build for iOS device
|
|
24
|
+
xcodebuild archive \
|
|
25
|
+
-scheme UnifiedVideoPlayer \
|
|
26
|
+
-archivePath ./build/ios.xcarchive \
|
|
27
|
+
-sdk iphoneos \
|
|
28
|
+
SKIP_INSTALL=NO
|
|
29
|
+
|
|
30
|
+
# Build for iOS simulator
|
|
31
|
+
xcodebuild archive \
|
|
32
|
+
-scheme UnifiedVideoPlayer \
|
|
33
|
+
-archivePath ./build/ios-sim.xcarchive \
|
|
34
|
+
-sdk iphonesimulator \
|
|
35
|
+
SKIP_INSTALL=NO
|
|
36
|
+
|
|
37
|
+
# Create XCFramework
|
|
38
|
+
xcodebuild -create-xcframework \
|
|
39
|
+
-framework ./build/ios.xcarchive/Products/Library/Frameworks/UnifiedVideoPlayer.framework \
|
|
40
|
+
-framework ./build/ios-sim.xcarchive/Products/Library/Frameworks/UnifiedVideoPlayer.framework \
|
|
41
|
+
-output ./UnifiedVideoPlayer.xcframework
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Step 2: Add to Your Existing iOS App
|
|
45
|
+
|
|
46
|
+
**In Xcode:**
|
|
47
|
+
1. Select your project in navigator
|
|
48
|
+
2. Select your app target
|
|
49
|
+
3. Go to "General" tab
|
|
50
|
+
4. Under "Frameworks, Libraries, and Embedded Content", click "+"
|
|
51
|
+
5. Select "Add Files..." and choose `UnifiedVideoPlayer.xcframework`
|
|
52
|
+
6. Set to "Embed & Sign"
|
|
53
|
+
|
|
54
|
+
### Step 3: Import and Use in Your App
|
|
55
|
+
|
|
56
|
+
```swift
|
|
57
|
+
// YourExistingViewController.swift
|
|
58
|
+
import UIKit
|
|
59
|
+
import UnifiedVideoPlayer // Import the framework
|
|
60
|
+
|
|
61
|
+
class YourExistingViewController: UIViewController {
|
|
62
|
+
|
|
63
|
+
private var videoPlayer: UnifiedVideoPlayer?
|
|
64
|
+
|
|
65
|
+
// Add video player to your existing view
|
|
66
|
+
func addVideoPlayer() {
|
|
67
|
+
// Create player instance
|
|
68
|
+
videoPlayer = UnifiedVideoPlayer()
|
|
69
|
+
|
|
70
|
+
// Configure player
|
|
71
|
+
let config = PlayerConfiguration(
|
|
72
|
+
autoPlay: true,
|
|
73
|
+
controls: true,
|
|
74
|
+
muted: false
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
// Initialize in your existing view hierarchy
|
|
78
|
+
videoPlayer?.initialize(
|
|
79
|
+
container: playerContainerView, // Your existing UIView
|
|
80
|
+
configuration: config
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
// Load video
|
|
84
|
+
videoPlayer?.load(url: "https://example.com/video.m3u8")
|
|
85
|
+
|
|
86
|
+
// Handle events
|
|
87
|
+
videoPlayer?.onReady = { [weak self] in
|
|
88
|
+
print("Player ready")
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
videoPlayer?.onError = { [weak self] error in
|
|
92
|
+
self?.handleVideoError(error)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Integrate with your existing UI
|
|
97
|
+
@IBAction func existingPlayButtonTapped(_ sender: UIButton) {
|
|
98
|
+
videoPlayer?.play()
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
@IBAction func existingPauseButtonTapped(_ sender: UIButton) {
|
|
102
|
+
videoPlayer?.pause()
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Option B: CocoaPods Integration
|
|
108
|
+
|
|
109
|
+
### Step 1: Create Podspec
|
|
110
|
+
Create `UnifiedVideoPlayer.podspec`:
|
|
111
|
+
|
|
112
|
+
```ruby
|
|
113
|
+
Pod::Spec.new do |s|
|
|
114
|
+
s.name = 'UnifiedVideoPlayer'
|
|
115
|
+
s.version = '1.0.0'
|
|
116
|
+
s.summary = 'Unified Video Player for iOS'
|
|
117
|
+
s.homepage = 'https://yourcompany.com'
|
|
118
|
+
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
|
119
|
+
s.author = { 'Your Company' => 'contact@yourcompany.com' }
|
|
120
|
+
s.source = { :git => 'https://github.com/yourcompany/unified-video-ios.git', :tag => s.version.to_s }
|
|
121
|
+
|
|
122
|
+
s.ios.deployment_target = '13.0'
|
|
123
|
+
s.swift_version = '5.0'
|
|
124
|
+
|
|
125
|
+
s.source_files = 'Sources/**/*.{swift,h,m}'
|
|
126
|
+
s.resources = 'Resources/**/*.{xib,storyboard,xcassets}'
|
|
127
|
+
|
|
128
|
+
s.frameworks = 'UIKit', 'AVFoundation', 'AVKit'
|
|
129
|
+
|
|
130
|
+
s.dependency 'Alamofire', '~> 5.0' # If needed
|
|
131
|
+
end
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Step 2: Add to Your App's Podfile
|
|
135
|
+
|
|
136
|
+
```ruby
|
|
137
|
+
# Your existing Podfile
|
|
138
|
+
target 'YourExistingApp' do
|
|
139
|
+
use_frameworks!
|
|
140
|
+
|
|
141
|
+
# Your existing pods
|
|
142
|
+
pod 'Firebase/Analytics'
|
|
143
|
+
pod 'SDWebImage'
|
|
144
|
+
|
|
145
|
+
# Add Unified Video Player
|
|
146
|
+
pod 'UnifiedVideoPlayer', :path => '../unified-video-framework/ios'
|
|
147
|
+
# Or from git:
|
|
148
|
+
# pod 'UnifiedVideoPlayer', :git => 'https://github.com/yourcompany/unified-video-ios.git'
|
|
149
|
+
end
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Step 3: Install
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
cd your-existing-ios-app
|
|
156
|
+
pod install
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Option C: Manual Framework Integration
|
|
160
|
+
|
|
161
|
+
### Step 1: Build Framework
|
|
162
|
+
|
|
163
|
+
Create `UnifiedVideoPlayer.framework`:
|
|
164
|
+
|
|
165
|
+
```swift
|
|
166
|
+
// UnifiedVideoPlayer.swift - Main class
|
|
167
|
+
import UIKit
|
|
168
|
+
import AVFoundation
|
|
169
|
+
import AVKit
|
|
170
|
+
|
|
171
|
+
@objc public class UnifiedVideoPlayer: NSObject {
|
|
172
|
+
|
|
173
|
+
private var player: AVPlayer?
|
|
174
|
+
private var playerLayer: AVPlayerLayer?
|
|
175
|
+
private var containerView: UIView?
|
|
176
|
+
|
|
177
|
+
// MARK: - Initialization
|
|
178
|
+
|
|
179
|
+
@objc public func initialize(container: UIView, configuration: [String: Any]? = nil) {
|
|
180
|
+
self.containerView = container
|
|
181
|
+
setupPlayer()
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// MARK: - Public Methods
|
|
185
|
+
|
|
186
|
+
@objc public func load(url: String) {
|
|
187
|
+
guard let videoURL = URL(string: url) else { return }
|
|
188
|
+
|
|
189
|
+
let asset = AVAsset(url: videoURL)
|
|
190
|
+
let playerItem = AVPlayerItem(asset: asset)
|
|
191
|
+
|
|
192
|
+
player?.replaceCurrentItem(with: playerItem)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
@objc public func play() {
|
|
196
|
+
player?.play()
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
@objc public func pause() {
|
|
200
|
+
player?.pause()
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
@objc public func seek(to time: Double) {
|
|
204
|
+
let cmTime = CMTime(seconds: time, preferredTimescale: 1000)
|
|
205
|
+
player?.seek(to: cmTime)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
@objc public func setVolume(_ volume: Float) {
|
|
209
|
+
player?.volume = volume
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
@objc public func getCurrentTime() -> Double {
|
|
213
|
+
return player?.currentTime().seconds ?? 0
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
@objc public func getDuration() -> Double {
|
|
217
|
+
return player?.currentItem?.duration.seconds ?? 0
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// MARK: - Private Methods
|
|
221
|
+
|
|
222
|
+
private func setupPlayer() {
|
|
223
|
+
player = AVPlayer()
|
|
224
|
+
|
|
225
|
+
playerLayer = AVPlayerLayer(player: player)
|
|
226
|
+
playerLayer?.frame = containerView?.bounds ?? .zero
|
|
227
|
+
playerLayer?.videoGravity = .resizeAspect
|
|
228
|
+
|
|
229
|
+
if let playerLayer = playerLayer {
|
|
230
|
+
containerView?.layer.addSublayer(playerLayer)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
setupObservers()
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
private func setupObservers() {
|
|
237
|
+
// Add time observer
|
|
238
|
+
let interval = CMTime(seconds: 1.0, preferredTimescale: 1000)
|
|
239
|
+
player?.addPeriodicTimeObserver(forInterval: interval, queue: .main) { [weak self] time in
|
|
240
|
+
self?.onTimeUpdate?(time.seconds)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Add other observers
|
|
244
|
+
NotificationCenter.default.addObserver(
|
|
245
|
+
self,
|
|
246
|
+
selector: #selector(playerDidFinishPlaying),
|
|
247
|
+
name: .AVPlayerItemDidPlayToEndTime,
|
|
248
|
+
object: player?.currentItem
|
|
249
|
+
)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
@objc private func playerDidFinishPlaying() {
|
|
253
|
+
onEnded?()
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// MARK: - Callbacks
|
|
257
|
+
|
|
258
|
+
@objc public var onReady: (() -> Void)?
|
|
259
|
+
@objc public var onPlay: (() -> Void)?
|
|
260
|
+
@objc public var onPause: (() -> Void)?
|
|
261
|
+
@objc public var onTimeUpdate: ((Double) -> Void)?
|
|
262
|
+
@objc public var onEnded: (() -> Void)?
|
|
263
|
+
@objc public var onError: ((Error) -> Void)?
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Step 2: Create Bridging Header (for Objective-C projects)
|
|
268
|
+
|
|
269
|
+
```objc
|
|
270
|
+
// UnifiedVideoPlayer-Bridging-Header.h
|
|
271
|
+
#import <UnifiedVideoPlayer/UnifiedVideoPlayer-Swift.h>
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Step 3: Use in Objective-C
|
|
275
|
+
|
|
276
|
+
```objc
|
|
277
|
+
// YourExistingViewController.m
|
|
278
|
+
#import "UnifiedVideoPlayer-Swift.h"
|
|
279
|
+
|
|
280
|
+
@interface YourExistingViewController ()
|
|
281
|
+
@property (nonatomic, strong) UnifiedVideoPlayer *videoPlayer;
|
|
282
|
+
@end
|
|
283
|
+
|
|
284
|
+
@implementation YourExistingViewController
|
|
285
|
+
|
|
286
|
+
- (void)addVideoPlayerToExistingView {
|
|
287
|
+
// Create player
|
|
288
|
+
self.videoPlayer = [[UnifiedVideoPlayer alloc] init];
|
|
289
|
+
|
|
290
|
+
// Initialize with your existing view
|
|
291
|
+
[self.videoPlayer initializeWithContainer:self.playerContainerView
|
|
292
|
+
configuration:nil];
|
|
293
|
+
|
|
294
|
+
// Load video
|
|
295
|
+
[self.videoPlayer loadWithUrl:@"https://example.com/video.mp4"];
|
|
296
|
+
|
|
297
|
+
// Set callbacks
|
|
298
|
+
self.videoPlayer.onReady = ^{
|
|
299
|
+
NSLog(@"Player ready");
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
// Play
|
|
303
|
+
[self.videoPlayer play];
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
@end
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
# Part 2: Android Integration (Existing Android App)
|
|
312
|
+
|
|
313
|
+
## Option A: AAR Library (Recommended)
|
|
314
|
+
|
|
315
|
+
### Step 1: Build AAR Library
|
|
316
|
+
|
|
317
|
+
```gradle
|
|
318
|
+
// unified-video-player/build.gradle
|
|
319
|
+
apply plugin: 'com.android.library'
|
|
320
|
+
|
|
321
|
+
android {
|
|
322
|
+
compileSdkVersion 33
|
|
323
|
+
|
|
324
|
+
defaultConfig {
|
|
325
|
+
minSdkVersion 21
|
|
326
|
+
targetSdkVersion 33
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
dependencies {
|
|
331
|
+
implementation 'com.google.android.exoplayer:exoplayer:2.18.5'
|
|
332
|
+
implementation 'com.google.android.exoplayer:exoplayer-hls:2.18.5'
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Generate AAR
|
|
336
|
+
./gradlew :unified-video-player:assembleRelease
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### Step 2: Add AAR to Your Existing App
|
|
340
|
+
|
|
341
|
+
```gradle
|
|
342
|
+
// Your app's build.gradle
|
|
343
|
+
dependencies {
|
|
344
|
+
// Your existing dependencies
|
|
345
|
+
implementation 'com.google.android.material:material:1.9.0'
|
|
346
|
+
|
|
347
|
+
// Add the AAR
|
|
348
|
+
implementation files('libs/unified-video-player.aar')
|
|
349
|
+
|
|
350
|
+
// Or from local module
|
|
351
|
+
implementation project(':unified-video-player')
|
|
352
|
+
}
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### Step 3: Use in Your Existing Activity/Fragment
|
|
356
|
+
|
|
357
|
+
```kotlin
|
|
358
|
+
// YourExistingActivity.kt
|
|
359
|
+
import com.unifiedvideo.player.UnifiedVideoPlayer
|
|
360
|
+
import com.unifiedvideo.player.PlayerConfiguration
|
|
361
|
+
|
|
362
|
+
class YourExistingActivity : AppCompatActivity() {
|
|
363
|
+
|
|
364
|
+
private lateinit var videoPlayer: UnifiedVideoPlayer
|
|
365
|
+
|
|
366
|
+
override fun onCreate(savedInstanceState: Bundle?) {
|
|
367
|
+
super.onCreate(savedInstanceState)
|
|
368
|
+
setContentView(R.layout.your_existing_layout)
|
|
369
|
+
|
|
370
|
+
// Add video player to your existing view
|
|
371
|
+
addVideoPlayer()
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
private fun addVideoPlayer() {
|
|
375
|
+
// Find your existing container view
|
|
376
|
+
val playerContainer = findViewById<FrameLayout>(R.id.player_container)
|
|
377
|
+
|
|
378
|
+
// Create and initialize player
|
|
379
|
+
videoPlayer = UnifiedVideoPlayer(this).apply {
|
|
380
|
+
initialize(
|
|
381
|
+
container = playerContainer,
|
|
382
|
+
configuration = PlayerConfiguration(
|
|
383
|
+
autoPlay = true,
|
|
384
|
+
controls = true,
|
|
385
|
+
muted = false
|
|
386
|
+
)
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
// Set event listeners
|
|
390
|
+
onReady = {
|
|
391
|
+
Log.d("Player", "Ready")
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
onError = { error ->
|
|
395
|
+
handleVideoError(error)
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Load video
|
|
399
|
+
load("https://example.com/video.m3u8")
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Integrate with your existing UI
|
|
404
|
+
fun onExistingPlayButtonClick() {
|
|
405
|
+
videoPlayer.play()
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
fun onExistingPauseButtonClick() {
|
|
409
|
+
videoPlayer.pause()
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
override fun onPause() {
|
|
413
|
+
super.onPause()
|
|
414
|
+
videoPlayer.pause()
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
override fun onDestroy() {
|
|
418
|
+
super.onDestroy()
|
|
419
|
+
videoPlayer.release()
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
## Option B: Gradle Dependency
|
|
425
|
+
|
|
426
|
+
### Step 1: Publish to Maven Repository
|
|
427
|
+
|
|
428
|
+
```gradle
|
|
429
|
+
// unified-video-player/build.gradle
|
|
430
|
+
apply plugin: 'maven-publish'
|
|
431
|
+
|
|
432
|
+
publishing {
|
|
433
|
+
publications {
|
|
434
|
+
release(MavenPublication) {
|
|
435
|
+
groupId = 'com.yourcompany'
|
|
436
|
+
artifactId = 'unified-video-player'
|
|
437
|
+
version = '1.0.0'
|
|
438
|
+
|
|
439
|
+
afterEvaluate {
|
|
440
|
+
from components.release
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
### Step 2: Add to Your App
|
|
448
|
+
|
|
449
|
+
```gradle
|
|
450
|
+
// Your app's build.gradle
|
|
451
|
+
dependencies {
|
|
452
|
+
implementation 'com.yourcompany:unified-video-player:1.0.0'
|
|
453
|
+
}
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
## Option C: Java Implementation (for Java-based apps)
|
|
457
|
+
|
|
458
|
+
```java
|
|
459
|
+
// YourExistingActivity.java
|
|
460
|
+
import com.unifiedvideo.player.UnifiedVideoPlayer;
|
|
461
|
+
import com.unifiedvideo.player.PlayerConfiguration;
|
|
462
|
+
import com.unifiedvideo.player.PlayerListener;
|
|
463
|
+
|
|
464
|
+
public class YourExistingActivity extends AppCompatActivity {
|
|
465
|
+
|
|
466
|
+
private UnifiedVideoPlayer videoPlayer;
|
|
467
|
+
|
|
468
|
+
@Override
|
|
469
|
+
protected void onCreate(Bundle savedInstanceState) {
|
|
470
|
+
super.onCreate(savedInstanceState);
|
|
471
|
+
setContentView(R.layout.your_existing_layout);
|
|
472
|
+
|
|
473
|
+
addVideoPlayer();
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
private void addVideoPlayer() {
|
|
477
|
+
// Find your existing container
|
|
478
|
+
FrameLayout playerContainer = findViewById(R.id.player_container);
|
|
479
|
+
|
|
480
|
+
// Create player
|
|
481
|
+
videoPlayer = new UnifiedVideoPlayer(this);
|
|
482
|
+
|
|
483
|
+
// Configure
|
|
484
|
+
PlayerConfiguration config = new PlayerConfiguration.Builder()
|
|
485
|
+
.setAutoPlay(true)
|
|
486
|
+
.setControls(true)
|
|
487
|
+
.setMuted(false)
|
|
488
|
+
.build();
|
|
489
|
+
|
|
490
|
+
// Initialize
|
|
491
|
+
videoPlayer.initialize(playerContainer, config);
|
|
492
|
+
|
|
493
|
+
// Set listener
|
|
494
|
+
videoPlayer.setPlayerListener(new PlayerListener() {
|
|
495
|
+
@Override
|
|
496
|
+
public void onReady() {
|
|
497
|
+
Log.d("Player", "Ready");
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
@Override
|
|
501
|
+
public void onError(Exception error) {
|
|
502
|
+
handleVideoError(error);
|
|
503
|
+
}
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
// Load video
|
|
507
|
+
videoPlayer.load("https://example.com/video.mp4");
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
@Override
|
|
511
|
+
protected void onPause() {
|
|
512
|
+
super.onPause();
|
|
513
|
+
if (videoPlayer != null) {
|
|
514
|
+
videoPlayer.pause();
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
@Override
|
|
519
|
+
protected void onDestroy() {
|
|
520
|
+
super.onDestroy();
|
|
521
|
+
if (videoPlayer != null) {
|
|
522
|
+
videoPlayer.release();
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
---
|
|
529
|
+
|
|
530
|
+
# Part 3: Complete SDK Implementation
|
|
531
|
+
|
|
532
|
+
## UnifiedVideoPlayer Android Library
|
|
533
|
+
|
|
534
|
+
```kotlin
|
|
535
|
+
// UnifiedVideoPlayer.kt - Complete implementation
|
|
536
|
+
package com.unifiedvideo.player
|
|
537
|
+
|
|
538
|
+
import android.content.Context
|
|
539
|
+
import android.net.Uri
|
|
540
|
+
import android.view.ViewGroup
|
|
541
|
+
import com.google.android.exoplayer2.*
|
|
542
|
+
import com.google.android.exoplayer2.source.MediaSource
|
|
543
|
+
import com.google.android.exoplayer2.source.ProgressiveMediaSource
|
|
544
|
+
import com.google.android.exoplayer2.source.hls.HlsMediaSource
|
|
545
|
+
import com.google.android.exoplayer2.ui.PlayerView
|
|
546
|
+
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
|
|
547
|
+
|
|
548
|
+
class UnifiedVideoPlayer(private val context: Context) {
|
|
549
|
+
|
|
550
|
+
private var exoPlayer: ExoPlayer? = null
|
|
551
|
+
private var playerView: PlayerView? = null
|
|
552
|
+
private var container: ViewGroup? = null
|
|
553
|
+
|
|
554
|
+
// Event callbacks
|
|
555
|
+
var onReady: (() -> Unit)? = null
|
|
556
|
+
var onPlay: (() -> Unit)? = null
|
|
557
|
+
var onPause: (() -> Unit)? = null
|
|
558
|
+
var onTimeUpdate: ((Long) -> Unit)? = null
|
|
559
|
+
var onError: ((Exception) -> Unit)? = null
|
|
560
|
+
var onEnded: (() -> Unit)? = null
|
|
561
|
+
|
|
562
|
+
fun initialize(container: ViewGroup, configuration: PlayerConfiguration? = null) {
|
|
563
|
+
this.container = container
|
|
564
|
+
|
|
565
|
+
// Create ExoPlayer
|
|
566
|
+
exoPlayer = ExoPlayer.Builder(context).build()
|
|
567
|
+
|
|
568
|
+
// Create PlayerView
|
|
569
|
+
playerView = PlayerView(context).apply {
|
|
570
|
+
player = exoPlayer
|
|
571
|
+
useController = configuration?.controls ?: true
|
|
572
|
+
layoutParams = ViewGroup.LayoutParams(
|
|
573
|
+
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
574
|
+
ViewGroup.LayoutParams.MATCH_PARENT
|
|
575
|
+
)
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// Add to container
|
|
579
|
+
container.addView(playerView)
|
|
580
|
+
|
|
581
|
+
// Setup listeners
|
|
582
|
+
setupListeners()
|
|
583
|
+
|
|
584
|
+
// Apply configuration
|
|
585
|
+
configuration?.let {
|
|
586
|
+
exoPlayer?.playWhenReady = it.autoPlay
|
|
587
|
+
exoPlayer?.volume = if (it.muted) 0f else 1f
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
fun load(url: String) {
|
|
592
|
+
val uri = Uri.parse(url)
|
|
593
|
+
val mediaSource = createMediaSource(uri)
|
|
594
|
+
|
|
595
|
+
exoPlayer?.apply {
|
|
596
|
+
setMediaSource(mediaSource)
|
|
597
|
+
prepare()
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
fun play() {
|
|
602
|
+
exoPlayer?.play()
|
|
603
|
+
onPlay?.invoke()
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
fun pause() {
|
|
607
|
+
exoPlayer?.pause()
|
|
608
|
+
onPause?.invoke()
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
fun seek(position: Long) {
|
|
612
|
+
exoPlayer?.seekTo(position)
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
fun setVolume(volume: Float) {
|
|
616
|
+
exoPlayer?.volume = volume.coerceIn(0f, 1f)
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
fun getCurrentPosition(): Long {
|
|
620
|
+
return exoPlayer?.currentPosition ?: 0
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
fun getDuration(): Long {
|
|
624
|
+
return exoPlayer?.duration ?: 0
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
fun release() {
|
|
628
|
+
exoPlayer?.release()
|
|
629
|
+
exoPlayer = null
|
|
630
|
+
playerView?.let {
|
|
631
|
+
container?.removeView(it)
|
|
632
|
+
}
|
|
633
|
+
playerView = null
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
private fun createMediaSource(uri: Uri): MediaSource {
|
|
637
|
+
val dataSourceFactory = DefaultDataSourceFactory(context, "UnifiedVideoPlayer")
|
|
638
|
+
|
|
639
|
+
return when {
|
|
640
|
+
uri.path?.contains(".m3u8") == true -> {
|
|
641
|
+
HlsMediaSource.Factory(dataSourceFactory)
|
|
642
|
+
.createMediaSource(MediaItem.fromUri(uri))
|
|
643
|
+
}
|
|
644
|
+
else -> {
|
|
645
|
+
ProgressiveMediaSource.Factory(dataSourceFactory)
|
|
646
|
+
.createMediaSource(MediaItem.fromUri(uri))
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
private fun setupListeners() {
|
|
652
|
+
exoPlayer?.addListener(object : Player.Listener {
|
|
653
|
+
override fun onPlaybackStateChanged(state: Int) {
|
|
654
|
+
when (state) {
|
|
655
|
+
Player.STATE_READY -> onReady?.invoke()
|
|
656
|
+
Player.STATE_ENDED -> onEnded?.invoke()
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
override fun onPlayerError(error: PlaybackException) {
|
|
661
|
+
onError?.invoke(error)
|
|
662
|
+
}
|
|
663
|
+
})
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
data class PlayerConfiguration(
|
|
668
|
+
val autoPlay: Boolean = false,
|
|
669
|
+
val controls: Boolean = true,
|
|
670
|
+
val muted: Boolean = false
|
|
671
|
+
)
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
---
|
|
675
|
+
|
|
676
|
+
# Part 4: Migration Guide
|
|
677
|
+
|
|
678
|
+
## For iOS Apps
|
|
679
|
+
|
|
680
|
+
### Step 1: Replace Existing Video Player
|
|
681
|
+
|
|
682
|
+
```swift
|
|
683
|
+
// Before (using AVPlayerViewController)
|
|
684
|
+
let player = AVPlayer(url: videoURL)
|
|
685
|
+
let playerVC = AVPlayerViewController()
|
|
686
|
+
playerVC.player = player
|
|
687
|
+
present(playerVC, animated: true)
|
|
688
|
+
|
|
689
|
+
// After (using UnifiedVideoPlayer)
|
|
690
|
+
let player = UnifiedVideoPlayer()
|
|
691
|
+
player.initialize(container: containerView)
|
|
692
|
+
player.load(url: videoURL.absoluteString)
|
|
693
|
+
player.play()
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
### Step 2: Update Event Handlers
|
|
697
|
+
|
|
698
|
+
```swift
|
|
699
|
+
// Before
|
|
700
|
+
NotificationCenter.default.addObserver(
|
|
701
|
+
self,
|
|
702
|
+
selector: #selector(playerDidFinish),
|
|
703
|
+
name: .AVPlayerItemDidPlayToEndTime,
|
|
704
|
+
object: nil
|
|
705
|
+
)
|
|
706
|
+
|
|
707
|
+
// After
|
|
708
|
+
player.onEnded = { [weak self] in
|
|
709
|
+
self?.handleVideoEnd()
|
|
710
|
+
}
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
## For Android Apps
|
|
714
|
+
|
|
715
|
+
### Step 1: Replace Existing Video Player
|
|
716
|
+
|
|
717
|
+
```kotlin
|
|
718
|
+
// Before (using VideoView)
|
|
719
|
+
videoView.setVideoURI(videoUri)
|
|
720
|
+
videoView.start()
|
|
721
|
+
|
|
722
|
+
// After (using UnifiedVideoPlayer)
|
|
723
|
+
videoPlayer.initialize(container)
|
|
724
|
+
videoPlayer.load(videoUri.toString())
|
|
725
|
+
videoPlayer.play()
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
### Step 2: Update Event Handlers
|
|
729
|
+
|
|
730
|
+
```kotlin
|
|
731
|
+
// Before
|
|
732
|
+
videoView.setOnCompletionListener {
|
|
733
|
+
handleVideoComplete()
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
// After
|
|
737
|
+
videoPlayer.onEnded = {
|
|
738
|
+
handleVideoComplete()
|
|
739
|
+
}
|
|
740
|
+
```
|
|
741
|
+
|
|
742
|
+
---
|
|
743
|
+
|
|
744
|
+
# Part 5: Testing Integration
|
|
745
|
+
|
|
746
|
+
## iOS Testing
|
|
747
|
+
|
|
748
|
+
```swift
|
|
749
|
+
// UnifiedVideoPlayerTests.swift
|
|
750
|
+
import XCTest
|
|
751
|
+
@testable import UnifiedVideoPlayer
|
|
752
|
+
|
|
753
|
+
class UnifiedVideoPlayerTests: XCTestCase {
|
|
754
|
+
|
|
755
|
+
var player: UnifiedVideoPlayer!
|
|
756
|
+
|
|
757
|
+
override func setUp() {
|
|
758
|
+
super.setUp()
|
|
759
|
+
player = UnifiedVideoPlayer()
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
func testPlayerInitialization() {
|
|
763
|
+
let container = UIView()
|
|
764
|
+
player.initialize(container: container)
|
|
765
|
+
|
|
766
|
+
XCTAssertNotNil(player)
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
func testVideoLoading() {
|
|
770
|
+
let expectation = self.expectation(description: "Video loads")
|
|
771
|
+
|
|
772
|
+
player.onReady = {
|
|
773
|
+
expectation.fulfill()
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
player.load(url: "https://example.com/test.mp4")
|
|
777
|
+
|
|
778
|
+
waitForExpectations(timeout: 10)
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
```
|
|
782
|
+
|
|
783
|
+
## Android Testing
|
|
784
|
+
|
|
785
|
+
```kotlin
|
|
786
|
+
// UnifiedVideoPlayerTest.kt
|
|
787
|
+
import androidx.test.ext.junit.runners.AndroidJUnit4
|
|
788
|
+
import org.junit.Test
|
|
789
|
+
import org.junit.runner.RunWith
|
|
790
|
+
import org.junit.Assert.*
|
|
791
|
+
|
|
792
|
+
@RunWith(AndroidJUnit4::class)
|
|
793
|
+
class UnifiedVideoPlayerTest {
|
|
794
|
+
|
|
795
|
+
@Test
|
|
796
|
+
fun testPlayerInitialization() {
|
|
797
|
+
val context = InstrumentationRegistry.getInstrumentation().targetContext
|
|
798
|
+
val player = UnifiedVideoPlayer(context)
|
|
799
|
+
|
|
800
|
+
assertNotNull(player)
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
@Test
|
|
804
|
+
fun testVideoLoading() {
|
|
805
|
+
val context = InstrumentationRegistry.getInstrumentation().targetContext
|
|
806
|
+
val player = UnifiedVideoPlayer(context)
|
|
807
|
+
val container = FrameLayout(context)
|
|
808
|
+
|
|
809
|
+
player.initialize(container)
|
|
810
|
+
player.load("https://example.com/test.mp4")
|
|
811
|
+
|
|
812
|
+
assertTrue(player.getDuration() >= 0)
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
```
|
|
816
|
+
|
|
817
|
+
---
|
|
818
|
+
|
|
819
|
+
# Part 6: Distribution
|
|
820
|
+
|
|
821
|
+
## iOS Distribution
|
|
822
|
+
|
|
823
|
+
### Via CocoaPods
|
|
824
|
+
```bash
|
|
825
|
+
# Tag and push
|
|
826
|
+
git tag '1.0.0'
|
|
827
|
+
git push --tags
|
|
828
|
+
|
|
829
|
+
# Publish pod
|
|
830
|
+
pod trunk push UnifiedVideoPlayer.podspec
|
|
831
|
+
```
|
|
832
|
+
|
|
833
|
+
### Via Swift Package Manager
|
|
834
|
+
```swift
|
|
835
|
+
// Package.swift
|
|
836
|
+
let package = Package(
|
|
837
|
+
name: "UnifiedVideoPlayer",
|
|
838
|
+
platforms: [.iOS(.v13)],
|
|
839
|
+
products: [
|
|
840
|
+
.library(name: "UnifiedVideoPlayer", targets: ["UnifiedVideoPlayer"])
|
|
841
|
+
],
|
|
842
|
+
targets: [
|
|
843
|
+
.target(name: "UnifiedVideoPlayer")
|
|
844
|
+
]
|
|
845
|
+
)
|
|
846
|
+
```
|
|
847
|
+
|
|
848
|
+
## Android Distribution
|
|
849
|
+
|
|
850
|
+
### Via Maven Central
|
|
851
|
+
```gradle
|
|
852
|
+
// Publish to Maven Central
|
|
853
|
+
./gradlew publish
|
|
854
|
+
```
|
|
855
|
+
|
|
856
|
+
### Via JitPack
|
|
857
|
+
```gradle
|
|
858
|
+
// Add to root build.gradle
|
|
859
|
+
allprojects {
|
|
860
|
+
repositories {
|
|
861
|
+
maven { url 'https://jitpack.io' }
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// Add dependency
|
|
866
|
+
dependencies {
|
|
867
|
+
implementation 'com.github.yourcompany:unified-video-android:1.0.0'
|
|
868
|
+
}
|
|
869
|
+
```
|
|
870
|
+
|
|
871
|
+
---
|
|
872
|
+
|
|
873
|
+
# 🎯 Quick Integration Checklist
|
|
874
|
+
|
|
875
|
+
## iOS
|
|
876
|
+
- [ ] Choose integration method (SPM/CocoaPods/Manual)
|
|
877
|
+
- [ ] Add framework to project
|
|
878
|
+
- [ ] Import in your existing ViewControllers
|
|
879
|
+
- [ ] Replace old video player code
|
|
880
|
+
- [ ] Test on device and simulator
|
|
881
|
+
- [ ] Update App Store description if needed
|
|
882
|
+
|
|
883
|
+
## Android
|
|
884
|
+
- [ ] Choose integration method (AAR/Gradle/Manual)
|
|
885
|
+
- [ ] Add library to project
|
|
886
|
+
- [ ] Import in your existing Activities/Fragments
|
|
887
|
+
- [ ] Replace old video player code
|
|
888
|
+
- [ ] Test on different Android versions
|
|
889
|
+
- [ ] Update Play Store description if needed
|
|
890
|
+
|
|
891
|
+
---
|
|
892
|
+
|
|
893
|
+
# 📚 Support & Documentation
|
|
894
|
+
|
|
895
|
+
- **iOS Documentation**: `/docs/ios/README.md`
|
|
896
|
+
- **Android Documentation**: `/docs/android/README.md`
|
|
897
|
+
- **API Reference**: `/docs/api/README.md`
|
|
898
|
+
- **Migration Guide**: `/docs/migration/README.md`
|
|
899
|
+
- **Sample Apps**: `/examples/`
|
|
900
|
+
|
|
901
|
+
---
|
|
902
|
+
|
|
903
|
+
This approach lets you keep your existing app structure and just add the video player as a component where needed!
|