Skip to content

Commit

Permalink
[video_player] Fixes video initialization future stall. (flutter#2134)
Browse files Browse the repository at this point in the history
Fixes issue where `initialize()` `Future` stalls when failing to load source data and does not throw error.

For example if the network is offline and you try to load a video. The video will attempt to load the source on Android through ExoPlayer. This error of failing to load is reported through the event stream to the client. However, this does not complete the initialization `Completer` leaving the `Future stalled waiting for the completer to complete which it never will.
  • Loading branch information
slightfoot authored and Michael Klimushyn committed Jan 6, 2020
1 parent 58f2698 commit f959157
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 14 deletions.
5 changes: 5 additions & 0 deletions packages/video_player/video_player/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 0.10.5+1

* Fixes issue where `initialize()` `Future` stalls when failing to load source
data and does not throw an error.

## 0.10.5

* Support `web` by default.
Expand Down
3 changes: 3 additions & 0 deletions packages/video_player/video_player/lib/video_player.dart
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,9 @@ class VideoPlayerController extends ValueNotifier<VideoPlayerValue> {
final PlatformException e = obj;
value = VideoPlayerValue.erroneous(e.message);
_timer?.cancel();
if (!initializingCompleter.isCompleted) {
initializingCompleter.completeError(obj);
}
}

_eventSubscription = VideoPlayerPlatform.instance
Expand Down
2 changes: 1 addition & 1 deletion packages/video_player/video_player/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: video_player
description: Flutter plugin for displaying inline video with other Flutter
widgets on Android and iOS.
version: 0.10.5
version: 0.10.5+1
homepage: https://github.com/flutter/plugins/tree/master/packages/video_player/video_player

flutter:
Expand Down
57 changes: 44 additions & 13 deletions packages/video_player/video_player/test/video_player_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,21 @@ void main() {
});
});

test('init errors', () async {
final VideoPlayerController controller = VideoPlayerController.network(
'http://testing.com/invalid_url',
);
try {
dynamic error;
fakeVideoPlayerPlatform.forceInitError = true;
await controller.initialize().catchError((dynamic e) => error = e);
final PlatformException platformEx = error;
expect(platformEx.code, equals('VideoError'));
} finally {
fakeVideoPlayerPlatform.forceInitError = false;
}
});

test('file', () async {
final VideoPlayerController controller =
VideoPlayerController.file(File('a.avi'));
Expand Down Expand Up @@ -437,6 +452,7 @@ class FakeVideoPlayerPlatform {
List<MethodCall> calls = <MethodCall>[];
List<Map<String, dynamic>> dataSourceDescriptions = <Map<String, dynamic>>[];
final Map<int, FakeVideoEventStream> streams = <int, FakeVideoEventStream>{};
bool forceInitError = false;
int nextTextureId = 0;
final Map<int, Duration> _positions = <int, Duration>{};

Expand All @@ -447,8 +463,8 @@ class FakeVideoPlayerPlatform {
initialized.complete(true);
break;
case 'create':
streams[nextTextureId] = FakeVideoEventStream(
nextTextureId, 100, 100, const Duration(seconds: 1));
streams[nextTextureId] = FakeVideoEventStream(nextTextureId, 100, 100,
const Duration(seconds: 1), forceInitError);
final Map<dynamic, dynamic> dataSource = call.arguments;
dataSourceDescriptions.add(dataSource.cast<String, dynamic>());
return Future<Map<String, int>>.sync(() {
Expand Down Expand Up @@ -481,7 +497,8 @@ class FakeVideoPlayerPlatform {
}

class FakeVideoEventStream {
FakeVideoEventStream(this.textureId, this.width, this.height, this.duration) {
FakeVideoEventStream(this.textureId, this.width, this.height, this.duration,
this.initWithError) {
eventsChannel = FakeEventsChannel(
'flutter.io/videoPlayer/videoEvents$textureId', onListen);
}
Expand All @@ -490,16 +507,20 @@ class FakeVideoEventStream {
int width;
int height;
Duration duration;
bool initWithError;
FakeEventsChannel eventsChannel;

void onListen() {
final Map<String, dynamic> initializedEvent = <String, dynamic>{
'event': 'initialized',
'duration': duration.inMilliseconds,
'width': width,
'height': height,
};
eventsChannel.sendEvent(initializedEvent);
if (!initWithError) {
eventsChannel.sendEvent(<String, dynamic>{
'event': 'initialized',
'duration': duration.inMilliseconds,
'width': width,
'height': height,
});
} else {
eventsChannel.sendError('VideoError', 'Video player had error XYZ');
}
}
}

Expand All @@ -522,13 +543,23 @@ class FakeEventsChannel {
}

void sendEvent(dynamic event) {
_sendMessage(const StandardMethodCodec().encodeSuccessEnvelope(event));
}

void sendError(String code, [String message, dynamic details]) {
_sendMessage(const StandardMethodCodec().encodeErrorEnvelope(
code: code,
message: message,
details: details,
));
}

void _sendMessage(ByteData data) {
// TODO(jackson): This has been deprecated and should be replaced
// with `ServicesBinding.instance.defaultBinaryMessenger` when it's
// available on all the versions of Flutter that we test.
// ignore: deprecated_member_use
defaultBinaryMessenger.handlePlatformMessage(
eventsMethodChannel.name,
const StandardMethodCodec().encodeSuccessEnvelope(event),
(ByteData data) {});
eventsMethodChannel.name, data, (ByteData data) {});
}
}

0 comments on commit f959157

Please sign in to comment.