/
StyledPlayerControlView.java
2078 lines (1891 loc) · 79 KB
/
StyledPlayerControlView.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.ui;
import static com.google.android.exoplayer2.Player.COMMAND_SEEK_BACK;
import static com.google.android.exoplayer2.Player.COMMAND_SEEK_FORWARD;
import static com.google.android.exoplayer2.Player.COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM;
import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_NEXT;
import static com.google.android.exoplayer2.Player.COMMAND_SEEK_TO_PREVIOUS;
import static com.google.android.exoplayer2.Player.EVENT_AVAILABLE_COMMANDS_CHANGED;
import static com.google.android.exoplayer2.Player.EVENT_IS_PLAYING_CHANGED;
import static com.google.android.exoplayer2.Player.EVENT_PLAYBACK_PARAMETERS_CHANGED;
import static com.google.android.exoplayer2.Player.EVENT_PLAYBACK_STATE_CHANGED;
import static com.google.android.exoplayer2.Player.EVENT_PLAY_WHEN_READY_CHANGED;
import static com.google.android.exoplayer2.Player.EVENT_POSITION_DISCONTINUITY;
import static com.google.android.exoplayer2.Player.EVENT_REPEAT_MODE_CHANGED;
import static com.google.android.exoplayer2.Player.EVENT_SEEK_BACK_INCREMENT_CHANGED;
import static com.google.android.exoplayer2.Player.EVENT_SEEK_FORWARD_INCREMENT_CHANGED;
import static com.google.android.exoplayer2.Player.EVENT_SHUFFLE_MODE_ENABLED_CHANGED;
import static com.google.android.exoplayer2.Player.EVENT_TIMELINE_CHANGED;
import static com.google.android.exoplayer2.Player.EVENT_TRACKS_CHANGED;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import static com.google.android.exoplayer2.util.Util.castNonNull;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.Typeface;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Looper;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.PopupWindow;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.core.content.res.ResourcesCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.ForwardingPlayer;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Player.Events;
import com.google.android.exoplayer2.Player.State;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.Tracks;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.trackselection.TrackSelectionOverride;
import com.google.android.exoplayer2.trackselection.TrackSelectionParameters;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.RepeatModeUtil;
import com.google.android.exoplayer2.util.Util;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Formatter;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* A view for controlling {@link Player} instances.
*
* <p>A StyledPlayerControlView can be customized by setting attributes (or calling corresponding
* methods), or overriding drawables.
*
* <h2>Attributes</h2>
*
* The following attributes can be set on a StyledPlayerControlView when used in a layout XML file:
*
* <ul>
* <li><b>{@code show_timeout}</b> - The time between the last user interaction and the controls
* being automatically hidden, in milliseconds. Use zero if the controls should not
* automatically timeout.
* <ul>
* <li>Corresponding method: {@link #setShowTimeoutMs(int)}
* <li>Default: {@link #DEFAULT_SHOW_TIMEOUT_MS}
* </ul>
* <li><b>{@code show_rewind_button}</b> - Whether the rewind button is shown.
* <ul>
* <li>Corresponding method: {@link #setShowRewindButton(boolean)}
* <li>Default: true
* </ul>
* <li><b>{@code show_fastforward_button}</b> - Whether the fast forward button is shown.
* <ul>
* <li>Corresponding method: {@link #setShowFastForwardButton(boolean)}
* <li>Default: true
* </ul>
* <li><b>{@code show_previous_button}</b> - Whether the previous button is shown.
* <ul>
* <li>Corresponding method: {@link #setShowPreviousButton(boolean)}
* <li>Default: true
* </ul>
* <li><b>{@code show_next_button}</b> - Whether the next button is shown.
* <ul>
* <li>Corresponding method: {@link #setShowNextButton(boolean)}
* <li>Default: true
* </ul>
* <li><b>{@code repeat_toggle_modes}</b> - A flagged enumeration value specifying which repeat
* mode toggle options are enabled. Valid values are: {@code none}, {@code one}, {@code all},
* or {@code one|all}.
* <ul>
* <li>Corresponding method: {@link #setRepeatToggleModes(int)}
* <li>Default: {@link #DEFAULT_REPEAT_TOGGLE_MODES}
* </ul>
* <li><b>{@code show_shuffle_button}</b> - Whether the shuffle button is shown.
* <ul>
* <li>Corresponding method: {@link #setShowShuffleButton(boolean)}
* <li>Default: false
* </ul>
* <li><b>{@code show_subtitle_button}</b> - Whether the subtitle button is shown.
* <ul>
* <li>Corresponding method: {@link #setShowSubtitleButton(boolean)}
* <li>Default: false
* </ul>
* <li><b>{@code animation_enabled}</b> - Whether an animation is used to show and hide the
* playback controls.
* <ul>
* <li>Corresponding method: {@link #setAnimationEnabled(boolean)}
* <li>Default: true
* </ul>
* <li><b>{@code time_bar_min_update_interval}</b> - Specifies the minimum interval between time
* bar position updates.
* <ul>
* <li>Corresponding method: {@link #setTimeBarMinUpdateInterval(int)}
* <li>Default: {@link #DEFAULT_TIME_BAR_MIN_UPDATE_INTERVAL_MS}
* </ul>
* <li><b>{@code controller_layout_id}</b> - Specifies the id of the layout to be inflated. See
* below for more details.
* <ul>
* <li>Corresponding method: None
* <li>Default: {@code R.layout.exo_styled_player_control_view}
* </ul>
* <li>All attributes that can be set on {@link DefaultTimeBar} can also be set on a
* StyledPlayerControlView, and will be propagated to the inflated {@link DefaultTimeBar}
* unless the layout is overridden to specify a custom {@code exo_progress} (see below).
* </ul>
*
* <h2>Overriding drawables</h2>
*
* The drawables used by StyledPlayerControlView (with its default layout file) can be overridden by
* drawables with the same names defined in your application. The drawables that can be overridden
* are:
*
* <ul>
* <li><b>{@code exo_styled_controls_play}</b> - The play icon.
* <li><b>{@code exo_styled_controls_pause}</b> - The pause icon.
* <li><b>{@code exo_styled_controls_rewind}</b> - The background of rewind icon.
* <li><b>{@code exo_styled_controls_fastforward}</b> - The background of fast forward icon.
* <li><b>{@code exo_styled_controls_previous}</b> - The previous icon.
* <li><b>{@code exo_styled_controls_next}</b> - The next icon.
* <li><b>{@code exo_styled_controls_repeat_off}</b> - The repeat icon for {@link
* Player#REPEAT_MODE_OFF}.
* <li><b>{@code exo_styled_controls_repeat_one}</b> - The repeat icon for {@link
* Player#REPEAT_MODE_ONE}.
* <li><b>{@code exo_styled_controls_repeat_all}</b> - The repeat icon for {@link
* Player#REPEAT_MODE_ALL}.
* <li><b>{@code exo_styled_controls_shuffle_off}</b> - The shuffle icon when shuffling is
* disabled.
* <li><b>{@code exo_styled_controls_shuffle_on}</b> - The shuffle icon when shuffling is enabled.
* <li><b>{@code exo_styled_controls_vr}</b> - The VR icon.
* </ul>
*/
public class StyledPlayerControlView extends FrameLayout {
static {
ExoPlayerLibraryInfo.registerModule("goog.exo.ui");
}
/**
* @deprecated Register a {@link StyledPlayerView.ControllerVisibilityListener} via {@link
* StyledPlayerView#setControllerVisibilityListener(StyledPlayerView.ControllerVisibilityListener)}
* instead. Using {@link StyledPlayerControlView} as a standalone class without {@link
* StyledPlayerView} is deprecated.
*/
@Deprecated
public interface VisibilityListener {
/**
* Called when the visibility changes.
*
* @param visibility The new visibility. Either {@link View#VISIBLE} or {@link View#GONE}.
*/
void onVisibilityChange(int visibility);
}
/** Listener to be notified when progress has been updated. */
public interface ProgressUpdateListener {
/**
* Called when progress needs to be updated.
*
* @param position The current position.
* @param bufferedPosition The current buffered position.
*/
void onProgressUpdate(long position, long bufferedPosition);
}
/**
* @deprecated Register a {@link StyledPlayerView.FullscreenButtonClickListener} via {@link
* StyledPlayerView#setFullscreenButtonClickListener(StyledPlayerView.FullscreenButtonClickListener)}
* instead. Using {@link StyledPlayerControlView} as a standalone class without {@link
* StyledPlayerView} is deprecated.
*/
@Deprecated
public interface OnFullScreenModeChangedListener {
/**
* Called to indicate a fullscreen mode change.
*
* @param isFullScreen {@code true} if the video rendering surface should be fullscreen {@code
* false} otherwise.
*/
void onFullScreenModeChanged(boolean isFullScreen);
}
/** The default show timeout, in milliseconds. */
public static final int DEFAULT_SHOW_TIMEOUT_MS = 5_000;
/** The default repeat toggle modes. */
public static final @RepeatModeUtil.RepeatToggleModes int DEFAULT_REPEAT_TOGGLE_MODES =
RepeatModeUtil.REPEAT_TOGGLE_MODE_NONE;
/** The default minimum interval between time bar position updates. */
public static final int DEFAULT_TIME_BAR_MIN_UPDATE_INTERVAL_MS = 200;
/** The maximum number of windows that can be shown in a multi-window time bar. */
public static final int MAX_WINDOWS_FOR_MULTI_WINDOW_TIME_BAR = 100;
/** The maximum interval between time bar position updates. */
private static final int MAX_UPDATE_INTERVAL_MS = 1_000;
// LINT.IfChange(playback_speeds)
private static final float[] PLAYBACK_SPEEDS =
new float[] {0.25f, 0.5f, 0.75f, 1f, 1.25f, 1.5f, 2f};
private static final int SETTINGS_PLAYBACK_SPEED_POSITION = 0;
private static final int SETTINGS_AUDIO_TRACK_SELECTION_POSITION = 1;
private final ComponentListener componentListener;
@SuppressWarnings("deprecation") // Using the deprecated type for now.
private final CopyOnWriteArrayList<VisibilityListener> visibilityListeners;
@Nullable private final View previousButton;
@Nullable private final View nextButton;
@Nullable private final View playPauseButton;
@Nullable private final View fastForwardButton;
@Nullable private final View rewindButton;
@Nullable private final TextView fastForwardButtonTextView;
@Nullable private final TextView rewindButtonTextView;
@Nullable private final ImageView repeatToggleButton;
@Nullable private final ImageView shuffleButton;
@Nullable private final View vrButton;
@Nullable private final TextView durationView;
@Nullable private final TextView positionView;
@Nullable private final TimeBar timeBar;
private final StringBuilder formatBuilder;
private final Formatter formatter;
private final Timeline.Period period;
private final Timeline.Window window;
private final Runnable updateProgressAction;
private final Drawable repeatOffButtonDrawable;
private final Drawable repeatOneButtonDrawable;
private final Drawable repeatAllButtonDrawable;
private final String repeatOffButtonContentDescription;
private final String repeatOneButtonContentDescription;
private final String repeatAllButtonContentDescription;
private final Drawable shuffleOnButtonDrawable;
private final Drawable shuffleOffButtonDrawable;
private final float buttonAlphaEnabled;
private final float buttonAlphaDisabled;
private final String shuffleOnContentDescription;
private final String shuffleOffContentDescription;
private final Drawable subtitleOnButtonDrawable;
private final Drawable subtitleOffButtonDrawable;
private final String subtitleOnContentDescription;
private final String subtitleOffContentDescription;
private final Drawable fullScreenExitDrawable;
private final Drawable fullScreenEnterDrawable;
private final String fullScreenExitContentDescription;
private final String fullScreenEnterContentDescription;
@Nullable private Player player;
@Nullable private ProgressUpdateListener progressUpdateListener;
@Nullable private OnFullScreenModeChangedListener onFullScreenModeChangedListener;
private boolean isFullScreen;
private boolean isAttachedToWindow;
private boolean showMultiWindowTimeBar;
private boolean multiWindowTimeBar;
private boolean scrubbing;
private int showTimeoutMs;
private int timeBarMinUpdateIntervalMs;
private @RepeatModeUtil.RepeatToggleModes int repeatToggleModes;
private long[] adGroupTimesMs;
private boolean[] playedAdGroups;
private long[] extraAdGroupTimesMs;
private boolean[] extraPlayedAdGroups;
private long currentWindowOffset;
private StyledPlayerControlViewLayoutManager controlViewLayoutManager;
private Resources resources;
private RecyclerView settingsView;
private SettingsAdapter settingsAdapter;
private PlaybackSpeedAdapter playbackSpeedAdapter;
private PopupWindow settingsWindow;
private boolean needToHideBars;
private int settingsWindowMargin;
private TextTrackSelectionAdapter textTrackSelectionAdapter;
private AudioTrackSelectionAdapter audioTrackSelectionAdapter;
// TODO(insun): Add setTrackNameProvider to use customized track name provider.
private TrackNameProvider trackNameProvider;
@Nullable private ImageView subtitleButton;
@Nullable private ImageView fullScreenButton;
@Nullable private ImageView minimalFullScreenButton;
@Nullable private View settingsButton;
@Nullable private View playbackSpeedButton;
@Nullable private View audioTrackButton;
public StyledPlayerControlView(Context context) {
this(context, /* attrs= */ null);
}
public StyledPlayerControlView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, /* defStyleAttr= */ 0);
}
public StyledPlayerControlView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, attrs);
}
@SuppressWarnings({
"nullness:argument",
"nullness:assignment",
"nullness:method.invocation",
"nullness:methodref.receiver.bound"
})
public StyledPlayerControlView(
Context context,
@Nullable AttributeSet attrs,
int defStyleAttr,
@Nullable AttributeSet playbackAttrs) {
super(context, attrs, defStyleAttr);
int controllerLayoutId = R.layout.exo_styled_player_control_view;
showTimeoutMs = DEFAULT_SHOW_TIMEOUT_MS;
repeatToggleModes = DEFAULT_REPEAT_TOGGLE_MODES;
timeBarMinUpdateIntervalMs = DEFAULT_TIME_BAR_MIN_UPDATE_INTERVAL_MS;
boolean showRewindButton = true;
boolean showFastForwardButton = true;
boolean showPreviousButton = true;
boolean showNextButton = true;
boolean showShuffleButton = false;
boolean showSubtitleButton = false;
boolean animationEnabled = true;
boolean showVrButton = false;
if (playbackAttrs != null) {
TypedArray a =
context
.getTheme()
.obtainStyledAttributes(
playbackAttrs,
R.styleable.StyledPlayerControlView,
defStyleAttr,
/* defStyleRes= */ 0);
try {
controllerLayoutId =
a.getResourceId(
R.styleable.StyledPlayerControlView_controller_layout_id, controllerLayoutId);
showTimeoutMs = a.getInt(R.styleable.StyledPlayerControlView_show_timeout, showTimeoutMs);
repeatToggleModes = getRepeatToggleModes(a, repeatToggleModes);
showRewindButton =
a.getBoolean(R.styleable.StyledPlayerControlView_show_rewind_button, showRewindButton);
showFastForwardButton =
a.getBoolean(
R.styleable.StyledPlayerControlView_show_fastforward_button, showFastForwardButton);
showPreviousButton =
a.getBoolean(
R.styleable.StyledPlayerControlView_show_previous_button, showPreviousButton);
showNextButton =
a.getBoolean(R.styleable.StyledPlayerControlView_show_next_button, showNextButton);
showShuffleButton =
a.getBoolean(
R.styleable.StyledPlayerControlView_show_shuffle_button, showShuffleButton);
showSubtitleButton =
a.getBoolean(
R.styleable.StyledPlayerControlView_show_subtitle_button, showSubtitleButton);
showVrButton =
a.getBoolean(R.styleable.StyledPlayerControlView_show_vr_button, showVrButton);
setTimeBarMinUpdateInterval(
a.getInt(
R.styleable.StyledPlayerControlView_time_bar_min_update_interval,
timeBarMinUpdateIntervalMs));
animationEnabled =
a.getBoolean(R.styleable.StyledPlayerControlView_animation_enabled, animationEnabled);
} finally {
a.recycle();
}
}
LayoutInflater.from(context).inflate(controllerLayoutId, /* root= */ this);
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
componentListener = new ComponentListener();
visibilityListeners = new CopyOnWriteArrayList<>();
period = new Timeline.Period();
window = new Timeline.Window();
formatBuilder = new StringBuilder();
formatter = new Formatter(formatBuilder, Locale.getDefault());
adGroupTimesMs = new long[0];
playedAdGroups = new boolean[0];
extraAdGroupTimesMs = new long[0];
extraPlayedAdGroups = new boolean[0];
updateProgressAction = this::updateProgress;
durationView = findViewById(R.id.exo_duration);
positionView = findViewById(R.id.exo_position);
subtitleButton = findViewById(R.id.exo_subtitle);
if (subtitleButton != null) {
subtitleButton.setOnClickListener(componentListener);
}
fullScreenButton = findViewById(R.id.exo_fullscreen);
initializeFullScreenButton(fullScreenButton, this::onFullScreenButtonClicked);
minimalFullScreenButton = findViewById(R.id.exo_minimal_fullscreen);
initializeFullScreenButton(minimalFullScreenButton, this::onFullScreenButtonClicked);
settingsButton = findViewById(R.id.exo_settings);
if (settingsButton != null) {
settingsButton.setOnClickListener(componentListener);
}
playbackSpeedButton = findViewById(R.id.exo_playback_speed);
if (playbackSpeedButton != null) {
playbackSpeedButton.setOnClickListener(componentListener);
}
audioTrackButton = findViewById(R.id.exo_audio_track);
if (audioTrackButton != null) {
audioTrackButton.setOnClickListener(componentListener);
}
TimeBar customTimeBar = findViewById(R.id.exo_progress);
View timeBarPlaceholder = findViewById(R.id.exo_progress_placeholder);
if (customTimeBar != null) {
timeBar = customTimeBar;
} else if (timeBarPlaceholder != null) {
// Propagate playbackAttrs as timebarAttrs so that DefaultTimeBar's custom attributes are
// transferred, but standard attributes (e.g. background) are not.
DefaultTimeBar defaultTimeBar =
new DefaultTimeBar(context, null, 0, playbackAttrs, R.style.ExoStyledControls_TimeBar);
defaultTimeBar.setId(R.id.exo_progress);
defaultTimeBar.setLayoutParams(timeBarPlaceholder.getLayoutParams());
ViewGroup parent = ((ViewGroup) timeBarPlaceholder.getParent());
int timeBarIndex = parent.indexOfChild(timeBarPlaceholder);
parent.removeView(timeBarPlaceholder);
parent.addView(defaultTimeBar, timeBarIndex);
timeBar = defaultTimeBar;
} else {
timeBar = null;
}
if (timeBar != null) {
timeBar.addListener(componentListener);
}
playPauseButton = findViewById(R.id.exo_play_pause);
if (playPauseButton != null) {
playPauseButton.setOnClickListener(componentListener);
}
previousButton = findViewById(R.id.exo_prev);
if (previousButton != null) {
previousButton.setOnClickListener(componentListener);
}
nextButton = findViewById(R.id.exo_next);
if (nextButton != null) {
nextButton.setOnClickListener(componentListener);
}
Typeface typeface = ResourcesCompat.getFont(context, R.font.roboto_medium_numbers);
View rewButton = findViewById(R.id.exo_rew);
rewindButtonTextView = rewButton == null ? findViewById(R.id.exo_rew_with_amount) : null;
if (rewindButtonTextView != null) {
rewindButtonTextView.setTypeface(typeface);
}
rewindButton = rewButton == null ? rewindButtonTextView : rewButton;
if (rewindButton != null) {
rewindButton.setOnClickListener(componentListener);
}
View ffwdButton = findViewById(R.id.exo_ffwd);
fastForwardButtonTextView = ffwdButton == null ? findViewById(R.id.exo_ffwd_with_amount) : null;
if (fastForwardButtonTextView != null) {
fastForwardButtonTextView.setTypeface(typeface);
}
fastForwardButton = ffwdButton == null ? fastForwardButtonTextView : ffwdButton;
if (fastForwardButton != null) {
fastForwardButton.setOnClickListener(componentListener);
}
repeatToggleButton = findViewById(R.id.exo_repeat_toggle);
if (repeatToggleButton != null) {
repeatToggleButton.setOnClickListener(componentListener);
}
shuffleButton = findViewById(R.id.exo_shuffle);
if (shuffleButton != null) {
shuffleButton.setOnClickListener(componentListener);
}
resources = context.getResources();
buttonAlphaEnabled =
(float) resources.getInteger(R.integer.exo_media_button_opacity_percentage_enabled) / 100;
buttonAlphaDisabled =
(float) resources.getInteger(R.integer.exo_media_button_opacity_percentage_disabled) / 100;
vrButton = findViewById(R.id.exo_vr);
if (vrButton != null) {
updateButton(/* enabled= */ false, vrButton);
}
controlViewLayoutManager = new StyledPlayerControlViewLayoutManager(this);
controlViewLayoutManager.setAnimationEnabled(animationEnabled);
String[] settingTexts = new String[2];
Drawable[] settingIcons = new Drawable[2];
settingTexts[SETTINGS_PLAYBACK_SPEED_POSITION] =
resources.getString(R.string.exo_controls_playback_speed);
settingIcons[SETTINGS_PLAYBACK_SPEED_POSITION] =
resources.getDrawable(R.drawable.exo_styled_controls_speed);
settingTexts[SETTINGS_AUDIO_TRACK_SELECTION_POSITION] =
resources.getString(R.string.exo_track_selection_title_audio);
settingIcons[SETTINGS_AUDIO_TRACK_SELECTION_POSITION] =
resources.getDrawable(R.drawable.exo_styled_controls_audiotrack);
settingsAdapter = new SettingsAdapter(settingTexts, settingIcons);
settingsWindowMargin = resources.getDimensionPixelSize(R.dimen.exo_settings_offset);
settingsView =
(RecyclerView)
LayoutInflater.from(context)
.inflate(R.layout.exo_styled_settings_list, /* root= */ null);
settingsView.setAdapter(settingsAdapter);
settingsView.setLayoutManager(new LinearLayoutManager(getContext()));
settingsWindow =
new PopupWindow(settingsView, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, true);
if (Util.SDK_INT < 23) {
// Work around issue where tapping outside of the menu area or pressing the back button
// doesn't dismiss the menu as expected. See: https://github.com/google/ExoPlayer/issues/8272.
settingsWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
}
settingsWindow.setOnDismissListener(componentListener);
needToHideBars = true;
trackNameProvider = new DefaultTrackNameProvider(getResources());
subtitleOnButtonDrawable = resources.getDrawable(R.drawable.exo_styled_controls_subtitle_on);
subtitleOffButtonDrawable = resources.getDrawable(R.drawable.exo_styled_controls_subtitle_off);
subtitleOnContentDescription =
resources.getString(R.string.exo_controls_cc_enabled_description);
subtitleOffContentDescription =
resources.getString(R.string.exo_controls_cc_disabled_description);
textTrackSelectionAdapter = new TextTrackSelectionAdapter();
audioTrackSelectionAdapter = new AudioTrackSelectionAdapter();
playbackSpeedAdapter =
new PlaybackSpeedAdapter(
resources.getStringArray(R.array.exo_controls_playback_speeds), PLAYBACK_SPEEDS);
fullScreenExitDrawable = resources.getDrawable(R.drawable.exo_styled_controls_fullscreen_exit);
fullScreenEnterDrawable =
resources.getDrawable(R.drawable.exo_styled_controls_fullscreen_enter);
repeatOffButtonDrawable = resources.getDrawable(R.drawable.exo_styled_controls_repeat_off);
repeatOneButtonDrawable = resources.getDrawable(R.drawable.exo_styled_controls_repeat_one);
repeatAllButtonDrawable = resources.getDrawable(R.drawable.exo_styled_controls_repeat_all);
shuffleOnButtonDrawable = resources.getDrawable(R.drawable.exo_styled_controls_shuffle_on);
shuffleOffButtonDrawable = resources.getDrawable(R.drawable.exo_styled_controls_shuffle_off);
fullScreenExitContentDescription =
resources.getString(R.string.exo_controls_fullscreen_exit_description);
fullScreenEnterContentDescription =
resources.getString(R.string.exo_controls_fullscreen_enter_description);
repeatOffButtonContentDescription =
resources.getString(R.string.exo_controls_repeat_off_description);
repeatOneButtonContentDescription =
resources.getString(R.string.exo_controls_repeat_one_description);
repeatAllButtonContentDescription =
resources.getString(R.string.exo_controls_repeat_all_description);
shuffleOnContentDescription = resources.getString(R.string.exo_controls_shuffle_on_description);
shuffleOffContentDescription =
resources.getString(R.string.exo_controls_shuffle_off_description);
// TODO(insun) : Make showing bottomBar configurable. (ex. show_bottom_bar attribute).
ViewGroup bottomBar = findViewById(R.id.exo_bottom_bar);
controlViewLayoutManager.setShowButton(bottomBar, true);
controlViewLayoutManager.setShowButton(fastForwardButton, showFastForwardButton);
controlViewLayoutManager.setShowButton(rewindButton, showRewindButton);
controlViewLayoutManager.setShowButton(previousButton, showPreviousButton);
controlViewLayoutManager.setShowButton(nextButton, showNextButton);
controlViewLayoutManager.setShowButton(shuffleButton, showShuffleButton);
controlViewLayoutManager.setShowButton(subtitleButton, showSubtitleButton);
controlViewLayoutManager.setShowButton(vrButton, showVrButton);
controlViewLayoutManager.setShowButton(
repeatToggleButton, repeatToggleModes != RepeatModeUtil.REPEAT_TOGGLE_MODE_NONE);
addOnLayoutChangeListener(this::onLayoutChange);
}
/**
* Returns the {@link Player} currently being controlled by this view, or null if no player is
* set.
*/
@Nullable
public Player getPlayer() {
return player;
}
/**
* Sets the {@link Player} to control.
*
* @param player The {@link Player} to control, or {@code null} to detach the current player. Only
* players which are accessed on the main thread are supported ({@code
* player.getApplicationLooper() == Looper.getMainLooper()}).
*/
public void setPlayer(@Nullable Player player) {
Assertions.checkState(Looper.myLooper() == Looper.getMainLooper());
Assertions.checkArgument(
player == null || player.getApplicationLooper() == Looper.getMainLooper());
if (this.player == player) {
return;
}
if (this.player != null) {
this.player.removeListener(componentListener);
}
this.player = player;
if (player != null) {
player.addListener(componentListener);
}
if (player instanceof ForwardingPlayer) {
player = ((ForwardingPlayer) player).getWrappedPlayer();
}
updateAll();
}
/**
* Sets whether the time bar should show all windows, as opposed to just the current one. If the
* timeline has a period with unknown duration or more than {@link
* #MAX_WINDOWS_FOR_MULTI_WINDOW_TIME_BAR} windows the time bar will fall back to showing a single
* window.
*
* @param showMultiWindowTimeBar Whether the time bar should show all windows.
*/
public void setShowMultiWindowTimeBar(boolean showMultiWindowTimeBar) {
this.showMultiWindowTimeBar = showMultiWindowTimeBar;
updateTimeline();
}
/**
* Sets the millisecond positions of extra ad markers relative to the start of the window (or
* timeline, if in multi-window mode) and whether each extra ad has been played or not. The
* markers are shown in addition to any ad markers for ads in the player's timeline.
*
* @param extraAdGroupTimesMs The millisecond timestamps of the extra ad markers to show, or
* {@code null} to show no extra ad markers.
* @param extraPlayedAdGroups Whether each ad has been played. Must be the same length as {@code
* extraAdGroupTimesMs}, or {@code null} if {@code extraAdGroupTimesMs} is {@code null}.
*/
public void setExtraAdGroupMarkers(
@Nullable long[] extraAdGroupTimesMs, @Nullable boolean[] extraPlayedAdGroups) {
if (extraAdGroupTimesMs == null) {
this.extraAdGroupTimesMs = new long[0];
this.extraPlayedAdGroups = new boolean[0];
} else {
extraPlayedAdGroups = checkNotNull(extraPlayedAdGroups);
Assertions.checkArgument(extraAdGroupTimesMs.length == extraPlayedAdGroups.length);
this.extraAdGroupTimesMs = extraAdGroupTimesMs;
this.extraPlayedAdGroups = extraPlayedAdGroups;
}
updateTimeline();
}
/**
* @deprecated Register a {@link StyledPlayerView.ControllerVisibilityListener} via {@link
* StyledPlayerView#setControllerVisibilityListener(StyledPlayerView.ControllerVisibilityListener)}
* instead. Using {@link StyledPlayerControlView} as a standalone class without {@link
* StyledPlayerView} is deprecated.
*/
@SuppressWarnings("deprecation")
@Deprecated
public void addVisibilityListener(VisibilityListener listener) {
checkNotNull(listener);
visibilityListeners.add(listener);
}
/**
* @deprecated Register a {@link StyledPlayerView.ControllerVisibilityListener} via {@link
* StyledPlayerView#setControllerVisibilityListener(StyledPlayerView.ControllerVisibilityListener)}
* instead. Using {@link StyledPlayerControlView} as a standalone class without {@link
* StyledPlayerView} is deprecated.
*/
@SuppressWarnings("deprecation")
@Deprecated
public void removeVisibilityListener(VisibilityListener listener) {
visibilityListeners.remove(listener);
}
/**
* Sets the {@link ProgressUpdateListener}.
*
* @param listener The listener to be notified about when progress is updated.
*/
public void setProgressUpdateListener(@Nullable ProgressUpdateListener listener) {
this.progressUpdateListener = listener;
}
/**
* Sets whether the rewind button is shown.
*
* @param showRewindButton Whether the rewind button is shown.
*/
public void setShowRewindButton(boolean showRewindButton) {
controlViewLayoutManager.setShowButton(rewindButton, showRewindButton);
updateNavigation();
}
/**
* Sets whether the fast forward button is shown.
*
* @param showFastForwardButton Whether the fast forward button is shown.
*/
public void setShowFastForwardButton(boolean showFastForwardButton) {
controlViewLayoutManager.setShowButton(fastForwardButton, showFastForwardButton);
updateNavigation();
}
/**
* Sets whether the previous button is shown.
*
* @param showPreviousButton Whether the previous button is shown.
*/
public void setShowPreviousButton(boolean showPreviousButton) {
controlViewLayoutManager.setShowButton(previousButton, showPreviousButton);
updateNavigation();
}
/**
* Sets whether the next button is shown.
*
* @param showNextButton Whether the next button is shown.
*/
public void setShowNextButton(boolean showNextButton) {
controlViewLayoutManager.setShowButton(nextButton, showNextButton);
updateNavigation();
}
/**
* Returns the playback controls timeout. The playback controls are automatically hidden after
* this duration of time has elapsed without user input.
*
* @return The duration in milliseconds. A non-positive value indicates that the controls will
* remain visible indefinitely.
*/
public int getShowTimeoutMs() {
return showTimeoutMs;
}
/**
* Sets the playback controls timeout. The playback controls are automatically hidden after this
* duration of time has elapsed without user input.
*
* @param showTimeoutMs The duration in milliseconds. A non-positive value will cause the controls
* to remain visible indefinitely.
*/
public void setShowTimeoutMs(int showTimeoutMs) {
this.showTimeoutMs = showTimeoutMs;
if (isFullyVisible()) {
controlViewLayoutManager.resetHideCallbacks();
}
}
/**
* Returns which repeat toggle modes are enabled.
*
* @return The currently enabled {@link RepeatModeUtil.RepeatToggleModes}.
*/
public @RepeatModeUtil.RepeatToggleModes int getRepeatToggleModes() {
return repeatToggleModes;
}
/**
* Sets which repeat toggle modes are enabled.
*
* @param repeatToggleModes A set of {@link RepeatModeUtil.RepeatToggleModes}.
*/
public void setRepeatToggleModes(@RepeatModeUtil.RepeatToggleModes int repeatToggleModes) {
this.repeatToggleModes = repeatToggleModes;
if (player != null) {
@Player.RepeatMode int currentMode = player.getRepeatMode();
if (repeatToggleModes == RepeatModeUtil.REPEAT_TOGGLE_MODE_NONE
&& currentMode != Player.REPEAT_MODE_OFF) {
player.setRepeatMode(Player.REPEAT_MODE_OFF);
} else if (repeatToggleModes == RepeatModeUtil.REPEAT_TOGGLE_MODE_ONE
&& currentMode == Player.REPEAT_MODE_ALL) {
player.setRepeatMode(Player.REPEAT_MODE_ONE);
} else if (repeatToggleModes == RepeatModeUtil.REPEAT_TOGGLE_MODE_ALL
&& currentMode == Player.REPEAT_MODE_ONE) {
player.setRepeatMode(Player.REPEAT_MODE_ALL);
}
}
controlViewLayoutManager.setShowButton(
repeatToggleButton, repeatToggleModes != RepeatModeUtil.REPEAT_TOGGLE_MODE_NONE);
updateRepeatModeButton();
}
/** Returns whether the shuffle button is shown. */
public boolean getShowShuffleButton() {
return controlViewLayoutManager.getShowButton(shuffleButton);
}
/**
* Sets whether the shuffle button is shown.
*
* @param showShuffleButton Whether the shuffle button is shown.
*/
public void setShowShuffleButton(boolean showShuffleButton) {
controlViewLayoutManager.setShowButton(shuffleButton, showShuffleButton);
updateShuffleButton();
}
/** Returns whether the subtitle button is shown. */
public boolean getShowSubtitleButton() {
return controlViewLayoutManager.getShowButton(subtitleButton);
}
/**
* Sets whether the subtitle button is shown.
*
* @param showSubtitleButton Whether the subtitle button is shown.
*/
public void setShowSubtitleButton(boolean showSubtitleButton) {
controlViewLayoutManager.setShowButton(subtitleButton, showSubtitleButton);
}
/** Returns whether the VR button is shown. */
public boolean getShowVrButton() {
return controlViewLayoutManager.getShowButton(vrButton);
}
/**
* Sets whether the VR button is shown.
*
* @param showVrButton Whether the VR button is shown.
*/
public void setShowVrButton(boolean showVrButton) {
controlViewLayoutManager.setShowButton(vrButton, showVrButton);
}
/**
* Sets listener for the VR button.
*
* @param onClickListener Listener for the VR button, or null to clear the listener.
*/
public void setVrButtonListener(@Nullable OnClickListener onClickListener) {
if (vrButton != null) {
vrButton.setOnClickListener(onClickListener);
updateButton(onClickListener != null, vrButton);
}
}
/**
* Sets whether an animation is used to show and hide the playback controls.
*
* @param animationEnabled Whether an animation is applied to show and hide playback controls.
*/
public void setAnimationEnabled(boolean animationEnabled) {
controlViewLayoutManager.setAnimationEnabled(animationEnabled);
}
/** Returns whether an animation is used to show and hide the playback controls. */
public boolean isAnimationEnabled() {
return controlViewLayoutManager.isAnimationEnabled();
}
/**
* Sets the minimum interval between time bar position updates.
*
* <p>Note that smaller intervals, e.g. 33ms, will result in a smooth movement but will use more
* CPU resources while the time bar is visible, whereas larger intervals, e.g. 200ms, will result
* in a step-wise update with less CPU usage.
*
* @param minUpdateIntervalMs The minimum interval between time bar position updates, in
* milliseconds.
*/
public void setTimeBarMinUpdateInterval(int minUpdateIntervalMs) {
// Do not accept values below 16ms (60fps) and larger than the maximum update interval.
timeBarMinUpdateIntervalMs =
Util.constrainValue(minUpdateIntervalMs, 16, MAX_UPDATE_INTERVAL_MS);
}
/**
* @deprecated Register a {@link StyledPlayerView.FullscreenButtonClickListener} via {@link
* StyledPlayerView#setFullscreenButtonClickListener(StyledPlayerView.FullscreenButtonClickListener)}
* instead. Using {@link StyledPlayerControlView} as a standalone class without {@link
* StyledPlayerView} is deprecated.
*/
@SuppressWarnings("deprecation")
@Deprecated
public void setOnFullScreenModeChangedListener(
@Nullable OnFullScreenModeChangedListener listener) {
onFullScreenModeChangedListener = listener;
updateFullScreenButtonVisibility(fullScreenButton, listener != null);
updateFullScreenButtonVisibility(minimalFullScreenButton, listener != null);
}
/**
* Shows the playback controls. If {@link #getShowTimeoutMs()} is positive then the controls will
* be automatically hidden after this duration of time has elapsed without user input.
*/
public void show() {
controlViewLayoutManager.show();
}
/** Hides the controller. */
public void hide() {
controlViewLayoutManager.hide();
}
/** Hides the controller without any animation. */
public void hideImmediately() {
controlViewLayoutManager.hideImmediately();
}
/** Returns whether the controller is fully visible, which means all UI controls are visible. */
public boolean isFullyVisible() {
return controlViewLayoutManager.isFullyVisible();
}
/** Returns whether the controller is currently visible. */
public boolean isVisible() {
return getVisibility() == VISIBLE;
}
@SuppressWarnings("deprecation") // Calling the deprecated listener for now.
/* package */ void notifyOnVisibilityChange() {
for (VisibilityListener visibilityListener : visibilityListeners) {
visibilityListener.onVisibilityChange(getVisibility());
}
}
/* package */ void updateAll() {
updatePlayPauseButton();
updateNavigation();
updateRepeatModeButton();
updateShuffleButton();
updateTrackLists();
updatePlaybackSpeedList();
updateTimeline();
}
private void updatePlayPauseButton() {
if (!isVisible() || !isAttachedToWindow) {
return;
}
if (playPauseButton != null) {
if (shouldShowPauseButton()) {
((ImageView) playPauseButton)
.setImageDrawable(resources.getDrawable(R.drawable.exo_styled_controls_pause));
playPauseButton.setContentDescription(
resources.getString(R.string.exo_controls_pause_description));
} else {
((ImageView) playPauseButton)
.setImageDrawable(resources.getDrawable(R.drawable.exo_styled_controls_play));
playPauseButton.setContentDescription(
resources.getString(R.string.exo_controls_play_description));
}
}
}
private void updateNavigation() {
if (!isVisible() || !isAttachedToWindow) {
return;
}
@Nullable Player player = this.player;
boolean enableSeeking = false;
boolean enablePrevious = false;
boolean enableRewind = false;
boolean enableFastForward = false;