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,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
+ }