28
28
import androidx .media3 .datasource .DefaultHttpDataSource ;
29
29
import androidx .media3 .exoplayer .ExoPlayer ;
30
30
import androidx .media3 .exoplayer .source .DefaultMediaSourceFactory ;
31
- import io .flutter .plugin .common .EventChannel ;
32
31
import io .flutter .view .TextureRegistry ;
33
- import java .util .Arrays ;
34
- import java .util .Collections ;
35
- import java .util .HashMap ;
36
- import java .util .List ;
37
32
import java .util .Map ;
38
33
39
34
final class VideoPlayer {
@@ -48,9 +43,7 @@ final class VideoPlayer {
48
43
49
44
private final TextureRegistry .SurfaceTextureEntry textureEntry ;
50
45
51
- private QueuingEventSink eventSink ;
52
-
53
- private final EventChannel eventChannel ;
46
+ private final VideoPlayerCallbacks videoPlayerEvents ;
54
47
55
48
private static final String USER_AGENT = "User-Agent" ;
56
49
@@ -62,13 +55,13 @@ final class VideoPlayer {
62
55
63
56
VideoPlayer (
64
57
Context context ,
65
- EventChannel eventChannel ,
58
+ VideoPlayerCallbacks events ,
66
59
TextureRegistry .SurfaceTextureEntry textureEntry ,
67
60
String dataSource ,
68
61
String formatHint ,
69
62
@ NonNull Map <String , String > httpHeaders ,
70
63
VideoPlayerOptions options ) {
71
- this .eventChannel = eventChannel ;
64
+ this .videoPlayerEvents = events ;
72
65
this .textureEntry = textureEntry ;
73
66
this .options = options ;
74
67
@@ -86,24 +79,23 @@ final class VideoPlayer {
86
79
exoPlayer .setMediaItem (mediaItem );
87
80
exoPlayer .prepare ();
88
81
89
- setUpVideoPlayer (exoPlayer , new QueuingEventSink () );
82
+ setUpVideoPlayer (exoPlayer );
90
83
}
91
84
92
85
// Constructor used to directly test members of this class.
93
86
@ VisibleForTesting
94
87
VideoPlayer (
95
88
ExoPlayer exoPlayer ,
96
- EventChannel eventChannel ,
89
+ VideoPlayerCallbacks events ,
97
90
TextureRegistry .SurfaceTextureEntry textureEntry ,
98
91
VideoPlayerOptions options ,
99
- QueuingEventSink eventSink ,
100
92
DefaultHttpDataSource .Factory httpDataSourceFactory ) {
101
- this .eventChannel = eventChannel ;
93
+ this .videoPlayerEvents = events ;
102
94
this .textureEntry = textureEntry ;
103
95
this .options = options ;
104
96
this .httpDataSourceFactory = httpDataSourceFactory ;
105
97
106
- setUpVideoPlayer (exoPlayer , eventSink );
98
+ setUpVideoPlayer (exoPlayer );
107
99
}
108
100
109
101
@ VisibleForTesting
@@ -118,37 +110,29 @@ public void configureHttpDataSourceFactory(@NonNull Map<String, String> httpHead
118
110
httpDataSourceFactory , httpHeaders , userAgent , httpHeadersNotEmpty );
119
111
}
120
112
121
- private void setUpVideoPlayer (ExoPlayer exoPlayer , QueuingEventSink eventSink ) {
113
+ private void setUpVideoPlayer (ExoPlayer exoPlayer ) {
122
114
this .exoPlayer = exoPlayer ;
123
- this .eventSink = eventSink ;
124
-
125
- eventChannel .setStreamHandler (
126
- new EventChannel .StreamHandler () {
127
- @ Override
128
- public void onListen (Object o , EventChannel .EventSink sink ) {
129
- eventSink .setDelegate (sink );
130
- }
131
-
132
- @ Override
133
- public void onCancel (Object o ) {
134
- eventSink .setDelegate (null );
135
- }
136
- });
137
115
138
116
surface = new Surface (textureEntry .surfaceTexture ());
139
117
exoPlayer .setVideoSurface (surface );
140
118
setAudioAttributes (exoPlayer , options .mixWithOthers );
141
119
120
+ // Avoids synthetic accessor.
121
+ VideoPlayerCallbacks events = this .videoPlayerEvents ;
122
+
142
123
exoPlayer .addListener (
143
124
new Listener () {
144
125
private boolean isBuffering = false ;
145
126
146
127
public void setBuffering (boolean buffering ) {
147
- if (isBuffering != buffering ) {
148
- isBuffering = buffering ;
149
- Map <String , Object > event = new HashMap <>();
150
- event .put ("event" , isBuffering ? "bufferingStart" : "bufferingEnd" );
151
- eventSink .success (event );
128
+ if (isBuffering == buffering ) {
129
+ return ;
130
+ }
131
+ isBuffering = buffering ;
132
+ if (buffering ) {
133
+ events .onBufferingStart ();
134
+ } else {
135
+ events .onBufferingEnd ();
152
136
}
153
137
}
154
138
@@ -163,11 +147,8 @@ public void onPlaybackStateChanged(final int playbackState) {
163
147
sendInitialized ();
164
148
}
165
149
} else if (playbackState == Player .STATE_ENDED ) {
166
- Map <String , Object > event = new HashMap <>();
167
- event .put ("event" , "completed" );
168
- eventSink .success (event );
150
+ events .onCompleted ();
169
151
}
170
-
171
152
if (playbackState != Player .STATE_BUFFERING ) {
172
153
setBuffering (false );
173
154
}
@@ -180,30 +161,20 @@ public void onPlayerError(@NonNull final PlaybackException error) {
180
161
// See https://exoplayer.dev/live-streaming.html#behindlivewindowexception-and-error_code_behind_live_window
181
162
exoPlayer .seekToDefaultPosition ();
182
163
exoPlayer .prepare ();
183
- } else if ( eventSink != null ) {
184
- eventSink . error ("VideoError" , "Video player had error " + error , null );
164
+ } else {
165
+ events . onError ("VideoError" , "Video player had error " + error , null );
185
166
}
186
167
}
187
168
188
169
@ Override
189
170
public void onIsPlayingChanged (boolean isPlaying ) {
190
- if (eventSink != null ) {
191
- Map <String , Object > event = new HashMap <>();
192
- event .put ("event" , "isPlayingStateUpdate" );
193
- event .put ("isPlaying" , isPlaying );
194
- eventSink .success (event );
195
- }
171
+ events .onIsPlayingStateUpdate (isPlaying );
196
172
}
197
173
});
198
174
}
199
175
200
176
void sendBufferingUpdate () {
201
- Map <String , Object > event = new HashMap <>();
202
- event .put ("event" , "bufferingUpdate" );
203
- List <? extends Number > range = Arrays .asList (0 , exoPlayer .getBufferedPosition ());
204
- // iOS supports a list of buffered ranges, so here is a list with a single range.
205
- event .put ("values" , Collections .singletonList (range ));
206
- eventSink .success (event );
177
+ videoPlayerEvents .onBufferingUpdate (exoPlayer .getBufferedPosition ());
207
178
}
208
179
209
180
private static void setAudioAttributes (ExoPlayer exoPlayer , boolean isMixMode ) {
@@ -248,43 +219,36 @@ long getPosition() {
248
219
@ SuppressWarnings ("SuspiciousNameCombination" )
249
220
@ VisibleForTesting
250
221
void sendInitialized () {
251
- if (isInitialized ) {
252
- Map <String , Object > event = new HashMap <>();
253
- event .put ("event" , "initialized" );
254
- event .put ("duration" , exoPlayer .getDuration ());
255
-
256
- VideoSize videoSize = exoPlayer .getVideoSize ();
257
- int width = videoSize .width ;
258
- int height = videoSize .height ;
259
- if (width != 0 && height != 0 ) {
260
- int rotationDegrees = videoSize .unappliedRotationDegrees ;
261
- // Switch the width/height if video was taken in portrait mode
262
- if (rotationDegrees == 90 || rotationDegrees == 270 ) {
263
- width = videoSize .height ;
264
- height = videoSize .width ;
265
- }
266
- event .put ("width" , width );
267
- event .put ("height" , height );
268
-
269
- // Rotating the video with ExoPlayer does not seem to be possible with a Surface,
270
- // so inform the Flutter code that the widget needs to be rotated to prevent
271
- // upside-down playback for videos with rotationDegrees of 180 (other orientations work
272
- // correctly without correction).
273
- if (rotationDegrees == 180 ) {
274
- event .put ("rotationCorrection" , rotationDegrees );
275
- }
222
+ if (!isInitialized ) {
223
+ return ;
224
+ }
225
+ VideoSize videoSize = exoPlayer .getVideoSize ();
226
+ int rotationCorrection = 0 ;
227
+ int width = videoSize .width ;
228
+ int height = videoSize .height ;
229
+ if (width != 0 && height != 0 ) {
230
+ int rotationDegrees = videoSize .unappliedRotationDegrees ;
231
+ // Switch the width/height if video was taken in portrait mode
232
+ if (rotationDegrees == 90 || rotationDegrees == 270 ) {
233
+ width = videoSize .height ;
234
+ height = videoSize .width ;
235
+ }
236
+ // Rotating the video with ExoPlayer does not seem to be possible with a Surface,
237
+ // so inform the Flutter code that the widget needs to be rotated to prevent
238
+ // upside-down playback for videos with rotationDegrees of 180 (other orientations work
239
+ // correctly without correction).
240
+ if (rotationDegrees == 180 ) {
241
+ rotationCorrection = rotationDegrees ;
276
242
}
277
-
278
- eventSink .success (event );
279
243
}
244
+ videoPlayerEvents .onInitialized (width , height , exoPlayer .getDuration (), rotationCorrection );
280
245
}
281
246
282
247
void dispose () {
283
248
if (isInitialized ) {
284
249
exoPlayer .stop ();
285
250
}
286
251
textureEntry .release ();
287
- eventChannel .setStreamHandler (null );
288
252
if (surface != null ) {
289
253
surface .release ();
290
254
}
0 commit comments