Skip to content

Commit a35f02d

Browse files
authored
Convert use of mini_player in the video_player_android test to AndroidVideoPlayer. (#7847)
Closes flutter/flutter#156424. Also improved an error message that came up as a result.
1 parent 11adfde commit a35f02d

File tree

4 files changed

+142
-121
lines changed

4 files changed

+142
-121
lines changed

packages/video_player/video_player_android/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## 2.7.13
2+
3+
* When `AndroidVideoPlayer` attempts to operate on a `textureId` that is not
4+
active (i.e. it was previously disposed or never created), the resulting
5+
platform exception is more informative than a "NullPointerException".
6+
17
## 2.7.12
28

39
* Fixes a [bug](https://github.com/flutter/flutter/issues/156451) where

packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -140,34 +140,50 @@ public void initialize() {
140140
return new TextureMessage.Builder().setTextureId(handle.id()).build();
141141
}
142142

143+
@NonNull
144+
private VideoPlayer getPlayer(long textureId) {
145+
VideoPlayer player = videoPlayers.get(textureId);
146+
147+
// Avoid a very ugly un-debuggable NPE that results in returning a null player.
148+
if (player == null) {
149+
String message = "No player found with textureId <" + textureId + ">";
150+
if (videoPlayers.size() == 0) {
151+
message += " and no active players created by the plugin.";
152+
}
153+
throw new IllegalStateException(message);
154+
}
155+
156+
return player;
157+
}
158+
143159
public void dispose(@NonNull TextureMessage arg) {
144-
VideoPlayer player = videoPlayers.get(arg.getTextureId());
160+
VideoPlayer player = getPlayer(arg.getTextureId());
145161
player.dispose();
146162
videoPlayers.remove(arg.getTextureId());
147163
}
148164

149165
public void setLooping(@NonNull LoopingMessage arg) {
150-
VideoPlayer player = videoPlayers.get(arg.getTextureId());
166+
VideoPlayer player = getPlayer(arg.getTextureId());
151167
player.setLooping(arg.getIsLooping());
152168
}
153169

154170
public void setVolume(@NonNull VolumeMessage arg) {
155-
VideoPlayer player = videoPlayers.get(arg.getTextureId());
171+
VideoPlayer player = getPlayer(arg.getTextureId());
156172
player.setVolume(arg.getVolume());
157173
}
158174

159175
public void setPlaybackSpeed(@NonNull PlaybackSpeedMessage arg) {
160-
VideoPlayer player = videoPlayers.get(arg.getTextureId());
176+
VideoPlayer player = getPlayer(arg.getTextureId());
161177
player.setPlaybackSpeed(arg.getSpeed());
162178
}
163179

164180
public void play(@NonNull TextureMessage arg) {
165-
VideoPlayer player = videoPlayers.get(arg.getTextureId());
181+
VideoPlayer player = getPlayer(arg.getTextureId());
166182
player.play();
167183
}
168184

169185
public @NonNull PositionMessage position(@NonNull TextureMessage arg) {
170-
VideoPlayer player = videoPlayers.get(arg.getTextureId());
186+
VideoPlayer player = getPlayer(arg.getTextureId());
171187
PositionMessage result =
172188
new PositionMessage.Builder()
173189
.setPosition(player.getPosition())
@@ -178,12 +194,12 @@ public void play(@NonNull TextureMessage arg) {
178194
}
179195

180196
public void seekTo(@NonNull PositionMessage arg) {
181-
VideoPlayer player = videoPlayers.get(arg.getTextureId());
197+
VideoPlayer player = getPlayer(arg.getTextureId());
182198
player.seekTo(arg.getPosition().intValue());
183199
}
184200

185201
public void pause(@NonNull TextureMessage arg) {
186-
VideoPlayer player = videoPlayers.get(arg.getTextureId());
202+
VideoPlayer player = getPlayer(arg.getTextureId());
187203
player.pause();
188204
}
189205

packages/video_player/video_player_android/example/integration_test/video_player_test.dart

Lines changed: 111 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5-
import 'dart:async';
65
import 'dart:io';
76
import 'dart:typed_data';
87

@@ -11,16 +10,9 @@ import 'package:flutter_test/flutter_test.dart';
1110
import 'package:integration_test/integration_test.dart';
1211
import 'package:path_provider/path_provider.dart';
1312
import 'package:video_player_android/video_player_android.dart';
14-
// TODO(stuartmorgan): Remove the use of MiniController in tests, as that is
15-
// testing test code; tests should instead be written directly against the
16-
// platform interface. (These tests were copied from the app-facing package
17-
// during federation and minimally modified, which is why they currently use the
18-
// controller.)
19-
import 'package:video_player_example/mini_controller.dart';
2013
import 'package:video_player_platform_interface/video_player_platform_interface.dart';
2114

2215
const Duration _playDuration = Duration(seconds: 1);
23-
2416
const String _videoAssetKey = 'assets/Butterfly-209.mp4';
2517

2618
// Returns the URL to load an asset from this example app as a network source.
@@ -38,132 +30,139 @@ String getUrlForAssetAsNetworkSource(String assetKey) {
3830

3931
void main() {
4032
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
33+
late AndroidVideoPlayer player;
34+
35+
setUp(() async {
36+
player = AndroidVideoPlayer();
37+
await player.init();
38+
});
4139

42-
late MiniController controller;
43-
tearDown(() async => controller.dispose());
40+
testWidgets('registers expected implementation', (_) async {
41+
AndroidVideoPlayer.registerWith();
42+
expect(VideoPlayerPlatform.instance, isA<AndroidVideoPlayer>());
43+
});
44+
45+
testWidgets('initializes at the start', (_) async {
46+
final int textureId = (await player.create(DataSource(
47+
sourceType: DataSourceType.asset,
48+
asset: _videoAssetKey,
49+
)))!;
4450

45-
group('asset videos', () {
46-
setUp(() {
47-
controller = MiniController.asset(_videoAssetKey);
48-
});
51+
expect(
52+
await _getDuration(player, textureId),
53+
const Duration(seconds: 7, milliseconds: 540),
54+
);
4955

50-
testWidgets('registers expected implementation',
51-
(WidgetTester tester) async {
52-
AndroidVideoPlayer.registerWith();
53-
expect(VideoPlayerPlatform.instance, isA<AndroidVideoPlayer>());
54-
});
56+
await player.dispose(textureId);
57+
});
5558

56-
testWidgets('can be initialized', (WidgetTester tester) async {
57-
await controller.initialize();
59+
testWidgets('can be played', (WidgetTester tester) async {
60+
final int textureId = (await player.create(DataSource(
61+
sourceType: DataSourceType.asset,
62+
asset: _videoAssetKey,
63+
)))!;
5864

59-
expect(controller.value.isInitialized, true);
60-
expect(await controller.position, Duration.zero);
61-
expect(controller.value.duration,
62-
const Duration(seconds: 7, milliseconds: 540));
63-
});
65+
await player.play(textureId);
66+
await tester.pumpAndSettle(_playDuration);
6467

65-
testWidgets('can be played', (WidgetTester tester) async {
66-
await controller.initialize();
68+
expect(await player.getPosition(textureId), greaterThan(Duration.zero));
69+
await player.dispose(textureId);
70+
});
6771

68-
await controller.play();
69-
await tester.pumpAndSettle(_playDuration);
72+
testWidgets('can seek', (WidgetTester tester) async {
73+
final int textureId = (await player.create(DataSource(
74+
sourceType: DataSourceType.asset,
75+
asset: _videoAssetKey,
76+
)))!;
7077

71-
expect(await controller.position, greaterThan(Duration.zero));
72-
});
78+
await player.seekTo(textureId, const Duration(seconds: 3));
79+
await tester.pumpAndSettle(_playDuration);
7380

74-
testWidgets('can seek', (WidgetTester tester) async {
75-
await controller.initialize();
81+
expect(
82+
await player.getPosition(textureId),
83+
greaterThanOrEqualTo(const Duration(seconds: 3)),
84+
);
85+
await player.dispose(textureId);
86+
});
7687

77-
await controller.seekTo(const Duration(seconds: 3));
88+
testWidgets('can pause', (WidgetTester tester) async {
89+
final int textureId = (await player.create(DataSource(
90+
sourceType: DataSourceType.asset,
91+
asset: _videoAssetKey,
92+
)))!;
7893

79-
expect(await controller.position, const Duration(seconds: 3));
80-
});
94+
await player.play(textureId);
95+
await tester.pumpAndSettle(_playDuration);
8196

82-
testWidgets('can be paused', (WidgetTester tester) async {
83-
await controller.initialize();
97+
await player.pause(textureId);
98+
final Duration pausedDuration = await player.getPosition(textureId);
99+
await tester.pumpAndSettle(_playDuration);
84100

85-
// Play for a second, then pause, and then wait a second.
86-
await controller.play();
87-
await tester.pumpAndSettle(_playDuration);
88-
await controller.pause();
89-
await tester.pumpAndSettle(_playDuration);
90-
final Duration pausedPosition = (await controller.position)!;
91-
await tester.pumpAndSettle(_playDuration);
101+
expect(await player.getPosition(textureId), pausedDuration);
102+
await player.dispose(textureId);
103+
});
92104

93-
// Verify that we stopped playing after the pause.
94-
expect(await controller.position, pausedPosition);
95-
});
105+
testWidgets('can play a video from a file', (WidgetTester tester) async {
106+
final Directory directory = await getTemporaryDirectory();
107+
final File file = File('${directory.path}/video.mp4');
108+
await file.writeAsBytes(
109+
Uint8List.fromList(
110+
(await rootBundle.load(_videoAssetKey)).buffer.asUint8List(),
111+
),
112+
);
113+
114+
final int textureId = (await player.create(DataSource(
115+
sourceType: DataSourceType.file,
116+
uri: file.path,
117+
)))!;
118+
119+
await player.play(textureId);
120+
await tester.pumpAndSettle(_playDuration);
121+
122+
expect(await player.getPosition(textureId), greaterThan(Duration.zero));
123+
await directory.delete(recursive: true);
124+
await player.dispose(textureId);
96125
});
97126

98-
group('file-based videos', () {
99-
setUp(() async {
100-
// Load the data from the asset.
101-
final String tempDir = (await getTemporaryDirectory()).path;
102-
final ByteData bytes = await rootBundle.load(_videoAssetKey);
127+
testWidgets('can play a video from network', (WidgetTester tester) async {
128+
final int textureId = (await player.create(DataSource(
129+
sourceType: DataSourceType.network,
130+
uri: getUrlForAssetAsNetworkSource(_videoAssetKey),
131+
)))!;
103132

104-
// Write it to a file to use as a source.
105-
final String filename = _videoAssetKey.split('/').last;
106-
final File file = File('$tempDir/$filename');
107-
await file.writeAsBytes(bytes.buffer.asInt8List());
133+
await player.play(textureId);
134+
await player.seekTo(textureId, const Duration(seconds: 5));
135+
await tester.pumpAndSettle(_playDuration);
136+
await player.pause(textureId);
108137

109-
controller = MiniController.file(file);
110-
});
138+
expect(await player.getPosition(textureId), greaterThan(Duration.zero));
111139

112-
testWidgets('test video player using static file() method as constructor',
113-
(WidgetTester tester) async {
114-
await controller.initialize();
140+
final DurationRange range = await _getBufferingRange(player, textureId);
141+
expect(range.start, Duration.zero);
142+
expect(range.end, greaterThan(Duration.zero));
115143

116-
await controller.play();
117-
await tester.pumpAndSettle(_playDuration);
144+
await player.dispose(textureId);
145+
});
146+
}
118147

119-
expect(await controller.position, greaterThan(Duration.zero));
120-
});
148+
Future<Duration> _getDuration(
149+
AndroidVideoPlayer player,
150+
int textureId,
151+
) {
152+
return player.videoEventsFor(textureId).firstWhere((VideoEvent event) {
153+
return event.eventType == VideoEventType.initialized;
154+
}).then((VideoEvent event) {
155+
return event.duration!;
121156
});
157+
}
122158

123-
group('network videos', () {
124-
setUp(() {
125-
final String videoUrl = getUrlForAssetAsNetworkSource(_videoAssetKey);
126-
controller = MiniController.network(videoUrl);
127-
});
128-
129-
testWidgets('reports buffering status', (WidgetTester tester) async {
130-
await controller.initialize();
131-
132-
final Completer<void> started = Completer<void>();
133-
final Completer<void> ended = Completer<void>();
134-
controller.addListener(() {
135-
if (!started.isCompleted && controller.value.isBuffering) {
136-
started.complete();
137-
}
138-
if (started.isCompleted &&
139-
!controller.value.isBuffering &&
140-
!ended.isCompleted) {
141-
ended.complete();
142-
}
143-
});
144-
145-
await controller.play();
146-
await controller.seekTo(const Duration(seconds: 5));
147-
await tester.pumpAndSettle(_playDuration);
148-
await controller.pause();
149-
150-
expect(await controller.position, greaterThan(Duration.zero));
151-
152-
await expectLater(started.future, completes);
153-
await expectLater(ended.future, completes);
154-
});
155-
156-
testWidgets('live stream duration != 0', (WidgetTester tester) async {
157-
final MiniController livestreamController = MiniController.network(
158-
'https://flutter.github.io/assets-for-api-docs/assets/videos/hls/bee.m3u8',
159-
);
160-
await livestreamController.initialize();
161-
162-
expect(livestreamController.value.isInitialized, true);
163-
// Live streams should have either a positive duration or C.TIME_UNSET if the duration is unknown
164-
// See https://exoplayer.dev/doc/reference/com/google/android/exoplayer2/Player.html#getDuration--
165-
expect(livestreamController.value.duration,
166-
(Duration duration) => duration != Duration.zero);
167-
});
159+
Future<DurationRange> _getBufferingRange(
160+
AndroidVideoPlayer player,
161+
int textureId,
162+
) {
163+
return player.videoEventsFor(textureId).firstWhere((VideoEvent event) {
164+
return event.eventType == VideoEventType.bufferingUpdate;
165+
}).then((VideoEvent event) {
166+
return event.buffered!.first;
168167
});
169168
}

packages/video_player/video_player_android/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: video_player_android
22
description: Android implementation of the video_player plugin.
33
repository: https://github.com/flutter/packages/tree/main/packages/video_player/video_player_android
44
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+video_player%22
5-
version: 2.7.12
5+
version: 2.7.13
66

77
environment:
88
sdk: ^3.5.0

0 commit comments

Comments
 (0)