[video_player] Improve DartDocs and test coverage (#2286)
Also adds a lint to prevent undocumented APIs going forward.
diff --git a/packages/video_player/video_player/CHANGELOG.md b/packages/video_player/video_player/CHANGELOG.md
index 1bac34f..d4824c4 100644
--- a/packages/video_player/video_player/CHANGELOG.md
+++ b/packages/video_player/video_player/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.10.3+3
+
+* Add DartDocs and unit tests.
+
## 0.10.3+2
* Update the homepage to point to the new plugin location
diff --git a/packages/video_player/video_player/analysis_options.yaml b/packages/video_player/video_player/analysis_options.yaml
new file mode 100644
index 0000000..6c7fd57
--- /dev/null
+++ b/packages/video_player/video_player/analysis_options.yaml
@@ -0,0 +1,11 @@
+# This exists to add a lint for missing API docs just on this specific package,
+# since not all packages have coverage for all their public members yet and
+# adding it in would be non-trivial. `public_member_api_docs` should be applied
+# to new packages going forward, and ideally the main `analysis_options.yaml`
+# file as soon as possible.
+
+include: ../../../analysis_options.yaml
+
+linter:
+ rules:
+ - public_member_api_docs
diff --git a/packages/video_player/video_player/example/lib/main.dart b/packages/video_player/video_player/example/lib/main.dart
index 43d9355..f55d3f7 100644
--- a/packages/video_player/video_player/example/lib/main.dart
+++ b/packages/video_player/video_player/example/lib/main.dart
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+// ignore_for_file: public_member_api_docs
+
/// An example of using the plugin, controlling lifecycle and playback of the
/// video.
diff --git a/packages/video_player/video_player/lib/video_player.dart b/packages/video_player/video_player/lib/video_player.dart
index f1b0e7c..f92693e 100644
--- a/packages/video_player/video_player/lib/video_player.dart
+++ b/packages/video_player/video_player/lib/video_player.dart
@@ -14,16 +14,46 @@
// performed.
..invokeMethod<void>('init');
+/// Describes a discrete segment of time within a video using a [start] and
+/// [end] [Duration].
class DurationRange {
+ /// Trusts that the given [start] and [end] are actually in order. They should
+ /// both be non-null.
DurationRange(this.start, this.end);
+ /// The beginning of the segment described relative to the beginning of the
+ /// entire video. Should be shorter than or equal to [end].
+ ///
+ /// For example, if the entire video is 4 minutes long and the range is from
+ /// 1:00-2:00, this should be a `Duration` of one minute.
final Duration start;
+
+ /// The end of the segment described as a duration relative to the beginning of
+ /// the entire video. This is expected to be non-null and longer than or equal
+ /// to [start].
+ ///
+ /// For example, if the entire video is 4 minutes long and the range is from
+ /// 1:00-2:00, this should be a `Duration` of two minutes.
final Duration end;
+ /// Assumes that [duration] is the total length of the video that this
+ /// DurationRange is a segment form. It returns the percentage that [start] is
+ /// through the entire video.
+ ///
+ /// For example, assume that the entire video is 4 minutes long. If [start] has
+ /// a duration of one minute, this will return `0.25` since the DurationRange
+ /// starts 25% of the way through the video's total length.
double startFraction(Duration duration) {
return start.inMilliseconds / duration.inMilliseconds;
}
+ /// Assumes that [duration] is the total length of the video that this
+ /// DurationRange is a segment form. It returns the percentage that [start] is
+ /// through the entire video.
+ ///
+ /// For example, assume that the entire video is 4 minutes long. If [end] has a
+ /// duration of two minutes, this will return `0.5` since the DurationRange
+ /// ends 50% of the way through the video's total length.
double endFraction(Duration duration) {
return end.inMilliseconds / duration.inMilliseconds;
}
@@ -32,11 +62,26 @@
String toString() => '$runtimeType(start: $start, end: $end)';
}
-enum VideoFormat { dash, hls, ss, other }
+/// The file format of the given video.
+enum VideoFormat {
+ /// Dynamic Adaptive Streaming over HTTP, also known as MPEG-DASH.
+ dash,
+
+ /// HTTP Live Streaming.
+ hls,
+
+ /// Smooth Streaming.
+ ss,
+
+ /// Any format other than the other ones defined in this enum.
+ other
+}
/// The duration, current position, buffering state, error state and settings
/// of a [VideoPlayerController].
class VideoPlayerValue {
+ /// Constructs a video with the given values. Only [duration] is required. The
+ /// rest will initialize with default values when unset.
VideoPlayerValue({
@required this.duration,
this.size,
@@ -49,8 +94,11 @@
this.errorDescription,
});
+ /// Returns an instance with a `null` [Duration].
VideoPlayerValue.uninitialized() : this(duration: null);
+ /// Returns an instance with a `null` [Duration] and the given
+ /// [errorDescription].
VideoPlayerValue.erroneous(String errorDescription)
: this(duration: null, errorDescription: errorDescription);
@@ -87,10 +135,19 @@
/// Is null when [initialized] is false.
final Size size;
+ /// Indicates whether or not the video has been loaded and is ready to play.
bool get initialized => duration != null;
+
+ /// Indicates whether or not the video is in an error state. If this is true
+ /// [errorDescription] should have information about the problem.
bool get hasError => errorDescription != null;
+
+ /// Returns [size.width] / [size.height] when size is non-null, or `1.0.` when
+ /// it is.
double get aspectRatio => size != null ? size.width / size.height : 1.0;
+ /// Returns a new instance that has the same values as this current instance,
+ /// except for any overrides passed in as arguments to [copyWidth].
VideoPlayerValue copyWith({
Duration duration,
Size size,
@@ -130,7 +187,19 @@
}
}
-enum DataSourceType { asset, network, file }
+/// The way in which the video was originally loaded. This has nothing to do
+/// with the video's file type. It's just the place from which the video is
+/// fetched from.
+enum DataSourceType {
+ /// The video was included in the app's asset files.
+ asset,
+
+ /// The video was downloaded from the internet.
+ network,
+
+ /// The video was loaded off of the local filesystem.
+ file
+}
/// Controls a platform video player, and provides updates when the state is
/// changing.
@@ -177,13 +246,20 @@
super(VideoPlayerValue(duration: null));
int _textureId;
+
+ /// The URI to the video file. This will be in different formats depending on
+ /// the [DataSourceType] of the original video.
final String dataSource;
+
+ /// **Android only**. Will override the platform's generic file format
+ /// detection with whatever is set here.
final VideoFormat formatHint;
/// Describes the type of data source this [VideoPlayerController]
/// is constructed with.
final DataSourceType dataSourceType;
+ /// Only set for [asset] videos. The package that the asset was loaded from.
final String package;
Timer _timer;
bool _isDisposed = false;
@@ -191,9 +267,12 @@
StreamSubscription<dynamic> _eventSubscription;
_VideoAppLifeCycleObserver _lifeCycleObserver;
+ /// This is just exposed for testing. It shouldn't be used by anyone depending
+ /// on the plugin.
@visibleForTesting
int get textureId => _textureId;
+ /// Attempts to open the given [dataSource] and load metadata about the video.
Future<void> initialize() async {
_lifeCycleObserver = _VideoAppLifeCycleObserver(this);
_lifeCycleObserver.initialize();
@@ -305,16 +384,24 @@
super.dispose();
}
+ /// Starts playing the video.
+ ///
+ /// This method returns a future that completes as soon as the "play" command
+ /// has been sent to the platform, not when playback itself is totally
+ /// finished.
Future<void> play() async {
value = value.copyWith(isPlaying: true);
await _applyPlayPause();
}
+ /// Sets whether or not the video should loop after playing once. See also
+ /// [VideoPlayerValue.isLooping].
Future<void> setLooping(bool looping) async {
value = value.copyWith(isLooping: looping);
await _applyLooping();
}
+ /// Pauses the video.
Future<void> pause() async {
value = value.copyWith(isPlaying: false);
await _applyPlayPause();
@@ -384,6 +471,11 @@
);
}
+ /// Sets the video's current timestamp to be at [moment]. The next
+ /// time the video is played it will resume from the given [moment].
+ ///
+ /// If [moment] is outside of the video's full range it will be automatically
+ /// and silently clamped.
Future<void> seekTo(Duration moment) async {
if (_isDisposed) {
return;
@@ -449,10 +541,13 @@
}
}
-/// Displays the video controlled by [controller].
+/// Widget that displays the video controlled by [controller].
class VideoPlayer extends StatefulWidget {
+ /// Uses the given [controller] for all video rendered in this widget.
VideoPlayer(this.controller);
+ /// The [VideoPlayerController] responsible for the video being rendered in
+ /// this widget.
final VideoPlayerController controller;
@override
@@ -503,15 +598,43 @@
}
}
+/// Used to configure the [VideoProgressIndicator] widget's colors for how it
+/// describes the video's status.
+///
+/// The widget uses default colors that are customizeable through this class.
class VideoProgressColors {
+ /// Any property can be set to any color. They each have defaults.
+ ///
+ /// [playedColor] defaults to red at 70% opacity. This fills up a portion of
+ /// the [VideoProgressIndicator] to represent how much of the video has played
+ /// so far.
+ ///
+ /// [bufferedColor] defaults to blue at 20% opacity. This fills up a portion
+ /// of [VideoProgressIndicator] to represent how much of the video has
+ /// buffered so far.
+ ///
+ /// [backgroundColor] defaults to gray at 50% opacity. This is the background
+ /// color behind both [playedColor] and [bufferedColor] to denote the total
+ /// size of the video compared to either of those values.
VideoProgressColors({
this.playedColor = const Color.fromRGBO(255, 0, 0, 0.7),
this.bufferedColor = const Color.fromRGBO(50, 50, 200, 0.2),
this.backgroundColor = const Color.fromRGBO(200, 200, 200, 0.5),
});
+ /// [playedColor] defaults to red at 70% opacity. This fills up a portion of
+ /// the [VideoProgressIndicator] to represent how much of the video has played
+ /// so far.
final Color playedColor;
+
+ /// [bufferedColor] defaults to blue at 20% opacity. This fills up a portion
+ /// of [VideoProgressIndicator] to represent how much of the video has
+ /// buffered so far.
final Color bufferedColor;
+
+ /// [backgroundColor] defaults to gray at 50% opacity. This is the background
+ /// color behind both [playedColor] and [bufferedColor] to denote the total
+ /// size of the video compared to either of those values.
final Color backgroundColor;
}
@@ -584,6 +707,12 @@
/// [padding] allows to specify some extra padding around the progress indicator
/// that will also detect the gestures.
class VideoProgressIndicator extends StatefulWidget {
+ /// Construct an instance that displays the play/buffering status of the video
+ /// controlled by [controller].
+ ///
+ /// Defaults will be used for everything except [controller] if they're not
+ /// provided. [allowScrubbing] defaults to false, and [padding] will default
+ /// to `top: 5.0`.
VideoProgressIndicator(
this.controller, {
VideoProgressColors colors,
@@ -591,9 +720,25 @@
this.padding = const EdgeInsets.only(top: 5.0),
}) : colors = colors ?? VideoProgressColors();
+ /// The [VideoPlayerController] that actually associates a video with this
+ /// widget.
final VideoPlayerController controller;
+
+ /// The default colors used throughout the indicator.
+ ///
+ /// See [VideoProgressColors] for default values.
final VideoProgressColors colors;
+
+ /// When true, the widget will detect touch input and try to seek the video
+ /// accordingly. The widget ignores such input when false.
+ ///
+ /// Defaults to false.
final bool allowScrubbing;
+
+ /// This allows for visual padding around the progress indicator that can
+ /// still detect gestures via [allowScrubbing].
+ ///
+ /// Defaults to `top: 5.0`.
final EdgeInsets padding;
@override
diff --git a/packages/video_player/video_player/pubspec.yaml b/packages/video_player/video_player/pubspec.yaml
index 32d1e98..cf85568 100644
--- a/packages/video_player/video_player/pubspec.yaml
+++ b/packages/video_player/video_player/pubspec.yaml
@@ -2,8 +2,8 @@
description: Flutter plugin for displaying inline video with other Flutter
widgets on Android and iOS.
author: Flutter Team <flutter-dev@googlegroups.com>
-version: 0.10.3+2
-homepage: https://github.com/flutter/plugins/tree/master/packages/video_player/video_player
+version: 0.10.3+3
+homepage: https://github.com/flutter/plugins/tree/master/packages/video_player
flutter:
plugin:
diff --git a/packages/video_player/video_player/test/video_player_test.dart b/packages/video_player/video_player/test/video_player_test.dart
index 9211f0c..10b5754 100644
--- a/packages/video_player/video_player/test/video_player_test.dart
+++ b/packages/video_player/video_player/test/video_player_test.dart
@@ -90,55 +90,338 @@
fakeVideoPlayerPlatform = FakeVideoPlayerPlatform();
});
- test('initialize asset', () async {
- final VideoPlayerController controller = VideoPlayerController.asset(
- 'a.avi',
- );
- await controller.initialize();
+ group('initialize', () {
+ test('asset', () async {
+ final VideoPlayerController controller = VideoPlayerController.asset(
+ 'a.avi',
+ );
+ await controller.initialize();
- expect(
- fakeVideoPlayerPlatform.dataSourceDescriptions[0], <String, dynamic>{
- 'asset': 'a.avi',
- 'package': null,
+ expect(
+ fakeVideoPlayerPlatform.dataSourceDescriptions[0],
+ <String, dynamic>{
+ 'asset': 'a.avi',
+ 'package': null,
+ });
+ });
+
+ test('network', () async {
+ final VideoPlayerController controller = VideoPlayerController.network(
+ 'https://127.0.0.1',
+ );
+ await controller.initialize();
+
+ expect(
+ fakeVideoPlayerPlatform.dataSourceDescriptions[0],
+ <String, dynamic>{
+ 'uri': 'https://127.0.0.1',
+ 'formatHint': null,
+ });
+ });
+
+ test('network with hint', () async {
+ final VideoPlayerController controller = VideoPlayerController.network(
+ 'https://127.0.0.1',
+ formatHint: VideoFormat.dash);
+ await controller.initialize();
+
+ expect(
+ fakeVideoPlayerPlatform.dataSourceDescriptions[0],
+ <String, dynamic>{
+ 'uri': 'https://127.0.0.1',
+ 'formatHint': 'dash',
+ });
+ });
+
+ test('file', () async {
+ final VideoPlayerController controller =
+ VideoPlayerController.file(File('a.avi'));
+ await controller.initialize();
+
+ expect(
+ fakeVideoPlayerPlatform.dataSourceDescriptions[0],
+ <String, dynamic>{
+ 'uri': 'file://a.avi',
+ });
});
});
- test('initialize network', () async {
+ test('dispose', () async {
+ final VideoPlayerController controller = VideoPlayerController.network(
+ 'https://127.0.0.1',
+ );
+ expect(controller.textureId, isNull);
+ expect(await controller.position, const Duration(seconds: 0));
+ controller.initialize();
+
+ await controller.dispose();
+
+ expect(controller.textureId, isNotNull);
+ expect(await controller.position, isNull);
+ });
+
+ test('play', () async {
final VideoPlayerController controller = VideoPlayerController.network(
'https://127.0.0.1',
);
await controller.initialize();
+ expect(controller.value.isPlaying, isFalse);
+ await controller.play();
- expect(
- fakeVideoPlayerPlatform.dataSourceDescriptions[0], <String, dynamic>{
- 'uri': 'https://127.0.0.1',
- 'formatHint': null,
- });
+ expect(controller.value.isPlaying, isTrue);
+ expect(fakeVideoPlayerPlatform.calls.last.method, 'play');
});
- test('initialize network with hint', () async {
+ test('setLooping', () async {
final VideoPlayerController controller = VideoPlayerController.network(
+ 'https://127.0.0.1',
+ );
+ await controller.initialize();
+ expect(controller.value.isLooping, isFalse);
+ await controller.setLooping(true);
+
+ expect(controller.value.isLooping, isTrue);
+ });
+
+ test('pause', () async {
+ final VideoPlayerController controller = VideoPlayerController.network(
+ 'https://127.0.0.1',
+ );
+ await controller.initialize();
+ await controller.play();
+ expect(controller.value.isPlaying, isTrue);
+
+ await controller.pause();
+
+ expect(controller.value.isPlaying, isFalse);
+ expect(fakeVideoPlayerPlatform.calls.last.method, 'pause');
+ });
+
+ group('seekTo', () {
+ test('works', () async {
+ final VideoPlayerController controller = VideoPlayerController.network(
'https://127.0.0.1',
- formatHint: VideoFormat.dash);
- await controller.initialize();
+ );
+ await controller.initialize();
+ expect(await controller.position, const Duration(seconds: 0));
- expect(
- fakeVideoPlayerPlatform.dataSourceDescriptions[0], <String, dynamic>{
- 'uri': 'https://127.0.0.1',
- 'formatHint': 'dash',
+ await controller.seekTo(const Duration(milliseconds: 500));
+
+ expect(await controller.position, const Duration(milliseconds: 500));
+ });
+
+ test('clamps values that are too high or low', () async {
+ final VideoPlayerController controller = VideoPlayerController.network(
+ 'https://127.0.0.1',
+ );
+ await controller.initialize();
+ expect(await controller.position, const Duration(seconds: 0));
+
+ await controller.seekTo(const Duration(seconds: 100));
+ expect(await controller.position, const Duration(seconds: 1));
+
+ await controller.seekTo(const Duration(seconds: -100));
+ expect(await controller.position, const Duration(seconds: 0));
});
});
- test('initialize file', () async {
- final VideoPlayerController controller =
- VideoPlayerController.file(File('a.avi'));
- await controller.initialize();
+ group('setVolume', () {
+ test('works', () async {
+ final VideoPlayerController controller = VideoPlayerController.network(
+ 'https://127.0.0.1',
+ );
+ await controller.initialize();
+ expect(controller.value.volume, 1.0);
- expect(
- fakeVideoPlayerPlatform.dataSourceDescriptions[0], <String, dynamic>{
- 'uri': 'file://a.avi',
+ const double volume = 0.5;
+ await controller.setVolume(volume);
+
+ expect(controller.value.volume, volume);
+ });
+
+ test('clamps values that are too high or low', () async {
+ final VideoPlayerController controller = VideoPlayerController.network(
+ 'https://127.0.0.1',
+ );
+ await controller.initialize();
+ expect(controller.value.volume, 1.0);
+
+ await controller.setVolume(-1);
+ expect(controller.value.volume, 0.0);
+
+ await controller.setVolume(11);
+ expect(controller.value.volume, 1.0);
});
});
+
+ group('Platform callbacks', () {
+ testWidgets('playing completed', (WidgetTester tester) async {
+ final VideoPlayerController controller = VideoPlayerController.network(
+ 'https://127.0.0.1',
+ );
+ await controller.initialize();
+ expect(controller.value.isPlaying, isFalse);
+ await controller.play();
+ expect(controller.value.isPlaying, isTrue);
+ final FakeVideoEventStream fakeVideoEventStream =
+ fakeVideoPlayerPlatform.streams[controller.textureId];
+ assert(fakeVideoEventStream != null);
+
+ fakeVideoEventStream.eventsChannel
+ .sendEvent(<String, dynamic>{'event': 'completed'});
+ await tester.pumpAndSettle();
+
+ expect(controller.value.isPlaying, isFalse);
+ expect(controller.value.position, controller.value.duration);
+ });
+
+ testWidgets('buffering status', (WidgetTester tester) async {
+ final VideoPlayerController controller = VideoPlayerController.network(
+ 'https://127.0.0.1',
+ );
+ await controller.initialize();
+ expect(controller.value.isBuffering, false);
+ expect(controller.value.buffered, isEmpty);
+ final FakeVideoEventStream fakeVideoEventStream =
+ fakeVideoPlayerPlatform.streams[controller.textureId];
+ assert(fakeVideoEventStream != null);
+
+ fakeVideoEventStream.eventsChannel
+ .sendEvent(<String, dynamic>{'event': 'bufferingStart'});
+ await tester.pumpAndSettle();
+ expect(controller.value.isBuffering, isTrue);
+
+ const Duration bufferStart = Duration(seconds: 0);
+ const Duration bufferEnd = Duration(milliseconds: 500);
+ fakeVideoEventStream.eventsChannel.sendEvent(<String, dynamic>{
+ 'event': 'bufferingUpdate',
+ 'values': <List<int>>[
+ <int>[bufferStart.inMilliseconds, bufferEnd.inMilliseconds]
+ ],
+ });
+ await tester.pumpAndSettle();
+ expect(controller.value.isBuffering, isTrue);
+ expect(controller.value.buffered.length, 1);
+ expect(controller.value.buffered[0].toString(),
+ DurationRange(bufferStart, bufferEnd).toString());
+
+ fakeVideoEventStream.eventsChannel
+ .sendEvent(<String, dynamic>{'event': 'bufferingEnd'});
+ await tester.pumpAndSettle();
+ expect(controller.value.isBuffering, isFalse);
+ });
+ });
+ });
+
+ group('DurationRange', () {
+ test('uses given values', () {
+ const Duration start = Duration(seconds: 2);
+ const Duration end = Duration(seconds: 8);
+
+ final DurationRange range = DurationRange(start, end);
+
+ expect(range.start, start);
+ expect(range.end, end);
+ expect(range.toString(), contains('start: $start, end: $end'));
+ });
+
+ test('calculates fractions', () {
+ const Duration start = Duration(seconds: 2);
+ const Duration end = Duration(seconds: 8);
+ const Duration total = Duration(seconds: 10);
+
+ final DurationRange range = DurationRange(start, end);
+
+ expect(range.startFraction(total), .2);
+ expect(range.endFraction(total), .8);
+ });
+ });
+
+ group('VideoPlayerValue', () {
+ test('uninitialized()', () {
+ final VideoPlayerValue uninitialized = VideoPlayerValue.uninitialized();
+
+ expect(uninitialized.duration, isNull);
+ expect(uninitialized.position, equals(const Duration(seconds: 0)));
+ expect(uninitialized.buffered, isEmpty);
+ expect(uninitialized.isPlaying, isFalse);
+ expect(uninitialized.isLooping, isFalse);
+ expect(uninitialized.isBuffering, isFalse);
+ expect(uninitialized.volume, 1.0);
+ expect(uninitialized.errorDescription, isNull);
+ expect(uninitialized.size, isNull);
+ expect(uninitialized.size, isNull);
+ expect(uninitialized.initialized, isFalse);
+ expect(uninitialized.hasError, isFalse);
+ expect(uninitialized.aspectRatio, 1.0);
+ });
+
+ test('erroneous()', () {
+ const String errorMessage = 'foo';
+ final VideoPlayerValue error = VideoPlayerValue.erroneous(errorMessage);
+
+ expect(error.duration, isNull);
+ expect(error.position, equals(const Duration(seconds: 0)));
+ expect(error.buffered, isEmpty);
+ expect(error.isPlaying, isFalse);
+ expect(error.isLooping, isFalse);
+ expect(error.isBuffering, isFalse);
+ expect(error.volume, 1.0);
+ expect(error.errorDescription, errorMessage);
+ expect(error.size, isNull);
+ expect(error.size, isNull);
+ expect(error.initialized, isFalse);
+ expect(error.hasError, isTrue);
+ expect(error.aspectRatio, 1.0);
+ });
+
+ test('toString()', () {
+ const Duration duration = Duration(seconds: 5);
+ const Size size = Size(400, 300);
+ const Duration position = Duration(seconds: 1);
+ final List<DurationRange> buffered = <DurationRange>[
+ DurationRange(const Duration(seconds: 0), const Duration(seconds: 4))
+ ];
+ const bool isPlaying = true;
+ const bool isLooping = true;
+ const bool isBuffering = true;
+ const double volume = 0.5;
+
+ final VideoPlayerValue value = VideoPlayerValue(
+ duration: duration,
+ size: size,
+ position: position,
+ buffered: buffered,
+ isPlaying: isPlaying,
+ isLooping: isLooping,
+ isBuffering: isBuffering,
+ volume: volume);
+
+ expect(value.toString(),
+ 'VideoPlayerValue(duration: 0:00:05.000000, size: Size(400.0, 300.0), position: 0:00:01.000000, buffered: [DurationRange(start: 0:00:00.000000, end: 0:00:04.000000)], isPlaying: true, isLooping: true, isBuffering: truevolume: 0.5, errorDescription: null)');
+ });
+
+ test('copyWith()', () {
+ final VideoPlayerValue original = VideoPlayerValue.uninitialized();
+ final VideoPlayerValue exactCopy = original.copyWith();
+
+ expect(exactCopy.toString(), original.toString());
+ });
+ });
+
+ test('VideoProgressColors', () {
+ const Color playedColor = Color.fromRGBO(0, 0, 255, 0.75);
+ const Color bufferedColor = Color.fromRGBO(0, 255, 0, 0.5);
+ const Color backgroundColor = Color.fromRGBO(255, 255, 0, 0.25);
+
+ final VideoProgressColors colors = VideoProgressColors(
+ playedColor: playedColor,
+ bufferedColor: bufferedColor,
+ backgroundColor: backgroundColor);
+
+ expect(colors.playedColor, playedColor);
+ expect(colors.bufferedColor, bufferedColor);
+ expect(colors.backgroundColor, backgroundColor);
});
}
@@ -150,16 +433,20 @@
final MethodChannel _channel = const MethodChannel('flutter.io/videoPlayer');
Completer<bool> initialized = Completer<bool>();
+ List<MethodCall> calls = <MethodCall>[];
List<Map<String, dynamic>> dataSourceDescriptions = <Map<String, dynamic>>[];
+ final Map<int, FakeVideoEventStream> streams = <int, FakeVideoEventStream>{};
int nextTextureId = 0;
+ final Map<int, Duration> _positions = <int, Duration>{};
Future<dynamic> onMethodCall(MethodCall call) {
+ calls.add(call);
switch (call.method) {
case 'init':
initialized.complete(true);
break;
case 'create':
- FakeVideoEventStream(
+ streams[nextTextureId] = FakeVideoEventStream(
nextTextureId, 100, 100, const Duration(seconds: 1));
final Map<dynamic, dynamic> dataSource = call.arguments;
dataSourceDescriptions.add(dataSource.cast<String, dynamic>());
@@ -169,11 +456,20 @@
};
});
break;
- case 'setLooping':
+ case 'position':
+ final Duration position = _positions[call.arguments['textureId']] ??
+ const Duration(seconds: 0);
+ return Future<int>.value(position.inMilliseconds);
break;
- case 'setVolume':
+ case 'seekTo':
+ _positions[call.arguments['textureId']] =
+ Duration(milliseconds: call.arguments['location']);
break;
+ case 'dispose':
case 'pause':
+ case 'play':
+ case 'setLooping':
+ case 'setVolume':
break;
default:
throw UnimplementedError(