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

Commit f100277

Browse files
nichtverstehensigurdm
authored andcommitted
Rebuild VideoPlayer once VideoPlayerController finishes initialization. (#767)
* Rebuild VideoPlayer once VideoPlayerController finishes initialization. Otherwise if VideoPlayer is built shortly after the controller starts initialization and doesn't yet have _textureId filled the widgets renders as an empty Container and never rebuilds to show the video later. * Queue VideoPlayer events in the EventSink. This ensure that initialization events that are generated in `onPlayerStateChanged` before the Dart side has chance to subscribe to the channel are queued and eventually delivered. Fixes flutter/flutter#21483.
1 parent f98d0d0 commit f100277

File tree

5 files changed

+123
-16
lines changed

5 files changed

+123
-16
lines changed

packages/video_player/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 0.6.5
2+
3+
* Eliminate race conditions around initialization: now initialization events are queued and guaranteed
4+
to be delivered to the Dart side. VideoPlayer widget is rebuilt upon completion of initialization.
5+
16
## 0.6.4
27

38
* Android: add support for hls, dash and ss video formats.
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package io.flutter.plugins.videoplayer;
2+
3+
import io.flutter.plugin.common.EventChannel;
4+
import java.util.ArrayList;
5+
6+
/**
7+
* And implementation of {@link EventChannel.EventSink} which can wrap an underlying sink.
8+
*
9+
* <p>It delivers messages immediately when downstream is available, but it queues messages before
10+
* the delegate event sink is set with setDelegate.
11+
*
12+
* <p>This class is not thread-safe. All calls must be done on the same thread or synchronized
13+
* externally.
14+
*/
15+
final class QueuingEventSink implements EventChannel.EventSink {
16+
private EventChannel.EventSink delegate;
17+
private ArrayList<Object> eventQueue = new ArrayList<>();
18+
private boolean done = false;
19+
20+
public void setDelegate(EventChannel.EventSink delegate) {
21+
this.delegate = delegate;
22+
maybeFlush();
23+
}
24+
25+
@Override
26+
public void endOfStream() {
27+
enqueue(new EndOfStreamEvent());
28+
maybeFlush();
29+
done = true;
30+
}
31+
32+
@Override
33+
public void error(String code, String message, Object details) {
34+
enqueue(new ErrorEvent(code, message, details));
35+
maybeFlush();
36+
}
37+
38+
@Override
39+
public void success(Object event) {
40+
enqueue(event);
41+
maybeFlush();
42+
}
43+
44+
private void enqueue(Object event) {
45+
if (done) {
46+
return;
47+
}
48+
eventQueue.add(event);
49+
}
50+
51+
private void maybeFlush() {
52+
if (delegate == null) {
53+
return;
54+
}
55+
for (Object event : eventQueue) {
56+
if (event instanceof EndOfStreamEvent) {
57+
delegate.endOfStream();
58+
} else if (event instanceof ErrorEvent) {
59+
ErrorEvent errorEvent = (ErrorEvent) event;
60+
delegate.error(errorEvent.code, errorEvent.message, errorEvent.details);
61+
} else {
62+
delegate.success(event);
63+
}
64+
}
65+
eventQueue.clear();
66+
}
67+
68+
private static class EndOfStreamEvent {}
69+
70+
private static class ErrorEvent {
71+
String code;
72+
String message;
73+
Object details;
74+
75+
ErrorEvent(String code, String message, Object details) {
76+
this.code = code;
77+
this.message = message;
78+
this.details = details;
79+
}
80+
}
81+
}

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

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ private static class VideoPlayer {
5959

6060
private final TextureRegistry.SurfaceTextureEntry textureEntry;
6161

62-
private EventChannel.EventSink eventSink;
62+
private QueuingEventSink eventSink = new QueuingEventSink();
6363

6464
private final EventChannel eventChannel;
6565

@@ -128,12 +128,12 @@ private void setupVideoPlayer(
128128
new EventChannel.StreamHandler() {
129129
@Override
130130
public void onListen(Object o, EventChannel.EventSink sink) {
131-
eventSink = sink;
131+
eventSink.setDelegate(sink);
132132
}
133133

134134
@Override
135135
public void onCancel(Object o) {
136-
eventSink = null;
136+
eventSink.setDelegate(null);
137137
}
138138
});
139139

@@ -148,14 +148,12 @@ public void onCancel(Object o) {
148148
public void onPlayerStateChanged(final boolean playWhenReady, final int playbackState) {
149149
super.onPlayerStateChanged(playWhenReady, playbackState);
150150
if (playbackState == Player.STATE_BUFFERING) {
151-
if (eventSink != null) {
152-
Map<String, Object> event = new HashMap<>();
153-
event.put("event", "bufferingUpdate");
154-
List<Integer> range = Arrays.asList(0, exoPlayer.getBufferedPercentage());
155-
// iOS supports a list of buffered ranges, so here is a list with a single range.
156-
event.put("values", Collections.singletonList(range));
157-
eventSink.success(event);
158-
}
151+
Map<String, Object> event = new HashMap<>();
152+
event.put("event", "bufferingUpdate");
153+
List<Integer> range = Arrays.asList(0, exoPlayer.getBufferedPercentage());
154+
// iOS supports a list of buffered ranges, so here is a list with a single range.
155+
event.put("values", Collections.singletonList(range));
156+
eventSink.success(event);
159157
} else if (playbackState == Player.STATE_READY && !isInitialized) {
160158
isInitialized = true;
161159
sendInitialized();
@@ -212,7 +210,7 @@ long getPosition() {
212210
}
213211

214212
private void sendInitialized() {
215-
if (isInitialized && eventSink != null) {
213+
if (isInitialized) {
216214
Map<String, Object> event = new HashMap<>();
217215
event.put("event", "initialized");
218216
event.put("duration", exoPlayer.getDuration());

packages/video_player/lib/video_player.dart

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -420,16 +420,39 @@ class _VideoAppLifeCycleObserver extends WidgetsBindingObserver {
420420
}
421421

422422
/// Displays the video controlled by [controller].
423-
class VideoPlayer extends StatelessWidget {
423+
class VideoPlayer extends StatefulWidget {
424424
final VideoPlayerController controller;
425425

426426
VideoPlayer(this.controller);
427427

428+
@override
429+
_VideoPlayerState createState() => new _VideoPlayerState();
430+
}
431+
432+
class _VideoPlayerState extends State<VideoPlayer> {
433+
int textureId;
434+
435+
@override
436+
void initState() {
437+
super.initState();
438+
textureId = widget.controller._textureId;
439+
// Need to listen for initialization events since the actual texture ID
440+
// becomes available after asynchronous initialization finishes.
441+
widget.controller.addListener(() {
442+
final int newTextureId = widget.controller._textureId;
443+
if (newTextureId != textureId) {
444+
setState(() {
445+
textureId = newTextureId;
446+
});
447+
}
448+
});
449+
}
450+
428451
@override
429452
Widget build(BuildContext context) {
430-
return controller._textureId == null
453+
return textureId == null
431454
? new Container()
432-
: new Texture(textureId: controller._textureId);
455+
: new Texture(textureId: textureId);
433456
}
434457
}
435458

packages/video_player/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: video_player
22
description: Flutter plugin for displaying inline video with other Flutter
33
widgets on Android and iOS.
44
author: Flutter Team <flutter-dev@googlegroups.com>
5-
version: 0.6.4
5+
version: 0.6.5
66
homepage: https://github.com/flutter/plugins/tree/master/packages/video_player
77

88
flutter:

0 commit comments

Comments
 (0)