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