@capgo/capacitor-video-player 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/CapgoCapacitorVideoPlayer.podspec +17 -0
  2. package/Package.swift +28 -0
  3. package/README.md +431 -0
  4. package/android/build.gradle +72 -0
  5. package/android/src/main/AndroidManifest.xml +3 -0
  6. package/android/src/main/java/com/capgo/videoplayer/FullscreenExoPlayerFragment.java +1406 -0
  7. package/android/src/main/java/com/capgo/videoplayer/Notifications/MyRunnable.java +21 -0
  8. package/android/src/main/java/com/capgo/videoplayer/Notifications/NotificationCenter.java +61 -0
  9. package/android/src/main/java/com/capgo/videoplayer/PickerVideo/AdapterVideoList.java +47 -0
  10. package/android/src/main/java/com/capgo/videoplayer/PickerVideo/ModelVideo.java +49 -0
  11. package/android/src/main/java/com/capgo/videoplayer/PickerVideo/PickerVideoFragment.java +116 -0
  12. package/android/src/main/java/com/capgo/videoplayer/PickerVideo/VideoRecyclerViewHolder.java +65 -0
  13. package/android/src/main/java/com/capgo/videoplayer/Utilities/FilesUtils.java +38 -0
  14. package/android/src/main/java/com/capgo/videoplayer/Utilities/FragmentUtils.java +32 -0
  15. package/android/src/main/java/com/capgo/videoplayer/VideoPlayer.java +71 -0
  16. package/android/src/main/java/com/capgo/videoplayer/VideoPlayerPlugin.java +1239 -0
  17. package/android/src/main/res/.gitkeep +0 -0
  18. package/android/src/main/res/drawable/bg_round_rect_white_50.xml +9 -0
  19. package/android/src/main/res/drawable/bg_rounded_rectangle_white_corner_rounded.xml +10 -0
  20. package/android/src/main/res/drawable/exo_close_btn.xml +12 -0
  21. package/android/src/main/res/drawable/gradient_transparent_middle.xml +12 -0
  22. package/android/src/main/res/drawable/ic_arrow_left.xml +5 -0
  23. package/android/src/main/res/drawable/ic_baseline_lq.xml +7 -0
  24. package/android/src/main/res/drawable/ic_exo_icon_fastforward.xml +35 -0
  25. package/android/src/main/res/drawable/ic_exo_icon_pause.xml +26 -0
  26. package/android/src/main/res/drawable/ic_exo_icon_play.xml +36 -0
  27. package/android/src/main/res/drawable/ic_exo_icon_rewind.xml +35 -0
  28. package/android/src/main/res/drawable/ic_expand.xml +5 -0
  29. package/android/src/main/res/drawable/ic_fit.xml +5 -0
  30. package/android/src/main/res/drawable/ic_image_background.xml +12 -0
  31. package/android/src/main/res/drawable/ic_img_16_9_background.xml +10 -0
  32. package/android/src/main/res/drawable/ic_img_9_16_background.xml +10 -0
  33. package/android/src/main/res/drawable/ic_outline_lock.xml +5 -0
  34. package/android/src/main/res/drawable/ic_outline_lock_open.xml +5 -0
  35. package/android/src/main/res/drawable/ic_pip_white.xml +5 -0
  36. package/android/src/main/res/drawable/ic_views.xml +18 -0
  37. package/android/src/main/res/drawable/ic_zoom.xml +5 -0
  38. package/android/src/main/res/layout/bridge_layout_main.xml +15 -0
  39. package/android/src/main/res/layout/exo_playback_control_view.xml +287 -0
  40. package/android/src/main/res/layout/exoplayer_layout_youtube.xml +361 -0
  41. package/android/src/main/res/layout/fragment_fs_exoplayer.xml +50 -0
  42. package/android/src/main/res/layout/fragment_picker_video.xml +21 -0
  43. package/android/src/main/res/layout/row_video.xml +76 -0
  44. package/android/src/main/res/values/colors.xml +14 -0
  45. package/android/src/main/res/values/strings.xml +3 -0
  46. package/android/src/main/res/values/styles.xml +3 -0
  47. package/dist/docs.json +686 -0
  48. package/dist/esm/definitions.d.ts +307 -0
  49. package/dist/esm/definitions.js +2 -0
  50. package/dist/esm/definitions.js.map +1 -0
  51. package/dist/esm/index.d.ts +4 -0
  52. package/dist/esm/index.js +7 -0
  53. package/dist/esm/index.js.map +1 -0
  54. package/dist/esm/web-utils/video-types.d.ts +4 -0
  55. package/dist/esm/web-utils/video-types.js +9 -0
  56. package/dist/esm/web-utils/video-types.js.map +1 -0
  57. package/dist/esm/web-utils/videoplayer.d.ts +30 -0
  58. package/dist/esm/web-utils/videoplayer.js +323 -0
  59. package/dist/esm/web-utils/videoplayer.js.map +1 -0
  60. package/dist/esm/web.d.ts +121 -0
  61. package/dist/esm/web.js +675 -0
  62. package/dist/esm/web.js.map +1 -0
  63. package/dist/plugin.cjs.js +1019 -0
  64. package/dist/plugin.cjs.js.map +1 -0
  65. package/dist/plugin.js +1021 -0
  66. package/dist/plugin.js.map +1 -0
  67. package/ios/Sources/VideoPlayerPlugin/VideoPlayer.swift +8 -0
  68. package/ios/Sources/VideoPlayerPlugin/VideoPlayerPlugin.swift +23 -0
  69. package/ios/Tests/VideoPlayerPluginTests/VideoPlayerPluginTests.swift +15 -0
  70. package/package.json +85 -0
@@ -0,0 +1,1406 @@
1
+ package com.capgo.videoplayer;
2
+
3
+ import android.annotation.SuppressLint;
4
+ import android.app.Activity;
5
+ import android.app.ActivityManager;
6
+ import android.app.PictureInPictureParams;
7
+ import android.content.ContentUris;
8
+ import android.content.Context;
9
+ import android.content.pm.ActivityInfo;
10
+ import android.content.pm.PackageManager;
11
+ import android.content.res.Configuration;
12
+ import android.content.res.TypedArray;
13
+ import android.graphics.Bitmap;
14
+ import android.graphics.BitmapFactory;
15
+ import android.graphics.Color;
16
+ import android.graphics.drawable.Drawable;
17
+ import android.net.Uri;
18
+ import android.os.AsyncTask;
19
+ import android.os.Build;
20
+ import android.os.Bundle;
21
+ import android.os.Handler;
22
+ import android.provider.MediaStore;
23
+ import android.support.v4.media.session.MediaSessionCompat;
24
+ import android.util.Log;
25
+ import android.util.Rational;
26
+ import android.util.TypedValue;
27
+ import android.view.KeyEvent;
28
+ import android.view.LayoutInflater;
29
+ import android.view.View;
30
+ import android.view.ViewGroup;
31
+ import android.view.WindowManager;
32
+ import android.widget.ImageButton;
33
+ import android.widget.ImageView;
34
+ import android.widget.LinearLayout;
35
+ import android.widget.ProgressBar;
36
+ import android.widget.TextView;
37
+ import android.widget.Toast;
38
+ import androidx.annotation.RequiresApi;
39
+ import androidx.appcompat.view.ContextThemeWrapper;
40
+ import androidx.constraintlayout.widget.ConstraintLayout;
41
+ import androidx.core.graphics.drawable.DrawableCompat;
42
+ import androidx.fragment.app.Fragment;
43
+ import androidx.mediarouter.app.MediaRouteButton;
44
+ import androidx.mediarouter.media.MediaControlIntent;
45
+ import androidx.mediarouter.media.MediaRouteSelector;
46
+ import androidx.mediarouter.media.MediaRouter;
47
+ import com.capgo.videoplayer.Notifications.NotificationCenter;
48
+ import com.getcapacitor.JSObject;
49
+ import com.google.android.exoplayer2.C;
50
+ import com.google.android.exoplayer2.DefaultLoadControl;
51
+ import com.google.android.exoplayer2.ExoPlayer;
52
+ import com.google.android.exoplayer2.LoadControl;
53
+ import com.google.android.exoplayer2.MediaItem;
54
+ import com.google.android.exoplayer2.MediaMetadata;
55
+ import com.google.android.exoplayer2.PlaybackParameters;
56
+ import com.google.android.exoplayer2.Player;
57
+ import com.google.android.exoplayer2.audio.AudioAttributes;
58
+ import com.google.android.exoplayer2.ext.cast.CastPlayer;
59
+ import com.google.android.exoplayer2.ext.cast.SessionAvailabilityListener;
60
+ import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector;
61
+ import com.google.android.exoplayer2.source.MediaSource;
62
+ import com.google.android.exoplayer2.source.MergingMediaSource;
63
+ import com.google.android.exoplayer2.source.ProgressiveMediaSource;
64
+ import com.google.android.exoplayer2.source.SingleSampleMediaSource;
65
+ import com.google.android.exoplayer2.source.dash.DashMediaSource;
66
+ import com.google.android.exoplayer2.source.hls.HlsMediaSource;
67
+ import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
68
+ import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
69
+ import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
70
+ import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
71
+ import com.google.android.exoplayer2.trackselection.TrackSelector;
72
+ import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
73
+ import com.google.android.exoplayer2.ui.CaptionStyleCompat;
74
+ import com.google.android.exoplayer2.ui.DefaultTimeBar;
75
+ import com.google.android.exoplayer2.ui.PlayerControlView;
76
+ import com.google.android.exoplayer2.ui.StyledPlayerView;
77
+ import com.google.android.exoplayer2.upstream.DataSource;
78
+ import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
79
+ import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
80
+ import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
81
+ import com.google.android.exoplayer2.util.MimeTypes;
82
+ import com.google.android.exoplayer2.util.Util;
83
+ import com.google.android.gms.cast.framework.CastButtonFactory;
84
+ import com.google.android.gms.cast.framework.CastContext;
85
+ import com.google.android.gms.cast.framework.CastState;
86
+ import com.google.android.gms.cast.framework.CastStateListener;
87
+ import com.google.android.gms.tasks.OnCompleteListener;
88
+ import com.google.android.gms.tasks.Task;
89
+ import java.io.IOException;
90
+ import java.io.InputStream;
91
+ import java.net.HttpURLConnection;
92
+ import java.net.URL;
93
+ import java.util.Arrays;
94
+ import java.util.HashMap;
95
+ import java.util.List;
96
+ import java.util.Locale;
97
+ import java.util.Map;
98
+ import java.util.concurrent.Executor;
99
+ import java.util.concurrent.Executors;
100
+ import org.json.JSONException;
101
+
102
+ public class FullscreenExoPlayerFragment extends Fragment {
103
+
104
+ public String videoPath;
105
+ public Float videoRate;
106
+ public String playerId;
107
+ public String subTitle;
108
+ public String language;
109
+ public JSObject subTitleOptions;
110
+ public JSObject headers;
111
+ public Boolean isTV;
112
+ public Boolean isInternal;
113
+ public Long videoId;
114
+ public Boolean exitOnEnd;
115
+ public Boolean loopOnEnd;
116
+ public Boolean pipEnabled;
117
+ public Boolean bkModeEnabled;
118
+ public Boolean showControls;
119
+ public String displayMode = "all";
120
+ public String title;
121
+ public String smallTitle;
122
+ public String accentColor;
123
+ public Boolean chromecast;
124
+ public String artwork;
125
+
126
+ private static final String TAG = FullscreenExoPlayerFragment.class.getName();
127
+ public static final long UNKNOWN_TIME = -1L;
128
+ private final List<String> supportedFormat = Arrays.asList(
129
+ new String[] { "mp4", "webm", "ogv", "3gp", "flv", "dash", "mpd", "m3u8", "ism", "ytube", "" }
130
+ );
131
+ private Player.Listener listener;
132
+ private StyledPlayerView styledPlayerView;
133
+ private String vType = null;
134
+ private static ExoPlayer player;
135
+ private boolean playWhenReady = true;
136
+ private boolean firstReadyToPlay = true;
137
+ private boolean isEnded = false;
138
+ private int currentWindow = 0;
139
+ private long playbackPosition = 0;
140
+ private Uri uri = null;
141
+ private Uri sturi = null;
142
+ private ProgressBar Pbar;
143
+ private View view;
144
+ private ImageButton closeBtn;
145
+ private ImageButton pipBtn;
146
+ private ImageButton resizeBtn;
147
+ private ConstraintLayout constLayout;
148
+ private LinearLayout linearLayout;
149
+ private TextView header_tv;
150
+ private TextView header_below;
151
+ private static ImageView cast_image;
152
+ private DefaultTimeBar exo_progress;
153
+ private TextView exo_position;
154
+ private TextView exo_duration;
155
+ private TextView exo_label_separation;
156
+ private TextView live_text;
157
+ private Context context;
158
+ private boolean isMuted = false;
159
+ private float curVolume = (float) 0.5;
160
+ private String stForeColor = "";
161
+ private String stBackColor = "";
162
+ private Integer stFontSize = 16;
163
+ private boolean isInPictureInPictureMode = false;
164
+ private TrackSelector trackSelector;
165
+ // Current playback position (in milliseconds).
166
+ private int mCurrentPosition;
167
+ private int mDuration;
168
+ private static final int videoStep = 10000;
169
+ private boolean isCastSession = false;
170
+
171
+ // Tag for the instance state bundle.
172
+ private static final String PLAYBACK_TIME = "play_time";
173
+
174
+ private PictureInPictureParams.Builder pictureInPictureParams;
175
+ private MediaSessionCompat mediaSession;
176
+ private MediaSessionConnector mediaSessionConnector;
177
+ private PlayerControlView.VisibilityListener visibilityListener;
178
+ private PackageManager packageManager;
179
+ private Boolean isPIPModeeEnabled = true;
180
+ final Handler handler = new Handler();
181
+ final Runnable mRunnable = new Runnable() {
182
+ @RequiresApi(api = Build.VERSION_CODES.N)
183
+ public void run() {
184
+ checkPIPPermission();
185
+ }
186
+ };
187
+
188
+ private Integer resizeStatus = AspectRatioFrameLayout.RESIZE_MODE_FIT;
189
+ private MediaRouteButton mediaRouteButton;
190
+ private CastContext castContext;
191
+ private CastPlayer castPlayer;
192
+ private MediaItem mediaItem;
193
+ private MediaRouter mRouter;
194
+ private MediaRouter.Callback mCallback = new EmptyCallback();
195
+ private MediaRouteSelector mSelector;
196
+ private CastStateListener castStateListener = null;
197
+ private Boolean playerReady = false;
198
+
199
+ /**
200
+ * Create Fragment View
201
+ * @param inflater
202
+ * @param container
203
+ * @param savedInstanceState
204
+ * @return View
205
+ */
206
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
207
+ context = container.getContext();
208
+ packageManager = context.getPackageManager();
209
+ view = inflater.inflate(R.layout.fragment_fs_exoplayer, container, false);
210
+ constLayout = view.findViewById(R.id.fsExoPlayer);
211
+ linearLayout = view.findViewById(R.id.linearLayout);
212
+ styledPlayerView = view.findViewById(R.id.videoViewId);
213
+ header_tv = view.findViewById(R.id.header_tv);
214
+ header_below = view.findViewById(R.id.header_below);
215
+ Pbar = view.findViewById(R.id.indeterminateBar);
216
+ exo_progress = view.findViewById(R.id.exo_progress);
217
+ exo_progress.setVisibility(View.GONE);
218
+ exo_position = view.findViewById(R.id.exo_position);
219
+ exo_position.setVisibility(View.GONE);
220
+ exo_duration = view.findViewById(R.id.exo_duration);
221
+ exo_duration.setVisibility(View.GONE);
222
+ exo_label_separation = view.findViewById(R.id.exo_label_separation);
223
+ exo_label_separation.setVisibility(View.GONE);
224
+ live_text = view.findViewById(R.id.live_text);
225
+ resizeBtn = view.findViewById(R.id.exo_resize);
226
+ cast_image = view.findViewById(R.id.cast_image);
227
+ mediaRouteButton = view.findViewById(R.id.media_route_button);
228
+ styledPlayerView.setShowPreviousButton(false);
229
+ styledPlayerView.setShowNextButton(false);
230
+ styledPlayerView.setShowFastForwardButton(false);
231
+ styledPlayerView.setShowRewindButton(false);
232
+
233
+ Activity mAct = getActivity();
234
+ if (displayMode.equals("landscape")) {
235
+ mAct.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
236
+ }
237
+ if (displayMode.equals("portrait")) {
238
+ mAct.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
239
+ }
240
+ if (!showControls) {
241
+ styledPlayerView.setUseController(false);
242
+ } else {
243
+ styledPlayerView.setUseController(true);
244
+ }
245
+
246
+ if (!chromecast) {
247
+ mediaRouteButton.setVisibility(View.GONE);
248
+ } else {
249
+ initializeCastService();
250
+ }
251
+
252
+ if (title != "") {
253
+ header_tv.setText(title);
254
+ }
255
+ if (smallTitle != "") {
256
+ header_below.setText(smallTitle);
257
+ }
258
+ if (accentColor != "") {
259
+ Pbar.getIndeterminateDrawable().setColorFilter(Color.parseColor(accentColor), android.graphics.PorterDuff.Mode.MULTIPLY);
260
+ exo_progress.setPlayedColor(Color.parseColor(accentColor));
261
+ exo_progress.setScrubberColor(Color.parseColor(accentColor));
262
+ }
263
+
264
+ closeBtn = view.findViewById(R.id.exo_close);
265
+ pipBtn = view.findViewById(R.id.exo_pip);
266
+ styledPlayerView.requestFocus();
267
+ linearLayout.setVisibility(View.INVISIBLE);
268
+ styledPlayerView.setControllerShowTimeoutMs(3000);
269
+ styledPlayerView.setControllerVisibilityListener(
270
+ new StyledPlayerView.ControllerVisibilityListener() {
271
+ @Override
272
+ public void onVisibilityChanged(int visibility) {
273
+ linearLayout.setVisibility(visibility);
274
+ }
275
+ }
276
+ );
277
+
278
+ listener = new Player.Listener() {
279
+ @Override
280
+ public void onPlayerStateChanged(boolean playWhenReady, int state) {
281
+ String stateString;
282
+ Map<String, Object> info = new HashMap<String, Object>() {
283
+ {
284
+ put("fromPlayerId", playerId);
285
+ put("currentTime", String.valueOf(player.getCurrentPosition() / 1000));
286
+ }
287
+ };
288
+
289
+ switch (state) {
290
+ case ExoPlayer.STATE_IDLE:
291
+ stateString = "ExoPlayer.STATE_IDLE -";
292
+ Toast.makeText(context, "Video Url not found", Toast.LENGTH_SHORT).show();
293
+ playerExit();
294
+ break;
295
+ case ExoPlayer.STATE_BUFFERING:
296
+ stateString = "ExoPlayer.STATE_BUFFERING -";
297
+ Pbar.setVisibility(View.VISIBLE);
298
+ break;
299
+ case ExoPlayer.STATE_READY:
300
+ stateString = "ExoPlayer.STATE_READY -";
301
+ Pbar.setVisibility(View.GONE);
302
+ playerReady = true;
303
+ if (!showControls) {
304
+ styledPlayerView.setUseController(false);
305
+ } else {
306
+ styledPlayerView.setUseController(true);
307
+ }
308
+ linearLayout.setVisibility(View.INVISIBLE);
309
+ Log.v(TAG, "**** in ExoPlayer.STATE_READY firstReadyToPlay " + firstReadyToPlay);
310
+
311
+ if (firstReadyToPlay) {
312
+ firstReadyToPlay = false;
313
+ NotificationCenter.defaultCenter().postNotification("playerItemReady", info);
314
+ play();
315
+ Log.v(TAG, "**** in ExoPlayer.STATE_READY firstReadyToPlay player.isPlaying" + player.isPlaying());
316
+ player.seekTo(currentWindow, playbackPosition);
317
+
318
+ // We show progress bar, position and duration only when the video is not live
319
+ if (!player.isCurrentMediaItemLive()) {
320
+ exo_progress.setVisibility(View.VISIBLE);
321
+ exo_position.setVisibility(View.VISIBLE);
322
+ exo_duration.setVisibility(View.VISIBLE);
323
+ exo_label_separation.setVisibility(View.VISIBLE);
324
+ styledPlayerView.setShowFastForwardButton(true);
325
+ styledPlayerView.setShowRewindButton(true);
326
+ } else {
327
+ live_text.setVisibility(View.VISIBLE);
328
+ }
329
+ } else {
330
+ Log.v(TAG, "**** in ExoPlayer.STATE_READY isPlaying " + player.isPlaying());
331
+ if (player.isPlaying()) {
332
+ Log.v(TAG, "**** in ExoPlayer.STATE_READY going to notify playerItemPlay ");
333
+ NotificationCenter.defaultCenter().postNotification("playerItemPlay", info);
334
+ resizeBtn.setVisibility(View.VISIBLE);
335
+
336
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && pipEnabled) {
337
+ pipBtn.setVisibility(View.VISIBLE);
338
+ }
339
+ } else {
340
+ Log.v(TAG, "**** in ExoPlayer.STATE_READY going to notify playerItemPause ");
341
+ NotificationCenter.defaultCenter().postNotification("playerItemPause", info);
342
+ }
343
+ }
344
+ break;
345
+ case ExoPlayer.STATE_ENDED:
346
+ stateString = "ExoPlayer.STATE_ENDED -";
347
+ Log.v(TAG, "**** in ExoPlayer.STATE_ENDED going to notify playerItemEnd ");
348
+
349
+ player.seekTo(0);
350
+ player.setVolume(curVolume);
351
+ player.setPlayWhenReady(false);
352
+ if (exitOnEnd) {
353
+ releasePlayer();
354
+ /*
355
+ Activity mAct = getActivity();
356
+ int mOrient = mAct.getRequestedOrientation();
357
+ if (mOrient == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {
358
+ mAct.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
359
+ }
360
+ */
361
+ NotificationCenter.defaultCenter().postNotification("playerItemEnd", info);
362
+ }
363
+ break;
364
+ default:
365
+ stateString = "UNKNOWN_STATE -";
366
+ break;
367
+ }
368
+ }
369
+ };
370
+
371
+ if (isTV) {
372
+ Toast.makeText(context, "Device is a TV ", Toast.LENGTH_SHORT).show();
373
+ }
374
+
375
+ if (!isInternal) {
376
+ uri = Uri.parse(videoPath);
377
+ sturi = subTitle != null ? Uri.parse(subTitle) : null;
378
+
379
+ stForeColor = subTitleOptions.has("foregroundColor") ? subTitleOptions.getString("foregroundColor") : "rgba(255,255,255,1)";
380
+ stBackColor = subTitleOptions.has("backgroundColor") ? subTitleOptions.getString("backgroundColor") : "rgba(0,0,0,1)";
381
+ stFontSize = subTitleOptions.has("fontSize") ? subTitleOptions.getInteger("fontSize") : 16;
382
+ // get video type
383
+ vType = getVideoType(uri);
384
+ Log.v(TAG, "display url: " + uri);
385
+ Log.v(TAG, "display subtitle url: " + sturi);
386
+ Log.v(TAG, "display isTV: " + isTV);
387
+ Log.v(TAG, "display vType: " + vType);
388
+ }
389
+ if (uri != null || isInternal) {
390
+ // go fullscreen
391
+ getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
392
+ getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
393
+ if (savedInstanceState != null) {
394
+ mCurrentPosition = savedInstanceState.getInt(PLAYBACK_TIME);
395
+ }
396
+
397
+ getActivity().runOnUiThread(
398
+ new Runnable() {
399
+ @Override
400
+ public void run() {
401
+ // Set the onKey listener
402
+ view.setFocusableInTouchMode(true);
403
+ view.requestFocus();
404
+ view.setOnKeyListener(
405
+ new View.OnKeyListener() {
406
+ @Override
407
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
408
+ if (event.getAction() == KeyEvent.ACTION_UP) {
409
+ long videoPosition = player.getCurrentPosition();
410
+ Log.v(TAG, "$$$$ onKey " + keyCode + " $$$$");
411
+ if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_HOME) {
412
+ Log.v(TAG, "$$$$ Going to backpress $$$$");
413
+ backPressed();
414
+ } else if (isTV) {
415
+ switch (keyCode) {
416
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
417
+ fastForward(videoPosition, 1);
418
+ break;
419
+ case KeyEvent.KEYCODE_DPAD_LEFT:
420
+ rewind(videoPosition, 1);
421
+ break;
422
+ case KeyEvent.KEYCODE_DPAD_CENTER:
423
+ play_pause();
424
+ break;
425
+ case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
426
+ fastForward(videoPosition, 2);
427
+ break;
428
+ case KeyEvent.KEYCODE_MEDIA_REWIND:
429
+ rewind(videoPosition, 2);
430
+ break;
431
+ }
432
+ }
433
+ return true;
434
+ } else {
435
+ return false;
436
+ }
437
+ }
438
+ }
439
+ );
440
+
441
+ // initialize the player
442
+ initializePlayer();
443
+
444
+ closeBtn.setOnClickListener(
445
+ new View.OnClickListener() {
446
+ @Override
447
+ public void onClick(View view) {
448
+ playerExit();
449
+ }
450
+ }
451
+ );
452
+ pipBtn.setOnClickListener(
453
+ new View.OnClickListener() {
454
+ @Override
455
+ public void onClick(View view) {
456
+ pictureInPictureMode();
457
+ }
458
+ }
459
+ );
460
+ resizeBtn.setOnClickListener(
461
+ new View.OnClickListener() {
462
+ @Override
463
+ public void onClick(View view) {
464
+ resizePressed();
465
+ }
466
+ }
467
+ );
468
+ }
469
+ }
470
+ );
471
+ } else {
472
+ Log.d(TAG, "Video path wrong or type not supported");
473
+ Toast.makeText(context, "Video path wrong or type not supported", Toast.LENGTH_SHORT).show();
474
+ }
475
+ adjustAspectRatio();
476
+ return view;
477
+ }
478
+
479
+ /**
480
+ * Sets the cast image in playerView when it is connected to a cast device
481
+ */
482
+ private class setCastImage extends AsyncTask<Void, Void, Bitmap> {
483
+
484
+ protected Bitmap doInBackground(Void... params) {
485
+ final String image = artwork;
486
+ if (image != "") {
487
+ try {
488
+ URL url = new URL(image);
489
+ HttpURLConnection connection = (HttpURLConnection) url.openConnection();
490
+ connection.setDoInput(true);
491
+ connection.connect();
492
+ InputStream input = connection.getInputStream();
493
+ Bitmap myBitmap = BitmapFactory.decodeStream(input);
494
+ return myBitmap;
495
+ } catch (IOException e) {
496
+ e.printStackTrace();
497
+ return null;
498
+ }
499
+ } else {
500
+ return null;
501
+ }
502
+ }
503
+
504
+ @Override
505
+ protected void onPostExecute(Bitmap result) {
506
+ cast_image.setImageBitmap(result);
507
+ }
508
+ }
509
+
510
+ /**
511
+ * Show controller
512
+ */
513
+ public void showController() {
514
+ styledPlayerView.showController();
515
+ }
516
+
517
+ /**
518
+ * isControllerIsFullyVisible
519
+ */
520
+ public boolean isControllerIsFullyVisible() {
521
+ return styledPlayerView.isControllerFullyVisible();
522
+ }
523
+
524
+ /**
525
+ * Perform backPressed Action
526
+ */
527
+ private void backPressed() {
528
+ if (isCastSession) {
529
+ playerExit();
530
+ return;
531
+ }
532
+ if (
533
+ !isInPictureInPictureMode &&
534
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.N &&
535
+ packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE) &&
536
+ isPIPModeeEnabled &&
537
+ pipEnabled &&
538
+ playerReady // <- playerReady: this prevents a crash if the user presses back before the player is ready (when enters in pip mode and tries to get the aspect ratio)
539
+ ) {
540
+ pictureInPictureMode();
541
+ } else {
542
+ playerExit();
543
+ }
544
+ }
545
+
546
+ private void resizePressed() {
547
+ if (resizeStatus == AspectRatioFrameLayout.RESIZE_MODE_FIT) {
548
+ styledPlayerView.setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FILL);
549
+ resizeStatus = AspectRatioFrameLayout.RESIZE_MODE_FILL;
550
+ resizeBtn.setImageResource(R.drawable.ic_zoom);
551
+ } else if (resizeStatus == AspectRatioFrameLayout.RESIZE_MODE_FILL) {
552
+ styledPlayerView.setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_ZOOM);
553
+ resizeStatus = AspectRatioFrameLayout.RESIZE_MODE_ZOOM;
554
+ resizeBtn.setImageResource(R.drawable.ic_fit);
555
+ } else {
556
+ styledPlayerView.setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FIT);
557
+ resizeStatus = AspectRatioFrameLayout.RESIZE_MODE_FIT;
558
+ resizeBtn.setImageResource(R.drawable.ic_expand);
559
+ }
560
+ }
561
+
562
+ public void playerExit() {
563
+ Map<String, Object> info = new HashMap<String, Object>() {
564
+ {
565
+ put("dismiss", "1");
566
+ put("currentTime", getCurrentTime());
567
+ }
568
+ };
569
+ if (player != null) {
570
+ player.seekTo(0);
571
+ player.setVolume(curVolume);
572
+ }
573
+ releasePlayer();
574
+ /*
575
+ Activity mAct = getActivity();
576
+ int mOrient = mAct.getRequestedOrientation();
577
+ if (mOrient == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {
578
+ mAct.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
579
+ }
580
+ */
581
+ // We control if the user lock the screen when the player is in pip mode
582
+ try {
583
+ NotificationCenter.defaultCenter().postNotification("playerFullscreenDismiss", info);
584
+ } catch (Exception e) {
585
+ Log.e(TAG, "Error in posting notification");
586
+ }
587
+ }
588
+
589
+ /**
590
+ * Perform pictureInPictureMode Action
591
+ */
592
+ private void pictureInPictureMode() {
593
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)) {
594
+ styledPlayerView.setUseController(false);
595
+ styledPlayerView.setControllerAutoShow(false);
596
+ linearLayout.setVisibility(View.INVISIBLE);
597
+ Log.v(TAG, "PIP break 1");
598
+ // require android O or higher
599
+ if (
600
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)
601
+ ) {
602
+ pictureInPictureParams = new PictureInPictureParams.Builder();
603
+ // setup height and width of the PIP window
604
+ Rational aspectRatio = new Rational(player.getVideoFormat().width, player.getVideoFormat().height);
605
+ pictureInPictureParams.setAspectRatio(aspectRatio).build();
606
+ getActivity().enterPictureInPictureMode(pictureInPictureParams.build());
607
+ Log.v(TAG, "PIP break 2");
608
+ } else {
609
+ getActivity().enterPictureInPictureMode();
610
+ Log.v(TAG, "PIP break 3");
611
+ }
612
+ isInPictureInPictureMode = getActivity().isInPictureInPictureMode();
613
+ if (sturi != null) {
614
+ setSubtitle(true);
615
+ }
616
+ if (player != null) play();
617
+
618
+ handler.postDelayed(mRunnable, 100);
619
+ Log.v(TAG, "PIP break 4");
620
+ } else {
621
+ Log.v(TAG, "pictureInPictureMode: doesn't support PIP");
622
+ }
623
+ }
624
+
625
+ @RequiresApi(api = Build.VERSION_CODES.N)
626
+ private void checkPIPPermission() {
627
+ isPIPModeeEnabled = isInPictureInPictureMode;
628
+ if (!isInPictureInPictureMode) {
629
+ backPressed();
630
+ }
631
+ }
632
+
633
+ /**
634
+ * Perform onStart Action
635
+ */
636
+ @Override
637
+ public void onStart() {
638
+ super.onStart();
639
+ //if (chromecast && castContext != null) mRouter.addCallback(mSelector, mCallback, MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
640
+
641
+ if (Util.SDK_INT >= 24) {
642
+ if (styledPlayerView != null) {
643
+ // If cast is playing then it doesn't start the local player once get backs from background
644
+ if (castContext != null && chromecast && castPlayer.isCastSessionAvailable()) return;
645
+
646
+ initializePlayer();
647
+ if (player.getCurrentPosition() != 0) {
648
+ firstReadyToPlay = false;
649
+ play();
650
+ }
651
+ } else {
652
+ getActivity().finishAndRemoveTask();
653
+ }
654
+ }
655
+ }
656
+
657
+ /**
658
+ * Perform onStop Action
659
+ */
660
+ @Override
661
+ public void onStop() {
662
+ super.onStop();
663
+ boolean isAppBackground = false;
664
+ if (bkModeEnabled) isAppBackground = isApplicationSentToBackground(context);
665
+ if (isInPictureInPictureMode) {
666
+ linearLayout.setVisibility(View.VISIBLE);
667
+ playerExit();
668
+ getActivity().finishAndRemoveTask();
669
+ }
670
+ }
671
+
672
+ /**
673
+ * Perform onDestroy Action
674
+ */
675
+ @Override
676
+ public void onDestroy() {
677
+ super.onDestroy();
678
+ if (chromecast) mRouter.removeCallback(mCallback);
679
+ releasePlayer();
680
+ }
681
+
682
+ /**
683
+ * Perform onPause Action
684
+ */
685
+ @Override
686
+ public void onPause() {
687
+ super.onPause();
688
+ if (chromecast) castContext.removeCastStateListener(castStateListener);
689
+ boolean isAppBackground = false;
690
+ if (bkModeEnabled) isAppBackground = isApplicationSentToBackground(context);
691
+
692
+ if (!isInPictureInPictureMode) {
693
+ if (Util.SDK_INT < 24) {
694
+ if (player != null) player.setPlayWhenReady(false);
695
+ releasePlayer();
696
+ } else {
697
+ if (isAppBackground) {
698
+ if (player != null) {
699
+ if (player.isPlaying()) play();
700
+ }
701
+ } else {
702
+ pause();
703
+ }
704
+ }
705
+ } else {
706
+ if (linearLayout.getVisibility() == View.VISIBLE) {
707
+ linearLayout.setVisibility(View.INVISIBLE);
708
+ }
709
+ if ((isInPictureInPictureMode || isAppBackground) && player != null) play();
710
+ }
711
+ }
712
+
713
+ /**
714
+ * Release the player
715
+ */
716
+ public void releasePlayer() {
717
+ if (player != null) {
718
+ playWhenReady = player.getPlayWhenReady();
719
+ playbackPosition = player.getCurrentPosition();
720
+ currentWindow = player.getCurrentWindowIndex();
721
+ mediaSessionConnector.setPlayer(null);
722
+ mediaSession.setActive(false);
723
+ player.setRepeatMode(player.REPEAT_MODE_OFF);
724
+ player.removeListener(listener);
725
+ player.release();
726
+ player = null;
727
+ showSystemUI();
728
+ resetVariables();
729
+ if (chromecast) {
730
+ castPlayer.release();
731
+ castPlayer = null;
732
+ }
733
+ }
734
+ }
735
+
736
+ /**
737
+ * Perform onResume Action
738
+ */
739
+ @Override
740
+ public void onResume() {
741
+ super.onResume();
742
+ //if (chromecast && castContext != null) castContext.addCastStateListener(castStateListener);
743
+ if (!isInPictureInPictureMode) {
744
+ hideSystemUi();
745
+ if ((Util.SDK_INT < 24 || player == null)) {
746
+ initializePlayer();
747
+ }
748
+ } else {
749
+ isInPictureInPictureMode = false;
750
+ if (
751
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)
752
+ ) {
753
+ if (!showControls) {
754
+ styledPlayerView.setUseController(false);
755
+ } else {
756
+ styledPlayerView.setUseController(true);
757
+ }
758
+ }
759
+ if (sturi != null) {
760
+ setSubtitle(false);
761
+ }
762
+ }
763
+ }
764
+
765
+ /**
766
+ * Hide System UI
767
+ */
768
+ @SuppressLint("InlinedApi")
769
+ private void hideSystemUi() {
770
+ if (styledPlayerView != null) styledPlayerView.setSystemUiVisibility(
771
+ View.SYSTEM_UI_FLAG_LOW_PROFILE |
772
+ View.SYSTEM_UI_FLAG_FULLSCREEN |
773
+ View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
774
+ View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY |
775
+ View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
776
+ View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
777
+ );
778
+ }
779
+
780
+ /**
781
+ * Leave the fullsreen mode and reset the status bar to visible
782
+ */
783
+ private void showSystemUI() {
784
+ getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
785
+ getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
786
+ getActivity().getWindow().getDecorView().setSystemUiVisibility(View.VISIBLE);
787
+ }
788
+
789
+ /**
790
+ * Initialize the player
791
+ */
792
+ private void initializePlayer() {
793
+ if (player == null) {
794
+ DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter.Builder(context).build();
795
+ ExoTrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory();
796
+ trackSelector = new DefaultTrackSelector(context, videoTrackSelectionFactory);
797
+ LoadControl loadControl = new DefaultLoadControl();
798
+ player = new ExoPlayer.Builder(context)
799
+ .setSeekBackIncrementMs(10000)
800
+ .setSeekForwardIncrementMs(10000)
801
+ .setTrackSelector(trackSelector)
802
+ .setLoadControl(loadControl)
803
+ .setBandwidthMeter(bandwidthMeter)
804
+ .build();
805
+ }
806
+
807
+ styledPlayerView.setPlayer(player);
808
+
809
+ MediaSource mediaSource;
810
+ if (!isInternal) {
811
+ if (videoPath.substring(0, 21).equals("file:///android_asset") || videoPath.substring(0, 15).equals("content://media")) {
812
+ mediaSource = buildAssetMediaSource(uri);
813
+ } else {
814
+ mediaSource = buildHttpMediaSource();
815
+ }
816
+ } else {
817
+ Uri videoUri = ContentUris.withAppendedId(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, videoId);
818
+ mediaSource = buildInternalMediaSource(videoUri);
819
+ }
820
+
821
+ if (mediaSource != null) {
822
+ player.setAudioAttributes(AudioAttributes.DEFAULT, true);
823
+ player.addListener(listener);
824
+ player.prepare(mediaSource, false, false);
825
+ if (loopOnEnd) {
826
+ player.setRepeatMode(player.REPEAT_MODE_ONE);
827
+ } else {
828
+ player.setRepeatMode(player.REPEAT_MODE_OFF);
829
+ }
830
+ }
831
+ Map<String, Object> info = new HashMap<String, Object>() {
832
+ {
833
+ put("fromPlayerId", playerId);
834
+ }
835
+ };
836
+ if (sturi != null) {
837
+ setSubtitle(false);
838
+ }
839
+ //Use Media Session Connector from the EXT library to enable MediaSession Controls in PIP.
840
+ mediaSession = new MediaSessionCompat(context, "capacitorvideoplayer");
841
+ mediaSessionConnector = new MediaSessionConnector(mediaSession);
842
+ mediaSessionConnector.setPlayer(player);
843
+ mediaSession.setActive(true);
844
+
845
+ NotificationCenter.defaultCenter().postNotification("initializePlayer", info);
846
+ }
847
+
848
+ private void setSubtitle(boolean transparent) {
849
+ int foreground;
850
+ int background;
851
+ if (!transparent) {
852
+ foreground = Color.WHITE;
853
+ background = Color.BLACK;
854
+ if (stForeColor.length() > 4 && stForeColor.substring(0, 4).equals("rgba")) {
855
+ foreground = getColorFromRGBA(stForeColor);
856
+ }
857
+ if (stBackColor.length() > 4 && stBackColor.substring(0, 4).equals("rgba")) {
858
+ background = getColorFromRGBA(stBackColor);
859
+ }
860
+ } else {
861
+ foreground = Color.TRANSPARENT;
862
+ background = Color.TRANSPARENT;
863
+ }
864
+ styledPlayerView
865
+ .getSubtitleView()
866
+ .setStyle(
867
+ new CaptionStyleCompat(foreground, background, Color.TRANSPARENT, CaptionStyleCompat.EDGE_TYPE_NONE, Color.WHITE, null)
868
+ );
869
+ styledPlayerView.getSubtitleView().setFixedTextSize(TypedValue.COMPLEX_UNIT_DIP, stFontSize);
870
+ styledPlayerView.setShowSubtitleButton(true);
871
+ }
872
+
873
+ /**
874
+ * Build the Asset MediaSource
875
+ */
876
+ private MediaSource buildAssetMediaSource(Uri uri) {
877
+ MediaSource mediaSource = null;
878
+ DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(context, "jeep-exoplayer-plugin");
879
+ mediaSource = new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(MediaItem.fromUri(uri));
880
+ // Get the subtitles if any
881
+ if (sturi != null) {
882
+ mediaSource = getSubTitle(mediaSource, sturi, dataSourceFactory);
883
+ }
884
+ return mediaSource;
885
+ }
886
+
887
+ /**
888
+ * Build the Internal MediaSource
889
+ */
890
+ private MediaSource buildInternalMediaSource(Uri uri) {
891
+ DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(context, "jeep-exoplayer-plugin");
892
+ return new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(MediaItem.fromUri(uri));
893
+ }
894
+
895
+ /**
896
+ * Build the Http MediaSource
897
+ * @return MediaSource
898
+ */
899
+ private MediaSource buildHttpMediaSource() {
900
+ MediaSource mediaSource = null;
901
+
902
+ DefaultHttpDataSource.Factory httpDataSourceFactory = new DefaultHttpDataSource.Factory();
903
+ httpDataSourceFactory.setUserAgent("jeep-exoplayer-plugin");
904
+ httpDataSourceFactory.setConnectTimeoutMs(DefaultHttpDataSource.DEFAULT_CONNECT_TIMEOUT_MILLIS);
905
+ httpDataSourceFactory.setReadTimeoutMs(1800000);
906
+ httpDataSourceFactory.setAllowCrossProtocolRedirects(true);
907
+
908
+ // If headers is not null and has data we pass them to the HttpDataSourceFactory
909
+ if (headers != null && headers.length() > 0) {
910
+ // We map the headers(JSObject) to a Map<String, String>
911
+ Map<String, String> headersMap = new HashMap<String, String>();
912
+ for (int i = 0; i < headers.names().length(); i++) {
913
+ try {
914
+ headersMap.put(headers.names().getString(i), headers.get(headers.names().getString(i)).toString());
915
+ } catch (JSONException e) {
916
+ e.printStackTrace();
917
+ }
918
+ }
919
+ httpDataSourceFactory.setDefaultRequestProperties(headersMap);
920
+ }
921
+
922
+ DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(context, httpDataSourceFactory);
923
+
924
+ if (
925
+ vType.equals("mp4") ||
926
+ vType.equals("webm") ||
927
+ vType.equals("ogv") ||
928
+ vType.equals("3gp") ||
929
+ vType.equals("flv") ||
930
+ vType.equals("")
931
+ ) {
932
+ mediaSource = new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(MediaItem.fromUri(uri));
933
+ } else if (vType.equals("dash") || vType.equals("mpd")) {
934
+ /* adaptive streaming Dash stream */
935
+ DashMediaSource.Factory mediaSourceFactory = new DashMediaSource.Factory(dataSourceFactory);
936
+ mediaSource = mediaSourceFactory.createMediaSource(MediaItem.fromUri(uri));
937
+ } else if (vType.equals("m3u8")) {
938
+ mediaSource = new HlsMediaSource.Factory(dataSourceFactory).createMediaSource(MediaItem.fromUri(uri));
939
+ } else if (vType.equals("ism")) {
940
+ mediaSource = new SsMediaSource.Factory(dataSourceFactory).createMediaSource(MediaItem.fromUri(uri));
941
+ }
942
+ // Get the subtitles if any
943
+ if (sturi != null) {
944
+ mediaSource = getSubTitle(mediaSource, sturi, dataSourceFactory);
945
+ }
946
+ return mediaSource;
947
+ }
948
+
949
+ /**
950
+ * Save instance state
951
+ */
952
+ @Override
953
+ public void onSaveInstanceState(Bundle outState) {
954
+ super.onSaveInstanceState(outState);
955
+
956
+ // Save the current playback position (in milliseconds) to the
957
+ // instance state bundle.
958
+ if (player != null) {
959
+ outState.putInt(PLAYBACK_TIME, (int) player.getCurrentPosition());
960
+ }
961
+ }
962
+
963
+ private int getColorFromRGBA(String rgbaColor) {
964
+ int ret = 0;
965
+ String color = rgbaColor.substring(rgbaColor.indexOf("(") + 1, rgbaColor.indexOf(")"));
966
+ List<String> colors = Arrays.asList(color.split(","));
967
+ if (colors.size() == 4) {
968
+ ret =
969
+ ((Math.round(Float.parseFloat(colors.get(3).trim()) * 255) & 0xff) << 24) |
970
+ ((Integer.parseInt(colors.get(0).trim()) & 0xff) << 16) |
971
+ ((Integer.parseInt(colors.get(1).trim()) & 0xff) << 8) |
972
+ (Integer.parseInt(colors.get(2).trim()) & 0xff);
973
+ }
974
+ return ret;
975
+ }
976
+
977
+ private MediaSource getSubTitle(MediaSource mediaSource, Uri sturi, DataSource.Factory dataSourceFactory) {
978
+ // Create mediaSource with subtitle
979
+ MediaSource[] mediaSources = new MediaSource[2];
980
+ mediaSources[0] = mediaSource;
981
+ String mimeType = getMimeType(sturi);
982
+
983
+ // We get the language label from the language code
984
+ String languageLabel = Locale.forLanguageTag(language).getDisplayLanguage();
985
+ MediaItem.SubtitleConfiguration subConfig = new MediaItem.SubtitleConfiguration.Builder(sturi)
986
+ .setMimeType(mimeType)
987
+ .setUri(sturi)
988
+ .setId(subTitle)
989
+ .setLabel(languageLabel)
990
+ .setRoleFlags(C.ROLE_FLAG_CAPTION)
991
+ .setSelectionFlags(C.SELECTION_FLAG_DEFAULT)
992
+ .setLanguage(language)
993
+ .build();
994
+
995
+ SingleSampleMediaSource subtitleSource = new SingleSampleMediaSource.Factory(dataSourceFactory).createMediaSource(
996
+ subConfig,
997
+ C.TIME_UNSET
998
+ );
999
+
1000
+ mediaSources[1] = subtitleSource;
1001
+
1002
+ mediaSource = new MergingMediaSource(mediaSources);
1003
+ return mediaSource;
1004
+ }
1005
+
1006
+ private String getMimeType(Uri sturi) {
1007
+ String lastSegment = sturi.getLastPathSegment();
1008
+ String extension = lastSegment.substring(lastSegment.lastIndexOf(".") + 1);
1009
+ String mimeType = "";
1010
+ if (extension.equals("vtt")) {
1011
+ mimeType = MimeTypes.TEXT_VTT;
1012
+ } else if (extension.equals("srt")) {
1013
+ mimeType = MimeTypes.APPLICATION_SUBRIP;
1014
+ } else if (extension.equals("ssa") || extension.equals("ass")) {
1015
+ mimeType = MimeTypes.TEXT_SSA;
1016
+ } else if (extension.equals("ttml") || extension.equals("dfxp") || extension.equals("xml")) {
1017
+ mimeType = MimeTypes.APPLICATION_TTML;
1018
+ }
1019
+ return mimeType;
1020
+ }
1021
+
1022
+ /**
1023
+ * Fast Forward TV
1024
+ */
1025
+ private void fastForward(long position, int times) {
1026
+ if (position < mDuration - videoStep) {
1027
+ if (player.isPlaying()) {
1028
+ player.setPlayWhenReady(false);
1029
+ }
1030
+ player.seekTo(position + (long) times * videoStep);
1031
+ play();
1032
+ }
1033
+ }
1034
+
1035
+ /**
1036
+ * Rewind TV
1037
+ */
1038
+ private void rewind(long position, int times) {
1039
+ if (position > videoStep) {
1040
+ if (player.isPlaying()) {
1041
+ player.setPlayWhenReady(false);
1042
+ }
1043
+ player.seekTo(position - (long) times * videoStep);
1044
+ play();
1045
+ }
1046
+ }
1047
+
1048
+ /**
1049
+ * Play Pause TV
1050
+ */
1051
+ private void play_pause() {
1052
+ player.setPlayWhenReady(!player.isPlaying());
1053
+ }
1054
+
1055
+ /**
1056
+ * Check if the player is playing
1057
+ * @return boolean
1058
+ */
1059
+ public boolean isPlaying() {
1060
+ return player.isPlaying();
1061
+ }
1062
+
1063
+ /**
1064
+ * Start the player
1065
+ */
1066
+ public void play() {
1067
+ PlaybackParameters param = new PlaybackParameters(videoRate);
1068
+ player.setPlaybackParameters(param);
1069
+
1070
+ /* If the user start the cast before the player is ready and playing, then the video will start
1071
+ in the device and chromecast at the same time. This is to avoid that behaviour.*/
1072
+ if (!isCastSession) player.setPlayWhenReady(true);
1073
+ }
1074
+
1075
+ /**
1076
+ * Pause the player
1077
+ */
1078
+ public void pause() {
1079
+ if (player != null) player.setPlayWhenReady(false);
1080
+ }
1081
+
1082
+ /**
1083
+ * Get the player duration
1084
+ * @return int in seconds
1085
+ */
1086
+ public int getDuration() {
1087
+ return player.getDuration() == UNKNOWN_TIME ? 0 : (int) (player.getDuration() / 1000);
1088
+ }
1089
+
1090
+ /**
1091
+ * Get the player current position
1092
+ * @return int in seconds
1093
+ */
1094
+ public int getCurrentTime() {
1095
+ return player.getCurrentPosition() == UNKNOWN_TIME ? 0 : (int) (player.getCurrentPosition() / 1000);
1096
+ }
1097
+
1098
+ /**
1099
+ * Set the player current position
1100
+ * @param timeSecond int
1101
+ */
1102
+ public void setCurrentTime(int timeSecond) {
1103
+ if (isInPictureInPictureMode) {
1104
+ styledPlayerView.setUseController(false);
1105
+ linearLayout.setVisibility(View.INVISIBLE);
1106
+ }
1107
+ long seekPosition = player.getCurrentPosition() == UNKNOWN_TIME
1108
+ ? 0
1109
+ : Math.min(Math.max(0, timeSecond * 1000), player.getDuration());
1110
+ player.seekTo(seekPosition);
1111
+ }
1112
+
1113
+ /**
1114
+ * Return the player volume
1115
+ * @return float
1116
+ */
1117
+ public float getVolume() {
1118
+ return player.getVolume();
1119
+ }
1120
+
1121
+ /**
1122
+ * Set the player volume
1123
+ * @param _volume float range 0,1
1124
+ */
1125
+ public void setVolume(float _volume) {
1126
+ float volume = Math.min(Math.max(0, _volume), 1L);
1127
+ player.setVolume(volume);
1128
+ }
1129
+
1130
+ /**
1131
+ * Return the player rate
1132
+ * @return float
1133
+ */
1134
+ public float getRate() {
1135
+ return videoRate;
1136
+ }
1137
+
1138
+ /**
1139
+ * Set the player rate
1140
+ * @param _rate float range [0.25f, 0.5f, 0.75f, 1f, 2f, 4f]
1141
+ */
1142
+ public void setRate(float _rate) {
1143
+ videoRate = _rate;
1144
+ PlaybackParameters param = new PlaybackParameters(videoRate);
1145
+ player.setPlaybackParameters(param);
1146
+ }
1147
+
1148
+ /**
1149
+ * Switch Off/On the player volume
1150
+ * @param _isMuted boolean
1151
+ */
1152
+ public void setMuted(boolean _isMuted) {
1153
+ isMuted = _isMuted;
1154
+ if (isMuted) {
1155
+ curVolume = player.getVolume();
1156
+ player.setVolume(0L);
1157
+ } else {
1158
+ player.setVolume(curVolume);
1159
+ }
1160
+ }
1161
+
1162
+ /**
1163
+ * Check if the player is muted
1164
+ * @return boolean
1165
+ */
1166
+ public boolean getMuted() {
1167
+ return isMuted;
1168
+ }
1169
+
1170
+ /**
1171
+ * Apply white color to MediaRouteButton
1172
+ * @param button
1173
+ */
1174
+ private void mediaRouteButtonColorWhite(MediaRouteButton button) {
1175
+ if (button == null) return;
1176
+ Context castContext = new ContextThemeWrapper(getContext(), androidx.mediarouter.R.style.Theme_MediaRouter);
1177
+
1178
+ TypedArray a = castContext.obtainStyledAttributes(
1179
+ null,
1180
+ androidx.mediarouter.R.styleable.MediaRouteButton,
1181
+ androidx.mediarouter.R.attr.mediaRouteButtonStyle,
1182
+ 0
1183
+ );
1184
+ Drawable drawable = a.getDrawable(androidx.mediarouter.R.styleable.MediaRouteButton_externalRouteEnabledDrawable);
1185
+ a.recycle();
1186
+ DrawableCompat.setTint(drawable, getContext().getResources().getColor(R.color.white));
1187
+ drawable.setState(button.getDrawableState());
1188
+ button.setRemoteIndicatorDrawable(drawable);
1189
+ }
1190
+
1191
+ /**
1192
+ * Get video Type from Uri
1193
+ * @param uri
1194
+ * @return video type
1195
+ */
1196
+ private String getVideoType(Uri uri) {
1197
+ String ret = null;
1198
+ Object obj = uri.getLastPathSegment();
1199
+ String lastSegment = (obj == null) ? "" : uri.getLastPathSegment();
1200
+ for (String type : supportedFormat) {
1201
+ if (ret != null) break;
1202
+ if (lastSegment.length() > 0 && lastSegment.contains(type)) ret = type;
1203
+ if (ret == null) {
1204
+ List<String> segments = uri.getPathSegments();
1205
+ if (segments.size() > 0) {
1206
+ String segment;
1207
+ if (segments.get(segments.size() - 1).equals("manifest")) {
1208
+ segment = segments.get(segments.size() - 2);
1209
+ } else {
1210
+ segment = segments.get(segments.size() - 1);
1211
+ }
1212
+ for (String sType : supportedFormat) {
1213
+ if (segment.contains(sType)) {
1214
+ ret = sType;
1215
+ break;
1216
+ }
1217
+ }
1218
+ }
1219
+ }
1220
+ }
1221
+ ret = (ret != null) ? ret : "";
1222
+ return ret;
1223
+ }
1224
+
1225
+ /**
1226
+ * Reset Variables for multiple runs
1227
+ */
1228
+ private void resetVariables() {
1229
+ vType = null;
1230
+ styledPlayerView = null;
1231
+ playWhenReady = true;
1232
+ firstReadyToPlay = true;
1233
+ isEnded = false;
1234
+ currentWindow = 0;
1235
+ playbackPosition = 0;
1236
+ uri = null;
1237
+ isMuted = false;
1238
+ curVolume = (float) 0.5;
1239
+ mCurrentPosition = 0;
1240
+ }
1241
+
1242
+ /**
1243
+ * Check if the application has been sent to the background
1244
+ * @param context
1245
+ * @return boolean
1246
+ */
1247
+ public boolean isApplicationSentToBackground(final Context context) {
1248
+ int pid = android.os.Process.myPid();
1249
+ ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
1250
+ List<ActivityManager.RunningAppProcessInfo> procInfos = am.getRunningAppProcesses();
1251
+ if (procInfos != null) {
1252
+ for (ActivityManager.RunningAppProcessInfo appProcess : procInfos) {
1253
+ if (appProcess.pid == pid) {
1254
+ return true;
1255
+ }
1256
+ }
1257
+ }
1258
+ return false;
1259
+ }
1260
+
1261
+ /**
1262
+ * Function to initialize the cast service and everything related to it
1263
+ * @return void
1264
+ */
1265
+ private void initializeCastService() {
1266
+ Executor executor = Executors.newSingleThreadExecutor();
1267
+ Task<CastContext> task = CastContext.getSharedInstance(context, executor);
1268
+
1269
+ task.addOnCompleteListener(
1270
+ new OnCompleteListener<CastContext>() {
1271
+ @Override
1272
+ public void onComplete(Task<CastContext> task) {
1273
+ if (task.isSuccessful()) {
1274
+ castContext = task.getResult();
1275
+ castPlayer = new CastPlayer(castContext);
1276
+ mRouter = MediaRouter.getInstance(context);
1277
+ mSelector = new MediaRouteSelector.Builder()
1278
+ .addControlCategories(
1279
+ Arrays.asList(MediaControlIntent.CATEGORY_LIVE_AUDIO, MediaControlIntent.CATEGORY_LIVE_VIDEO)
1280
+ )
1281
+ .build();
1282
+
1283
+ mediaRouteButtonColorWhite(mediaRouteButton);
1284
+ if (
1285
+ castContext != null && castContext.getCastState() != CastState.NO_DEVICES_AVAILABLE
1286
+ ) mediaRouteButton.setVisibility(View.VISIBLE);
1287
+
1288
+ castStateListener = (state) -> {
1289
+ if (state == CastState.NO_DEVICES_AVAILABLE) {
1290
+ mediaRouteButton.setVisibility(View.GONE);
1291
+ } else {
1292
+ if (mediaRouteButton.getVisibility() == View.GONE) {
1293
+ mediaRouteButton.setVisibility(View.VISIBLE);
1294
+ }
1295
+ }
1296
+ };
1297
+ CastButtonFactory.setUpMediaRouteButton(context, mediaRouteButton);
1298
+
1299
+ MediaMetadata movieMetadata;
1300
+ if (artwork != "") {
1301
+ movieMetadata = new MediaMetadata.Builder()
1302
+ .setTitle(title)
1303
+ .setSubtitle(smallTitle)
1304
+ .setMediaType(MediaMetadata.MEDIA_TYPE_MOVIE)
1305
+ .setArtworkUri(Uri.parse(artwork))
1306
+ .build();
1307
+ new setCastImage().execute();
1308
+ } else {
1309
+ movieMetadata = new MediaMetadata.Builder().setTitle(title).setSubtitle(smallTitle).build();
1310
+ }
1311
+ mediaItem = new MediaItem.Builder()
1312
+ .setUri(videoPath)
1313
+ .setMimeType(MimeTypes.VIDEO_UNKNOWN)
1314
+ .setMediaMetadata(movieMetadata)
1315
+ .build();
1316
+
1317
+ castPlayer.setSessionAvailabilityListener(
1318
+ new SessionAvailabilityListener() {
1319
+ @Override
1320
+ public void onCastSessionAvailable() {
1321
+ isCastSession = true;
1322
+ final Long videoPosition = player.getCurrentPosition();
1323
+ if (pipEnabled) {
1324
+ pipBtn.setVisibility(View.GONE);
1325
+ }
1326
+ resizeBtn.setVisibility(View.GONE);
1327
+ player.setPlayWhenReady(false);
1328
+ cast_image.setVisibility(View.VISIBLE);
1329
+ castPlayer.setMediaItem(mediaItem, videoPosition);
1330
+ styledPlayerView.setPlayer(castPlayer);
1331
+ styledPlayerView.setControllerShowTimeoutMs(0);
1332
+ styledPlayerView.setControllerHideOnTouch(false);
1333
+ //We perform a click because for some weird reason, the layout is black until the user clicks on it
1334
+ styledPlayerView.performClick();
1335
+ }
1336
+
1337
+ @Override
1338
+ public void onCastSessionUnavailable() {
1339
+ isCastSession = false;
1340
+ final Long videoPosition = castPlayer.getCurrentPosition();
1341
+ if (pipEnabled) {
1342
+ pipBtn.setVisibility(View.VISIBLE);
1343
+ }
1344
+ resizeBtn.setVisibility(View.VISIBLE);
1345
+ cast_image.setVisibility(View.GONE);
1346
+ styledPlayerView.setPlayer(player);
1347
+ player.setPlayWhenReady(true);
1348
+ player.seekTo(videoPosition);
1349
+ styledPlayerView.setControllerShowTimeoutMs(3000);
1350
+ styledPlayerView.setControllerHideOnTouch(true);
1351
+ }
1352
+ }
1353
+ );
1354
+
1355
+ castPlayer.addListener(
1356
+ new Player.Listener() {
1357
+ @Override
1358
+ public void onPlayerStateChanged(boolean playWhenReady, int state) {
1359
+ Map<String, Object> info = new HashMap<String, Object>() {
1360
+ {
1361
+ put("fromPlayerId", playerId);
1362
+ put("currentTime", String.valueOf(player.getCurrentPosition() / 1000));
1363
+ }
1364
+ };
1365
+ switch (state) {
1366
+ case CastPlayer.STATE_READY:
1367
+ if (castPlayer.isPlaying()) {
1368
+ NotificationCenter.defaultCenter().postNotification("playerItemPlay", info);
1369
+ } else {
1370
+ NotificationCenter.defaultCenter().postNotification("playerItemPause", info);
1371
+ }
1372
+ break;
1373
+ default:
1374
+ break;
1375
+ }
1376
+ }
1377
+ }
1378
+ );
1379
+
1380
+ castContext.addCastStateListener(castStateListener);
1381
+ mRouter.addCallback(mSelector, mCallback, MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
1382
+ } else {
1383
+ Exception e = task.getException();
1384
+ e.printStackTrace();
1385
+ }
1386
+ }
1387
+ }
1388
+ );
1389
+ }
1390
+
1391
+ private final class EmptyCallback extends MediaRouter.Callback {}
1392
+
1393
+ @Override
1394
+ public void onConfigurationChanged(Configuration newConfig) {
1395
+ super.onConfigurationChanged(newConfig);
1396
+ adjustAspectRatio();
1397
+ }
1398
+
1399
+ private void adjustAspectRatio() {
1400
+ if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
1401
+ styledPlayerView.setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FILL);
1402
+ } else if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
1403
+ styledPlayerView.setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FIT);
1404
+ }
1405
+ }
1406
+ }