Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/video_player/video_player/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## NEXT

* Updates minimum supported SDK version to Flutter 3.29/Dart 3.7.

## 2.10.0

* Adds support for platform views as an optional way of displaying a video on Android and iOS.
Expand Down
21 changes: 13 additions & 8 deletions packages/video_player/video_player/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,11 @@ class _VideoAppState extends State<VideoApp> {
@override
void initState() {
super.initState();
_controller = VideoPlayerController.networkUrl(Uri.parse(
'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4'))
_controller = VideoPlayerController.networkUrl(
Uri.parse(
'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4',
),
)
..initialize().then((_) {
// Ensure the first frame is shown after the video is initialized, even before the play button has been pressed.
setState(() {});
Expand All @@ -91,12 +94,13 @@ class _VideoAppState extends State<VideoApp> {
title: 'Video Demo',
home: Scaffold(
body: Center(
child: _controller.value.isInitialized
? AspectRatio(
aspectRatio: _controller.value.aspectRatio,
child: VideoPlayer(_controller),
)
: Container(),
child:
_controller.value.isInitialized
? AspectRatio(
aspectRatio: _controller.value.aspectRatio,
child: VideoPlayer(_controller),
)
: Container(),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Expand All @@ -120,6 +124,7 @@ class _VideoAppState extends State<VideoApp> {
super.dispose();
}
}

```

## Usage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,10 @@ void main() {
await another.pause();

// Expect that `another` played.
expect(another.value.position,
(Duration position) => position > Duration.zero);
expect(
another.value.position,
(Duration position) => position > Duration.zero,
);

await expectLater(started.future, completes);
await expectLater(ended.future, completes);
Expand All @@ -75,12 +77,9 @@ void main() {

// TODO(tarrinneal): Remove once other test is enabled,
// https://github.com/flutter/flutter/issues/164651
testWidgets(
'no-op',
(WidgetTester tester) async {
expect(true, true);
},
);
testWidgets('no-op', (WidgetTester tester) async {
expect(true, true);
});
}

Widget renderVideoWidget(VideoPlayerController controller) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,77 +49,71 @@ void main() {
expect(controller.value.position, Duration.zero);
expect(controller.value.isPlaying, false);
// The WebM version has a slightly different duration than the MP4.
expect(controller.value.duration,
const Duration(seconds: 7, milliseconds: kIsWeb ? 544 : 540));
expect(
controller.value.duration,
const Duration(seconds: 7, milliseconds: kIsWeb ? 544 : 540),
);
});

testWidgets(
'live stream duration != 0',
(WidgetTester tester) async {
final VideoPlayerController networkController =
VideoPlayerController.networkUrl(
Uri.parse(
'https://flutter.github.io/assets-for-api-docs/assets/videos/hls/bee.m3u8'),
);
await networkController.initialize();
testWidgets('live stream duration != 0', (WidgetTester tester) async {
final VideoPlayerController
networkController = VideoPlayerController.networkUrl(
Uri.parse(
'https://flutter.github.io/assets-for-api-docs/assets/videos/hls/bee.m3u8',
),
);
await networkController.initialize();

expect(networkController.value.isInitialized, true);
// Live streams should have either a positive duration or C.TIME_UNSET if the duration is unknown
// See https://exoplayer.dev/doc/reference/com/google/android/exoplayer2/Player.html#getDuration--
expect(networkController.value.duration,
(Duration duration) => duration != Duration.zero);
},
skip: kIsWeb,
);
expect(networkController.value.isInitialized, true);
// Live streams should have either a positive duration or C.TIME_UNSET if the duration is unknown
// See https://exoplayer.dev/doc/reference/com/google/android/exoplayer2/Player.html#getDuration--
expect(
networkController.value.duration,
(Duration duration) => duration != Duration.zero,
);
}, skip: kIsWeb);

testWidgets(
'can be played',
(WidgetTester tester) async {
await controller.initialize();
// Mute to allow playing without DOM interaction on Web.
// See https://developers.google.com/web/updates/2017/09/autoplay-policy-changes
await controller.setVolume(0);
testWidgets('can be played', (WidgetTester tester) async {
await controller.initialize();
// Mute to allow playing without DOM interaction on Web.
// See https://developers.google.com/web/updates/2017/09/autoplay-policy-changes
await controller.setVolume(0);

await controller.play();
await tester.pumpAndSettle(_playDuration);
await controller.play();
await tester.pumpAndSettle(_playDuration);

expect(controller.value.isPlaying, true);
expect(controller.value.position,
(Duration position) => position > Duration.zero);
},
);
expect(controller.value.isPlaying, true);
expect(
controller.value.position,
(Duration position) => position > Duration.zero,
);
});

testWidgets(
'can seek',
(WidgetTester tester) async {
await controller.initialize();
testWidgets('can seek', (WidgetTester tester) async {
await controller.initialize();

await controller.seekTo(const Duration(seconds: 3));
await controller.seekTo(const Duration(seconds: 3));

expect(controller.value.position, const Duration(seconds: 3));
},
);
expect(controller.value.position, const Duration(seconds: 3));
});

testWidgets(
'can be paused',
(WidgetTester tester) async {
await controller.initialize();
// Mute to allow playing without DOM interaction on Web.
// See https://developers.google.com/web/updates/2017/09/autoplay-policy-changes
await controller.setVolume(0);
testWidgets('can be paused', (WidgetTester tester) async {
await controller.initialize();
// Mute to allow playing without DOM interaction on Web.
// See https://developers.google.com/web/updates/2017/09/autoplay-policy-changes
await controller.setVolume(0);

// Play for a second, then pause, and then wait a second.
await controller.play();
await tester.pumpAndSettle(_playDuration);
await controller.pause();
final Duration pausedPosition = controller.value.position;
await tester.pumpAndSettle(_playDuration);
// Play for a second, then pause, and then wait a second.
await controller.play();
await tester.pumpAndSettle(_playDuration);
await controller.pause();
final Duration pausedPosition = controller.value.position;
await tester.pumpAndSettle(_playDuration);

// Verify that we stopped playing after the pause.
expect(controller.value.isPlaying, false);
expect(controller.value.position, pausedPosition);
},
);
// Verify that we stopped playing after the pause.
expect(controller.value.isPlaying, false);
expect(controller.value.position, pausedPosition);
});

testWidgets(
'stay paused when seeking after video completed',
Expand All @@ -142,7 +136,8 @@ void main() {
// https://github.com/flutter/flutter/issues/141145 is fixed.
if ((!kIsWeb && Platform.isAndroid) && controller.value.isPlaying) {
markTestSkipped(
'Skipping due to https://github.com/flutter/flutter/issues/141145');
'Skipping due to https://github.com/flutter/flutter/issues/141145',
);
return;
}
expect(controller.value.isPlaying, false);
Expand All @@ -166,7 +161,8 @@ void main() {
// See https://developers.google.com/web/updates/2017/09/autoplay-policy-changes
await controller.setVolume(0);
await controller.seekTo(
controller.value.duration - const Duration(milliseconds: 10));
controller.value.duration - const Duration(milliseconds: 10),
);
await controller.play();
await tester.pumpAndSettle(_playDuration);
// Android emulators in our CI have frequent flake where the video
Expand All @@ -178,7 +174,8 @@ void main() {
// https://github.com/flutter/flutter/issues/141145 is fixed.
if ((!kIsWeb && Platform.isAndroid) && controller.value.isPlaying) {
markTestSkipped(
'Skipping due to https://github.com/flutter/flutter/issues/141145');
'Skipping due to https://github.com/flutter/flutter/issues/141145',
);
return;
}
expect(controller.value.isPlaying, false);
Expand All @@ -187,52 +184,61 @@ void main() {
await controller.play();
await tester.pumpAndSettle(_playDuration);

expect(controller.value.position,
lessThanOrEqualTo(controller.value.duration));
expect(
controller.value.position,
lessThanOrEqualTo(controller.value.duration),
);
},
// Flaky on the web, headless browsers don't like to seek to non-buffered
// positions of a video (and since this isn't even injecting the video
// element on the page, the video never starts buffering with the test)
skip: kIsWeb,
);

testWidgets('test video player view with local asset',
(WidgetTester tester) async {
final Completer<void> loaded = Completer<void>();
Future<bool> started() async {
await controller.initialize();
await controller.play();
loaded.complete();
return true;
}

await tester.pumpWidget(Material(
child: Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: FutureBuilder<bool>(
future: started(),
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
if (snapshot.data ?? false) {
return AspectRatio(
aspectRatio: controller.value.aspectRatio,
child: VideoPlayer(controller),
);
} else {
return const Text('waiting for video to load');
}
},
testWidgets(
'test video player view with local asset',
(WidgetTester tester) async {
final Completer<void> loaded = Completer<void>();
Future<bool> started() async {
await controller.initialize();
await controller.play();
loaded.complete();
return true;
}

await tester.pumpWidget(
Material(
child: Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: FutureBuilder<bool>(
future: started(),
builder: (
BuildContext context,
AsyncSnapshot<bool> snapshot,
) {
if (snapshot.data ?? false) {
return AspectRatio(
aspectRatio: controller.value.aspectRatio,
child: VideoPlayer(controller),
);
} else {
return const Text('waiting for video to load');
}
},
),
),
),
),
),
));
);

await loaded.future;
await tester.pumpAndSettle();
expect(controller.value.isPlaying, true);
},
// Web does not support local assets.
skip: kIsWeb);
await loaded.future;
await tester.pumpAndSettle();
expect(controller.value.isPlaying, true);
},
// Web does not support local assets.
skip: kIsWeb,
);
});

group('file-based videos', () {
Expand All @@ -249,8 +255,9 @@ void main() {
controller = VideoPlayerController.file(file);
});

testWidgets('test video player using static file() method as constructor',
(WidgetTester tester) async {
testWidgets('test video player using static file() method as constructor', (
WidgetTester tester,
) async {
await controller.initialize();

await controller.play();
Expand All @@ -264,7 +271,8 @@ void main() {
group('network videos', () {
setUp(() {
controller = VideoPlayerController.networkUrl(
Uri.parse(getUrlForAssetAsNetworkSource(_videoAssetKey)));
Uri.parse(getUrlForAssetAsNetworkSource(_videoAssetKey)),
);
});

testWidgets(
Expand Down Expand Up @@ -293,17 +301,19 @@ void main() {
await controller.pause();

expect(controller.value.isPlaying, false);
expect(controller.value.position,
(Duration position) => position > Duration.zero);
expect(
controller.value.position,
(Duration position) => position > Duration.zero,
);

await expectLater(started.future, completes);
await expectLater(ended.future, completes);
},
skip:
// MEDIA_ELEMENT_ERROR on web, see https://github.com/flutter/flutter/issues/169219
kIsWeb ||
// Hanging on Android, see https://github.com/flutter/flutter/issues/160797
defaultTargetPlatform == TargetPlatform.android,
// Hanging on Android, see https://github.com/flutter/flutter/issues/160797
defaultTargetPlatform == TargetPlatform.android,
);
});

Expand Down
Loading