@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.
Files changed (33) hide show
  1. package/CapgoCapacitorJwPlayer.podspec +18 -0
  2. package/Package.swift +30 -0
  3. package/README.md +493 -0
  4. package/android/build.gradle +83 -0
  5. package/android/src/main/AndroidManifest.xml +37 -0
  6. package/android/src/main/java/ee/forgr/capacitor_jw_player/JwPlayerPlugin.java +529 -0
  7. package/android/src/main/java/ee/forgr/capacitor_jw_player/PlayerActivity.java +469 -0
  8. package/android/src/main/res/.gitkeep +0 -0
  9. package/android/src/main/res/drawable/close_button_background.xml +5 -0
  10. package/android/src/main/res/layout/activity_player.xml +28 -0
  11. package/android/src/main/res/values/colors.xml +16 -0
  12. package/android/src/main/res/values/strings.xml +3 -0
  13. package/android/src/main/res/values/styles.xml +32 -0
  14. package/dist/docs.json +459 -0
  15. package/dist/esm/definitions.d.ts +156 -0
  16. package/dist/esm/definitions.js +2 -0
  17. package/dist/esm/definitions.js.map +1 -0
  18. package/dist/esm/index.d.ts +4 -0
  19. package/dist/esm/index.js +7 -0
  20. package/dist/esm/index.js.map +1 -0
  21. package/dist/esm/web.d.ts +57 -0
  22. package/dist/esm/web.js +65 -0
  23. package/dist/esm/web.js.map +1 -0
  24. package/dist/plugin.cjs.js +79 -0
  25. package/dist/plugin.cjs.js.map +1 -0
  26. package/dist/plugin.js +82 -0
  27. package/dist/plugin.js.map +1 -0
  28. package/ios/Sources/JwPlayerPlugin/JwPlayer.swift +24 -0
  29. package/ios/Sources/JwPlayerPlugin/JwPlayerPlugin.swift +749 -0
  30. package/ios/Sources/JwPlayerPlugin/LinkedList.swift +219 -0
  31. package/ios/Sources/JwPlayerPlugin/VideosToPlay.swift +56 -0
  32. package/ios/Tests/JwPlayerPluginTests/JwPlayerPluginTests.swift +15 -0
  33. 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,5 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <shape xmlns:android="http://schemas.android.com/apk/res/android"
3
+ android:shape="oval">
4
+ <solid android:color="#80000000" /> <!-- Semi-transparent black -->
5
+ </shape>
@@ -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,3 @@
1
+ <resources>
2
+ <string name="close_player">Close Player</string>
3
+ </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>