@picsa/capacitor-video-player 8.0.3 → 8.0.4

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.
@@ -435,14 +435,17 @@ public class FullscreenExoPlayerFragment extends Fragment {
435
435
  }
436
436
  if (uri != null || Boolean.TRUE.equals(isInternal)) {
437
437
  // go fullscreen
438
- getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
439
- getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
438
+ Activity hostActivity = getActivity();
439
+ if (hostActivity != null && hostActivity.getWindow() != null) {
440
+ hostActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
441
+ hostActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
442
+ }
440
443
  if (savedInstanceState != null) {
441
444
  mCurrentPosition = savedInstanceState.getInt(PLAYBACK_TIME);
442
445
  }
443
446
 
444
- getActivity()
445
- .runOnUiThread(
447
+ if (hostActivity != null) {
448
+ hostActivity.runOnUiThread(
446
449
  new Runnable() {
447
450
  @Override
448
451
  public void run() {
@@ -516,10 +519,11 @@ public class FullscreenExoPlayerFragment extends Fragment {
516
519
  }
517
520
  }
518
521
  );
519
- } else {
520
- Log.d(TAG, "Video path wrong or type not supported");
521
- Toast.makeText(context, "Video path wrong or type not supported", Toast.LENGTH_SHORT).show();
522
- }
522
+ }
523
+ } else {
524
+ Log.d(TAG, "Video path wrong or type not supported");
525
+ Toast.makeText(context, "Video path wrong or type not supported", Toast.LENGTH_SHORT).show();
526
+ }
523
527
  adjustAspectRatio();
524
528
  return view;
525
529
  }
@@ -531,7 +535,7 @@ public class FullscreenExoPlayerFragment extends Fragment {
531
535
 
532
536
  protected Bitmap doInBackground(Void... params) {
533
537
  final String image = artwork;
534
- if (image != "") {
538
+ if (image != null && !image.trim().isEmpty()) {
535
539
  try {
536
540
  URL url = new URL(image);
537
541
  HttpURLConnection connection = (HttpURLConnection) url.openConnection();
@@ -540,7 +544,7 @@ public class FullscreenExoPlayerFragment extends Fragment {
540
544
  InputStream input = connection.getInputStream();
541
545
  Bitmap myBitmap = BitmapFactory.decodeStream(input);
542
546
  return myBitmap;
543
- } catch (IOException e) {
547
+ } catch (IOException | IllegalArgumentException e) {
544
548
  e.printStackTrace();
545
549
  return null;
546
550
  }
@@ -945,7 +949,9 @@ public class FullscreenExoPlayerFragment extends Fragment {
945
949
  } else {
946
950
  isInPictureInPictureMode = false;
947
951
  if (
948
- Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)
952
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
953
+ packageManager != null &&
954
+ packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)
949
955
  ) {
950
956
  boolean isShowControls = showControls != null ? showControls : true;
951
957
  if (!isShowControls) {
@@ -999,14 +1005,19 @@ public class FullscreenExoPlayerFragment extends Fragment {
999
1005
  if (styledPlayerView == null) {
1000
1006
  return;
1001
1007
  }
1008
+ Context playerContext = context != null ? context : getContext();
1009
+ if (playerContext == null) {
1010
+ Log.w(TAG, "initializePlayer: context is null, skipping");
1011
+ return;
1012
+ }
1002
1013
  if (player == null) {
1003
1014
  ExoTrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory();
1004
- trackSelector = new DefaultTrackSelector(context, videoTrackSelectionFactory);
1015
+ trackSelector = new DefaultTrackSelector(playerContext, videoTrackSelectionFactory);
1005
1016
  LoadControl loadControl = new DefaultLoadControl();
1006
1017
 
1007
1018
  // Media3 Migration Note: DefaultBandwidthMeter now configured automatically by default
1008
1019
  player =
1009
- new ExoPlayer.Builder(context)
1020
+ new ExoPlayer.Builder(playerContext)
1010
1021
  .setSeekBackIncrementMs(10000)
1011
1022
  .setSeekForwardIncrementMs(10000)
1012
1023
  .setTrackSelector(trackSelector)
@@ -1018,6 +1029,24 @@ public class FullscreenExoPlayerFragment extends Fragment {
1018
1029
 
1019
1030
  MediaSource mediaSource = null;
1020
1031
  if (!Boolean.TRUE.equals(isInternal)) {
1032
+ // Re-derive uri/vType from videoPath on every init. They are cleared by
1033
+ // resetVariables() after release, and onStart/onResume can re-enter
1034
+ // initializePlayer() without going through onCreateView, which was the
1035
+ // sole place they were previously computed (NPE on vType.equals()).
1036
+ if (uri == null && videoPath != null && !videoPath.trim().isEmpty()) {
1037
+ try {
1038
+ uri = Uri.parse(videoPath);
1039
+ } catch (Exception e) {
1040
+ Log.e(TAG, "Invalid video path: " + videoPath, e);
1041
+ uri = null;
1042
+ }
1043
+ }
1044
+ if (vType == null && uri != null) {
1045
+ vType = getVideoType(uri);
1046
+ }
1047
+ if (vType == null) {
1048
+ vType = "";
1049
+ }
1021
1050
  if (videoPath != null && (videoPath.startsWith("file:///android_asset") || videoPath.startsWith("content://media"))) {
1022
1051
  mediaSource = buildAssetMediaSource(uri);
1023
1052
  } else if (videoPath != null && !videoPath.trim().isEmpty()) {
@@ -1051,9 +1080,13 @@ public class FullscreenExoPlayerFragment extends Fragment {
1051
1080
  }
1052
1081
  // Media3 Migration Note: MediaSessionConnector replaced with MediaSession.Builder; null check prevents duplicate session ID
1053
1082
  if (mediaSession == null) {
1054
- mediaSession = new MediaSession.Builder(context, player)
1055
- .setId("capacitorvideoplayer")
1056
- .build();
1083
+ try {
1084
+ mediaSession = new MediaSession.Builder(playerContext, player)
1085
+ .setId("capacitorvideoplayer")
1086
+ .build();
1087
+ } catch (Exception e) {
1088
+ Log.e(TAG, "Failed to build MediaSession", e);
1089
+ }
1057
1090
  }
1058
1091
 
1059
1092
  NotificationCenter.defaultCenter().postNotification("initializePlayer", info);
@@ -1091,9 +1124,16 @@ public class FullscreenExoPlayerFragment extends Fragment {
1091
1124
  * Build the Asset MediaSource
1092
1125
  */
1093
1126
  private MediaSource buildAssetMediaSource(Uri uri) {
1127
+ if (uri == null) {
1128
+ return null;
1129
+ }
1130
+ Context dataContext = context != null ? context : getContext();
1131
+ if (dataContext == null) {
1132
+ return null;
1133
+ }
1094
1134
  MediaSource mediaSource = null;
1095
1135
  // Media3 Migration Note: DefaultDataSourceFactory replaced with DefaultDataSource.Factory
1096
- DataSource.Factory dataSourceFactory = new DefaultDataSource.Factory(context);
1136
+ DataSource.Factory dataSourceFactory = new DefaultDataSource.Factory(dataContext);
1097
1137
  mediaSource = new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(MediaItem.fromUri(uri));
1098
1138
  // Get the subtitles if any
1099
1139
  if (sturi != null) {
@@ -1106,7 +1146,14 @@ public class FullscreenExoPlayerFragment extends Fragment {
1106
1146
  * Build the Internal MediaSource
1107
1147
  */
1108
1148
  private MediaSource buildInternalMediaSource(Uri uri) {
1109
- DataSource.Factory dataSourceFactory = new DefaultDataSource.Factory(context);
1149
+ if (uri == null) {
1150
+ return null;
1151
+ }
1152
+ Context dataContext = context != null ? context : getContext();
1153
+ if (dataContext == null) {
1154
+ return null;
1155
+ }
1156
+ DataSource.Factory dataSourceFactory = new DefaultDataSource.Factory(dataContext);
1110
1157
  return new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(MediaItem.fromUri(uri));
1111
1158
  }
1112
1159
 
@@ -1115,6 +1162,13 @@ public class FullscreenExoPlayerFragment extends Fragment {
1115
1162
  * @return MediaSource
1116
1163
  */
1117
1164
  private MediaSource buildHttpMediaSource() {
1165
+ if (uri == null) {
1166
+ Log.e(TAG, "buildHttpMediaSource: uri is null, cannot build media source");
1167
+ return null;
1168
+ }
1169
+ // vType may still be null if getVideoType was never called (e.g. re-init
1170
+ // after release). Default to progressive rather than NPE on equals().
1171
+ String type = vType != null ? vType : "";
1118
1172
  MediaSource mediaSource = null;
1119
1173
 
1120
1174
  DefaultHttpDataSource.Factory httpDataSourceFactory = new DefaultHttpDataSource.Factory();
@@ -1124,38 +1178,57 @@ public class FullscreenExoPlayerFragment extends Fragment {
1124
1178
  httpDataSourceFactory.setAllowCrossProtocolRedirects(true);
1125
1179
 
1126
1180
  // If headers is not null and has data we pass them to the HttpDataSourceFactory
1127
- if (headers != null && headers.length() > 0) {
1128
- // We map the headers(JSObject) to a Map<String, String>
1129
- Map<String, String> headersMap = new HashMap<String, String>();
1130
- for (int i = 0; i < headers.names().length(); i++) {
1131
- try {
1132
- headersMap.put(headers.names().getString(i), headers.get(headers.names().getString(i)).toString());
1133
- } catch (JSONException e) {
1134
- e.printStackTrace();
1181
+ if (headers != null) {
1182
+ try {
1183
+ if (headers.length() > 0 && headers.names() != null) {
1184
+ // We map the headers(JSObject) to a Map<String, String>
1185
+ Map<String, String> headersMap = new HashMap<String, String>();
1186
+ for (int i = 0; i < headers.names().length(); i++) {
1187
+ try {
1188
+ String key = headers.names().getString(i);
1189
+ Object value = headers.get(key);
1190
+ if (key != null && value != null) {
1191
+ headersMap.put(key, value.toString());
1192
+ }
1193
+ } catch (JSONException e) {
1194
+ e.printStackTrace();
1195
+ }
1196
+ }
1197
+ httpDataSourceFactory.setDefaultRequestProperties(headersMap);
1135
1198
  }
1199
+ } catch (Exception e) {
1200
+ Log.e(TAG, "Error applying headers", e);
1136
1201
  }
1137
- httpDataSourceFactory.setDefaultRequestProperties(headersMap);
1138
1202
  }
1139
1203
 
1140
- DataSource.Factory dataSourceFactory = new DefaultDataSource.Factory(context, httpDataSourceFactory);
1204
+ DataSource.Factory dataSourceFactory;
1205
+ Context dataContext = context != null ? context : getContext();
1206
+ if (dataContext == null) {
1207
+ Log.e(TAG, "buildHttpMediaSource: context is null, cannot build media source");
1208
+ return null;
1209
+ }
1210
+ dataSourceFactory = new DefaultDataSource.Factory(dataContext, httpDataSourceFactory);
1141
1211
 
1142
1212
  if (
1143
- vType.equals("mp4") ||
1144
- vType.equals("webm") ||
1145
- vType.equals("ogv") ||
1146
- vType.equals("3gp") ||
1147
- vType.equals("flv") ||
1148
- vType.equals("")
1213
+ "mp4".equals(type) ||
1214
+ "webm".equals(type) ||
1215
+ "ogv".equals(type) ||
1216
+ "3gp".equals(type) ||
1217
+ "flv".equals(type) ||
1218
+ "".equals(type)
1149
1219
  ) {
1150
1220
  mediaSource = new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(MediaItem.fromUri(uri));
1151
- } else if (vType.equals("dash") || vType.equals("mpd")) {
1221
+ } else if ("dash".equals(type) || "mpd".equals(type)) {
1152
1222
  /* adaptive streaming Dash stream */
1153
1223
  DashMediaSource.Factory mediaSourceFactory = new DashMediaSource.Factory(dataSourceFactory);
1154
1224
  mediaSource = mediaSourceFactory.createMediaSource(MediaItem.fromUri(uri));
1155
- } else if (vType.equals("m3u8")) {
1225
+ } else if ("m3u8".equals(type)) {
1156
1226
  mediaSource = new HlsMediaSource.Factory(dataSourceFactory).createMediaSource(MediaItem.fromUri(uri));
1157
- } else if (vType.equals("ism")) {
1227
+ } else if ("ism".equals(type)) {
1158
1228
  mediaSource = new SsMediaSource.Factory(dataSourceFactory).createMediaSource(MediaItem.fromUri(uri));
1229
+ } else {
1230
+ Log.w(TAG, "buildHttpMediaSource: unsupported video type '" + type + "', falling back to progressive");
1231
+ mediaSource = new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(MediaItem.fromUri(uri));
1159
1232
  }
1160
1233
  // Get the subtitles if any
1161
1234
  if (sturi != null) {
@@ -1200,13 +1273,27 @@ public class FullscreenExoPlayerFragment extends Fragment {
1200
1273
  }
1201
1274
 
1202
1275
  private MediaSource getSubTitle(MediaSource mediaSource, Uri sturi, DataSource.Factory dataSourceFactory) {
1276
+ if (mediaSource == null || sturi == null) {
1277
+ return mediaSource;
1278
+ }
1203
1279
  // Create mediaSource with subtitle
1204
1280
  MediaSource[] mediaSources = new MediaSource[2];
1205
1281
  mediaSources[0] = mediaSource;
1206
1282
  String mimeType = getMimeType(sturi);
1283
+ if (mimeType == null || mimeType.isEmpty()) {
1284
+ Log.w(TAG, "getSubTitle: unknown subtitle mime type, skipping subtitles");
1285
+ return mediaSource;
1286
+ }
1207
1287
 
1208
1288
  // We get the language label from the language code
1209
- String languageLabel = Locale.forLanguageTag(language).getDisplayLanguage();
1289
+ String safeLanguage = language != null ? language : "";
1290
+ String languageLabel;
1291
+ try {
1292
+ languageLabel = Locale.forLanguageTag(safeLanguage).getDisplayLanguage();
1293
+ } catch (Exception e) {
1294
+ Log.e(TAG, "Invalid language tag: " + safeLanguage, e);
1295
+ languageLabel = safeLanguage;
1296
+ }
1210
1297
  MediaItem.SubtitleConfiguration subConfig = new MediaItem.SubtitleConfiguration.Builder(sturi)
1211
1298
  .setMimeType(mimeType)
1212
1299
  .setUri(sturi)
@@ -1214,7 +1301,7 @@ public class FullscreenExoPlayerFragment extends Fragment {
1214
1301
  .setLabel(languageLabel)
1215
1302
  .setRoleFlags(C.ROLE_FLAG_CAPTION)
1216
1303
  .setSelectionFlags(C.SELECTION_FLAG_DEFAULT)
1217
- .setLanguage(language)
1304
+ .setLanguage(safeLanguage)
1218
1305
  .build();
1219
1306
 
1220
1307
  SingleSampleMediaSource subtitleSource = new SingleSampleMediaSource.Factory(dataSourceFactory)
@@ -1227,16 +1314,29 @@ public class FullscreenExoPlayerFragment extends Fragment {
1227
1314
  }
1228
1315
 
1229
1316
  private String getMimeType(Uri sturi) {
1230
- String lastSegment = sturi.getLastPathSegment();
1231
- String extension = lastSegment.substring(lastSegment.lastIndexOf(".") + 1);
1317
+ if (sturi == null) {
1318
+ return "";
1319
+ }
1320
+ String lastSegment;
1321
+ try {
1322
+ lastSegment = sturi.getLastPathSegment();
1323
+ } catch (Exception e) {
1324
+ Log.e(TAG, "getMimeType: failed to get last path segment", e);
1325
+ return "";
1326
+ }
1327
+ if (lastSegment == null || lastSegment.isEmpty()) {
1328
+ return "";
1329
+ }
1330
+ int dotIndex = lastSegment.lastIndexOf(".");
1331
+ String extension = (dotIndex != -1 ? lastSegment.substring(dotIndex + 1) : lastSegment).toLowerCase(Locale.ROOT);
1232
1332
  String mimeType = "";
1233
- if (extension.equals("vtt")) {
1333
+ if ("vtt".equals(extension)) {
1234
1334
  mimeType = MimeTypes.TEXT_VTT;
1235
- } else if (extension.equals("srt")) {
1335
+ } else if ("srt".equals(extension)) {
1236
1336
  mimeType = MimeTypes.APPLICATION_SUBRIP;
1237
- } else if (extension.equals("ssa") || extension.equals("ass")) {
1337
+ } else if ("ssa".equals(extension) || "ass".equals(extension)) {
1238
1338
  mimeType = MimeTypes.TEXT_SSA;
1239
- } else if (extension.equals("ttml") || extension.equals("dfxp") || extension.equals("xml")) {
1339
+ } else if ("ttml".equals(extension) || "dfxp".equals(extension) || "xml".equals(extension)) {
1240
1340
  mimeType = MimeTypes.APPLICATION_TTML;
1241
1341
  }
1242
1342
  return mimeType;
@@ -1487,6 +1587,9 @@ public class FullscreenExoPlayerFragment extends Fragment {
1487
1587
  );
1488
1588
  Drawable drawable = a.getDrawable(androidx.mediarouter.R.styleable.MediaRouteButton_externalRouteEnabledDrawable);
1489
1589
  a.recycle();
1590
+ if (drawable == null) {
1591
+ return;
1592
+ }
1490
1593
  DrawableCompat.setTint(drawable, getContext().getResources().getColor(R.color.white));
1491
1594
  drawable.setState(button.getDrawableState());
1492
1595
  button.setRemoteIndicatorDrawable(drawable);
@@ -1498,6 +1601,9 @@ public class FullscreenExoPlayerFragment extends Fragment {
1498
1601
  * @return video type
1499
1602
  */
1500
1603
  private String getVideoType(Uri uri) {
1604
+ if (uri == null) {
1605
+ return "";
1606
+ }
1501
1607
  String ret = null;
1502
1608
  Object obj = uri.getLastPathSegment();
1503
1609
  String lastSegment = (obj == null) ? "" : uri.getLastPathSegment();
@@ -1506,17 +1612,19 @@ public class FullscreenExoPlayerFragment extends Fragment {
1506
1612
  if (lastSegment.length() > 0 && lastSegment.contains(type)) ret = type;
1507
1613
  if (ret == null) {
1508
1614
  List<String> segments = uri.getPathSegments();
1509
- if (segments.size() > 0) {
1615
+ if (segments != null && segments.size() > 0) {
1510
1616
  String segment;
1511
- if (segments.get(segments.size() - 1).equals("manifest")) {
1617
+ if (segments.get(segments.size() - 1).equals("manifest") && segments.size() >= 2) {
1512
1618
  segment = segments.get(segments.size() - 2);
1513
1619
  } else {
1514
1620
  segment = segments.get(segments.size() - 1);
1515
1621
  }
1516
- for (String sType : supportedFormat) {
1517
- if (segment.contains(sType)) {
1518
- ret = sType;
1519
- break;
1622
+ if (segment != null) {
1623
+ for (String sType : supportedFormat) {
1624
+ if (segment.contains(sType)) {
1625
+ ret = sType;
1626
+ break;
1627
+ }
1520
1628
  }
1521
1629
  }
1522
1630
  }
@@ -1548,8 +1656,14 @@ public class FullscreenExoPlayerFragment extends Fragment {
1548
1656
  * @return boolean
1549
1657
  */
1550
1658
  public boolean isApplicationSentToBackground(final Context context) {
1659
+ if (context == null) {
1660
+ return false;
1661
+ }
1551
1662
  int pid = android.os.Process.myPid();
1552
1663
  ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
1664
+ if (am == null) {
1665
+ return false;
1666
+ }
1553
1667
  List<ActivityManager.RunningAppProcessInfo> procInfos = am.getRunningAppProcesses();
1554
1668
  if (procInfos != null) {
1555
1669
  for (ActivityManager.RunningAppProcessInfo appProcess : procInfos) {
@@ -1605,13 +1719,17 @@ public class FullscreenExoPlayerFragment extends Fragment {
1605
1719
  CastButtonFactory.setUpMediaRouteButton(context, mediaRouteButton);
1606
1720
 
1607
1721
  MediaMetadata movieMetadata;
1608
- if (artwork != "") {
1609
- movieMetadata = new MediaMetadata.Builder()
1610
- .setTitle(title)
1611
- .setSubtitle(smallTitle)
1612
- .setMediaType(MediaMetadata.MEDIA_TYPE_MOVIE)
1613
- .setArtworkUri(Uri.parse(artwork))
1614
- .build();
1722
+ if (artwork != null && !artwork.trim().isEmpty()) {
1723
+ MediaMetadata.Builder metadataBuilder = new MediaMetadata.Builder()
1724
+ .setTitle(title)
1725
+ .setSubtitle(smallTitle)
1726
+ .setMediaType(MediaMetadata.MEDIA_TYPE_MOVIE);
1727
+ try {
1728
+ metadataBuilder.setArtworkUri(Uri.parse(artwork));
1729
+ } catch (Exception e) {
1730
+ Log.e(TAG, "Invalid artwork uri: " + artwork, e);
1731
+ }
1732
+ movieMetadata = metadataBuilder.build();
1615
1733
  new setCastImage().execute();
1616
1734
  } else {
1617
1735
  movieMetadata = new MediaMetadata.Builder()
@@ -1694,7 +1812,7 @@ public class FullscreenExoPlayerFragment extends Fragment {
1694
1812
  };
1695
1813
  switch (state) {
1696
1814
  case CastPlayer.STATE_READY:
1697
- if (castPlayer.isPlaying()) {
1815
+ if (castPlayer != null && castPlayer.isPlaying()) {
1698
1816
  NotificationCenter.defaultCenter().postNotification("playerItemPlay", info);
1699
1817
  } else {
1700
1818
  NotificationCenter.defaultCenter().postNotification("playerItemPause", info);
@@ -1708,11 +1826,17 @@ public class FullscreenExoPlayerFragment extends Fragment {
1708
1826
  );
1709
1827
 
1710
1828
  castContext.addCastStateListener(castStateListener);
1711
- mRouter.addCallback(mSelector, mCallback, MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
1829
+ if (mRouter != null && mSelector != null && mCallback != null) {
1830
+ mRouter.addCallback(mSelector, mCallback, MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
1831
+ }
1712
1832
 
1713
1833
  } else {
1714
1834
  Exception e = task.getException();
1715
- e.printStackTrace();
1835
+ if (e != null) {
1836
+ e.printStackTrace();
1837
+ } else {
1838
+ Log.e(TAG, "initializeCastService: CastContext task failed with no exception");
1839
+ }
1716
1840
  }
1717
1841
  }
1718
1842
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@picsa/capacitor-video-player",
3
- "version": "8.0.3",
3
+ "version": "8.0.4",
4
4
  "description": "custom Native Capacitor plugin to play a video",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",