unified-video-framework 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/.github/workflows/ci.yml +253 -0
  2. package/ANDROID_TV_IMPLEMENTATION.md +313 -0
  3. package/COMPLETION_STATUS.md +165 -0
  4. package/CONTRIBUTING.md +376 -0
  5. package/FINAL_STATUS_REPORT.md +170 -0
  6. package/FRAMEWORK_REVIEW.md +247 -0
  7. package/IMPROVEMENTS_SUMMARY.md +168 -0
  8. package/LICENSE +21 -0
  9. package/NATIVE_APP_INTEGRATION_GUIDE.md +903 -0
  10. package/PAYWALL_RENTAL_FLOW.md +499 -0
  11. package/PLATFORM_SETUP_GUIDE.md +1636 -0
  12. package/README.md +315 -0
  13. package/RUN_LOCALLY.md +151 -0
  14. package/apps/demo/cast-sender-min.html +173 -0
  15. package/apps/demo/custom-player.html +883 -0
  16. package/apps/demo/demo.html +990 -0
  17. package/apps/demo/enhanced-player.html +3556 -0
  18. package/apps/demo/index.html +159 -0
  19. package/apps/rental-api/.env.example +24 -0
  20. package/apps/rental-api/README.md +23 -0
  21. package/apps/rental-api/migrations/001_init.sql +35 -0
  22. package/apps/rental-api/migrations/002_videos.sql +10 -0
  23. package/apps/rental-api/migrations/003_add_gateway_subref.sql +4 -0
  24. package/apps/rental-api/migrations/004_update_gateways.sql +4 -0
  25. package/apps/rental-api/migrations/005_seed_demo_video.sql +5 -0
  26. package/apps/rental-api/package-lock.json +2045 -0
  27. package/apps/rental-api/package.json +33 -0
  28. package/apps/rental-api/scripts/run-migration.js +42 -0
  29. package/apps/rental-api/scripts/update-video-currency.js +21 -0
  30. package/apps/rental-api/scripts/update-video-price.js +19 -0
  31. package/apps/rental-api/src/config.ts +14 -0
  32. package/apps/rental-api/src/db.ts +10 -0
  33. package/apps/rental-api/src/routes/cashfree.ts +167 -0
  34. package/apps/rental-api/src/routes/pesapal.ts +92 -0
  35. package/apps/rental-api/src/routes/rentals.ts +242 -0
  36. package/apps/rental-api/src/routes/webhooks.ts +73 -0
  37. package/apps/rental-api/src/server.ts +41 -0
  38. package/apps/rental-api/src/services/entitlements.ts +45 -0
  39. package/apps/rental-api/src/services/payments.ts +22 -0
  40. package/apps/rental-api/tsconfig.json +17 -0
  41. package/check-urls.ps1 +74 -0
  42. package/comparison-report.md +181 -0
  43. package/docs/PAYWALL.md +95 -0
  44. package/docs/PLAYER_UI_VISIBILITY.md +431 -0
  45. package/docs/README.md +7 -0
  46. package/docs/SYSTEM_ARCHITECTURE.md +612 -0
  47. package/docs/VDOCIPHER_CLONE_REQUIREMENTS.md +403 -0
  48. package/examples/android/JavaSampleApp/MainActivity.java +641 -0
  49. package/examples/android/JavaSampleApp/activity_main.xml +226 -0
  50. package/examples/android/SampleApp/MainActivity.kt +430 -0
  51. package/examples/ios/SampleApp/ViewController.swift +337 -0
  52. package/examples/ios/SwiftUISampleApp/ContentView.swift +304 -0
  53. package/iOS_IMPLEMENTATION_OPTIONS.md +470 -0
  54. package/ios/UnifiedVideoPlayer/UnifiedVideoPlayer.podspec +33 -0
  55. package/jest.config.js +33 -0
  56. package/jitpack.yml +5 -0
  57. package/lerna.json +35 -0
  58. package/package.json +69 -0
  59. package/packages/PLATFORM_STATUS.md +163 -0
  60. package/packages/android/build.gradle +135 -0
  61. package/packages/android/src/main/AndroidManifest.xml +36 -0
  62. package/packages/android/src/main/java/com/unifiedvideo/player/PlayerConfiguration.java +221 -0
  63. package/packages/android/src/main/java/com/unifiedvideo/player/UnifiedVideoPlayer.java +1037 -0
  64. package/packages/android/src/main/java/com/unifiedvideo/player/UnifiedVideoPlayer.kt +707 -0
  65. package/packages/android/src/main/java/com/unifiedvideo/player/analytics/AnalyticsProvider.java +9 -0
  66. package/packages/android/src/main/java/com/unifiedvideo/player/cast/CastManager.java +141 -0
  67. package/packages/android/src/main/java/com/unifiedvideo/player/cast/CastOptionsProvider.java +29 -0
  68. package/packages/android/src/main/java/com/unifiedvideo/player/overlay/WatermarkOverlayView.java +88 -0
  69. package/packages/android/src/main/java/com/unifiedvideo/player/pip/PipActionReceiver.java +33 -0
  70. package/packages/android/src/main/java/com/unifiedvideo/player/services/PlaybackService.java +110 -0
  71. package/packages/android/src/main/java/com/unifiedvideo/player/services/PlayerHolder.java +19 -0
  72. package/packages/core/package.json +34 -0
  73. package/packages/core/src/BasePlayer.ts +250 -0
  74. package/packages/core/src/VideoPlayer.ts +237 -0
  75. package/packages/core/src/VideoPlayerFactory.ts +145 -0
  76. package/packages/core/src/index.ts +20 -0
  77. package/packages/core/src/interfaces/IVideoPlayer.ts +184 -0
  78. package/packages/core/src/interfaces.ts +240 -0
  79. package/packages/core/src/utils/EventEmitter.ts +66 -0
  80. package/packages/core/src/utils/PlatformDetector.ts +300 -0
  81. package/packages/core/tsconfig.json +20 -0
  82. package/packages/enact/package.json +51 -0
  83. package/packages/enact/src/VideoPlayer.js +365 -0
  84. package/packages/enact/src/adapters/TizenAdapter.js +354 -0
  85. package/packages/enact/src/index.js +82 -0
  86. package/packages/ios/BUILD_INSTRUCTIONS.md +108 -0
  87. package/packages/ios/FIX_EMBED_ISSUE.md +142 -0
  88. package/packages/ios/GETTING_STARTED.md +100 -0
  89. package/packages/ios/Package.swift +35 -0
  90. package/packages/ios/README.md +84 -0
  91. package/packages/ios/Sources/UnifiedVideoPlayer/Analytics/AnalyticsEmitter.swift +26 -0
  92. package/packages/ios/Sources/UnifiedVideoPlayer/DRM/FairPlayDRMManager.swift +102 -0
  93. package/packages/ios/Sources/UnifiedVideoPlayer/Info.plist +24 -0
  94. package/packages/ios/Sources/UnifiedVideoPlayer/Remote/RemoteCommandCenter.swift +109 -0
  95. package/packages/ios/Sources/UnifiedVideoPlayer/UnifiedVideoPlayer.swift +811 -0
  96. package/packages/ios/Sources/UnifiedVideoPlayer/UnifiedVideoPlayerView.swift +640 -0
  97. package/packages/ios/Sources/UnifiedVideoPlayer/Utilities/Color+Hex.swift +36 -0
  98. package/packages/ios/UnifiedVideoPlayer.podspec +27 -0
  99. package/packages/ios/UnifiedVideoPlayer.xcodeproj/project.pbxproj +385 -0
  100. package/packages/ios/build_framework.sh +55 -0
  101. package/packages/react-native/android/src/main/java/com/unifiedvideo/UnifiedVideoPlayerModule.kt +482 -0
  102. package/packages/react-native/ios/UnifiedVideoPlayer.swift +436 -0
  103. package/packages/react-native/package.json +51 -0
  104. package/packages/react-native/src/ReactNativePlayer.tsx +423 -0
  105. package/packages/react-native/src/VideoPlayer.tsx +224 -0
  106. package/packages/react-native/src/index.ts +28 -0
  107. package/packages/react-native/src/utils/EventEmitter.ts +66 -0
  108. package/packages/react-native/tsconfig.json +31 -0
  109. package/packages/roku/components/UnifiedVideoPlayer.brs +400 -0
  110. package/packages/roku/package.json +44 -0
  111. package/packages/roku/source/VideoPlayer.brs +231 -0
  112. package/packages/roku/source/main.brs +28 -0
  113. package/packages/web/GETTING_STARTED.md +292 -0
  114. package/packages/web/jest.config.js +28 -0
  115. package/packages/web/jest.setup.ts +110 -0
  116. package/packages/web/package.json +50 -0
  117. package/packages/web/src/SecureVideoPlayer.ts +1164 -0
  118. package/packages/web/src/WebPlayer.ts +3110 -0
  119. package/packages/web/src/__tests__/WebPlayer.test.ts +314 -0
  120. package/packages/web/src/index.ts +14 -0
  121. package/packages/web/src/paywall/PaywallController.ts +215 -0
  122. package/packages/web/src/react/WebPlayerView.tsx +177 -0
  123. package/packages/web/tsconfig.json +23 -0
  124. package/packages/web/webpack.config.js +45 -0
  125. package/server.js +131 -0
  126. package/server.py +84 -0
  127. package/test-urls.ps1 +97 -0
  128. package/test-video-urls.ps1 +87 -0
  129. package/tsconfig.json +39 -0
@@ -0,0 +1,82 @@
1
+ /**
2
+ * @unified-video/enact
3
+ * Enact TV implementation for Samsung Tizen and LG webOS
4
+ */
5
+
6
+ // Export main video player
7
+ export { default as VideoPlayer } from './VideoPlayer';
8
+
9
+ // Export TV-specific adapters
10
+ export { default as TizenAdapter } from './adapters/TizenAdapter';
11
+
12
+ // Export TV utilities
13
+ export const TVUtils = {
14
+ /**
15
+ * Check if running on Samsung Tizen TV
16
+ */
17
+ isTizen: () => {
18
+ return typeof window !== 'undefined' && window.tizen !== undefined;
19
+ },
20
+
21
+ /**
22
+ * Check if running on LG webOS TV
23
+ */
24
+ isWebOS: () => {
25
+ return typeof window !== 'undefined' && window.webOS !== undefined;
26
+ },
27
+
28
+ /**
29
+ * Get TV platform info
30
+ */
31
+ getPlatformInfo: () => {
32
+ if (TVUtils.isTizen()) {
33
+ return {
34
+ platform: 'tizen',
35
+ version: window.tizen?.systeminfo?.getCapability('http://tizen.org/feature/platform.version')
36
+ };
37
+ }
38
+ if (TVUtils.isWebOS()) {
39
+ return {
40
+ platform: 'webos',
41
+ version: window.webOS?.platform?.tv?.version
42
+ };
43
+ }
44
+ return {
45
+ platform: 'unknown',
46
+ version: null
47
+ };
48
+ },
49
+
50
+ /**
51
+ * Register TV remote key handlers
52
+ */
53
+ registerRemoteKeys: () => {
54
+ if (TVUtils.isTizen()) {
55
+ // Register Tizen remote control keys
56
+ if (window.tizen && window.tizen.tvinputdevice) {
57
+ const keys = [
58
+ 'MediaPlayPause',
59
+ 'MediaPlay',
60
+ 'MediaPause',
61
+ 'MediaStop',
62
+ 'MediaFastForward',
63
+ 'MediaRewind'
64
+ ];
65
+ keys.forEach(key => {
66
+ try {
67
+ window.tizen.tvinputdevice.registerKey(key);
68
+ } catch (e) {
69
+ console.warn(`Failed to register key: ${key}`);
70
+ }
71
+ });
72
+ }
73
+ }
74
+ // webOS handles keys automatically
75
+ }
76
+ };
77
+
78
+ // Export version
79
+ export const VERSION = '1.0.0';
80
+
81
+ // Export platform identifier
82
+ export const PLATFORM = 'tv';
@@ -0,0 +1,108 @@
1
+ # iOS Build Instructions - Fixing Architecture Issues
2
+
3
+ ## Problem
4
+ The error "UnifiedVideoPlayer.swiftmodule is not built for arm64" occurs when the framework isn't compiled for the correct architecture.
5
+
6
+ ## Solution
7
+
8
+ ### Option 1: Using Swift Package Manager (Recommended)
9
+
10
+ 1. In Xcode, go to **File > Add Package Dependencies**
11
+ 2. Click "Add Local..." and navigate to `/packages/ios/`
12
+ 3. Select the package and add it to your target
13
+ 4. Swift Package Manager will automatically build for the correct architecture
14
+
15
+ ### Option 2: Build Universal Framework
16
+
17
+ 1. Open Terminal and navigate to `/packages/ios/`
18
+ 2. Run the build script:
19
+ ```bash
20
+ chmod +x build_framework.sh
21
+ ./build_framework.sh
22
+ ```
23
+ 3. This will create a universal XCFramework in `Output/UnifiedVideoPlayer.xcframework`
24
+ 4. Drag the `.xcframework` into your Xcode project
25
+
26
+ ### Option 3: Direct Xcode Project Build
27
+
28
+ 1. Open `/packages/ios/UnifiedVideoPlayer.xcodeproj` in Xcode
29
+ 2. Select your target device/simulator from the scheme selector
30
+ 3. Go to **Product > Build** (⌘B)
31
+ 4. The framework will be built for the selected architecture
32
+
33
+ ### Option 4: Manual Architecture Configuration
34
+
35
+ If you're still experiencing issues:
36
+
37
+ 1. In your app's Xcode project, select your target
38
+ 2. Go to **Build Settings**
39
+ 3. Search for "Architectures"
40
+ 4. Ensure these settings:
41
+ - **Architectures**: Standard Architectures (arm64, arm64e)
42
+ - **Valid Architectures**: arm64 arm64e x86_64
43
+ - **Build Active Architecture Only**:
44
+ - Debug: Yes (for faster builds)
45
+ - Release: No (for distribution)
46
+ - **Excluded Architectures**: Leave empty or remove any entries
47
+
48
+ ### Architecture Reference
49
+
50
+ - **arm64**: Physical iOS devices (iPhone 6s and later)
51
+ - **arm64e**: Newer iOS devices (iPhone XS and later)
52
+ - **x86_64**: Intel-based Mac simulators
53
+ - **arm64** (simulator): Apple Silicon Mac simulators
54
+
55
+ ### Troubleshooting
56
+
57
+ 1. **Clean Build Folder**: Product > Clean Build Folder (⇧⌘K)
58
+ 2. **Delete Derived Data**:
59
+ - Xcode > Settings > Locations
60
+ - Click arrow next to Derived Data path
61
+ - Delete the folder for your project
62
+ 3. **Reset Package Caches**: File > Packages > Reset Package Caches
63
+ 4. **Check Deployment Target**: Ensure iOS deployment target is 13.0 or higher
64
+
65
+ ### Using in Your Project
66
+
67
+ ```swift
68
+ import UnifiedVideoPlayer
69
+
70
+ struct ContentView: View {
71
+ var body: some View {
72
+ UnifiedVideoPlayerView(url: URL(string: "https://example.com/video.mp4")!)
73
+ }
74
+ }
75
+ ```
76
+
77
+ ## Build from Command Line
78
+
79
+ For CI/CD or command-line builds:
80
+
81
+ ```bash
82
+ # Build for simulator
83
+ xcodebuild -scheme UnifiedVideoPlayer \
84
+ -sdk iphonesimulator \
85
+ -configuration Release \
86
+ -arch x86_64 \
87
+ -arch arm64 \
88
+ build
89
+
90
+ # Build for device
91
+ xcodebuild -scheme UnifiedVideoPlayer \
92
+ -sdk iphoneos \
93
+ -configuration Release \
94
+ -arch arm64 \
95
+ build
96
+
97
+ # Create XCFramework (universal)
98
+ xcodebuild -create-xcframework \
99
+ -framework path/to/device.framework \
100
+ -framework path/to/simulator.framework \
101
+ -output UnifiedVideoPlayer.xcframework
102
+ ```
103
+
104
+ ## Notes
105
+
106
+ - Always use `.xcframework` for distributing binary frameworks as it supports multiple architectures
107
+ - When archiving for App Store, ensure "Build Active Architecture Only" is set to NO
108
+ - For development, you can use "Build Active Architecture Only = YES" for faster builds
@@ -0,0 +1,142 @@
1
+ # Fix for "Library not loaded" Runtime Error
2
+
3
+ ## Problem
4
+ The error `Library not loaded: @rpath/UnifiedVideoPlayer.framework/UnifiedVideoPlayer` occurs when the framework is linked but not embedded in the app bundle.
5
+
6
+ ## Solution Steps
7
+
8
+ ### Step 1: Check Framework Embedding in Xcode
9
+
10
+ 1. **Select your app target** in Xcode
11
+ 2. Go to **General** tab
12
+ 3. Scroll to **Frameworks, Libraries, and Embedded Content**
13
+ 4. Find `UnifiedVideoPlayer` in the list
14
+ 5. **IMPORTANT**: Change the "Embed" setting from "Do Not Embed" to **"Embed & Sign"**
15
+
16
+ ![Embed Setting](https://developer.apple.com/library/archive/technotes/tn2435/Art/tn2435_embed.png)
17
+
18
+ ### Step 2: Verify Build Phases
19
+
20
+ 1. Select your app target
21
+ 2. Go to **Build Phases** tab
22
+ 3. Expand **Embed Frameworks** section (create if it doesn't exist)
23
+ 4. Ensure `UnifiedVideoPlayer.framework` is listed
24
+ 5. Check "Code Sign On Copy" checkbox
25
+
26
+ If the framework is missing from Embed Frameworks:
27
+ - Click the "+" button
28
+ - Add `UnifiedVideoPlayer.framework`
29
+ - Enable "Code Sign On Copy"
30
+
31
+ ### Step 3: Clean and Rebuild
32
+
33
+ ```bash
34
+ # In Xcode
35
+ 1. Product > Clean Build Folder (⇧⌘K)
36
+ 2. Close Xcode
37
+ 3. Delete DerivedData:
38
+ rm -rf ~/Library/Developer/Xcode/DerivedData/Flicknexs-*
39
+ 4. Reopen Xcode
40
+ 5. Product > Build (⌘B)
41
+ ```
42
+
43
+ ### Step 4: Alternative - Manual Framework Setup
44
+
45
+ If Swift Package Manager continues to have issues, use the pre-built framework:
46
+
47
+ 1. Remove the Swift Package:
48
+ - Select your project in navigator
49
+ - Select project (not target) in editor
50
+ - Go to "Package Dependencies" tab
51
+ - Select UnifiedVideoPlayer and click "-" to remove
52
+
53
+ 2. Build the framework manually:
54
+ ```bash
55
+ cd packages/ios
56
+ xcodebuild -scheme UnifiedVideoPlayer \
57
+ -configuration Release \
58
+ -derivedDataPath build \
59
+ -arch arm64 \
60
+ -sdk iphoneos \
61
+ BUILD_LIBRARY_FOR_DISTRIBUTION=YES
62
+ ```
63
+
64
+ 3. Add the built framework:
65
+ - Drag `build/Build/Products/Release-iphoneos/UnifiedVideoPlayer.framework` to your project
66
+ - Select "Copy items if needed"
67
+ - Add to your app target
68
+ - **Set to "Embed & Sign"**
69
+
70
+ ### Step 5: Verify Runtime Search Paths
71
+
72
+ 1. Select your app target
73
+ 2. Go to **Build Settings**
74
+ 3. Search for "Runpath Search Paths"
75
+ 4. Ensure it includes:
76
+ ```
77
+ @executable_path/Frameworks
78
+ @loader_path/Frameworks
79
+ ```
80
+
81
+ ### Step 6: Check Code Signing
82
+
83
+ 1. Select your app target
84
+ 2. Go to **Signing & Capabilities**
85
+ 3. Ensure "Automatically manage signing" is enabled
86
+ 4. Or manually select your development team and provisioning profile
87
+
88
+ ## Quick Checklist
89
+
90
+ - [ ] Framework is set to "Embed & Sign" in General > Frameworks, Libraries, and Embedded Content
91
+ - [ ] Framework appears in Build Phases > Embed Frameworks
92
+ - [ ] "Code Sign On Copy" is checked
93
+ - [ ] Runtime Search Paths includes @executable_path/Frameworks
94
+ - [ ] Clean Build Folder performed
95
+ - [ ] DerivedData deleted
96
+ - [ ] Code signing configured properly
97
+
98
+ ## If Still Not Working
99
+
100
+ Try creating a simple test to verify the framework loads:
101
+
102
+ ```swift
103
+ // In AppDelegate.swift or App.swift
104
+ import UnifiedVideoPlayer
105
+
106
+ func testFrameworkLoad() {
107
+ print("UnifiedVideoPlayer loaded successfully")
108
+ let player = UnifiedVideoPlayer()
109
+ print("Player created: \(player)")
110
+ }
111
+
112
+ // Call this in application:didFinishLaunchingWithOptions
113
+ testFrameworkLoad()
114
+ ```
115
+
116
+ ## Common Causes
117
+
118
+ 1. **Wrong Embed Setting**: Most common - framework not set to "Embed & Sign"
119
+ 2. **Missing from Embed Frameworks**: Framework linked but not copied to app bundle
120
+ 3. **Architecture Mismatch**: Framework not built for device architecture
121
+ 4. **Code Signing Issues**: Framework not properly signed
122
+ 5. **Corrupt DerivedData**: Old build artifacts causing issues
123
+
124
+ ## Terminal Commands for Debugging
125
+
126
+ ```bash
127
+ # Check if framework is in the app bundle (after building)
128
+ ls -la ~/Library/Developer/Xcode/DerivedData/Flicknexs-*/Build/Products/Debug-iphoneos/Flicknexs.app/Frameworks/
129
+
130
+ # Check framework architectures
131
+ lipo -info ~/Library/Developer/Xcode/DerivedData/Flicknexs-*/Build/Products/Debug-iphoneos/PackageFrameworks/UnifiedVideoPlayer.framework/UnifiedVideoPlayer
132
+
133
+ # Verify code signing
134
+ codesign -dv ~/Library/Developer/Xcode/DerivedData/Flicknexs-*/Build/Products/Debug-iphoneos/Flicknexs.app/Frameworks/UnifiedVideoPlayer.framework
135
+ ```
136
+
137
+ ## Contact Support
138
+
139
+ If none of these solutions work, the issue might be specific to your project configuration. Check:
140
+ - Your minimum deployment target (should be iOS 13.0+)
141
+ - Any custom build scripts that might interfere
142
+ - Other third-party frameworks that might conflict
@@ -0,0 +1,100 @@
1
+ # Getting Started with UnifiedVideoPlayer (iOS)
2
+
3
+ This guide shows how to install, configure dynamic theme color, and play a video using the iOS SDK under `packages/ios`.
4
+
5
+ ## 1) Install the SDK
6
+
7
+ - CocoaPods (public repo suggested)
8
+ - Ensure `UnifiedVideoPlayer.podspec` points to your Git repo + tag.
9
+ - Podfile:
10
+ ```ruby
11
+ platform :ios, '13.0'
12
+ use_frameworks!
13
+
14
+ target 'YourApp' do
15
+ pod 'UnifiedVideoPlayer', :git => 'https://github.com/yourcompany/unified-video-ios.git', :tag => '1.0.0'
16
+ end
17
+ ```
18
+ - Run `pod install`
19
+
20
+ - Swift Package Manager
21
+ - In Xcode: File > Add Packages… > Add Local… and choose `packages/ios` or use the hosted repo URL.
22
+
23
+ ## 2) Enable Capabilities (recommended)
24
+ - Background Modes: enable "Audio, AirPlay, and Picture in Picture"
25
+ - If your DRM or CDN endpoints require it, configure ATS exceptions in your app's Info.plist.
26
+
27
+ ## 3) Configure Dynamic Color and Video URL
28
+
29
+ Dynamic color is set via `PlayerConfiguration.themeColorHex` (supports `#RRGGBB` or `#AARRGGBB`).
30
+ The video URL is set on `MediaSource(url:)` or by using the SwiftUI helper with a URL.
31
+
32
+ ### UIKit example
33
+ ```swift
34
+ import UnifiedVideoPlayer
35
+
36
+ let player = UnifiedVideoPlayer()
37
+ let config = PlayerConfiguration()
38
+ config.autoPlay = true
39
+ config.themeColorHex = "#FF5722" // dynamic brand color
40
+
41
+ player.initialize(container: playerContainerView, configuration: config)
42
+
43
+ let source = MediaSource(url: "https://example.com/stream.m3u8")
44
+ source.metadata = ["title": "My Demo Video"]
45
+ // Optional DRM
46
+ // let drm = DRMConfiguration(type: "fairplay", licenseUrl: "https://license.example.com/fps")
47
+ // drm.certificateUrl = "https://license.example.com/cert"
48
+ // drm.headers = ["X-Tenant-ID": "default"]
49
+ // source.drm = drm
50
+
51
+ player.load(source: source)
52
+ ```
53
+
54
+ ### SwiftUI example
55
+ ```swift
56
+ import UnifiedVideoPlayer
57
+
58
+ let cfg = PlayerConfiguration()
59
+ cfg.autoPlay = true
60
+ cfg.themeColorHex = "#4CAF50"
61
+
62
+ // In your View body:
63
+ CustomUnifiedVideoPlayer(
64
+ url: "https://example.com/stream.m3u8",
65
+ configuration: cfg
66
+ )
67
+ ```
68
+
69
+ What does the theme color affect?
70
+ - UIKit: tint color of the default play/pause control the SDK overlays on top of the video.
71
+ - SwiftUI: tint/accent color for control icons and sliders.
72
+
73
+ ## 4) Subtitles, AirPlay, PiP (optional)
74
+ - Subtitles: provide `source.subtitles = [SubtitleTrack(...)]` (full parsing depends on your format).
75
+ - AirPlay: use `let airPlayView = player.makeAirPlayPickerView()` and add to your UI.
76
+ - PiP: call `player.startPictureInPicture()` and `player.stopPictureInPicture()`.
77
+
78
+ ## 5) FairPlay DRM (optional)
79
+ ```swift
80
+ let drm = DRMConfiguration(type: "fairplay", licenseUrl: "https://license.example.com/fps")
81
+ drm.certificateUrl = "https://license.example.com/cert"
82
+ drm.headers = ["X-Tenant-ID": "default"]
83
+ source.drm = drm
84
+ player.load(source: source)
85
+ ```
86
+
87
+ ## 6) Lock Screen Controls & Background Audio
88
+ - Enabled automatically; SDK configures AVAudioSession and Remote Command Center.
89
+ - Provide `source.metadata["title"]` to display track title on the lock screen.
90
+
91
+ ## 7) Troubleshooting
92
+ - If you see "not built for arm64": follow `BUILD_INSTRUCTIONS.md` in `packages/ios`.
93
+ - If you get "Library not loaded": follow `FIX_EMBED_ISSUE.md` in `packages/ios`.
94
+
95
+ ## 8) Publishing to CocoaPods
96
+ - Ensure `UnifiedVideoPlayer.podspec`:
97
+ - `s.source` points to a public Git repo URL
98
+ - `s.version` matches your Git tag
99
+ - Run `pod lib lint` and `pod trunk push` as appropriate.
100
+
@@ -0,0 +1,35 @@
1
+ // swift-tools-version:5.5
2
+ import PackageDescription
3
+
4
+ let package = Package(
5
+ name: "UnifiedVideoPlayer",
6
+ platforms: [
7
+ .iOS(.v13),
8
+ .tvOS(.v13),
9
+ .macCatalyst(.v13)
10
+ ],
11
+ products: [
12
+ .library(
13
+ name: "UnifiedVideoPlayer",
14
+ type: .dynamic,
15
+ targets: ["UnifiedVideoPlayer"]
16
+ )
17
+ ],
18
+ dependencies: [],
19
+ targets: [
20
+ .target(
21
+ name: "UnifiedVideoPlayer",
22
+ dependencies: [],
23
+ path: "Sources/UnifiedVideoPlayer",
24
+ swiftSettings: [
25
+ .unsafeFlags(["-enable-library-evolution"])
26
+ ]
27
+ ),
28
+ .testTarget(
29
+ name: "UnifiedVideoPlayerTests",
30
+ dependencies: ["UnifiedVideoPlayer"],
31
+ path: "Tests/UnifiedVideoPlayerTests"
32
+ )
33
+ ],
34
+ swiftLanguageVersions: [.v5]
35
+ )
@@ -0,0 +1,84 @@
1
+ # UnifiedVideoPlayer (iOS SDK)
2
+
3
+ A unified iOS video player SDK with:
4
+ - HLS playback (AVPlayer)
5
+ - FairPlay DRM (SPC/CKC)
6
+ - Subtitles/audio track selection (AVMediaSelection)
7
+ - Picture-in-Picture (AVPictureInPictureController)
8
+ - AirPlay (AVRoutePickerView)
9
+ - Remote Command Center + Now Playing (lock screen controls)
10
+ - Background audio (AVAudioSession)
11
+
12
+ ## Installation
13
+
14
+ ### CocoaPods
15
+ 1) Ensure this SDK is in a public Git repository. Update the podspec `s.source` to point at that repo and tag.
16
+ 2) In your Podfile:
17
+ ```ruby
18
+ platform :ios, '13.0'
19
+ use_frameworks!
20
+
21
+ target 'YourApp' do
22
+ pod 'UnifiedVideoPlayer', :git => 'https://github.com/yourcompany/unified-video-ios.git', :tag => '1.0.0'
23
+ end
24
+ ```
25
+ 3) Run:
26
+ ```bash
27
+ pod install
28
+ ```
29
+
30
+ ### Swift Package Manager
31
+ Add the package at the repository URL or use Add Local Package pointing to `packages/ios`.
32
+
33
+ ## Quick Start
34
+ ```swift
35
+ import UnifiedVideoPlayer
36
+
37
+ let containerView = UIView(frame: .zero)
38
+ let player = UnifiedVideoPlayer()
39
+ let config = PlayerConfiguration()
40
+ config.autoPlay = true
41
+
42
+ player.initialize(container: containerView, configuration: config)
43
+
44
+ let source = MediaSource(url: "https://example.com/stream.m3u8")
45
+ source.metadata = ["title": "Demo Stream"]
46
+ player.onReady = { print("ready") }
47
+ player.onQualityChange = { br in print("bitrate: \(br)") }
48
+
49
+ player.load(source: source)
50
+ ```
51
+
52
+ ### FairPlay DRM
53
+ ```swift
54
+ let drm = DRMConfiguration(type: "fairplay", licenseUrl: "https://license.example.com/fps")
55
+ drm.certificateUrl = "https://license.example.com/cert"
56
+ drm.headers = ["X-Tenant-ID": "default"]
57
+
58
+ let source = MediaSource(url: "https://cdn.example.com/protected/playlist.m3u8")
59
+ source.drm = drm
60
+ player.load(source: source)
61
+ ```
62
+
63
+ ### Tracks
64
+ ```swift
65
+ let audios = player.audioTracks() // [String]
66
+ let subs = player.subtitleTracks() // [String]
67
+ player.selectAudioTrack(index: 0)
68
+ player.selectSubtitleTrack(index: -1) // off
69
+ ```
70
+
71
+ ### PiP & AirPlay
72
+ ```swift
73
+ player.startPictureInPicture()
74
+ player.stopPictureInPicture()
75
+ let airPlay = player.makeAirPlayPickerView()
76
+ ```
77
+
78
+ ## Capabilities
79
+ - Enable Background Modes > Audio
80
+ - For DRM endpoints, configure ATS exceptions if necessary.
81
+
82
+ ## License
83
+ MIT
84
+
@@ -0,0 +1,26 @@
1
+ import Foundation
2
+ import AVFoundation
3
+
4
+ final class AnalyticsEmitter {
5
+ private var observer: NSObjectProtocol?
6
+ var onBitrate: ((Double) -> Void)?
7
+
8
+ func startObserving(item: AVPlayerItem) {
9
+ stopObserving()
10
+ observer = NotificationCenter.default.addObserver(
11
+ forName: .AVPlayerItemNewAccessLogEntry,
12
+ object: item,
13
+ queue: .main
14
+ ) { [weak self] _ in
15
+ guard let event = item.accessLog()?.events.last else { return }
16
+ let br = event.indicatedBitrate > 0 ? event.indicatedBitrate : event.observedBitrate
17
+ self?.onBitrate?(br)
18
+ }
19
+ }
20
+
21
+ func stopObserving() {
22
+ if let obs = observer { NotificationCenter.default.removeObserver(obs) }
23
+ observer = nil
24
+ }
25
+ }
26
+
@@ -0,0 +1,102 @@
1
+ import Foundation
2
+ import AVFoundation
3
+
4
+ final class FairPlayDRMManager: NSObject, AVAssetResourceLoaderDelegate {
5
+ private let drm: DRMConfiguration
6
+ private var certificateData: Data?
7
+ private let queue = DispatchQueue(label: "uvp.fairplay")
8
+
9
+ init(drm: DRMConfiguration) {
10
+ self.drm = drm
11
+ super.init()
12
+ }
13
+
14
+ // MARK: AVAssetResourceLoaderDelegate
15
+ func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -> Bool {
16
+ queue.async { [weak self] in
17
+ self?.handle(loadingRequest: loadingRequest)
18
+ }
19
+ return true
20
+ }
21
+
22
+ func resourceLoader(_ resourceLoader: AVAssetResourceLoader, didCancel loadingRequest: AVAssetResourceLoadingRequest) {
23
+ // No-op
24
+ }
25
+
26
+ private func handle(loadingRequest: AVAssetResourceLoadingRequest) {
27
+ guard let requestURL = loadingRequest.request.url, requestURL.scheme == "skd" else {
28
+ loadingRequest.finishLoading(with: NSError(domain: "UnifiedVideoPlayer.FairPlay", code: -10, userInfo: [NSLocalizedDescriptionKey: "Invalid or missing SKD URL"]))
29
+ return
30
+ }
31
+
32
+ do {
33
+ let cert = try fetchCertificate()
34
+ let contentIdData: Data
35
+ if let cid = requestURL.host?.data(using: .utf8) ?? requestURL.absoluteString.data(using: .utf8) {
36
+ contentIdData = cid
37
+ } else {
38
+ throw NSError(domain: "UnifiedVideoPlayer.FairPlay", code: -11, userInfo: [NSLocalizedDescriptionKey: "Unable to derive content ID"])
39
+ }
40
+
41
+ let spcData = try loadingRequest.streamingContentKeyRequestData(forApp: cert, contentIdentifier: contentIdData, options: nil)
42
+ requestCKC(spc: spcData) { [weak loadingRequest] result in
43
+ guard let lr = loadingRequest else { return }
44
+ switch result {
45
+ case .success(let ckc):
46
+ lr.dataRequest?.respond(with: ckc)
47
+ lr.finishLoading()
48
+ case .failure(let error):
49
+ lr.finishLoading(with: error)
50
+ }
51
+ }
52
+ } catch {
53
+ loadingRequest.finishLoading(with: error)
54
+ }
55
+ }
56
+
57
+ private func fetchCertificate() throws -> Data {
58
+ if let cached = certificateData { return cached }
59
+ guard let certURLString = drm.certificateUrl, let certURL = URL(string: certURLString) else {
60
+ throw NSError(domain: "UnifiedVideoPlayer.FairPlay", code: -12, userInfo: [NSLocalizedDescriptionKey: "certificateUrl is missing"])
61
+ }
62
+ var request = URLRequest(url: certURL)
63
+ if let headers = drm.headers { headers.forEach { request.addValue($0.value, forHTTPHeaderField: $0.key) } }
64
+ let sem = DispatchSemaphore(value: 0)
65
+ var resultData: Data?
66
+ var resultError: Error?
67
+ URLSession.shared.dataTask(with: request) { data, _, error in
68
+ resultData = data
69
+ resultError = error
70
+ sem.signal()
71
+ }.resume()
72
+ _ = sem.wait(timeout: .now() + 30)
73
+ if let e = resultError { throw e }
74
+ guard let data = resultData else {
75
+ throw NSError(domain: "UnifiedVideoPlayer.FairPlay", code: -13, userInfo: [NSLocalizedDescriptionKey: "Empty certificate response"])
76
+ }
77
+ certificateData = data
78
+ return data
79
+ }
80
+
81
+ private func requestCKC(spc: Data, completion: @escaping (Result<Data, Error>) -> Void) {
82
+ guard let licenseURL = URL(string: drm.licenseUrl) else {
83
+ completion(.failure(NSError(domain: "UnifiedVideoPlayer.FairPlay", code: -14, userInfo: [NSLocalizedDescriptionKey: "Invalid licenseUrl"])) )
84
+ return
85
+ }
86
+ var request = URLRequest(url: licenseURL)
87
+ request.httpMethod = "POST"
88
+ request.addValue("application/octet-stream", forHTTPHeaderField: "Content-Type")
89
+ if let headers = drm.headers { headers.forEach { request.addValue($0.value, forHTTPHeaderField: $0.key) } }
90
+ request.httpBody = spc
91
+
92
+ URLSession.shared.dataTask(with: request) { data, _, error in
93
+ if let e = error { completion(.failure(e)); return }
94
+ guard let data = data else {
95
+ completion(.failure(NSError(domain: "UnifiedVideoPlayer.FairPlay", code: -15, userInfo: [NSLocalizedDescriptionKey: "Empty CKC response"])) )
96
+ return
97
+ }
98
+ completion(.success(data))
99
+ }.resume()
100
+ }
101
+ }
102
+