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
7 changes: 6 additions & 1 deletion packages/video_player/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
## NEXT
## 2.4.0

* Update video_player to 2.4.2.
* Update video_player_platform_interface to 5.1.2.
* Update the example app and integration_test.
* Migrate to new analysis options.
* Remove obsolete dependency on pedantic.
* Code cleanups.

## 2.3.2
Expand Down
10 changes: 7 additions & 3 deletions packages/video_player/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ This package is not an _endorsed_ implementation of `video_player`. Therefore, y

```yaml
dependencies:
video_player: ^2.2.6
video_player_tizen: ^2.3.2
video_player: ^2.4.2
video_player_tizen: ^2.4.0
```

Then you can import `video_player` in your Dart code:
Expand All @@ -42,7 +42,11 @@ For detailed usage, see https://pub.dev/packages/video_player#example.

## Limitations

The `httpHeaders` option of `VideoPlayerController.network` and the `mixWithOthers` option of `VideoPlayerOptions` will be silently ignored in Tizen platform.
The following options are not supported on Tizen.

- The `httpHeaders` option of `VideoPlayerController.network`
- `VideoPlayerOptions.allowBackgroundPlayback`
- `VideoPlayerOptions.mixWithOthers`

This plugin has some limitations on TV devices.

Expand Down
1 change: 0 additions & 1 deletion packages/video_player/analysis_options.yaml

This file was deleted.

Binary file added packages/video_player/example/assets/Audio.mp3
Binary file not shown.
Binary file not shown.
228 changes: 169 additions & 59 deletions packages/video_player/example/integration_test/video_player_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,44 @@
// found in the LICENSE file.

import 'dart:async';
import 'dart:io';
import 'dart:typed_data';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:integration_test/integration_test.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:path_provider/path_provider.dart';
import 'package:video_player/video_player.dart';

const Duration _playDuration = Duration(seconds: 1);

// Use WebM for web to allow CI to use Chromium.
const String _videoAssetKey =
kIsWeb ? 'assets/Butterfly-209.webm' : 'assets/Butterfly-209.mp4';

// Returns the URL to load an asset from this example app as a network source.
//
// TODO(stuartmorgan): Convert this to a local `HttpServer` that vends the
// assets directly, https://github.com/flutter/flutter/issues/95420
String getUrlForAssetAsNetworkSource(String assetKey) {
return 'https://github.com/flutter/plugins/blob/'
// This hash can be rolled forward to pick up newly-added assets.
'cb381ced070d356799dddf24aca38ce0579d3d7b'
'/packages/video_player/video_player/example/'
'$assetKey'
'?raw=true';
}

void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
late VideoPlayerController _controller;
tearDown(() async => _controller.dispose());

group('asset videos', () {
setUp(() {
_controller = VideoPlayerController.asset('assets/Butterfly-209.mp4');
_controller = VideoPlayerController.asset(_videoAssetKey);
});

testWidgets('can be initialized', (WidgetTester tester) async {
Expand All @@ -28,73 +49,27 @@ void main() {
expect(_controller.value.isInitialized, true);
expect(_controller.value.position, const Duration(seconds: 0));
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: 540));
const Duration(seconds: 7, milliseconds: kIsWeb ? 544 : 540));
});

testWidgets(
'reports buffering status',
(WidgetTester tester) async {
VideoPlayerController networkController = VideoPlayerController.network(
'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4',
);
await networkController.initialize();
// Mute to allow playing without DOM interaction on Web.
// See https://developers.google.com/web/updates/2017/09/autoplay-policy-changes
await networkController.setVolume(0);
final Completer<void> started = Completer();
final Completer<void> ended = Completer();
bool startedBuffering = false;
bool endedBuffering = false;
networkController.addListener(() {
if (networkController.value.isBuffering && !startedBuffering) {
startedBuffering = true;
started.complete();
}
if (startedBuffering &&
!networkController.value.isBuffering &&
!endedBuffering) {
endedBuffering = true;
ended.complete();
}
});

await networkController.play();
await networkController.seekTo(const Duration(seconds: 5));
await tester.pumpAndSettle(_playDuration);
await networkController.pause();

expect(networkController.value.isPlaying, false);
expect(networkController.value.position,
(Duration position) => position > const Duration(seconds: 0));

await started;
expect(startedBuffering, true);

await ended;
expect(endedBuffering, true);
},
skip: !(kIsWeb || defaultTargetPlatform == TargetPlatform.android),
);

testWidgets(
'live stream duration != 0',
(WidgetTester tester) async {
VideoPlayerController networkController = VideoPlayerController.network(
'https://cph-p2p-msl.akamaized.net/hls/live/2000341/test/master.m3u8',
final VideoPlayerController networkController =
VideoPlayerController.network(
'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--
if (defaultTargetPlatform == TargetPlatform.android ||
defaultTargetPlatform == TargetPlatform.iOS) {
expect(networkController.value.duration,
(Duration duration) => duration != Duration.zero);
}
expect(networkController.value.duration,
(Duration duration) => duration != Duration.zero);
},
skip: (kIsWeb),
skip: kIsWeb,
);

testWidgets(
Expand Down Expand Up @@ -153,7 +128,7 @@ void main() {
// 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);
Duration timeBeforeEnd =
final Duration timeBeforeEnd =
_controller.value.duration - const Duration(milliseconds: 500);
await _controller.seekTo(timeBeforeEnd);
await _controller.play();
Expand Down Expand Up @@ -207,7 +182,7 @@ void main() {
child: FutureBuilder<bool>(
future: started(),
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
if (snapshot.data == true) {
if (snapshot.data ?? false) {
return AspectRatio(
aspectRatio: _controller.value.aspectRatio,
child: VideoPlayer(_controller),
Expand All @@ -223,6 +198,141 @@ void main() {

await tester.pumpAndSettle();
expect(_controller.value.isPlaying, true);
}, skip: kIsWeb); // Web does not support local assets.
},
skip: kIsWeb || // Web does not support local assets.
// Extremely flaky on iOS: https://github.com/flutter/flutter/issues/86915
defaultTargetPlatform == TargetPlatform.iOS);
});

group('file-based videos', () {
setUp(() async {
// Load the data from the asset.
final String tempDir = (await getTemporaryDirectory()).path;
final ByteData bytes = await rootBundle.load(_videoAssetKey);

// Write it to a file to use as a source.
final String filename = _videoAssetKey.split('/').last;
final File file = File('$tempDir/$filename');
await file.writeAsBytes(bytes.buffer.asInt8List());

_controller = VideoPlayerController.file(file);
});

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

await _controller.play();
expect(_controller.value.isPlaying, true);

await _controller.pause();
expect(_controller.value.isPlaying, false);
}, skip: kIsWeb);
});

group('network videos', () {
setUp(() {
_controller = VideoPlayerController.network(
getUrlForAssetAsNetworkSource(_videoAssetKey));
});

testWidgets(
'reports buffering status',
(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);
final Completer<void> started = Completer<void>();
final Completer<void> ended = Completer<void>();
_controller.addListener(() {
if (!started.isCompleted && _controller.value.isBuffering) {
started.complete();
}
if (started.isCompleted &&
!_controller.value.isBuffering &&
!ended.isCompleted) {
ended.complete();
}
});

await _controller.play();
await _controller.seekTo(const Duration(seconds: 5));
await tester.pumpAndSettle(_playDuration);
await _controller.pause();

expect(_controller.value.isPlaying, false);
expect(_controller.value.position,
(Duration position) => position > const Duration(seconds: 0));

await expectLater(started.future, completes);
await expectLater(ended.future, completes);
},
skip: !(kIsWeb || defaultTargetPlatform == TargetPlatform.android),
);
});

// Audio playback is tested to prevent accidental regression,
// but could be removed in the future.
group('asset audios', () {
setUp(() {
_controller = VideoPlayerController.asset('assets/Audio.mp3');
});

testWidgets('can be initialized', (WidgetTester tester) async {
await _controller.initialize();

expect(_controller.value.isInitialized, true);
expect(_controller.value.position, const Duration(seconds: 0));
expect(_controller.value.isPlaying, false);
// Due to the duration calculation accurancy between platforms,
// the milliseconds on Web will be a slightly different from natives.
// The audio was made with 44100 Hz, 192 Kbps CBR, and 32 bits.
expect(
_controller.value.duration,
const Duration(seconds: 5, milliseconds: kIsWeb ? 42 : 41),
);
});

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);

expect(_controller.value.isPlaying, true);
expect(
_controller.value.position,
(Duration position) => position > const Duration(milliseconds: 0),
);
});

testWidgets('can seek', (WidgetTester tester) async {
await _controller.initialize();
await _controller.seekTo(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);

// 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);
});
});
}
Loading