Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit 4530941

Browse files
committed
[video_player] Fixes issue where initialize() future stalls when failing to load source data and does not throw error.
Fix formatting. Add tests for errors during init.
1 parent 8380988 commit 4530941

2 files changed

Lines changed: 90 additions & 32 deletions

File tree

packages/video_player/lib/video_player.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,9 @@ class VideoPlayerController extends ValueNotifier<VideoPlayerValue> {
274274
final PlatformException e = obj;
275275
value = VideoPlayerValue.erroneous(e.message);
276276
_timer?.cancel();
277+
if (!initializingCompleter.isCompleted) {
278+
initializingCompleter.completeError(obj);
279+
}
277280
}
278281

279282
_eventSubscription = _eventChannelFor(_textureId)

packages/video_player/test/video_player_test.dart

Lines changed: 87 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@
44

55
import 'dart:async';
66
import 'dart:io';
7+
78
import 'package:flutter/foundation.dart';
89
import 'package:flutter/services.dart';
910
import 'package:flutter/widgets.dart';
10-
import 'package:video_player/video_player.dart';
1111
import 'package:flutter_test/flutter_test.dart';
12+
import 'package:video_player/video_player.dart';
1213

1314
class FakeController extends ValueNotifier<VideoPlayerValue>
1415
implements VideoPlayerController {
@@ -24,23 +25,31 @@ class FakeController extends ValueNotifier<VideoPlayerValue>
2425

2526
@override
2627
String get dataSource => '';
28+
2729
@override
2830
DataSourceType get dataSourceType => DataSourceType.file;
31+
2932
@override
3033
String get package => null;
34+
3135
@override
3236
Future<Duration> get position async => value.position;
3337

3438
@override
3539
Future<void> seekTo(Duration moment) async {}
40+
3641
@override
3742
Future<void> setVolume(double volume) async {}
43+
3844
@override
3945
Future<void> initialize() async {}
46+
4047
@override
4148
Future<void> pause() async {}
49+
4250
@override
4351
Future<void> play() async {}
52+
4453
@override
4554
Future<void> setLooping(bool looping) async {}
4655

@@ -139,6 +148,20 @@ void main() {
139148
'uri': 'file://a.avi',
140149
});
141150
});
151+
152+
test('capture video initialization errors', () async {
153+
final VideoPlayerController controller =
154+
VideoPlayerController.network('http://testing.com/invalid_url');
155+
try {
156+
dynamic error;
157+
fakeVideoPlayerPlatform.forceInitError = true;
158+
await controller.initialize().catchError((dynamic e) => error = e);
159+
final PlatformException platformEx = error;
160+
expect(platformEx.code, equals('VideoError'));
161+
} finally {
162+
fakeVideoPlayerPlatform.forceInitError = false;
163+
}
164+
});
142165
});
143166
}
144167

@@ -149,32 +172,40 @@ class FakeVideoPlayerPlatform {
149172

150173
final MethodChannel _channel = const MethodChannel('flutter.io/videoPlayer');
151174

152-
Completer<bool> initialized = Completer<bool>();
153-
List<Map<String, dynamic>> dataSourceDescriptions = <Map<String, dynamic>>[];
154-
int nextTextureId = 0;
175+
final Completer<bool> initialized = Completer<bool>();
176+
final List<Map<String, dynamic>> dataSourceDescriptions =
177+
<Map<String, dynamic>>[];
178+
179+
bool forceInitError = false;
180+
181+
int _nextTextureId = 0;
155182

156183
Future<dynamic> onMethodCall(MethodCall call) {
157184
switch (call.method) {
158185
case 'init':
159186
initialized.complete(true);
160187
break;
161188
case 'create':
162-
FakeVideoEventStream(
163-
nextTextureId, 100, 100, const Duration(seconds: 1));
164-
final Map<dynamic, dynamic> dataSource = call.arguments;
165-
dataSourceDescriptions.add(dataSource.cast<String, dynamic>());
166-
return Future<Map<String, int>>.sync(() {
167-
return <String, int>{
168-
'textureId': nextTextureId++,
169-
};
170-
});
189+
{
190+
FakeVideoEventStream(_nextTextureId, 100, 100,
191+
const Duration(seconds: 1), forceInitError);
192+
final Map<dynamic, dynamic> dataSource = call.arguments;
193+
dataSourceDescriptions.add(dataSource.cast<String, dynamic>());
194+
return Future<Map<String, int>>.sync(() {
195+
return <String, int>{
196+
'textureId': _nextTextureId++,
197+
};
198+
});
199+
}
171200
break;
172201
case 'setLooping':
173202
break;
174203
case 'setVolume':
175204
break;
176205
case 'pause':
177206
break;
207+
case 'dispose':
208+
break;
178209
default:
179210
throw UnimplementedError(
180211
'${call.method} is not implemented by the FakeVideoPlayerPlatform');
@@ -184,54 +215,78 @@ class FakeVideoPlayerPlatform {
184215
}
185216

186217
class FakeVideoEventStream {
187-
FakeVideoEventStream(this.textureId, this.width, this.height, this.duration) {
188-
eventsChannel = FakeEventsChannel(
189-
'flutter.io/videoPlayer/videoEvents$textureId', onListen);
218+
FakeVideoEventStream(this.textureId, this.width, this.height, this.duration,
219+
this.initWithError) {
220+
_eventsChannel = FakeEventsChannel(
221+
'flutter.io/videoPlayer/videoEvents$textureId', onListen, onCancel);
190222
}
191223

192-
int textureId;
193-
int width;
194-
int height;
195-
Duration duration;
196-
FakeEventsChannel eventsChannel;
224+
FakeEventsChannel _eventsChannel;
225+
final int textureId;
226+
final int width;
227+
final int height;
228+
final Duration duration;
229+
final bool initWithError;
197230

198231
void onListen() {
199-
final Map<String, dynamic> initializedEvent = <String, dynamic>{
200-
'event': 'initialized',
201-
'duration': duration.inMilliseconds,
202-
'width': width,
203-
'height': height,
204-
};
205-
eventsChannel.sendEvent(initializedEvent);
232+
if (!initWithError) {
233+
_eventsChannel.sendEvent(<String, dynamic>{
234+
'event': 'initialized',
235+
'duration': duration.inMilliseconds,
236+
'width': width,
237+
'height': height,
238+
});
239+
} else {
240+
_eventsChannel.sendError('VideoError', 'Video player had error XYZ');
241+
}
242+
}
243+
244+
void onCancel() {
245+
//
206246
}
207247
}
208248

209249
class FakeEventsChannel {
210-
FakeEventsChannel(String name, this.onListen) {
250+
FakeEventsChannel(String name, this.onListen, this.onCancel) {
211251
eventsMethodChannel = MethodChannel(name);
212252
eventsMethodChannel.setMockMethodCallHandler(onMethodCall);
213253
}
214254

215255
MethodChannel eventsMethodChannel;
216256
VoidCallback onListen;
257+
VoidCallback onCancel;
217258

218259
Future<dynamic> onMethodCall(MethodCall call) {
219260
switch (call.method) {
220261
case 'listen':
221262
onListen();
222263
break;
264+
case 'cancel':
265+
eventsMethodChannel.setMockMethodCallHandler(null);
266+
onCancel();
267+
break;
223268
}
224269
return Future<void>.sync(() {});
225270
}
226271

227272
void sendEvent(dynamic event) {
273+
_sendMessage(const StandardMethodCodec().encodeSuccessEnvelope(event));
274+
}
275+
276+
void sendError(String code, [String message, dynamic details]) {
277+
_sendMessage(const StandardMethodCodec().encodeErrorEnvelope(
278+
code: code,
279+
message: message,
280+
details: details,
281+
));
282+
}
283+
284+
void _sendMessage(ByteData data) {
228285
// TODO(jackson): This has been deprecated and should be replaced
229286
// with `ServicesBinding.instance.defaultBinaryMessenger` when it's
230287
// available on all the versions of Flutter that we test.
231288
// ignore: deprecated_member_use
232289
defaultBinaryMessenger.handlePlatformMessage(
233-
eventsMethodChannel.name,
234-
const StandardMethodCodec().encodeSuccessEnvelope(event),
235-
(ByteData data) {});
290+
eventsMethodChannel.name, data, (ByteData data) {});
236291
}
237292
}

0 commit comments

Comments
 (0)