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,226 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<!-- activity_main.xml - Layout for Java Sample App -->
|
|
3
|
+
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
4
|
+
xmlns:tools="http://schemas.android.com/tools"
|
|
5
|
+
android:layout_width="match_parent"
|
|
6
|
+
android:layout_height="match_parent"
|
|
7
|
+
android:orientation="vertical"
|
|
8
|
+
android:background="#000000"
|
|
9
|
+
tools:context=".MainActivity">
|
|
10
|
+
|
|
11
|
+
<!-- Video Player Container -->
|
|
12
|
+
<FrameLayout
|
|
13
|
+
android:id="@+id/playerContainer"
|
|
14
|
+
android:layout_width="match_parent"
|
|
15
|
+
android:layout_height="0dp"
|
|
16
|
+
android:layout_weight="1"
|
|
17
|
+
android:background="#000000">
|
|
18
|
+
|
|
19
|
+
<!-- Loading Indicator -->
|
|
20
|
+
<ProgressBar
|
|
21
|
+
android:id="@+id/loadingProgressBar"
|
|
22
|
+
android:layout_width="wrap_content"
|
|
23
|
+
android:layout_height="wrap_content"
|
|
24
|
+
android:layout_gravity="center"
|
|
25
|
+
android:visibility="gone" />
|
|
26
|
+
|
|
27
|
+
<!-- Error Text -->
|
|
28
|
+
<TextView
|
|
29
|
+
android:id="@+id/errorTextView"
|
|
30
|
+
android:layout_width="wrap_content"
|
|
31
|
+
android:layout_height="wrap_content"
|
|
32
|
+
android:layout_gravity="center"
|
|
33
|
+
android:textColor="#FF0000"
|
|
34
|
+
android:textSize="14sp"
|
|
35
|
+
android:padding="16dp"
|
|
36
|
+
android:visibility="gone" />
|
|
37
|
+
|
|
38
|
+
</FrameLayout>
|
|
39
|
+
|
|
40
|
+
<!-- Controls Container -->
|
|
41
|
+
<LinearLayout
|
|
42
|
+
android:layout_width="match_parent"
|
|
43
|
+
android:layout_height="wrap_content"
|
|
44
|
+
android:orientation="vertical"
|
|
45
|
+
android:background="#1A1A1A"
|
|
46
|
+
android:padding="16dp">
|
|
47
|
+
|
|
48
|
+
<!-- Progress Section -->
|
|
49
|
+
<LinearLayout
|
|
50
|
+
android:layout_width="match_parent"
|
|
51
|
+
android:layout_height="wrap_content"
|
|
52
|
+
android:orientation="horizontal"
|
|
53
|
+
android:gravity="center_vertical">
|
|
54
|
+
|
|
55
|
+
<TextView
|
|
56
|
+
android:id="@+id/currentTimeText"
|
|
57
|
+
android:layout_width="wrap_content"
|
|
58
|
+
android:layout_height="wrap_content"
|
|
59
|
+
android:text="00:00"
|
|
60
|
+
android:textColor="#FFFFFF"
|
|
61
|
+
android:textSize="12sp"
|
|
62
|
+
android:minWidth="45dp" />
|
|
63
|
+
|
|
64
|
+
<SeekBar
|
|
65
|
+
android:id="@+id/progressSeekBar"
|
|
66
|
+
android:layout_width="0dp"
|
|
67
|
+
android:layout_height="wrap_content"
|
|
68
|
+
android:layout_weight="1"
|
|
69
|
+
android:layout_marginStart="8dp"
|
|
70
|
+
android:layout_marginEnd="8dp"
|
|
71
|
+
android:progressTint="#FFFFFF"
|
|
72
|
+
android:thumbTint="#FFFFFF" />
|
|
73
|
+
|
|
74
|
+
<TextView
|
|
75
|
+
android:id="@+id/durationText"
|
|
76
|
+
android:layout_width="wrap_content"
|
|
77
|
+
android:layout_height="wrap_content"
|
|
78
|
+
android:text="00:00"
|
|
79
|
+
android:textColor="#FFFFFF"
|
|
80
|
+
android:textSize="12sp"
|
|
81
|
+
android:minWidth="45dp" />
|
|
82
|
+
|
|
83
|
+
</LinearLayout>
|
|
84
|
+
|
|
85
|
+
<!-- Main Control Buttons -->
|
|
86
|
+
<LinearLayout
|
|
87
|
+
android:layout_width="match_parent"
|
|
88
|
+
android:layout_height="wrap_content"
|
|
89
|
+
android:orientation="horizontal"
|
|
90
|
+
android:gravity="center"
|
|
91
|
+
android:layout_marginTop="8dp">
|
|
92
|
+
|
|
93
|
+
<Button
|
|
94
|
+
android:id="@+id/skipBackwardButton"
|
|
95
|
+
android:layout_width="wrap_content"
|
|
96
|
+
android:layout_height="wrap_content"
|
|
97
|
+
android:text="⏪ 10s"
|
|
98
|
+
android:textColor="#FFFFFF"
|
|
99
|
+
android:backgroundTint="#333333"
|
|
100
|
+
android:layout_marginEnd="8dp" />
|
|
101
|
+
|
|
102
|
+
<Button
|
|
103
|
+
android:id="@+id/playPauseButton"
|
|
104
|
+
android:layout_width="wrap_content"
|
|
105
|
+
android:layout_height="wrap_content"
|
|
106
|
+
android:text="Play"
|
|
107
|
+
android:textColor="#FFFFFF"
|
|
108
|
+
android:backgroundTint="#0066CC"
|
|
109
|
+
android:layout_marginEnd="8dp" />
|
|
110
|
+
|
|
111
|
+
<Button
|
|
112
|
+
android:id="@+id/skipForwardButton"
|
|
113
|
+
android:layout_width="wrap_content"
|
|
114
|
+
android:layout_height="wrap_content"
|
|
115
|
+
android:text="10s ⏩"
|
|
116
|
+
android:textColor="#FFFFFF"
|
|
117
|
+
android:backgroundTint="#333333" />
|
|
118
|
+
|
|
119
|
+
</LinearLayout>
|
|
120
|
+
|
|
121
|
+
<!-- Volume Control -->
|
|
122
|
+
<LinearLayout
|
|
123
|
+
android:layout_width="match_parent"
|
|
124
|
+
android:layout_height="wrap_content"
|
|
125
|
+
android:orientation="horizontal"
|
|
126
|
+
android:gravity="center_vertical"
|
|
127
|
+
android:layout_marginTop="8dp">
|
|
128
|
+
|
|
129
|
+
<TextView
|
|
130
|
+
android:layout_width="wrap_content"
|
|
131
|
+
android:layout_height="wrap_content"
|
|
132
|
+
android:text="Volume:"
|
|
133
|
+
android:textColor="#FFFFFF"
|
|
134
|
+
android:textSize="14sp"
|
|
135
|
+
android:layout_marginEnd="8dp" />
|
|
136
|
+
|
|
137
|
+
<SeekBar
|
|
138
|
+
android:id="@+id/volumeSeekBar"
|
|
139
|
+
android:layout_width="0dp"
|
|
140
|
+
android:layout_height="wrap_content"
|
|
141
|
+
android:layout_weight="1"
|
|
142
|
+
android:progressTint="#FFFFFF"
|
|
143
|
+
android:thumbTint="#FFFFFF" />
|
|
144
|
+
|
|
145
|
+
<Button
|
|
146
|
+
android:id="@+id/muteButton"
|
|
147
|
+
android:layout_width="wrap_content"
|
|
148
|
+
android:layout_height="wrap_content"
|
|
149
|
+
android:text="Mute"
|
|
150
|
+
android:textColor="#FFFFFF"
|
|
151
|
+
android:backgroundTint="#333333"
|
|
152
|
+
android:layout_marginStart="8dp" />
|
|
153
|
+
|
|
154
|
+
</LinearLayout>
|
|
155
|
+
|
|
156
|
+
<!-- Additional Controls -->
|
|
157
|
+
<LinearLayout
|
|
158
|
+
android:layout_width="match_parent"
|
|
159
|
+
android:layout_height="wrap_content"
|
|
160
|
+
android:orientation="horizontal"
|
|
161
|
+
android:layout_marginTop="8dp">
|
|
162
|
+
|
|
163
|
+
<Button
|
|
164
|
+
android:id="@+id/loadVideoButton"
|
|
165
|
+
android:layout_width="0dp"
|
|
166
|
+
android:layout_height="wrap_content"
|
|
167
|
+
android:layout_weight="1"
|
|
168
|
+
android:text="Load Video"
|
|
169
|
+
android:textColor="#FFFFFF"
|
|
170
|
+
android:backgroundTint="#555555"
|
|
171
|
+
android:layout_marginEnd="4dp" />
|
|
172
|
+
|
|
173
|
+
<Button
|
|
174
|
+
android:id="@+id/qualityButton"
|
|
175
|
+
android:layout_width="0dp"
|
|
176
|
+
android:layout_height="wrap_content"
|
|
177
|
+
android:layout_weight="1"
|
|
178
|
+
android:text="Quality"
|
|
179
|
+
android:textColor="#FFFFFF"
|
|
180
|
+
android:backgroundTint="#555555"
|
|
181
|
+
android:layout_marginStart="4dp"
|
|
182
|
+
android:layout_marginEnd="4dp" />
|
|
183
|
+
|
|
184
|
+
<Button
|
|
185
|
+
android:id="@+id/speedButton"
|
|
186
|
+
android:layout_width="0dp"
|
|
187
|
+
android:layout_height="wrap_content"
|
|
188
|
+
android:layout_weight="1"
|
|
189
|
+
android:text="Speed"
|
|
190
|
+
android:textColor="#FFFFFF"
|
|
191
|
+
android:backgroundTint="#555555"
|
|
192
|
+
android:layout_marginStart="4dp" />
|
|
193
|
+
|
|
194
|
+
</LinearLayout>
|
|
195
|
+
|
|
196
|
+
<!-- Status Information -->
|
|
197
|
+
<LinearLayout
|
|
198
|
+
android:layout_width="match_parent"
|
|
199
|
+
android:layout_height="wrap_content"
|
|
200
|
+
android:orientation="vertical"
|
|
201
|
+
android:layout_marginTop="8dp"
|
|
202
|
+
android:padding="8dp"
|
|
203
|
+
android:background="#2A2A2A">
|
|
204
|
+
|
|
205
|
+
<TextView
|
|
206
|
+
android:id="@+id/stateTextView"
|
|
207
|
+
android:layout_width="wrap_content"
|
|
208
|
+
android:layout_height="wrap_content"
|
|
209
|
+
android:text="State: Idle"
|
|
210
|
+
android:textColor="#AAAAAA"
|
|
211
|
+
android:textSize="12sp" />
|
|
212
|
+
|
|
213
|
+
<TextView
|
|
214
|
+
android:id="@+id/infoTextView"
|
|
215
|
+
android:layout_width="wrap_content"
|
|
216
|
+
android:layout_height="wrap_content"
|
|
217
|
+
android:text="Ready to load video"
|
|
218
|
+
android:textColor="#AAAAAA"
|
|
219
|
+
android:textSize="12sp"
|
|
220
|
+
android:layout_marginTop="4dp" />
|
|
221
|
+
|
|
222
|
+
</LinearLayout>
|
|
223
|
+
|
|
224
|
+
</LinearLayout>
|
|
225
|
+
|
|
226
|
+
</LinearLayout>
|
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MainActivity.kt
|
|
3
|
+
* UnifiedVideoPlayer Sample App
|
|
4
|
+
*
|
|
5
|
+
* Example of integrating UnifiedVideoPlayer into an existing Android app
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
package com.unifiedvideo.sampleapp
|
|
9
|
+
|
|
10
|
+
import android.os.Bundle
|
|
11
|
+
import android.util.Log
|
|
12
|
+
import android.view.View
|
|
13
|
+
import android.widget.*
|
|
14
|
+
import androidx.appcompat.app.AlertDialog
|
|
15
|
+
import androidx.appcompat.app.AppCompatActivity
|
|
16
|
+
import com.google.android.material.snackbar.Snackbar
|
|
17
|
+
import com.unifiedvideo.player.*
|
|
18
|
+
import java.util.concurrent.TimeUnit
|
|
19
|
+
|
|
20
|
+
class MainActivity : AppCompatActivity() {
|
|
21
|
+
|
|
22
|
+
companion object {
|
|
23
|
+
private const val TAG = "SampleApp"
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// UI Components
|
|
27
|
+
private lateinit var playerContainer: FrameLayout
|
|
28
|
+
private lateinit var playPauseButton: Button
|
|
29
|
+
private lateinit var progressSeekBar: SeekBar
|
|
30
|
+
private lateinit var currentTimeText: TextView
|
|
31
|
+
private lateinit var durationText: TextView
|
|
32
|
+
private lateinit var volumeSeekBar: SeekBar
|
|
33
|
+
private lateinit var muteButton: Button
|
|
34
|
+
private lateinit var loadingProgressBar: ProgressBar
|
|
35
|
+
private lateinit var errorTextView: TextView
|
|
36
|
+
private lateinit var skipBackwardButton: Button
|
|
37
|
+
private lateinit var skipForwardButton: Button
|
|
38
|
+
private lateinit var loadVideoButton: Button
|
|
39
|
+
private lateinit var qualityButton: Button
|
|
40
|
+
private lateinit var speedButton: Button
|
|
41
|
+
|
|
42
|
+
// Player instance
|
|
43
|
+
private lateinit var videoPlayer: UnifiedVideoPlayer
|
|
44
|
+
private var isSeeking = false
|
|
45
|
+
|
|
46
|
+
// Sample video URLs
|
|
47
|
+
private val sampleVideos = listOf(
|
|
48
|
+
"https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4" to "MP4 Video",
|
|
49
|
+
"https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8" to "HLS Stream",
|
|
50
|
+
"https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8" to "Test Stream",
|
|
51
|
+
"https://dash.akamaized.net/akamai/bbb_30fps/bbb_30fps.mpd" to "DASH Stream"
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
override fun onCreate(savedInstanceState: Bundle?) {
|
|
55
|
+
super.onCreate(savedInstanceState)
|
|
56
|
+
setContentView(R.layout.activity_main)
|
|
57
|
+
|
|
58
|
+
setupUI()
|
|
59
|
+
setupVideoPlayer()
|
|
60
|
+
loadSampleVideo()
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private fun setupUI() {
|
|
64
|
+
// Initialize UI components
|
|
65
|
+
playerContainer = findViewById(R.id.playerContainer)
|
|
66
|
+
playPauseButton = findViewById(R.id.playPauseButton)
|
|
67
|
+
progressSeekBar = findViewById(R.id.progressSeekBar)
|
|
68
|
+
currentTimeText = findViewById(R.id.currentTimeText)
|
|
69
|
+
durationText = findViewById(R.id.durationText)
|
|
70
|
+
volumeSeekBar = findViewById(R.id.volumeSeekBar)
|
|
71
|
+
muteButton = findViewById(R.id.muteButton)
|
|
72
|
+
loadingProgressBar = findViewById(R.id.loadingProgressBar)
|
|
73
|
+
errorTextView = findViewById(R.id.errorTextView)
|
|
74
|
+
skipBackwardButton = findViewById(R.id.skipBackwardButton)
|
|
75
|
+
skipForwardButton = findViewById(R.id.skipForwardButton)
|
|
76
|
+
loadVideoButton = findViewById(R.id.loadVideoButton)
|
|
77
|
+
qualityButton = findViewById(R.id.qualityButton)
|
|
78
|
+
speedButton = findViewById(R.id.speedButton)
|
|
79
|
+
|
|
80
|
+
// Setup initial states
|
|
81
|
+
errorTextView.visibility = View.GONE
|
|
82
|
+
loadingProgressBar.visibility = View.GONE
|
|
83
|
+
volumeSeekBar.max = 100
|
|
84
|
+
volumeSeekBar.progress = 100
|
|
85
|
+
|
|
86
|
+
// Setup click listeners
|
|
87
|
+
setupClickListeners()
|
|
88
|
+
setupSeekBarListeners()
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private fun setupVideoPlayer() {
|
|
92
|
+
// Create player instance
|
|
93
|
+
videoPlayer = UnifiedVideoPlayer(this)
|
|
94
|
+
|
|
95
|
+
// Configure player
|
|
96
|
+
val config = PlayerConfiguration.Builder()
|
|
97
|
+
.setAutoPlay(false)
|
|
98
|
+
.setControls(false) // Using custom controls
|
|
99
|
+
.setMuted(false)
|
|
100
|
+
.setDebug(true)
|
|
101
|
+
.setUseStyledControls(false)
|
|
102
|
+
.setAllowBackgroundPlayback(false)
|
|
103
|
+
.build()
|
|
104
|
+
|
|
105
|
+
// Initialize with container
|
|
106
|
+
videoPlayer.initialize(playerContainer, config)
|
|
107
|
+
|
|
108
|
+
// Setup event listeners
|
|
109
|
+
setupPlayerEventListeners()
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private fun setupPlayerEventListeners() {
|
|
113
|
+
// Ready event
|
|
114
|
+
videoPlayer.onReady = {
|
|
115
|
+
runOnUiThread {
|
|
116
|
+
handlePlayerReady()
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Play/Pause events
|
|
121
|
+
videoPlayer.onPlay = {
|
|
122
|
+
runOnUiThread {
|
|
123
|
+
updatePlayPauseButton(true)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
videoPlayer.onPause = {
|
|
128
|
+
runOnUiThread {
|
|
129
|
+
updatePlayPauseButton(false)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Time updates
|
|
134
|
+
videoPlayer.onTimeUpdate = { currentTime ->
|
|
135
|
+
if (!isSeeking) {
|
|
136
|
+
runOnUiThread {
|
|
137
|
+
updateProgress(currentTime)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Buffering
|
|
143
|
+
videoPlayer.onBuffering = { isBuffering ->
|
|
144
|
+
runOnUiThread {
|
|
145
|
+
handleBuffering(isBuffering)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Metadata loaded
|
|
150
|
+
videoPlayer.onLoadedMetadata = { metadata ->
|
|
151
|
+
runOnUiThread {
|
|
152
|
+
handleMetadataLoaded(metadata)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Error handling
|
|
157
|
+
videoPlayer.onError = { error ->
|
|
158
|
+
runOnUiThread {
|
|
159
|
+
handleError(error)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// State changes
|
|
164
|
+
videoPlayer.onStateChange = { state ->
|
|
165
|
+
Log.d(TAG, "Player state changed: $state")
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Video ended
|
|
169
|
+
videoPlayer.onEnded = {
|
|
170
|
+
runOnUiThread {
|
|
171
|
+
handleVideoEnded()
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Video size changed
|
|
176
|
+
videoPlayer.onVideoSizeChanged = { width, height ->
|
|
177
|
+
Log.d(TAG, "Video size: ${width}x${height}")
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private fun setupClickListeners() {
|
|
182
|
+
playPauseButton.setOnClickListener {
|
|
183
|
+
videoPlayer.togglePlayPause()
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
muteButton.setOnClickListener {
|
|
187
|
+
videoPlayer.toggleMute()
|
|
188
|
+
muteButton.text = if (muteButton.text == "Mute") "Unmute" else "Mute"
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
skipBackwardButton.setOnClickListener {
|
|
192
|
+
videoPlayer.seekBackward(10)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
skipForwardButton.setOnClickListener {
|
|
196
|
+
videoPlayer.seekForward(10)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
loadVideoButton.setOnClickListener {
|
|
200
|
+
showVideoSelectionDialog()
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
qualityButton.setOnClickListener {
|
|
204
|
+
showQualitySelectionDialog()
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
speedButton.setOnClickListener {
|
|
208
|
+
showSpeedSelectionDialog()
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
private fun setupSeekBarListeners() {
|
|
213
|
+
progressSeekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
|
|
214
|
+
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
|
|
215
|
+
if (fromUser) {
|
|
216
|
+
currentTimeText.text = formatTime(progress.toLong())
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
override fun onStartTrackingTouch(seekBar: SeekBar) {
|
|
221
|
+
isSeeking = true
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
override fun onStopTrackingTouch(seekBar: SeekBar) {
|
|
225
|
+
videoPlayer.seekTo(seekBar.progress.toLong())
|
|
226
|
+
isSeeking = false
|
|
227
|
+
}
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
volumeSeekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
|
|
231
|
+
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
|
|
232
|
+
if (fromUser) {
|
|
233
|
+
videoPlayer.setVolume(progress / 100f)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
override fun onStartTrackingTouch(seekBar: SeekBar) {}
|
|
238
|
+
override fun onStopTrackingTouch(seekBar: SeekBar) {}
|
|
239
|
+
})
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private fun loadSampleVideo() {
|
|
243
|
+
// Load the first sample video
|
|
244
|
+
val (url, _) = sampleVideos[0]
|
|
245
|
+
val source = MediaSource(url = url)
|
|
246
|
+
videoPlayer.load(source)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Event Handlers
|
|
250
|
+
private fun handlePlayerReady() {
|
|
251
|
+
loadingProgressBar.visibility = View.GONE
|
|
252
|
+
playPauseButton.isEnabled = true
|
|
253
|
+
Log.d(TAG, "Player is ready")
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
private fun handleBuffering(isBuffering: Boolean) {
|
|
257
|
+
loadingProgressBar.visibility = if (isBuffering) View.VISIBLE else View.GONE
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private fun handleMetadataLoaded(metadata: Map<String, Any>) {
|
|
261
|
+
Log.d(TAG, "Metadata loaded: $metadata")
|
|
262
|
+
|
|
263
|
+
val duration = metadata["duration"] as? Long ?: 0
|
|
264
|
+
progressSeekBar.max = duration.toInt()
|
|
265
|
+
durationText.text = formatTime(duration)
|
|
266
|
+
|
|
267
|
+
metadata["width"]?.let { width ->
|
|
268
|
+
metadata["height"]?.let { height ->
|
|
269
|
+
Log.d(TAG, "Video resolution: ${width}x${height}")
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
private fun handleError(error: Exception) {
|
|
275
|
+
loadingProgressBar.visibility = View.GONE
|
|
276
|
+
errorTextView.visibility = View.VISIBLE
|
|
277
|
+
errorTextView.text = "Error: ${error.message}"
|
|
278
|
+
playPauseButton.isEnabled = false
|
|
279
|
+
|
|
280
|
+
Snackbar.make(playerContainer, "Playback error occurred", Snackbar.LENGTH_LONG).show()
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
private fun handleVideoEnded() {
|
|
284
|
+
updatePlayPauseButton(false)
|
|
285
|
+
progressSeekBar.progress = 0
|
|
286
|
+
currentTimeText.text = "00:00"
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// UI Updates
|
|
290
|
+
private fun updatePlayPauseButton(isPlaying: Boolean) {
|
|
291
|
+
playPauseButton.text = if (isPlaying) "Pause" else "Play"
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
private fun updateProgress(currentTime: Long) {
|
|
295
|
+
progressSeekBar.progress = currentTime.toInt()
|
|
296
|
+
currentTimeText.text = formatTime(currentTime)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
private fun formatTime(milliseconds: Long): String {
|
|
300
|
+
val minutes = TimeUnit.MILLISECONDS.toMinutes(milliseconds)
|
|
301
|
+
val seconds = TimeUnit.MILLISECONDS.toSeconds(milliseconds) % 60
|
|
302
|
+
return String.format("%02d:%02d", minutes, seconds)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Dialogs
|
|
306
|
+
private fun showVideoSelectionDialog() {
|
|
307
|
+
val items = sampleVideos.map { it.second }.toTypedArray()
|
|
308
|
+
|
|
309
|
+
AlertDialog.Builder(this)
|
|
310
|
+
.setTitle("Select Video")
|
|
311
|
+
.setItems(items) { _, which ->
|
|
312
|
+
val (url, _) = sampleVideos[which]
|
|
313
|
+
videoPlayer.load(url)
|
|
314
|
+
}
|
|
315
|
+
.setPositiveButton("Custom URL") { _, _ ->
|
|
316
|
+
showCustomURLDialog()
|
|
317
|
+
}
|
|
318
|
+
.setNegativeButton("Cancel", null)
|
|
319
|
+
.show()
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
private fun showCustomURLDialog() {
|
|
323
|
+
val editText = EditText(this).apply {
|
|
324
|
+
hint = "https://example.com/video.mp4"
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
AlertDialog.Builder(this)
|
|
328
|
+
.setTitle("Enter Video URL")
|
|
329
|
+
.setView(editText)
|
|
330
|
+
.setPositiveButton("Load") { _, _ ->
|
|
331
|
+
val url = editText.text.toString()
|
|
332
|
+
if (url.isNotEmpty()) {
|
|
333
|
+
videoPlayer.load(url)
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
.setNegativeButton("Cancel", null)
|
|
337
|
+
.show()
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
private fun showQualitySelectionDialog() {
|
|
341
|
+
val qualities = arrayOf("Auto", "HD (1080p)", "SD (480p)", "Low (360p)")
|
|
342
|
+
val qualityValues = arrayOf("auto", "hd", "sd", "low")
|
|
343
|
+
|
|
344
|
+
AlertDialog.Builder(this)
|
|
345
|
+
.setTitle("Select Video Quality")
|
|
346
|
+
.setItems(qualities) { _, which ->
|
|
347
|
+
videoPlayer.setVideoQuality(qualityValues[which])
|
|
348
|
+
Toast.makeText(this, "Quality: ${qualities[which]}", Toast.LENGTH_SHORT).show()
|
|
349
|
+
}
|
|
350
|
+
.setNegativeButton("Cancel", null)
|
|
351
|
+
.show()
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
private fun showSpeedSelectionDialog() {
|
|
355
|
+
val speeds = arrayOf("0.5x", "0.75x", "1.0x", "1.25x", "1.5x", "2.0x")
|
|
356
|
+
val speedValues = floatArrayOf(0.5f, 0.75f, 1.0f, 1.25f, 1.5f, 2.0f)
|
|
357
|
+
|
|
358
|
+
AlertDialog.Builder(this)
|
|
359
|
+
.setTitle("Playback Speed")
|
|
360
|
+
.setItems(speeds) { _, which ->
|
|
361
|
+
videoPlayer.setPlaybackSpeed(speedValues[which])
|
|
362
|
+
Toast.makeText(this, "Speed: ${speeds[which]}", Toast.LENGTH_SHORT).show()
|
|
363
|
+
}
|
|
364
|
+
.setNegativeButton("Cancel", null)
|
|
365
|
+
.show()
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Advanced Usage Examples
|
|
369
|
+
private fun loadDRMProtectedContent() {
|
|
370
|
+
val drm = DRMConfiguration(
|
|
371
|
+
type = "widevine",
|
|
372
|
+
licenseUrl = "https://license.server.com/widevine",
|
|
373
|
+
headers = mapOf(
|
|
374
|
+
"X-Custom-Header" to "value",
|
|
375
|
+
"Authorization" to "Bearer token"
|
|
376
|
+
)
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
val source = MediaSource(
|
|
380
|
+
url = "https://example.com/protected-content.mpd",
|
|
381
|
+
type = "dash",
|
|
382
|
+
drm = drm
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
videoPlayer.load(source)
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
private fun loadVideoWithSubtitles() {
|
|
389
|
+
val subtitles = listOf(
|
|
390
|
+
SubtitleTrack(
|
|
391
|
+
url = "https://example.com/subtitles-en.vtt",
|
|
392
|
+
language = "en",
|
|
393
|
+
label = "English"
|
|
394
|
+
),
|
|
395
|
+
SubtitleTrack(
|
|
396
|
+
url = "https://example.com/subtitles-es.vtt",
|
|
397
|
+
language = "es",
|
|
398
|
+
label = "Spanish"
|
|
399
|
+
)
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
val source = MediaSource(
|
|
403
|
+
url = "https://example.com/video.mp4",
|
|
404
|
+
subtitles = subtitles
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
videoPlayer.load(source)
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Lifecycle
|
|
411
|
+
override fun onResume() {
|
|
412
|
+
super.onResume()
|
|
413
|
+
videoPlayer.onResume()
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
override fun onPause() {
|
|
417
|
+
super.onPause()
|
|
418
|
+
videoPlayer.onPause()
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
override fun onStop() {
|
|
422
|
+
super.onStop()
|
|
423
|
+
videoPlayer.onStop()
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
override fun onDestroy() {
|
|
427
|
+
super.onDestroy()
|
|
428
|
+
videoPlayer.release()
|
|
429
|
+
}
|
|
430
|
+
}
|