Skip to content

Commit 5d8d954

Browse files
[video_player_avfoundation] Create a Dart player instance (#10490)
Refactors the Dart internals to create a pleyer instance class on the Dart side, and intermediates the event stream with an instance-managed stream controller that would allow for alternate event sources (e.g., Dart code). This prepares for future moves of more logic to Dart, and parallels Android Dart code changes made in #9771 Part of flutter/flutter#172763 ## Pre-Review Checklist [^1]: Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling.
1 parent 122e435 commit 5d8d954

File tree

5 files changed

+152
-101
lines changed

5 files changed

+152
-101
lines changed

packages/video_player/video_player_avfoundation/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 2.8.8
2+
3+
* Refactors Dart internals for maintainability.
4+
15
## 2.8.7
26

37
* Updates to Pigeon 26.

packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPVideoPlayerPlugin.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ - (int64_t)configurePlayer:(FVPVideoPlayer *)player
116116
// Set up the event channel.
117117
FVPEventBridge *eventBridge = [[FVPEventBridge alloc]
118118
initWithMessenger:messenger
119-
channelName:[NSString stringWithFormat:@"flutter.io/videoPlayer/videoEvents%@",
119+
channelName:[NSString stringWithFormat:@"flutter.dev/videoPlayer/videoEvents%@",
120120
channelSuffix]];
121121
player.eventListener = eventBridge;
122122

packages/video_player/video_player_avfoundation/lib/src/avfoundation_video_player.dart

Lines changed: 102 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -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) {

packages/video_player/video_player_avfoundation/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: video_player_avfoundation
22
description: iOS and macOS implementation of the video_player plugin.
33
repository: https://github.com/flutter/packages/tree/main/packages/video_player/video_player_avfoundation
44
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+video_player%22
5-
version: 2.8.7
5+
version: 2.8.8
66

77
environment:
88
sdk: ^3.9.0

0 commit comments

Comments
 (0)