@capgo/capacitor-jw-player 0.0.1
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/CapgoCapacitorJwPlayer.podspec +18 -0
- package/Package.swift +30 -0
- package/README.md +493 -0
- package/android/build.gradle +83 -0
- package/android/src/main/AndroidManifest.xml +37 -0
- package/android/src/main/java/ee/forgr/capacitor_jw_player/JwPlayerPlugin.java +529 -0
- package/android/src/main/java/ee/forgr/capacitor_jw_player/PlayerActivity.java +469 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/android/src/main/res/drawable/close_button_background.xml +5 -0
- package/android/src/main/res/layout/activity_player.xml +28 -0
- package/android/src/main/res/values/colors.xml +16 -0
- package/android/src/main/res/values/strings.xml +3 -0
- package/android/src/main/res/values/styles.xml +32 -0
- package/dist/docs.json +459 -0
- package/dist/esm/definitions.d.ts +156 -0
- package/dist/esm/definitions.js +2 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +57 -0
- package/dist/esm/web.js +65 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +79 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +82 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Sources/JwPlayerPlugin/JwPlayer.swift +24 -0
- package/ios/Sources/JwPlayerPlugin/JwPlayerPlugin.swift +749 -0
- package/ios/Sources/JwPlayerPlugin/LinkedList.swift +219 -0
- package/ios/Sources/JwPlayerPlugin/VideosToPlay.swift +56 -0
- package/ios/Tests/JwPlayerPluginTests/JwPlayerPluginTests.swift +15 -0
- package/package.json +80 -0
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
package ee.forgr.capacitor_jw_player;
|
|
2
|
+
|
|
3
|
+
import android.app.Activity;
|
|
4
|
+
import android.content.Intent;
|
|
5
|
+
import android.content.res.Configuration;
|
|
6
|
+
import android.graphics.Rect;
|
|
7
|
+
import android.os.Build;
|
|
8
|
+
import android.os.Bundle;
|
|
9
|
+
import android.util.DisplayMetrics;
|
|
10
|
+
import android.util.Log;
|
|
11
|
+
import android.util.Rational;
|
|
12
|
+
import android.view.KeyEvent;
|
|
13
|
+
import android.view.View;
|
|
14
|
+
import android.view.ViewGroup;
|
|
15
|
+
import android.view.WindowManager;
|
|
16
|
+
import android.widget.ImageButton;
|
|
17
|
+
import android.widget.ImageView;
|
|
18
|
+
|
|
19
|
+
import androidx.appcompat.app.AppCompatActivity;
|
|
20
|
+
|
|
21
|
+
import com.jwplayer.pub.api.JWPlayer;
|
|
22
|
+
import com.jwplayer.pub.api.PlayerState;
|
|
23
|
+
import com.jwplayer.pub.api.UiGroup;
|
|
24
|
+
import com.jwplayer.pub.api.configuration.PlayerConfig;
|
|
25
|
+
// import com.jwplayer.pub.api.configuration.PlaylistConfig; // Already commented/removed - ensure it's gone
|
|
26
|
+
import com.jwplayer.pub.api.configuration.UiConfig;
|
|
27
|
+
import com.jwplayer.pub.api.events.*;
|
|
28
|
+
import com.jwplayer.pub.api.events.listeners.VideoPlayerEvents;
|
|
29
|
+
import com.jwplayer.pub.api.events.listeners.AdvertisingEvents;
|
|
30
|
+
import com.jwplayer.pub.api.license.LicenseUtil;
|
|
31
|
+
import com.jwplayer.pub.api.media.captions.Caption;
|
|
32
|
+
import com.jwplayer.pub.api.media.captions.CaptionType;
|
|
33
|
+
import com.jwplayer.pub.api.media.meta.Metadata;
|
|
34
|
+
import com.jwplayer.pub.api.media.playlists.PlaylistItem;
|
|
35
|
+
import com.jwplayer.pub.ui.viewmodels.ControlbarViewModel;
|
|
36
|
+
import com.jwplayer.pub.view.JWPlayerView;
|
|
37
|
+
import com.jwplayer.ui.views.ControlbarView;
|
|
38
|
+
|
|
39
|
+
import java.util.ArrayList;
|
|
40
|
+
import java.util.List;
|
|
41
|
+
|
|
42
|
+
public class PlayerActivity extends AppCompatActivity implements
|
|
43
|
+
VideoPlayerEvents.OnFullscreenListener,
|
|
44
|
+
VideoPlayerEvents.OnReadyListener,
|
|
45
|
+
VideoPlayerEvents.OnErrorListener,
|
|
46
|
+
VideoPlayerEvents.OnSetupErrorListener,
|
|
47
|
+
VideoPlayerEvents.OnPlaylistCompleteListener,
|
|
48
|
+
VideoPlayerEvents.OnPlaylistItemListener,
|
|
49
|
+
VideoPlayerEvents.OnPauseListener,
|
|
50
|
+
VideoPlayerEvents.OnPlayListener,
|
|
51
|
+
VideoPlayerEvents.OnCompleteListener,
|
|
52
|
+
VideoPlayerEvents.OnSeekListener,
|
|
53
|
+
VideoPlayerEvents.OnSeekedListener,
|
|
54
|
+
VideoPlayerEvents.OnTimeListener,
|
|
55
|
+
AdvertisingEvents.OnAdErrorListener,
|
|
56
|
+
AdvertisingEvents.OnAdWarningListener,
|
|
57
|
+
VideoPlayerEvents.OnMetaListener
|
|
58
|
+
{
|
|
59
|
+
|
|
60
|
+
private static final String TAG = "PlayerActivity";
|
|
61
|
+
public static final String EXTRA_MEDIA_URL = "mediaUrl";
|
|
62
|
+
public static final String EXTRA_MEDIA_TYPE = "mediaType";
|
|
63
|
+
|
|
64
|
+
private JWPlayerView mPlayerView;
|
|
65
|
+
private JWPlayer mPlayer;
|
|
66
|
+
private ImageButton mCloseButton;
|
|
67
|
+
|
|
68
|
+
@Override
|
|
69
|
+
protected void onCreate(Bundle savedInstanceState) {
|
|
70
|
+
super.onCreate(savedInstanceState);
|
|
71
|
+
Log.d(TAG, "onCreate");
|
|
72
|
+
|
|
73
|
+
setContentView(R.layout.activity_player);
|
|
74
|
+
|
|
75
|
+
// Make activity fullscreen - moved after setContentView
|
|
76
|
+
if (getWindow() != null) {
|
|
77
|
+
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
|
|
78
|
+
WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
|
79
|
+
// Keep the screen on during playback
|
|
80
|
+
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
|
81
|
+
} else {
|
|
82
|
+
Log.e(TAG, "Window is null in onCreate!");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Hide navigation bar and enable immersive mode
|
|
86
|
+
hideSystemUI();
|
|
87
|
+
|
|
88
|
+
mPlayerView = findViewById(R.id.jwplayer);
|
|
89
|
+
mPlayer = mPlayerView.getPlayer();
|
|
90
|
+
mPlayer.registerActivityForPip(this, getSupportActionBar());
|
|
91
|
+
|
|
92
|
+
// Delte fullscreen button
|
|
93
|
+
ControlbarView customControlBar = mPlayerView.getControlsContainer().getControlbarView();
|
|
94
|
+
ImageView enterFullScreenButton = customControlBar.findViewById(com.longtailvideo.jwplayer.R.id.controlbar_enter_fullscreen_btn);
|
|
95
|
+
ImageView exitFullScreenButton = customControlBar.findViewById(com.longtailvideo.jwplayer.R.id.controlbar_exit_fullscreen_btn);
|
|
96
|
+
if (enterFullScreenButton != null && enterFullScreenButton.getParent() != null) {
|
|
97
|
+
((ViewGroup)enterFullScreenButton.getParent()).removeView(enterFullScreenButton);
|
|
98
|
+
}
|
|
99
|
+
if (exitFullScreenButton != null && exitFullScreenButton.getParent() != null) {
|
|
100
|
+
((ViewGroup)exitFullScreenButton.getParent()).removeView(exitFullScreenButton);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Setup Close Button
|
|
104
|
+
mCloseButton = findViewById(R.id.close_button);
|
|
105
|
+
mCloseButton.setOnClickListener(v -> finish());
|
|
106
|
+
|
|
107
|
+
ControlbarViewModel controlbarViewModel =
|
|
108
|
+
(ControlbarViewModel) mPlayer.getViewModelForUiGroup(UiGroup.CONTROLBAR);
|
|
109
|
+
|
|
110
|
+
controlbarViewModel.isUiLayerVisible().observe(
|
|
111
|
+
this,
|
|
112
|
+
isVisible -> {
|
|
113
|
+
if (isInPictureInPictureMode()) {
|
|
114
|
+
mCloseButton.setVisibility(View.GONE);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
mCloseButton.setVisibility(isVisible ? View.VISIBLE : View.INVISIBLE);
|
|
118
|
+
}
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
// Set the static instance reference in the plugin
|
|
122
|
+
JwPlayerPlugin.setStaticPlayerInstance(mPlayer);
|
|
123
|
+
|
|
124
|
+
mPlayerView.setOnClickListener(v -> toggleSystemUI());
|
|
125
|
+
|
|
126
|
+
// Initialize JW Player license (ensure this is also done in Plugin if needed)
|
|
127
|
+
// new LicenseUtil().setLicenseKey(this, "YOUR_LICENSE_KEY"); // Ideally get from Intent or Plugin
|
|
128
|
+
|
|
129
|
+
// Setup JW Player listeners
|
|
130
|
+
setupPlayerListeners();
|
|
131
|
+
|
|
132
|
+
// Get playback info from Intent
|
|
133
|
+
Intent intent = getIntent();
|
|
134
|
+
String mediaUrl = intent.getStringExtra(EXTRA_MEDIA_URL);
|
|
135
|
+
String mediaType = intent.getStringExtra(EXTRA_MEDIA_TYPE);
|
|
136
|
+
|
|
137
|
+
if (mediaUrl != null && mediaType != null) {
|
|
138
|
+
Log.d(TAG, "Received Media URL: " + mediaUrl + ", Type: " + mediaType);
|
|
139
|
+
loadMedia(mediaUrl, mediaType);
|
|
140
|
+
} else {
|
|
141
|
+
Log.e(TAG, "Error: Media URL or Type not provided in Intent.");
|
|
142
|
+
// Notify plugin of error?
|
|
143
|
+
finish(); // Close if no media info
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private void loadMedia(String mediaUrl, String mediaType) {
|
|
148
|
+
PlaylistItem playlistItem = new PlaylistItem.Builder()
|
|
149
|
+
.file(mediaUrl)
|
|
150
|
+
// .title("Video Title") // Optional
|
|
151
|
+
// .description("Video Description") // Optional
|
|
152
|
+
.build();
|
|
153
|
+
|
|
154
|
+
List<PlaylistItem> playlist = new ArrayList<>();
|
|
155
|
+
playlist.add(playlistItem);
|
|
156
|
+
|
|
157
|
+
PlayerConfig config = new PlayerConfig.Builder()
|
|
158
|
+
.playlist(playlist)
|
|
159
|
+
.autostart(true)
|
|
160
|
+
.build();
|
|
161
|
+
|
|
162
|
+
mPlayer.setup(config);
|
|
163
|
+
Log.d(TAG, "Player setup initiated.");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private void setupPlayerListeners() {
|
|
167
|
+
mPlayer.addListener(EventType.READY, this);
|
|
168
|
+
mPlayer.addListener(EventType.ERROR, this);
|
|
169
|
+
mPlayer.addListener(EventType.SETUP_ERROR, this);
|
|
170
|
+
mPlayer.addListener(EventType.FULLSCREEN, this);
|
|
171
|
+
mPlayer.addListener(EventType.PLAYLIST_COMPLETE, this);
|
|
172
|
+
mPlayer.addListener(EventType.PLAYLIST_ITEM, this);
|
|
173
|
+
mPlayer.addListener(EventType.PAUSE, this);
|
|
174
|
+
mPlayer.addListener(EventType.PLAY, this);
|
|
175
|
+
mPlayer.addListener(EventType.COMPLETE, this);
|
|
176
|
+
mPlayer.addListener(EventType.SEEK, this);
|
|
177
|
+
mPlayer.addListener(EventType.SEEKED, this);
|
|
178
|
+
mPlayer.addListener(EventType.TIME, this);
|
|
179
|
+
mPlayer.addListener(EventType.AD_ERROR, this);
|
|
180
|
+
mPlayer.addListener(EventType.AD_WARNING, this);
|
|
181
|
+
mPlayer.addListener(EventType.META, this);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
@Override
|
|
185
|
+
protected void onResume() {
|
|
186
|
+
super.onResume();
|
|
187
|
+
Log.d(TAG, "onResume");
|
|
188
|
+
if (mPlayer != null) {
|
|
189
|
+
// mPlayer.play(); // Resume playback
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
@Override
|
|
194
|
+
protected void onPause() {
|
|
195
|
+
super.onPause();
|
|
196
|
+
Log.d(TAG, "onPause");
|
|
197
|
+
if (mPlayer != null && !isInPictureInPictureMode()) { // Don't pause if entering PiP
|
|
198
|
+
// mPlayer.pause();
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
@Override
|
|
203
|
+
protected void onDestroy() {
|
|
204
|
+
super.onDestroy();
|
|
205
|
+
Log.d(TAG, "onDestroy");
|
|
206
|
+
// Clean up resources
|
|
207
|
+
if (mPlayerView != null) {
|
|
208
|
+
// mPlayerView.onDestroy(); // Check JW Player docs if needed
|
|
209
|
+
}
|
|
210
|
+
if (getWindow() != null) {
|
|
211
|
+
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
|
212
|
+
}
|
|
213
|
+
// Notify plugin that player was dismissed AND clear static instance
|
|
214
|
+
JwPlayerPlugin.onPlayerDismissed(); // This already clears the static instance
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
@Override
|
|
218
|
+
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
|
219
|
+
// Exit fullscreen when the back button is pressed
|
|
220
|
+
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
|
221
|
+
finish();
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
return super.onKeyDown(keyCode, event);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
@Override
|
|
228
|
+
public void onWindowFocusChanged(boolean hasFocus) {
|
|
229
|
+
super.onWindowFocusChanged(hasFocus);
|
|
230
|
+
if (hasFocus && getWindow() != null) {
|
|
231
|
+
hideSystemUI();
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// --- Fullscreen Handling --- Not needed as Activity is fullscreen by default
|
|
236
|
+
public void onFullscreen(FullscreenEvent fullscreenEvent) {
|
|
237
|
+
Log.d(TAG, "onFullscreen event: " + fullscreenEvent.getFullscreen());
|
|
238
|
+
// Activity is already fullscreen, this listener might be for internal JW Player state
|
|
239
|
+
// We don't need to change Android's fullscreen state here.
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// --- Picture-in-Picture --- (Basic handling)
|
|
243
|
+
@Override
|
|
244
|
+
public void onUserLeaveHint() {
|
|
245
|
+
// Enter PiP if supported when user navigates away (e.g., home button)
|
|
246
|
+
super.onUserLeaveHint();
|
|
247
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
|
|
248
|
+
getPackageManager().hasSystemFeature(getPackageManager().FEATURE_PICTURE_IN_PICTURE)) {
|
|
249
|
+
if (mPlayer != null && mPlayer.getState() == PlayerState.PLAYING) {
|
|
250
|
+
Log.d(TAG, "User leaving hint, entering PiP mode.");
|
|
251
|
+
// enterPictureInPictureMode();
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
@Override
|
|
257
|
+
public void onStop() {
|
|
258
|
+
Log.d(TAG, "On stop triggered?");
|
|
259
|
+
super.onStop();
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
@Override
|
|
263
|
+
public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration newConfig) {
|
|
264
|
+
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
|
|
265
|
+
mPlayer.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
|
|
266
|
+
Log.d(TAG, "onPictureInPictureModeChanged: " + isInPictureInPictureMode);
|
|
267
|
+
if (isInPictureInPictureMode) {
|
|
268
|
+
// PiP is active, hide controls like the close button
|
|
269
|
+
mCloseButton.setVisibility(View.GONE);
|
|
270
|
+
} else {
|
|
271
|
+
|
|
272
|
+
// PiP is inactive, restore UI
|
|
273
|
+
mCloseButton.setVisibility(View.VISIBLE);
|
|
274
|
+
if (getWindow() != null) {
|
|
275
|
+
hideSystemUI(); // Re-hide system bars when exiting PiP
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
// Notify plugin about PiP status change?
|
|
279
|
+
JwPlayerPlugin.notifyPipChanged(isInPictureInPictureMode);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// --- Player Event Listeners --- (Notify plugin)
|
|
283
|
+
|
|
284
|
+
public void onReady(ReadyEvent readyEvent) {
|
|
285
|
+
Log.d(TAG, "onReady");
|
|
286
|
+
JwPlayerPlugin.notifyReady();
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
public void onSetupError(SetupErrorEvent setupErrorEvent) {
|
|
290
|
+
Log.e(TAG, "onSetupError: " + setupErrorEvent.getMessage());
|
|
291
|
+
JwPlayerPlugin.notifyError("setupError", setupErrorEvent.getMessage());
|
|
292
|
+
finish();
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
public void onError(ErrorEvent errorEvent) {
|
|
296
|
+
Log.e(TAG, "onError: " + errorEvent.getMessage());
|
|
297
|
+
JwPlayerPlugin.notifyError("error", errorEvent.getMessage());
|
|
298
|
+
// Decide if we should finish based on error type?
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
public void onAdError(AdErrorEvent adErrorEvent) {
|
|
302
|
+
Log.e(TAG, "onAdError: " + adErrorEvent.getMessage());
|
|
303
|
+
JwPlayerPlugin.notifyError("adError", adErrorEvent.getMessage());
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
public void onAdWarning(AdWarningEvent adWarningEvent) {
|
|
307
|
+
Log.w(TAG, "onAdWarning: " + adWarningEvent.getMessage());
|
|
308
|
+
JwPlayerPlugin.notifyWarning("adWarning", adWarningEvent.getMessage());
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
public void onPlaylistItem(PlaylistItemEvent playlistItemEvent) {
|
|
312
|
+
Log.d(TAG, "onPlaylistItem: index=" + playlistItemEvent.getIndex());
|
|
313
|
+
// Need to serialize item data if required by JS
|
|
314
|
+
JwPlayerPlugin.notifyPlaylistItem(playlistItemEvent.getIndex(), playlistItemEvent.getPlaylistItem());
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
public void onPlaylistComplete(PlaylistCompleteEvent playlistCompleteEvent) {
|
|
318
|
+
Log.d(TAG, "onPlaylistComplete");
|
|
319
|
+
JwPlayerPlugin.notifyPlaylistComplete();
|
|
320
|
+
finish(); // Close activity when playlist finishes
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
public void onPause(PauseEvent pauseEvent) {
|
|
324
|
+
Log.d(TAG, "onPause event: state=" + pauseEvent.getOldState());
|
|
325
|
+
JwPlayerPlugin.notifyPause(pauseEvent.getPauseReason().name());
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
public void onPlay(PlayEvent playEvent) {
|
|
329
|
+
Log.d(TAG, "onPlay event: state=" + playEvent.getOldState());
|
|
330
|
+
JwPlayerPlugin.notifyPlay(playEvent.getPlayReason().name());
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
public void onMeta(MetaEvent metaEvent) {
|
|
334
|
+
Metadata metadata = metaEvent.getMetadata();
|
|
335
|
+
// Calculate aspect ratio and ensure it's within PiP bounds (2.38:1 to 1:2.38)
|
|
336
|
+
// The aspect ratio allows up to .39 but, i want to be safe and set it to 2.38
|
|
337
|
+
float aspectRatio = (float) metadata.getWidth() / metadata.getHeight();
|
|
338
|
+
int width = metadata.getWidth();
|
|
339
|
+
int height = metadata.getHeight();
|
|
340
|
+
|
|
341
|
+
if (aspectRatio > 2.38f) {
|
|
342
|
+
// Too wide, adjust width, let's not handle it
|
|
343
|
+
return;
|
|
344
|
+
} else if (aspectRatio < 1/2.38f) {
|
|
345
|
+
// Too tall, adjust height, let's not handle it for now
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
Rational rational = new Rational(width, height);
|
|
350
|
+
// Get the display metrics for the current screen
|
|
351
|
+
DisplayMetrics displayMetrics = new DisplayMetrics();
|
|
352
|
+
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
|
|
353
|
+
int screenWidth = displayMetrics.widthPixels;
|
|
354
|
+
|
|
355
|
+
// Calculate one-third of screen width and round to nearest even number
|
|
356
|
+
int thirdWidth = (int) Math.round((double)screenWidth / 3.0);
|
|
357
|
+
if (thirdWidth % 2 != 0) {
|
|
358
|
+
// If odd, round up to next even number
|
|
359
|
+
thirdWidth += 1;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
int thirdHeight = (int) Math.round(((double)screenWidth / 3.0) * aspectRatio);
|
|
363
|
+
if (thirdHeight % 2 != 0) {
|
|
364
|
+
// If odd, round up to next even number
|
|
365
|
+
thirdHeight += 1;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (mPlayer != null) {
|
|
369
|
+
mPlayer.setPipAspectRatio(rational);
|
|
370
|
+
mPlayer.setPipSourceRectHint(new Rect(0, 0, thirdWidth, thirdHeight));
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
public void onComplete(CompleteEvent completeEvent) {
|
|
375
|
+
Log.d(TAG, "onComplete event");
|
|
376
|
+
JwPlayerPlugin.notifyComplete();
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
public void onSeek(SeekEvent seekEvent) {
|
|
380
|
+
Log.d(TAG, "onSeek event: position=" + seekEvent.getPosition() + ", offset=" + seekEvent.getOffset());
|
|
381
|
+
JwPlayerPlugin.notifySeek(seekEvent.getPosition(), seekEvent.getOffset());
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
public void onSeeked(SeekedEvent seekedEvent) {
|
|
385
|
+
Log.d(TAG, "onSeeked event");
|
|
386
|
+
JwPlayerPlugin.notifySeeked();
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
public void onTime(TimeEvent timeEvent) {
|
|
390
|
+
// Log.v(TAG, "onTime event: position=" + timeEvent.getPosition() + ", duration=" + timeEvent.getDuration());
|
|
391
|
+
JwPlayerPlugin.notifyTime(timeEvent.getPosition(), timeEvent.getDuration());
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// --- System UI Helpers ---
|
|
395
|
+
private void hideSystemUI() {
|
|
396
|
+
// Check if window is null
|
|
397
|
+
if (getWindow() == null) {
|
|
398
|
+
Log.e(TAG, "Window is null in hideSystemUI!");
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// For AppCompatActivity, use the support library methods
|
|
403
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
404
|
+
getWindow().setDecorFitsSystemWindows(false);
|
|
405
|
+
android.view.WindowInsetsController insetsController = getWindow().getInsetsController();
|
|
406
|
+
if (insetsController != null) {
|
|
407
|
+
insetsController.hide(
|
|
408
|
+
android.view.WindowInsets.Type.statusBars() |
|
|
409
|
+
android.view.WindowInsets.Type.navigationBars());
|
|
410
|
+
insetsController.setSystemBarsBehavior(
|
|
411
|
+
android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
|
|
412
|
+
}
|
|
413
|
+
} else {
|
|
414
|
+
// Legacy method for older Android versions
|
|
415
|
+
View decorView = getWindow().getDecorView();
|
|
416
|
+
decorView.setSystemUiVisibility(
|
|
417
|
+
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
|
418
|
+
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
|
419
|
+
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
|
420
|
+
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
|
421
|
+
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
|
422
|
+
| View.SYSTEM_UI_FLAG_FULLSCREEN);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
private void showSystemUI() {
|
|
427
|
+
// Check if window is null
|
|
428
|
+
if (getWindow() == null) {
|
|
429
|
+
Log.e(TAG, "Window is null in showSystemUI!");
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
434
|
+
getWindow().setDecorFitsSystemWindows(true);
|
|
435
|
+
getWindow().getInsetsController().show(
|
|
436
|
+
android.view.WindowInsets.Type.statusBars() |
|
|
437
|
+
android.view.WindowInsets.Type.navigationBars());
|
|
438
|
+
} else {
|
|
439
|
+
// Legacy method for older Android versions
|
|
440
|
+
View decorView = getWindow().getDecorView();
|
|
441
|
+
decorView.setSystemUiVisibility(
|
|
442
|
+
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
|
443
|
+
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
|
444
|
+
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
private void toggleSystemUI() {
|
|
449
|
+
// Check if window is null
|
|
450
|
+
if (getWindow() == null) {
|
|
451
|
+
Log.e(TAG, "Window is null in toggleSystemUI!");
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
456
|
+
// For Android 11+, use InsetsController to check visibility
|
|
457
|
+
// Just toggle - there's no direct visibility check for R+
|
|
458
|
+
hideSystemUI();
|
|
459
|
+
} else {
|
|
460
|
+
View decorView = getWindow().getDecorView();
|
|
461
|
+
boolean isVisible = (decorView.getSystemUiVisibility() & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0;
|
|
462
|
+
if (isVisible) {
|
|
463
|
+
hideSystemUI();
|
|
464
|
+
} else {
|
|
465
|
+
showSystemUI();
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
3
|
+
xmlns:tools="http://schemas.android.com/tools"
|
|
4
|
+
android:layout_width="match_parent"
|
|
5
|
+
android:layout_height="match_parent"
|
|
6
|
+
android:background="@android:color/black"
|
|
7
|
+
tools:context=".PlayerActivity">
|
|
8
|
+
|
|
9
|
+
<com.jwplayer.pub.view.JWPlayerView
|
|
10
|
+
android:id="@+id/jwplayer"
|
|
11
|
+
android:layout_width="match_parent"
|
|
12
|
+
android:layout_height="match_parent"
|
|
13
|
+
android:layout_centerInParent="true" />
|
|
14
|
+
|
|
15
|
+
<!-- Optional: Add a custom close button if needed -->
|
|
16
|
+
<ImageButton
|
|
17
|
+
android:id="@+id/close_button"
|
|
18
|
+
android:layout_width="40dp"
|
|
19
|
+
android:layout_height="40dp"
|
|
20
|
+
android:layout_alignParentTop="true"
|
|
21
|
+
android:layout_alignParentStart="true"
|
|
22
|
+
android:layout_margin="16dp"
|
|
23
|
+
android:background="@drawable/close_button_background"
|
|
24
|
+
android:src="@android:drawable/ic_menu_close_clear_cancel"
|
|
25
|
+
android:contentDescription="@string/close_player"
|
|
26
|
+
android:elevation="4dp"/>
|
|
27
|
+
|
|
28
|
+
</RelativeLayout>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<resources>
|
|
3
|
+
<color name="colorPrimary">#000000</color>
|
|
4
|
+
<color name="colorPrimaryDark">#000000</color>
|
|
5
|
+
<color name="colorAccent">#FF0000</color>
|
|
6
|
+
|
|
7
|
+
<!-- JW Player requires these colors -->
|
|
8
|
+
<color name="jw_color_pressed">#33FFFFFF</color>
|
|
9
|
+
<color name="jw_color_focused">#33FFFFFF</color>
|
|
10
|
+
<color name="jw_color_selected">#FFFFFFFF</color>
|
|
11
|
+
<color name="jw_color_disabled">#80FFFFFF</color>
|
|
12
|
+
<color name="jw_color_primary">#FFFFFFFF</color>
|
|
13
|
+
<color name="jw_color_medium">#CCFFFFFF</color>
|
|
14
|
+
<color name="jw_color_inactive">#66FFFFFF</color>
|
|
15
|
+
<color name="jw_color_background">#CC000000</color>
|
|
16
|
+
</resources>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<resources>
|
|
3
|
+
<!-- Custom theme for JW Player that inherits from AppCompat.NoActionBar -->
|
|
4
|
+
<style name="JWPlayerTheme" parent="Theme.AppCompat.NoActionBar">
|
|
5
|
+
<item name="colorPrimary">@android:color/black</item>
|
|
6
|
+
<item name="colorPrimaryDark">@android:color/black</item>
|
|
7
|
+
<item name="colorAccent">#FF0000</item> <!-- Red accent color for controls -->
|
|
8
|
+
<item name="android:windowBackground">@android:color/black</item>
|
|
9
|
+
<item name="android:windowFullscreen">true</item>
|
|
10
|
+
|
|
11
|
+
<!-- Material Components theme attributes -->
|
|
12
|
+
<item name="shapeAppearanceSmallComponent">@style/ShapeAppearance.App.SmallComponent</item>
|
|
13
|
+
<item name="shapeAppearanceMediumComponent">@style/ShapeAppearance.App.MediumComponent</item>
|
|
14
|
+
<item name="shapeAppearanceLargeComponent">@style/ShapeAppearance.App.LargeComponent</item>
|
|
15
|
+
</style>
|
|
16
|
+
|
|
17
|
+
<!-- Shape appearance styles -->
|
|
18
|
+
<style name="ShapeAppearance.App.SmallComponent" parent="ShapeAppearance.MaterialComponents.SmallComponent">
|
|
19
|
+
<item name="cornerFamily">rounded</item>
|
|
20
|
+
<item name="cornerSize">4dp</item>
|
|
21
|
+
</style>
|
|
22
|
+
|
|
23
|
+
<style name="ShapeAppearance.App.MediumComponent" parent="ShapeAppearance.MaterialComponents.MediumComponent">
|
|
24
|
+
<item name="cornerFamily">rounded</item>
|
|
25
|
+
<item name="cornerSize">8dp</item>
|
|
26
|
+
</style>
|
|
27
|
+
|
|
28
|
+
<style name="ShapeAppearance.App.LargeComponent" parent="ShapeAppearance.MaterialComponents.LargeComponent">
|
|
29
|
+
<item name="cornerFamily">rounded</item>
|
|
30
|
+
<item name="cornerSize">12dp</item>
|
|
31
|
+
</style>
|
|
32
|
+
</resources>
|