suragch/audio_video_progress_bar

Thumb initial position is always zero

Closed this issue · 11 comments

By default, the progress bar thumb is on position zero despite the fact that the progress value is set to different value. I tried to resolve this issue and I think this is because the double _thumbValue = 0.0 . It should be given a value in constructor. I tried to fix this and came with this solution:
audio_video_progress_bar.zip
P. S. I also implemented an isDraggable named parameter, in order to restrict the possibility to move the thumb if needed.

Could you give a code example that shows the error? (preferably as an in-answer code block or a link to a GitHub repo rather and a zip file) I'm not sure I really understand.

The isDraggable option sounds like a different issue. Could you open another issue for that one and explain your use case?

I think the bug that he is trying to show is the one on this video.

video_bug.mp4

To replicate it you just need to add the seekbar inside a TweenAnimationBuilder, the code to replicated is below:

class AnimatedVisibility extends StatelessWidget {
  final Duration duration;
  final Widget child;
  final bool visible;

  const AnimatedVisibility({
    Key? key,
    required this.duration,
    required this.child,
    required this.visible,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return TweenAnimationBuilder<double>(
      tween: Tween<double>(
        begin: visible ? 0.0 : 1.0,
        end: visible ? 1.0 : 0.0,
      ),
      child: child,
      duration: duration,
      builder: (context, value, child) {
        return Visibility(
          visible: value != 0,
          child: Opacity(
            opacity: value,
            child: child,
          ),
        );
      },
    );
  }
}
Align(
  alignment: Alignment.bottomCenter,
  child: AnimatedVisibility(
    visible: _isSeekbarVisible,
    duration: VideoPlayer.hideControlsAnimation,
    child: Seekbar(
      position: widget.videoPlayerController.progress,
      onChanged: (progress) {
        widget.videoPlayerController.setProgress(progress);
      },
      onChanging: _showSeekbarForAMoment,
    ),
  ),
)
class Seekbar extends StatefulWidget {
  final Stream<DurationState> position;
  final Color color;
  final Function(Duration position) onChanged;
  final VoidCallback? onChanging;

  const Seekbar({
    Key? key,
    required this.position,
    this.color = Colors.red,
    required this.onChanged,
    this.onChanging,
  }) : super(key: key);

  @override
  _SeekbarState createState() => _SeekbarState();
}

class _SeekbarState extends State<Seekbar> {
  double? changedPosition;

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<DurationState>(
      stream: widget.position,
      initialData: DurationState(
        progress: Duration.zero,
        total: Duration.zero,
        buffered: Duration.zero,
      ),
      builder: (context, snapshot) {
        var videoProgress = snapshot.data ??
            DurationState(
              progress: Duration.zero,
              buffered: Duration.zero,
              total: Duration.zero,
            );
        return Padding(
          padding: EdgeInsets.only(left: 12.0, right: 16.0, bottom: 16.0, top: 16),
          child: ProgressBar(
            progress: videoProgress.progress,
            buffered: videoProgress.buffered,
            total: videoProgress.total,
            barHeight: 2.0,
            progressBarColor: AppColors.orange_1,
            baseBarColor: AppColors.whiteFF.withOpacity(0.4),
            bufferedBarColor: AppColors.whiteFF,
            thumbRadius: 6.0,
            thumbColor: AppColors.orange_1,
            thumbGlowRadius: 12.0,
            thumbGlowColor: AppColors.orange_1.withOpacity(0.4),
            timeLabelLocation: TimeLabelLocation.none,
            onSeek: (duration) {
              widget.onChanging?.call();
              widget.onChanged(duration);
            },
          ),
        );
      },
    );
  }

So the effect that is trying to be achieved is fade in and fade out for the progress bar, correct? I'm able to achieve that effect with AnimatedOpacity like so:

StreamBuilder<DurationState> _progressBar() {
  return StreamBuilder<DurationState>(
    stream: _durationState,
    builder: (context, snapshot) {
      final durationState = snapshot.data;
      final progress = durationState?.progress ?? Duration.zero;
      final buffered = durationState?.buffered ?? Duration.zero;
      final total = durationState?.total ?? Duration.zero;
      return AnimatedOpacity(
        opacity: _visibility,
        duration: const Duration(milliseconds: 300),
        child: ProgressBar(
          progress: progress,
          buffered: buffered,
          total: total,
          onSeek: (duration) {
            _player.seek(duration);
          },
          timeLabelLocation: _labelLocation,
          timeLabelType: _labelType,
          timeLabelTextStyle: _labelStyle,
        ),
      );
    },
  );
}

This is from a modified version of the example project. Here is what it looks like:

visibility

I don't get the thumb jumping to zero.

Theoretically the thumb is just a ratio of the progress to the total time. It should only be zero if the progress or the total time is zero. That might indicate an error with these values.

I have the same issue using BLoC and ProgressBar widget.

return BlocBuilder<ClipPlaybackBloc, ClipPlaybackState>(
      builder: (context, state) {
        return ProgressBar(
            thumbRadius: 10,
            timeLabelTextStyle: TextStyle(color: Colors.white),
            timeLabelLocation: TimeLabelLocation.none,
            baseBarColor: Colors.white.withOpacity(0.24),
            bufferedBarColor: Colors.white.withOpacity(0.24),
            thumbColor: Colors.white,
            progressBarColor: Theme.of(context).accentColor,
            progress: state.newDuration,
            buffered: Duration(milliseconds: 2000),
            total: Duration(milliseconds: 10000),
            onSeek: (newDuration) {
              _handleTimeChange(context, newDuration);
            });
      },
    );

Everything works fine until I decide to hide/show ProgressBar widget. Please see attached gif:
new_player_3

If you add print(state.newDuration) before the return statement, what value does it show when you make the ProgressBar visible again?

I added logger for _handleTimeChange method and output is as expected.

I/flutter ( 4708): 15:57:32.154 (+0:06:22.702952): Clip Playback Player Progress Bar: User selected new duration:  0:00:01.745000
I/flutter ( 4708): 15:57:33.818 (+0:06:24.367607): Clip Playback Player Progress Bar: User selected new duration:  0:00:07.686000

edit:

this is what I got before return statement:

I/flutter ( 4708): 16:03:48.946 (+0:12:39.495752): Clip Playback Player Progress Bar: User selected new duration:  0:00:03.073000
I/flutter ( 4708): 16:03:50.130 (+0:12:40.679133): Clip Playback Player Progress Bar: User selected new duration:  0:00:08.285000

I managed to workaround this ussue by using following method instead of Visibility widget

return AnimatedOpacity(opacity: state.isFullScreen ? 0.0 : 1.0, duration: Duration(milliseconds: 500), child: child);

Several people have mentioned the problem when using the Visibility widget. I'll need to look more into why that is. I'm glad AnimatedOpacity works for you, though.

I suppose this is because thumb widget doesn't depend on progress value. You can remember state for progress but widget can not recover thumb position after it's being rebuilt.

@enseitankad0 Thanks for your comment. That put me on the right track to solving the problem.

I looked more into the Visibility widget. I learned that by default it removes its child from the widget tree. This has the effect of losing any state that the child may have. For ProgressBar the _thumbValue was internal state. The problems above could have been solved if people had set the maintainState parameter of Visibility to true. The AnimatedOpacity widget, on the other hand, does not remove the widget from the tree, so the state is never lost, which is why that option also worked.

That explanation aside, I updated ProgressBar so that _thumbValue is initialized from the values of progress and total rather than a hard-coded 0.0 when the widget is first created. This removes the need to save state when hiding the widget, so Visibility works now without needing to set maintainState.

I've published the change as version 0.6.2.

If anyone can confirm that it works I'll close this issue.

I'll close this issue for now but I can reopen it if there are any more problems related to it.