sarbagyastha / youtube_player_flutter

A Flutter plugin for inline playback or streaming of YouTube videos using the official iFrame Player API.
https://youtube.sarbagyastha.com.np
BSD 3-Clause "New" or "Revised" License
713 stars 827 forks source link

Progress Bar component outside of video player #109

Closed Skquark closed 5 years ago

Skquark commented 5 years ago

I'm wanting to have the ProgressBar placed outside the widget's bottom and below the video, but when I put it there, it's not able to find the controller with the _controller = YoutubePlayerController.of(context); So I duplicated the ProgressBar.dart as my own component and modded it to pass in YoutubePlayerController (and some other minor mods to add option to not allow moving forward on the progress, which functions) and I got it partially working, but after tinkering with it, it's not calling positionListener on the controller to update the progress bar. It looks correct, but something is off. Here's my modified version I hope someone can help me with: YoutubeProgressBar.dart

import 'package:flutter/material.dart';
import 'package:youtube_player_flutter/youtube_player_flutter.dart';

/// A widget to display video progress bar outside player. Mod by Alan..
class YoutubeProgressBar extends StatefulWidget {
  final ProgressBarColors colors;
  final bool isExpanded;
  final bool noForward;
  final YoutubePlayerController controller;

  YoutubeProgressBar({
    this.colors,
    this.isExpanded = false,
    this.noForward = false,
    this.controller,
  });

  @override
  _YoutubeProgressBarState createState() {
    return _YoutubeProgressBarState();
  }
}

class _YoutubeProgressBarState extends State<YoutubeProgressBar> {
  YoutubePlayerController _controller;

  Offset _touchPoint = Offset.zero;

  double _playedValue = 0.0;
  double _bufferedValue = 0.0;

  bool _touchDown = false;

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print("Progress bar Change Dependencies");
    _controller = widget.controller; //YoutubePlayerController.of(context)
    if (_controller == null) _controller = YoutubePlayerController.of(context);
      //..addListener(positionListener);
      _controller.addListener(positionListener);
    //widget.controller.addListener(positionListener);
  }

  @override
  void dispose() {
    widget.controller?.removeListener(positionListener);
    super.dispose();
  }

  void positionListener() {
    int _totalDuration = widget.controller.value.duration?.inMilliseconds;
    print("positionListener duration: $_totalDuration");
    if (mounted && _totalDuration != null && _totalDuration != 0) {
      setState(() {
        _playedValue = widget.controller.value.position.inMilliseconds / _totalDuration;
        _bufferedValue = widget.controller.value.buffered;
        print("Played $_playedValue - Buffered $_bufferedValue - Total $_totalDuration");
      });
    }
  }

  void _setValue() {
    _playedValue = _touchPoint.dx / context.size.width;
  }

  void _checkTouchPoint() {
    if (_touchPoint.dx <= 0) {
      _touchPoint = Offset(0, _touchPoint.dy);
    }
    if (_touchPoint.dx >= context.size.width) {
      _touchPoint = Offset(context.size.width, _touchPoint.dy);
    }
  }

  Offset _getTouchPoint(Offset touchPoint) {
    if (touchPoint.dx <= 0) {
      touchPoint = Offset(0, touchPoint.dy);
    }
    if (touchPoint.dx >= context.size.width) {
      touchPoint = Offset(context.size.width, touchPoint.dy);
    }
    return touchPoint;
  }

  void _seekToRelativePosition(Offset globalPosition) {
    final RenderBox box = context.findRenderObject();
    Offset touchPoint = box.globalToLocal(globalPosition);
    touchPoint = _getTouchPoint(touchPoint);
    //_touchPoint = box.globalToLocal(globalPosition);
    //_checkTouchPoint();
    final double relative = touchPoint.dx / box.size.width;
    final Duration position = widget.controller.value.duration * relative;
    int diff = position.compareTo(widget.controller.value.position);
    bool isForward = diff > 0;
    print("Seek to $relative forward:$isForward diff:$diff position: ${position.inMilliseconds} current:" + _controller.value.position.inMilliseconds.toString());
    if (widget.noForward && isForward) {
      print("Sorry, you can't fast-forward");
      setState(() => _touchDown = false);
    } else {
      _touchPoint = touchPoint;
      _checkTouchPoint();
      widget.controller.seekTo(position);
    }
  }

  Widget _buildBar() {
    return GestureDetector(
      onHorizontalDragDown: (details) {
        _seekToRelativePosition(details.globalPosition);
        setState(() {
          _setValue();
          _touchDown = true;
        });
      },
      onHorizontalDragUpdate: (details) {
        _seekToRelativePosition(details.globalPosition);
        setState(() {
          _setValue();
        });
      },
      onHorizontalDragEnd: (details) {
        setState(() {
          _touchDown = false;
        });
        widget.controller.play();
      },
      child: Container(
        constraints: BoxConstraints.expand(height: 7.0 * 2),
        child: CustomPaint(
          painter: _YoutubeProgressBarPainter(
            progressWidth: 2.0,
            handleRadius: 7.0,
            playedValue: _playedValue,
            bufferedValue: _bufferedValue,
            colors: widget.colors,
            touchDown: _touchDown,
            themeData: Theme.of(context),
          ),
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) =>
      widget.isExpanded ? Expanded(child: _buildBar()) : _buildBar();
}

class _YoutubeProgressBarPainter extends CustomPainter {
  final double progressWidth;
  final double handleRadius;
  final double playedValue;
  final double bufferedValue;
  final ProgressBarColors colors;
  final bool touchDown;
  final ThemeData themeData;

  _YoutubeProgressBarPainter({
    this.progressWidth,
    this.handleRadius,
    this.playedValue,
    this.bufferedValue,
    this.colors,
    this.touchDown,
    this.themeData,
  });

  @override
  bool shouldRepaint(_YoutubeProgressBarPainter old) {
    return playedValue != old.playedValue ||
        bufferedValue != old.bufferedValue ||
        touchDown != old.touchDown;
  }

  @override
  void paint(Canvas canvas, Size size) {
    final Paint paint = Paint()
      ..isAntiAlias = true
      ..strokeCap = StrokeCap.square
      ..strokeWidth = progressWidth;

    final centerY = size.height / 2.0;
    final barLength = size.width - handleRadius * 2.0;

    final Offset startPoint = Offset(handleRadius, centerY);
    final Offset endPoint = Offset(size.width - handleRadius, centerY);
    final Offset progressPoint =
        Offset(barLength * playedValue + handleRadius, centerY);
    final Offset secondProgressPoint =
        Offset(barLength * bufferedValue + handleRadius, centerY);

    paint.color =
        colors?.backgroundColor ?? themeData.accentColor.withOpacity(0.38);
    canvas.drawLine(startPoint, endPoint, paint);

    paint.color = colors?.bufferedColor ?? Colors.white70;
    canvas.drawLine(startPoint, secondProgressPoint, paint);

    paint.color = colors?.playedColor ?? themeData.accentColor;
    canvas.drawLine(startPoint, progressPoint, paint);

    final Paint handlePaint = Paint()..isAntiAlias = true;

    handlePaint.color = Colors.transparent;
    canvas.drawCircle(progressPoint, centerY, handlePaint);

    final Color _handleColor = colors?.handleColor ?? themeData.accentColor;

    if (touchDown) {
      handlePaint.color = _handleColor.withOpacity(0.4);
      canvas.drawCircle(progressPoint, handleRadius * 2, handlePaint);
    }

    handlePaint.color = _handleColor;
    canvas.drawCircle(progressPoint, handleRadius, handlePaint);
  }
}

When I was using it with internal variable _controller, it wasn't getting the video values but it worked when I called it as widget.controller. However, the addListener isn't updating when it's playing.. Any thoughts?

sarbagyastha commented 5 years ago

I will update all the widgets with optional controller parameter tomorrow.

sarbagyastha commented 5 years ago

Added in Version 6.0.0.