| // Copyright 2017 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'dart:async'; |
| |
| import 'package:lib.mediaplayer.dart/audio_player_controller.dart'; |
| |
| import 'package:flutter/foundation.dart'; |
| import 'package:flutter/widgets.dart'; |
| |
| /// Notifier for animating widgets that reflect player progress. This class |
| /// can be used with |AnimatedBuilder| to create widgets that animate based on |
| /// the progress of an |AudioPlayerController| or |MediaPlayerController|. |
| /// Builders should call either |withResolution| or |withExcursion|, typically |
| /// when setting |AnimatedBuilder.animation|. Builders that call those methods |
| /// should run when the controller updates. This is because |ProgressNotifier| |
| /// doesn't allow for transitions such as play/pause or seeking. It needs the |
| /// one of the 'with' methods to be called when those transitions occur. |
| class ProgressNotifier extends ChangeNotifier { |
| AudioPlayerController _controller; |
| Duration _resolution; |
| Timer _timer; |
| bool _disposed = false; |
| |
| /// Constructs a |ProgressNotifier|. |
| ProgressNotifier(this._controller) : assert(_controller != null); |
| |
| @override |
| void dispose() { |
| _disposed = true; |
| _controller = null; |
| _timer?.cancel(); |
| _timer = null; |
| super.dispose(); |
| } |
| |
| /// Registers a one-time progress callback with the controller. |
| void _register() { |
| if (!_controller.playing) { |
| _timer = null; |
| return; |
| } |
| |
| // TODO(dalesat): Take the rate into account once we support that. |
| |
| int resolutionMicroseconds = _resolution.inMicroseconds; |
| int delayMicroseconds = resolutionMicroseconds - |
| (_controller.progress.inMicroseconds % resolutionMicroseconds); |
| |
| _timer = Timer(Duration(microseconds: delayMicroseconds), () { |
| _timer = null; |
| |
| if (_disposed || !_controller.playing) { |
| return; |
| } |
| |
| notifyListeners(); |
| _register(); |
| }); |
| } |
| |
| /// Sets the resolution and returns this |ProgressNotifier|. This method |
| /// should be called in a builder that runs when the controller notifies of |
| /// a state change. |
| // ignore: avoid_returning_this |
| ProgressNotifier withResolution(Duration resolution) { |
| if (_disposed) { |
| return this; |
| } |
| |
| if (_timer == null || _resolution != resolution) { |
| _resolution = resolution; |
| _timer?.cancel(); |
| _register(); |
| } |
| |
| return this; |
| } |
| |
| /// Sets the excursion and returns this |ProgressNotifier|. This method |
| /// should be called in a builder that runs when the controller notifies of |
| /// a state change. This method is useful for sliders where |excursion| is |
| /// the distance the slider moves. Use |LayoutBuilder| to get the width of |
| /// the slider. |
| ProgressNotifier withExcursion(double excursion, BuildContext context) { |
| double effectiveExcursion = |
| excursion * MediaQuery.of(context).devicePixelRatio; |
| return withResolution(Duration( |
| microseconds: |
| (_controller.duration.inMicroseconds / effectiveExcursion).ceil())); |
| } |
| } |