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,641 @@
1
+ /**
2
+ * MainActivity.java
3
+ * UnifiedVideoPlayer Java Sample App
4
+ *
5
+ * Example of integrating UnifiedVideoPlayer into an existing Android app using Java
6
+ */
7
+
8
+ package com.unifiedvideo.javasampleapp;
9
+
10
+ import android.content.DialogInterface;
11
+ import android.os.Bundle;
12
+ import android.util.Log;
13
+ import android.view.View;
14
+ import android.widget.Button;
15
+ import android.widget.EditText;
16
+ import android.widget.FrameLayout;
17
+ import android.widget.ProgressBar;
18
+ import android.widget.SeekBar;
19
+ import android.widget.TextView;
20
+ import android.widget.Toast;
21
+
22
+ import androidx.appcompat.app.AlertDialog;
23
+ import androidx.appcompat.app.AppCompatActivity;
24
+
25
+ import com.google.android.material.snackbar.Snackbar;
26
+ import com.unifiedvideo.player.*;
27
+
28
+ import java.util.ArrayList;
29
+ import java.util.HashMap;
30
+ import java.util.List;
31
+ import java.util.Map;
32
+ import java.util.concurrent.TimeUnit;
33
+
34
+ public class MainActivity extends AppCompatActivity implements UnifiedVideoPlayer.PlayerEventListener {
35
+
36
+ private static final String TAG = "JavaSampleApp";
37
+
38
+ // UI Components
39
+ private FrameLayout playerContainer;
40
+ private Button playPauseButton;
41
+ private SeekBar progressSeekBar;
42
+ private TextView currentTimeText;
43
+ private TextView durationText;
44
+ private SeekBar volumeSeekBar;
45
+ private Button muteButton;
46
+ private ProgressBar loadingProgressBar;
47
+ private TextView errorTextView;
48
+ private Button skipBackwardButton;
49
+ private Button skipForwardButton;
50
+ private Button loadVideoButton;
51
+ private Button qualityButton;
52
+ private Button speedButton;
53
+ private TextView stateTextView;
54
+ private TextView infoTextView;
55
+
56
+ // Player instance
57
+ private UnifiedVideoPlayer videoPlayer;
58
+ private boolean isSeeking = false;
59
+
60
+ // Sample video data
61
+ private static class VideoInfo {
62
+ String url;
63
+ String title;
64
+
65
+ VideoInfo(String url, String title) {
66
+ this.url = url;
67
+ this.title = title;
68
+ }
69
+ }
70
+
71
+ private final List<VideoInfo> sampleVideos = new ArrayList<>();
72
+
73
+ @Override
74
+ protected void onCreate(Bundle savedInstanceState) {
75
+ super.onCreate(savedInstanceState);
76
+ setContentView(R.layout.activity_main);
77
+
78
+ initializeSampleVideos();
79
+ initializeUI();
80
+ setupVideoPlayer();
81
+ loadSampleVideo();
82
+ }
83
+
84
+ /**
85
+ * Initialize sample video list
86
+ */
87
+ private void initializeSampleVideos() {
88
+ sampleVideos.add(new VideoInfo(
89
+ "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4",
90
+ "MP4 Video"
91
+ ));
92
+ sampleVideos.add(new VideoInfo(
93
+ "https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8",
94
+ "HLS Stream"
95
+ ));
96
+ sampleVideos.add(new VideoInfo(
97
+ "https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8",
98
+ "Test Stream"
99
+ ));
100
+ sampleVideos.add(new VideoInfo(
101
+ "https://dash.akamaized.net/akamai/bbb_30fps/bbb_30fps.mpd",
102
+ "DASH Stream"
103
+ ));
104
+ }
105
+
106
+ /**
107
+ * Initialize UI components
108
+ */
109
+ private void initializeUI() {
110
+ // Find views
111
+ playerContainer = findViewById(R.id.playerContainer);
112
+ playPauseButton = findViewById(R.id.playPauseButton);
113
+ progressSeekBar = findViewById(R.id.progressSeekBar);
114
+ currentTimeText = findViewById(R.id.currentTimeText);
115
+ durationText = findViewById(R.id.durationText);
116
+ volumeSeekBar = findViewById(R.id.volumeSeekBar);
117
+ muteButton = findViewById(R.id.muteButton);
118
+ loadingProgressBar = findViewById(R.id.loadingProgressBar);
119
+ errorTextView = findViewById(R.id.errorTextView);
120
+ skipBackwardButton = findViewById(R.id.skipBackwardButton);
121
+ skipForwardButton = findViewById(R.id.skipForwardButton);
122
+ loadVideoButton = findViewById(R.id.loadVideoButton);
123
+ qualityButton = findViewById(R.id.qualityButton);
124
+ speedButton = findViewById(R.id.speedButton);
125
+ stateTextView = findViewById(R.id.stateTextView);
126
+ infoTextView = findViewById(R.id.infoTextView);
127
+
128
+ // Setup initial states
129
+ errorTextView.setVisibility(View.GONE);
130
+ loadingProgressBar.setVisibility(View.GONE);
131
+ volumeSeekBar.setMax(100);
132
+ volumeSeekBar.setProgress(100);
133
+
134
+ // Setup click listeners
135
+ setupClickListeners();
136
+ setupSeekBarListeners();
137
+ }
138
+
139
+ /**
140
+ * Setup the video player
141
+ */
142
+ private void setupVideoPlayer() {
143
+ // Create player instance
144
+ videoPlayer = new UnifiedVideoPlayer(this);
145
+
146
+ // Configure player
147
+ PlayerConfiguration config = new PlayerConfiguration.Builder()
148
+ .setAutoPlay(false)
149
+ .setControls(false) // Using custom controls
150
+ .setMuted(false)
151
+ .setDebug(true)
152
+ .setUseStyledControls(false)
153
+ .setAllowBackgroundPlayback(false)
154
+ .build();
155
+
156
+ // Initialize with container
157
+ videoPlayer.initialize(playerContainer, config);
158
+
159
+ // Set event listener
160
+ videoPlayer.setEventListener(this);
161
+ }
162
+
163
+ /**
164
+ * Setup click listeners for buttons
165
+ */
166
+ private void setupClickListeners() {
167
+ playPauseButton.setOnClickListener(new View.OnClickListener() {
168
+ @Override
169
+ public void onClick(View v) {
170
+ videoPlayer.togglePlayPause();
171
+ }
172
+ });
173
+
174
+ muteButton.setOnClickListener(new View.OnClickListener() {
175
+ @Override
176
+ public void onClick(View v) {
177
+ videoPlayer.toggleMute();
178
+ muteButton.setText(muteButton.getText().equals("Mute") ? "Unmute" : "Mute");
179
+ }
180
+ });
181
+
182
+ skipBackwardButton.setOnClickListener(new View.OnClickListener() {
183
+ @Override
184
+ public void onClick(View v) {
185
+ videoPlayer.seekBackward(10);
186
+ }
187
+ });
188
+
189
+ skipForwardButton.setOnClickListener(new View.OnClickListener() {
190
+ @Override
191
+ public void onClick(View v) {
192
+ videoPlayer.seekForward(10);
193
+ }
194
+ });
195
+
196
+ loadVideoButton.setOnClickListener(new View.OnClickListener() {
197
+ @Override
198
+ public void onClick(View v) {
199
+ showVideoSelectionDialog();
200
+ }
201
+ });
202
+
203
+ qualityButton.setOnClickListener(new View.OnClickListener() {
204
+ @Override
205
+ public void onClick(View v) {
206
+ showQualitySelectionDialog();
207
+ }
208
+ });
209
+
210
+ speedButton.setOnClickListener(new View.OnClickListener() {
211
+ @Override
212
+ public void onClick(View v) {
213
+ showSpeedSelectionDialog();
214
+ }
215
+ });
216
+ }
217
+
218
+ /**
219
+ * Setup SeekBar listeners
220
+ */
221
+ private void setupSeekBarListeners() {
222
+ progressSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
223
+ @Override
224
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
225
+ if (fromUser) {
226
+ currentTimeText.setText(formatTime(progress));
227
+ }
228
+ }
229
+
230
+ @Override
231
+ public void onStartTrackingTouch(SeekBar seekBar) {
232
+ isSeeking = true;
233
+ }
234
+
235
+ @Override
236
+ public void onStopTrackingTouch(SeekBar seekBar) {
237
+ videoPlayer.seekTo(seekBar.getProgress());
238
+ isSeeking = false;
239
+ }
240
+ });
241
+
242
+ volumeSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
243
+ @Override
244
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
245
+ if (fromUser) {
246
+ videoPlayer.setVolume(progress / 100f);
247
+ }
248
+ }
249
+
250
+ @Override
251
+ public void onStartTrackingTouch(SeekBar seekBar) {
252
+ // Not needed
253
+ }
254
+
255
+ @Override
256
+ public void onStopTrackingTouch(SeekBar seekBar) {
257
+ // Not needed
258
+ }
259
+ });
260
+ }
261
+
262
+ /**
263
+ * Load the first sample video
264
+ */
265
+ private void loadSampleVideo() {
266
+ VideoInfo firstVideo = sampleVideos.get(0);
267
+ MediaSourceInfo source = new MediaSourceInfo(firstVideo.url);
268
+ videoPlayer.load(source);
269
+ updateInfoText("Loading: " + firstVideo.title);
270
+ }
271
+
272
+ // PlayerEventListener implementation
273
+
274
+ @Override
275
+ public void onReady() {
276
+ runOnUiThread(new Runnable() {
277
+ @Override
278
+ public void run() {
279
+ loadingProgressBar.setVisibility(View.GONE);
280
+ playPauseButton.setEnabled(true);
281
+ Log.d(TAG, "Player is ready");
282
+ updateStateText("Ready");
283
+ updateInfoText("Video loaded successfully");
284
+ }
285
+ });
286
+ }
287
+
288
+ @Override
289
+ public void onPlay() {
290
+ runOnUiThread(new Runnable() {
291
+ @Override
292
+ public void run() {
293
+ playPauseButton.setText("Pause");
294
+ updateStateText("Playing");
295
+ }
296
+ });
297
+ }
298
+
299
+ @Override
300
+ public void onPause() {
301
+ runOnUiThread(new Runnable() {
302
+ @Override
303
+ public void run() {
304
+ playPauseButton.setText("Play");
305
+ updateStateText("Paused");
306
+ }
307
+ });
308
+ }
309
+
310
+ @Override
311
+ public void onTimeUpdate(final long currentTime) {
312
+ if (!isSeeking) {
313
+ runOnUiThread(new Runnable() {
314
+ @Override
315
+ public void run() {
316
+ progressSeekBar.setProgress((int) currentTime);
317
+ currentTimeText.setText(formatTime(currentTime));
318
+ }
319
+ });
320
+ }
321
+ }
322
+
323
+ @Override
324
+ public void onBuffering(final boolean isBuffering) {
325
+ runOnUiThread(new Runnable() {
326
+ @Override
327
+ public void run() {
328
+ loadingProgressBar.setVisibility(isBuffering ? View.VISIBLE : View.GONE);
329
+ if (isBuffering) {
330
+ updateStateText("Buffering...");
331
+ }
332
+ }
333
+ });
334
+ }
335
+
336
+ @Override
337
+ public void onSeek(long position) {
338
+ Log.d(TAG, "Seeked to: " + position);
339
+ }
340
+
341
+ @Override
342
+ public void onEnded() {
343
+ runOnUiThread(new Runnable() {
344
+ @Override
345
+ public void run() {
346
+ playPauseButton.setText("Play");
347
+ progressSeekBar.setProgress(0);
348
+ currentTimeText.setText("00:00");
349
+ updateStateText("Ended");
350
+ updateInfoText("Playback completed");
351
+ }
352
+ });
353
+ }
354
+
355
+ @Override
356
+ public void onError(final Exception error) {
357
+ runOnUiThread(new Runnable() {
358
+ @Override
359
+ public void run() {
360
+ loadingProgressBar.setVisibility(View.GONE);
361
+ errorTextView.setVisibility(View.VISIBLE);
362
+ errorTextView.setText("Error: " + error.getMessage());
363
+ playPauseButton.setEnabled(false);
364
+ updateStateText("Error");
365
+
366
+ Snackbar.make(playerContainer, "Playback error occurred", Snackbar.LENGTH_LONG).show();
367
+ }
368
+ });
369
+ }
370
+
371
+ @Override
372
+ public void onLoadedMetadata(final Map<String, Object> metadata) {
373
+ runOnUiThread(new Runnable() {
374
+ @Override
375
+ public void run() {
376
+ Log.d(TAG, "Metadata loaded: " + metadata.toString());
377
+
378
+ Long duration = (Long) metadata.get("duration");
379
+ if (duration != null) {
380
+ progressSeekBar.setMax(duration.intValue());
381
+ durationText.setText(formatTime(duration));
382
+ }
383
+
384
+ Integer width = (Integer) metadata.get("width");
385
+ Integer height = (Integer) metadata.get("height");
386
+ if (width != null && height != null) {
387
+ updateInfoText("Resolution: " + width + "x" + height);
388
+ }
389
+ }
390
+ });
391
+ }
392
+
393
+ @Override
394
+ public void onVolumeChange(final float volume) {
395
+ runOnUiThread(new Runnable() {
396
+ @Override
397
+ public void run() {
398
+ volumeSeekBar.setProgress((int) (volume * 100));
399
+ }
400
+ });
401
+ }
402
+
403
+ @Override
404
+ public void onStateChange(final UnifiedVideoPlayer.PlayerState state) {
405
+ runOnUiThread(new Runnable() {
406
+ @Override
407
+ public void run() {
408
+ Log.d(TAG, "State changed: " + state);
409
+ updateStateText(state.toString());
410
+ }
411
+ });
412
+ }
413
+
414
+ @Override
415
+ public void onProgress(long bufferedPosition) {
416
+ // Can be used to show buffer progress
417
+ }
418
+
419
+ @Override
420
+ public void onVideoSizeChanged(final int width, final int height) {
421
+ runOnUiThread(new Runnable() {
422
+ @Override
423
+ public void run() {
424
+ Log.d(TAG, "Video size: " + width + "x" + height);
425
+ }
426
+ });
427
+ }
428
+
429
+ // Helper methods
430
+
431
+ private String formatTime(long milliseconds) {
432
+ long minutes = TimeUnit.MILLISECONDS.toMinutes(milliseconds);
433
+ long seconds = TimeUnit.MILLISECONDS.toSeconds(milliseconds) % 60;
434
+ return String.format("%02d:%02d", minutes, seconds);
435
+ }
436
+
437
+ private void updateStateText(String state) {
438
+ stateTextView.setText("State: " + state);
439
+ }
440
+
441
+ private void updateInfoText(String info) {
442
+ infoTextView.setText(info);
443
+ }
444
+
445
+ // Dialog methods
446
+
447
+ private void showVideoSelectionDialog() {
448
+ String[] items = new String[sampleVideos.size() + 1];
449
+ for (int i = 0; i < sampleVideos.size(); i++) {
450
+ items[i] = sampleVideos.get(i).title;
451
+ }
452
+ items[sampleVideos.size()] = "Custom URL...";
453
+
454
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
455
+ builder.setTitle("Select Video");
456
+ builder.setItems(items, new DialogInterface.OnClickListener() {
457
+ @Override
458
+ public void onClick(DialogInterface dialog, int which) {
459
+ if (which < sampleVideos.size()) {
460
+ VideoInfo selected = sampleVideos.get(which);
461
+ videoPlayer.load(selected.url);
462
+ updateInfoText("Loading: " + selected.title);
463
+ } else {
464
+ showCustomURLDialog();
465
+ }
466
+ }
467
+ });
468
+ builder.setNegativeButton("Cancel", null);
469
+ builder.show();
470
+ }
471
+
472
+ private void showCustomURLDialog() {
473
+ final EditText editText = new EditText(this);
474
+ editText.setHint("https://example.com/video.mp4");
475
+
476
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
477
+ builder.setTitle("Enter Video URL");
478
+ builder.setView(editText);
479
+ builder.setPositiveButton("Load", new DialogInterface.OnClickListener() {
480
+ @Override
481
+ public void onClick(DialogInterface dialog, int which) {
482
+ String url = editText.getText().toString();
483
+ if (!url.isEmpty()) {
484
+ videoPlayer.load(url);
485
+ updateInfoText("Loading custom URL");
486
+ }
487
+ }
488
+ });
489
+ builder.setNegativeButton("Cancel", null);
490
+ builder.show();
491
+ }
492
+
493
+ private void showQualitySelectionDialog() {
494
+ final String[] qualities = {"Auto", "HD (1080p)", "SD (480p)", "Low (360p)"};
495
+ final String[] qualityValues = {"auto", "hd", "sd", "low"};
496
+
497
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
498
+ builder.setTitle("Select Video Quality");
499
+ builder.setItems(qualities, new DialogInterface.OnClickListener() {
500
+ @Override
501
+ public void onClick(DialogInterface dialog, int which) {
502
+ videoPlayer.setVideoQuality(qualityValues[which]);
503
+ Toast.makeText(MainActivity.this, "Quality: " + qualities[which], Toast.LENGTH_SHORT).show();
504
+ updateInfoText("Quality changed to: " + qualities[which]);
505
+ }
506
+ });
507
+ builder.setNegativeButton("Cancel", null);
508
+ builder.show();
509
+ }
510
+
511
+ private void showSpeedSelectionDialog() {
512
+ final String[] speeds = {"0.5x", "0.75x", "1.0x", "1.25x", "1.5x", "2.0x"};
513
+ final float[] speedValues = {0.5f, 0.75f, 1.0f, 1.25f, 1.5f, 2.0f};
514
+
515
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
516
+ builder.setTitle("Playback Speed");
517
+ builder.setItems(speeds, new DialogInterface.OnClickListener() {
518
+ @Override
519
+ public void onClick(DialogInterface dialog, int which) {
520
+ videoPlayer.setPlaybackSpeed(speedValues[which]);
521
+ Toast.makeText(MainActivity.this, "Speed: " + speeds[which], Toast.LENGTH_SHORT).show();
522
+ updateInfoText("Playback speed: " + speeds[which]);
523
+ }
524
+ });
525
+ builder.setNegativeButton("Cancel", null);
526
+ builder.show();
527
+ }
528
+
529
+ // Advanced usage examples
530
+
531
+ /**
532
+ * Load DRM protected content
533
+ */
534
+ private void loadDRMProtectedContent() {
535
+ Map<String, String> headers = new HashMap<>();
536
+ headers.put("X-Custom-Header", "value");
537
+ headers.put("Authorization", "Bearer token");
538
+
539
+ DRMConfiguration drm = new DRMConfiguration(
540
+ "widevine",
541
+ "https://license.server.com/widevine",
542
+ headers,
543
+ false,
544
+ false
545
+ );
546
+
547
+ MediaSourceInfo source = new MediaSourceInfo(
548
+ "https://example.com/protected-content.mpd",
549
+ "dash",
550
+ drm,
551
+ null,
552
+ null
553
+ );
554
+
555
+ videoPlayer.load(source);
556
+ updateInfoText("Loading DRM protected content");
557
+ }
558
+
559
+ /**
560
+ * Load video with subtitles
561
+ */
562
+ private void loadVideoWithSubtitles() {
563
+ List<SubtitleTrack> subtitles = new ArrayList<>();
564
+ subtitles.add(new SubtitleTrack(
565
+ "https://example.com/subtitles-en.vtt",
566
+ "en",
567
+ "English"
568
+ ));
569
+ subtitles.add(new SubtitleTrack(
570
+ "https://example.com/subtitles-es.vtt",
571
+ "es",
572
+ "Spanish"
573
+ ));
574
+
575
+ MediaSourceInfo source = new MediaSourceInfo(
576
+ "https://example.com/video.mp4",
577
+ "mp4",
578
+ null,
579
+ null,
580
+ subtitles
581
+ );
582
+
583
+ videoPlayer.load(source);
584
+ updateInfoText("Loading video with subtitles");
585
+ }
586
+
587
+ /**
588
+ * Load video with custom metadata
589
+ */
590
+ private void loadVideoWithMetadata() {
591
+ Map<String, Object> metadata = new HashMap<>();
592
+ metadata.put("title", "Sample Video");
593
+ metadata.put("description", "This is a sample video");
594
+ metadata.put("duration", 120000L);
595
+
596
+ MediaSourceInfo source = new MediaSourceInfo(
597
+ "https://example.com/video.mp4",
598
+ "mp4",
599
+ null,
600
+ metadata,
601
+ null
602
+ );
603
+
604
+ videoPlayer.load(source);
605
+ updateInfoText("Loading video with metadata");
606
+ }
607
+
608
+ // Lifecycle methods
609
+
610
+ @Override
611
+ protected void onResume() {
612
+ super.onResume();
613
+ if (videoPlayer != null) {
614
+ videoPlayer.onResume();
615
+ }
616
+ }
617
+
618
+ @Override
619
+ protected void onPause() {
620
+ super.onPause();
621
+ if (videoPlayer != null) {
622
+ videoPlayer.onPause();
623
+ }
624
+ }
625
+
626
+ @Override
627
+ protected void onStop() {
628
+ super.onStop();
629
+ if (videoPlayer != null) {
630
+ videoPlayer.onStop();
631
+ }
632
+ }
633
+
634
+ @Override
635
+ protected void onDestroy() {
636
+ super.onDestroy();
637
+ if (videoPlayer != null) {
638
+ videoPlayer.release();
639
+ }
640
+ }
641
+ }