@@ -22,23 +22,16 @@ class AVFoundationVideoPlayer extends VideoPlayerPlatform {
2222 AVFoundationVideoPlayer ({
2323 @visibleForTesting AVFoundationVideoPlayerApi ? pluginApi,
2424 @visibleForTesting
25- VideoPlayerInstanceApi Function (int playerId)? playerProvider ,
25+ VideoPlayerInstanceApi Function (int playerId)? playerApiProvider ,
2626 }) : _api = pluginApi ?? AVFoundationVideoPlayerApi (),
27- _playerProvider = playerProvider ?? _productionApiProvider;
27+ _playerApiProvider = playerApiProvider ?? _productionApiProvider;
2828
2929 final AVFoundationVideoPlayerApi _api;
3030 // A method to create VideoPlayerInstanceApi instances, which can be
3131 // overridden for testing.
32- final VideoPlayerInstanceApi Function (int mapId) _playerProvider ;
32+ final VideoPlayerInstanceApi Function (int mapId) _playerApiProvider ;
3333
34- /// A map that associates player ID with a view state.
35- /// This is used to determine which view type to use when building a view.
36- @visibleForTesting
37- final Map <int , VideoPlayerViewState > playerViewStates =
38- < int , VideoPlayerViewState > {};
39-
40- final Map <int , VideoPlayerInstanceApi > _players =
41- < int , VideoPlayerInstanceApi > {};
34+ final Map <int , _PlayerInstance > _players = < int , _PlayerInstance > {};
4235
4336 /// Registers this class as the default instance of [VideoPlayerPlatform] .
4437 static void registerWith () {
@@ -52,9 +45,8 @@ class AVFoundationVideoPlayer extends VideoPlayerPlatform {
5245
5346 @override
5447 Future <void > dispose (int playerId) async {
55- final VideoPlayerInstanceApi ? player = _players.remove (playerId);
48+ final _PlayerInstance ? player = _players.remove (playerId);
5649 await player? .dispose ();
57- playerViewStates.remove (playerId);
5850 }
5951
6052 @override
@@ -118,18 +110,24 @@ class AVFoundationVideoPlayer extends VideoPlayerPlatform {
118110 playerId = await _api.createForPlatformView (pigeonCreationOptions);
119111 state = const VideoPlayerPlatformViewState ();
120112 }
121- playerViewStates[playerId] = state;
122- ensureApiInitialized (playerId);
113+ ensurePlayerInitialized (playerId, state);
123114
124115 return playerId;
125116 }
126117
127118 /// Returns the API instance for [playerId] , creating it if it doesn't already
128119 /// exist.
129120 @visibleForTesting
130- VideoPlayerInstanceApi ensureApiInitialized (int playerId) {
131- return _players.putIfAbsent (playerId, () {
132- return _playerProvider (playerId);
121+ void ensurePlayerInitialized (int playerId, VideoPlayerViewState viewState) {
122+ _players.putIfAbsent (playerId, () {
123+ return _PlayerInstance (
124+ _playerApiProvider (playerId),
125+ viewState,
126+ eventChannel: EventChannel (
127+ // This must match the channel name used in FVPVideoPlayerPlugin.m.
128+ 'flutter.dev/videoPlayer/videoEvents$playerId ' ,
129+ ),
130+ );
133131 });
134132 }
135133
@@ -162,48 +160,17 @@ class AVFoundationVideoPlayer extends VideoPlayerPlatform {
162160
163161 @override
164162 Future <void > seekTo (int playerId, Duration position) {
165- return _playerWith (id: playerId).seekTo (position.inMilliseconds );
163+ return _playerWith (id: playerId).seekTo (position);
166164 }
167165
168166 @override
169167 Future <Duration > getPosition (int playerId) async {
170- final int position = await _playerWith (id: playerId).getPosition ();
171- return Duration (milliseconds: position);
168+ return _playerWith (id: playerId).getPosition ();
172169 }
173170
174171 @override
175172 Stream <VideoEvent > videoEventsFor (int playerId) {
176- return _eventChannelFor (playerId).receiveBroadcastStream ().map ((
177- dynamic event,
178- ) {
179- final map = event as Map <dynamic , dynamic >;
180- return switch (map['event' ]) {
181- 'initialized' => VideoEvent (
182- eventType: VideoEventType .initialized,
183- duration: Duration (milliseconds: map['duration' ] as int ),
184- size: Size (
185- (map['width' ] as num ? )? .toDouble () ?? 0.0 ,
186- (map['height' ] as num ? )? .toDouble () ?? 0.0 ,
187- ),
188- ),
189- 'completed' => VideoEvent (eventType: VideoEventType .completed),
190- 'bufferingUpdate' => VideoEvent (
191- buffered: (map['values' ] as List <dynamic >)
192- .map <DurationRange >(_toDurationRange)
193- .toList (),
194- eventType: VideoEventType .bufferingUpdate,
195- ),
196- 'bufferingStart' => VideoEvent (
197- eventType: VideoEventType .bufferingStart,
198- ),
199- 'bufferingEnd' => VideoEvent (eventType: VideoEventType .bufferingEnd),
200- 'isPlayingStateUpdate' => VideoEvent (
201- eventType: VideoEventType .isPlayingStateUpdate,
202- isPlaying: map['isPlaying' ] as bool ,
203- ),
204- _ => VideoEvent (eventType: VideoEventType .unknown),
205- };
206- });
173+ return _playerWith (id: playerId).videoEvents;
207174 }
208175
209176 @override
@@ -219,16 +186,13 @@ class AVFoundationVideoPlayer extends VideoPlayerPlatform {
219186 @override
220187 Widget buildViewWithOptions (VideoViewOptions options) {
221188 final int playerId = options.playerId;
222- final VideoPlayerViewState ? viewState = playerViewStates[ playerId] ;
189+ final VideoPlayerViewState viewState = _playerWith (id : playerId).viewState ;
223190
224191 return switch (viewState) {
225192 VideoPlayerTextureViewState (: final int textureId) => Texture (
226193 textureId: textureId,
227194 ),
228195 VideoPlayerPlatformViewState () => _buildPlatformView (playerId),
229- null => throw Exception (
230- 'Could not find corresponding view type for playerId: $playerId ' ,
231- ),
232196 };
233197 }
234198
@@ -245,13 +209,90 @@ class AVFoundationVideoPlayer extends VideoPlayerPlatform {
245209 );
246210 }
247211
248- EventChannel _eventChannelFor (int playerId) {
249- return EventChannel ('flutter.io/videoPlayer/videoEvents$playerId ' );
212+ _PlayerInstance _playerWith ({required int id}) {
213+ final _PlayerInstance ? player = _players[id];
214+ return player ?? (throw StateError ('No active player with ID $id .' ));
250215 }
216+ }
251217
252- VideoPlayerInstanceApi _playerWith ({required int id}) {
253- final VideoPlayerInstanceApi ? player = _players[id];
254- return player ?? (throw StateError ('No active player with ID $id .' ));
218+ /// An instance of a video player, corresponding to a single player ID in
219+ /// [AVFoundationVideoPlayer] .
220+ class _PlayerInstance {
221+ _PlayerInstance (
222+ this ._api,
223+ this .viewState, {
224+ required EventChannel eventChannel,
225+ }) : _eventChannel = eventChannel;
226+
227+ final VideoPlayerInstanceApi _api;
228+ final VideoPlayerViewState viewState;
229+ final EventChannel _eventChannel;
230+ final StreamController <VideoEvent > _eventStreamController =
231+ StreamController <VideoEvent >.broadcast ();
232+ StreamSubscription <dynamic >? _eventSubscription;
233+
234+ Future <void > play () => _api.play ();
235+
236+ Future <void > pause () => _api.pause ();
237+
238+ Future <void > setLooping (bool looping) => _api.setLooping (looping);
239+
240+ Future <void > setVolume (double volume) => _api.setVolume (volume);
241+
242+ Future <void > setPlaybackSpeed (double speed) => _api.setPlaybackSpeed (speed);
243+
244+ Future <void > seekTo (Duration position) {
245+ return _api.seekTo (position.inMilliseconds);
246+ }
247+
248+ Future <Duration > getPosition () async {
249+ return Duration (milliseconds: await _api.getPosition ());
250+ }
251+
252+ Stream <VideoEvent > get videoEvents {
253+ _eventSubscription ?? = _eventChannel.receiveBroadcastStream ().listen (
254+ _onStreamEvent,
255+ onError: (Object e) {
256+ _eventStreamController.addError (e);
257+ },
258+ );
259+
260+ return _eventStreamController.stream;
261+ }
262+
263+ Future <void > dispose () async {
264+ await _eventSubscription? .cancel ();
265+ unawaited (_eventStreamController.close ());
266+ await _api.dispose ();
267+ }
268+
269+ void _onStreamEvent (dynamic event) {
270+ final map = event as Map <dynamic , dynamic >;
271+ // The strings here must all match the strings in FVPEventBridge.m.
272+ _eventStreamController.add (switch (map['event' ]) {
273+ 'initialized' => VideoEvent (
274+ eventType: VideoEventType .initialized,
275+ duration: Duration (milliseconds: map['duration' ] as int ),
276+ size: Size (
277+ (map['width' ] as num ? )? .toDouble () ?? 0.0 ,
278+ (map['height' ] as num ? )? .toDouble () ?? 0.0 ,
279+ ),
280+ ),
281+ 'completed' => VideoEvent (eventType: VideoEventType .completed),
282+ 'bufferingUpdate' => VideoEvent (
283+ buffered: (map['values' ] as List <dynamic >)
284+ .map <DurationRange >(_toDurationRange)
285+ .toList (),
286+ eventType: VideoEventType .bufferingUpdate,
287+ ),
288+ 'bufferingStart' => VideoEvent (eventType: VideoEventType .bufferingStart),
289+ 'bufferingEnd' => VideoEvent (eventType: VideoEventType .bufferingEnd),
290+ 'isPlayingStateUpdate' => VideoEvent (
291+ eventType: VideoEventType .isPlayingStateUpdate,
292+ isPlaying: map['isPlaying' ] as bool ,
293+ ),
294+ _ => VideoEvent (eventType: VideoEventType .unknown),
295+ });
255296 }
256297
257298 DurationRange _toDurationRange (dynamic value) {
0 commit comments