2727import android .widget .LinearLayout ;
2828import android .widget .RelativeLayout ;
2929import android .widget .TextView ;
30-
30+ import android .widget .Button ;
31+ import android .widget .SeekBar ;
3132import androidx .annotation .DrawableRes ;
3233import androidx .annotation .NonNull ;
3334import androidx .annotation .Nullable ;
4445import androidx .media3 .ui .PlayerView ;
4546import androidx .media3 .ui .TimeBar ;
4647
48+ import com .google .android .material .dialog .MaterialAlertDialogBuilder ;
4749import org .quantumbadger .redreader .R ;
4850import org .quantumbadger .redreader .common .AndroidCommon ;
4951import org .quantumbadger .redreader .common .General ;
@@ -70,6 +72,9 @@ public interface Listener {
7072 @ Nullable private final DefaultTimeBar mTimeBarView ;
7173 @ Nullable private final TextView mTimeTextView ;
7274
75+ @ Nullable private final TextView mSpeedTextView ;
76+ private float mCurrentPlaybackSpeed = 1.0f ;
77+
7378 private boolean mReleased ;
7479
7580 public ExoPlayerWrapperView (
@@ -233,6 +238,15 @@ public ExoPlayerWrapperView(
233238 addButton (zoomButton .get (), buttons );
234239 }
235240
241+ addButton (createButton (
242+ context ,
243+ mControlView ,
244+ R .drawable .ic_time_dark ,
245+ R .string .video_speed_setting ,
246+ view -> {
247+ openSpeedSettingDialog (context );
248+ }), buttons );
249+
236250 mTimeBarView = new DefaultTimeBar (context , null );
237251 controlBar .addView (mTimeBarView );
238252
@@ -279,11 +293,20 @@ public void run() {
279293 updateProgressRunnable .run ();
280294
281295 {
296+ final LinearLayout timeAndSpeedLayout = new LinearLayout (context );
297+ timeAndSpeedLayout .setOrientation (LinearLayout .HORIZONTAL );
298+ controlBar .addView (timeAndSpeedLayout );
299+
282300 mTimeTextView = new TextView (context );
283- controlBar .addView (mTimeTextView );
301+ timeAndSpeedLayout .addView (mTimeTextView );
284302 mTimeTextView .setTextColor (Color .WHITE );
285303 mTimeTextView .setTextSize (18 );
286304
305+ mSpeedTextView = new TextView (context );
306+ timeAndSpeedLayout .addView (mSpeedTextView );
307+ mSpeedTextView .setTextColor (Color .WHITE );
308+ mSpeedTextView .setText (String .format (Locale .US , "(%.2fx)" , mCurrentPlaybackSpeed ));
309+
287310 final int marginSidesPx = General .dpToPixels (context , 16 );
288311 final int marginBottomPx = General .dpToPixels (context , 8 );
289312
@@ -300,6 +323,7 @@ public void run() {
300323 mControlView = null ;
301324 mTimeBarView = null ;
302325 mTimeTextView = null ;
326+ mSpeedTextView = null ;
303327 }
304328
305329 videoPlayerView .setLayoutParams (new FrameLayout .LayoutParams (
@@ -436,4 +460,120 @@ public static String msToMinutesAndSecondsString(final long ms) {
436460
437461 return String .format (Locale .US , "%d:%02d" , mins , secs );
438462 }
463+ private void openSpeedSettingDialog (final Context context ) {
464+ // Pause video playback before opening the dialog
465+ mVideoPlayer .setPlayWhenReady (false );
466+
467+ final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder (context );
468+ builder .setTitle (R .string .video_speed_instruction );
469+
470+ final LinearLayout speedSettingsLayout = new LinearLayout (context );
471+ speedSettingsLayout .setOrientation (LinearLayout .VERTICAL );
472+ speedSettingsLayout .setPadding (20 , 20 , 20 , 20 );
473+
474+ final TextView speedValue = new TextView (context );
475+ speedValue .setText (String .format (
476+ Locale .US ,
477+ getResources ().getString (R .string .video_speed_term ) + ": %.2fx" ,
478+ mCurrentPlaybackSpeed )
479+ );
480+ speedValue .setTextSize (18 );
481+ speedValue .setPadding (10 , 10 , 10 , 10 );
482+
483+ final SeekBar speedSeekBar = new SeekBar (context );
484+ speedSeekBar .setMax (300 - 1 ); // 3.00 is max for now
485+
486+ final LinearLayout .LayoutParams seekBarParams = new LinearLayout .LayoutParams (
487+ LinearLayout .LayoutParams .MATCH_PARENT ,
488+ LinearLayout .LayoutParams .WRAP_CONTENT
489+ );
490+ seekBarParams .height = 150 ;
491+ speedSeekBar .setLayoutParams (seekBarParams );
492+
493+ speedSeekBar .setProgress ((int ) ((mCurrentPlaybackSpeed - 0.01 ) * 100 ));
494+
495+ // Listener for changes in the SeekBar's position
496+ speedSeekBar .setOnSeekBarChangeListener (new SeekBar .OnSeekBarChangeListener () {
497+ @ Override
498+ public void onProgressChanged (final SeekBar seekBar ,
499+ final int progress ,final boolean fromUser ) {
500+ final float selectedSpeed = 0.01f * (progress + 1 );
501+ speedValue .setText (String .format (
502+ Locale .US ,
503+ getResources ().getString (R .string .video_speed_term ) + ": %.2fx" ,
504+ selectedSpeed )
505+ );
506+ }
507+
508+ @ Override
509+ public void onStartTrackingTouch (final SeekBar seekBar ) { }
510+
511+ @ Override
512+ public void onStopTrackingTouch (final SeekBar seekBar ) { }
513+ });
514+
515+ speedSettingsLayout .addView (speedValue );
516+ speedSettingsLayout .addView (speedSeekBar );
517+
518+ // Creating rows for the speed preset buttons
519+ final LinearLayout [] rows = new LinearLayout [4 ];
520+ for (int i = 0 ; i < rows .length ; i ++) {
521+ rows [i ] = new LinearLayout (context );
522+ rows [i ].setOrientation (LinearLayout .HORIZONTAL );
523+ speedSettingsLayout .addView (rows [i ]);
524+ }
525+
526+ // Labels and values for the preset speed buttons
527+ final String [] speedLabels = {"0.5" , "1.0" , "1.5" , "2.0" };
528+ final int [] speedProgressValues = {49 , 99 , 149 , 199 };
529+
530+ // Adding preset speed buttons to the layout
531+ for (int i = 0 ; i < speedLabels .length ; i ++) {
532+ addPresetSpeedButton (rows [i / 4 ], speedLabels [i ],
533+ speedProgressValues [i ], speedSeekBar , speedValue , context );
534+ }
535+
536+ builder .setView (speedSettingsLayout );
537+
538+ builder .setPositiveButton (R .string .dialog_go , (dialog , which ) -> {
539+ mCurrentPlaybackSpeed = 0.01f * (speedSeekBar .getProgress () + 1 );
540+ mVideoPlayer .setPlaybackSpeed (mCurrentPlaybackSpeed );
541+ mSpeedTextView .setText (String .format (Locale .US , "(%.2fx)" , mCurrentPlaybackSpeed ));
542+ // Resume video playback when dialog is dismissed
543+ mVideoPlayer .setPlayWhenReady (true );
544+ });
545+ builder .setNegativeButton (R .string .dialog_cancel , (dialog , which ) -> {
546+ // Resume video playback when dialog is dismissed
547+ mVideoPlayer .setPlayWhenReady (true );
548+ });
549+
550+ builder .setOnCancelListener (dialog -> {
551+ // Resume video playback when dialog is canceled
552+ mVideoPlayer .setPlayWhenReady (true );
553+ });
554+
555+ builder .show ();
556+ }
557+
558+ // Method to add a preset speed button to the layout
559+ private void addPresetSpeedButton (final LinearLayout rowLayout , final String label ,
560+ final int progress , final SeekBar seekBar ,
561+ final TextView speedValue , final Context context ) {
562+ final Button speedButton = new Button (context );
563+ speedButton .setText (label );
564+ // Setting button click behavior to update the SeekBar and speedValue TextView
565+ speedButton .setOnClickListener (v -> {
566+ seekBar .setProgress (progress );
567+ final float selectedSpeed = 0.01f * (progress + 1 );
568+ speedValue .setText (String .format (
569+ Locale .US ,
570+ getResources ().getString (R .string .video_speed_term ) + ": %.2fx" ,
571+ selectedSpeed )
572+ );
573+ });
574+
575+ final LinearLayout .LayoutParams params = new LinearLayout .LayoutParams (
576+ 0 , LayoutParams .WRAP_CONTENT , 1.0f );
577+ rowLayout .addView (speedButton , params );
578+ }
439579}
0 commit comments