Repository navigation
[video_player] Convert player classes to Swift - #13197
Conversation
There was a problem hiding this comment.
Code Review
This pull request converts the native player implementation of the AVFoundation video player plugin from Objective-C to Swift, updating the minimum supported SDK versions to Flutter 3.47/Dart 3.13, iOS 15.0, and macOS 12.0. It consolidates the Pigeon-generated message APIs into a single file and refactors the player classes (VideoPlayer and TextureBasedVideoPlayer) and tests accordingly. Feedback on the changes includes addressing a potential retain cycle in nested asynchronous closures within VideoPlayer.swift, removing an unused error variable, and utilizing Swift's native comparison operators for CMTime instead of C-style functions.
|
Note for review: if you want to do a side-by-side comparison with the Obj-C code, you'll want to do that with the commit before 66e328f, and then review from there on separately. I did the initial conversion by editing the code inline, so it had the same structure and order, but then in that commit when I was doing final cleanup I reordered the file since the method order was a bit random. |
|
version/changelog override: the incidental changes to |
|
Removed the image picker incidental changes since #13224 will solve that problem as a separate PR, which is cleaner. This is now blocked from landing on that (or a similar) PR landing, but can still be reviewed as-is. |
stuartmorgan
left a comment
There was a problem hiding this comment.
I think I got everything. Replacing the deprecated methods required some non-trivial refactoring, and pulled another file into the conversion, so it'll need non-trivial re-review.
| let asset = currentItem.asset | ||
|
|
||
| // Get tracks from media selection (for HLS streams) | ||
| let audioGroup = asset.mediaSelectionGroup(forMediaCharacteristic: .audible) |
There was a problem hiding this comment.
Fixed in both locations. This required making both audio functions async in the Pigeon definition, so required some restructuring.
(The code will get less convoluted when the plugin switches to Pigeon's new async support, but that should be another PR.)
| from: option.commonMetadata, withKey: AVMetadataKey.commonKeyTitle, keySpace: .common) | ||
| let commonMetadataTitle: String? = titleItems.first?.stringValue | ||
|
|
||
| let isSelected = currentSelection?.isEqual(option) ?? false |
There was a problem hiding this comment.
Done. I also inlined all of these values since the local variables aren't adding anything.
| return | ||
| } | ||
|
|
||
| urlAsset.loadValuesAsynchronously(forKeys: [assetVariantsKey]) { |
There was a problem hiding this comment.
All three instances have been replaced now. This involved migrating FVPAVFactory.* to Swift, since load(_:isolation) is an async function using isolated.
(Maybe there's some way to declare that in Obj-C that would auto-bridge correctly, but I don't know it, and all the code needs to be converted anyway.)
| let status = urlAsset.statusOfValue(forKey: assetVariantsKey, error: nil) | ||
|
|
||
| if status == .loaded { | ||
| let variants = urlAsset.variants |
There was a problem hiding this comment.
Replaced with load(.variants).
|
|
||
| private func itemDidPlayToEndTime(_ item: AVPlayerItem) { | ||
| if isLooping { | ||
| item.seek(to: CMTime.zero) { _ in } |
There was a problem hiding this comment.
Indeed; fixed all instances.
| // Fixes https://github.com/flutter/flutter/issues/48670 | ||
| if time.isIndefinite { return -9_223_372_036_854_775_807 } | ||
| if time.timescale == 0 { return 0 } | ||
| return time.value * 1000 / Int64(time.timescale) |
| videoComposition.renderSize = CGSize(width: width, height: height) | ||
|
|
||
| videoComposition.sourceTrackIDForFrameTiming = videoTrack.trackID | ||
| if frameDurationIsValid(videoTrack.minFrameDuration) { |
There was a problem hiding this comment.
Replaced with load(.minFrameDuration)
| // Prefer more detailed error information from tracks loading. | ||
| var nsError: NSError? | ||
| var error: Error? | ||
| if player.currentItem?.asset.statusOfValue( |
| (error as? NSError)?.localizedFailureReason, | ||
| underlyingError?.localizedDescription, | ||
| underlyingError?.localizedFailureReason, | ||
| ].compactMap({ $0 }) |
There was a problem hiding this comment.
Reverted to NSMutableOrderedSet.
|
version override: the app-facing package change doesn't need to be published immediately since the main package itself does still support older OS versions via resolution to older implementations on older OS versions. |
| } | ||
|
|
||
| var duration: CMTime { | ||
| return avAsset.duration |
There was a problem hiding this comment.
There was a problem hiding this comment.
I was confused, because I ran min-16 analyze locally before I uploaded this, to make sure I'd caught everything, and it passed. I thought maybe I had messed that up, but CI is green even though I merged in your bump to iOS 16 min.
So I checked the logs, and sure enough it's complaining about things:
/Volumes/Work/s/w/ir/x/w/packages/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/VideoPlayer.swift:132:52: warning: 'naturalSize' was deprecated in iOS 16.0: Use load(.naturalSize) instead
transform: transform, size: videoTrack.naturalSize)
^
/Volumes/Work/s/w/ir/x/w/packages/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/VideoPlayer.swift:183:46: warning: 'duration' was deprecated in iOS 16.0: Use load(.duration) instead
let duration = player.currentItem?.asset.duration.value ?? 0
^
/Volumes/Work/s/w/ir/x/w/packages/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/VideoPlayer.swift:247:44: warning: 'languageCode' was deprecated in iOS 16: renamed to 'language.languageCode.identifier'
languageCode: option.locale?.languageCode,
^
/Volumes/Work/s/w/ir/x/w/packages/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/VideoPlayer.swift:247:44: note: use 'language.languageCode.identifier' instead
languageCode: option.locale?.languageCode,
^~~~~~~~~~~~
language.languageCode.identifier
/Volumes/Work/s/w/ir/x/w/packages/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/VideoPlayer.swift:249:54: warning: 'stringValue' was deprecated in iOS 16.0: Use load(.stringValue) instead
commonMetadataTitle: titleItems.first?.stringValue)
^
/Volumes/Work/s/w/ir/x/w/packages/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/VideoPlayer.swift:493:28: warning: 'naturalSize' was deprecated in iOS 16.0: Use load(.naturalSize) instead
var width = videoTrack.naturalSize.width
^
/Volumes/Work/s/w/ir/x/w/packages/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/VideoPlayer.swift:494:29: warning: 'naturalSize' was deprecated in iOS 16.0: Use load(.naturalSize) instead
var height = videoTrack.naturalSize.height
^
/Volumes/Work/s/w/ir/x/w/packages/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/VideoPlayer.swift:498:26: warning: 'naturalSize' was deprecated in iOS 16.0: Use load(.naturalSize) instead
width = videoTrack.naturalSize.height
^
/Volumes/Work/s/w/ir/x/w/packages/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/VideoPlayer.swift:499:27: warning: 'naturalSize' was deprecated in iOS 16.0: Use load(.naturalSize) instead
height = videoTrack.naturalSize.width
^
/Volumes/Work/s/w/ir/x/w/packages/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/VideoPlayer.swift:609:60: warning: 'duration' was deprecated in iOS 16.0: Use load(.duration) instead
return convertCMTimeToMillis(player.currentItem?.asset.duration ?? .zero)
There was a problem hiding this comment.
Replaced with load(.duration). I've now correctly verified locally that the the PR is free of deprecation warnings at an iOS 16 level.
| func selectAudioTrack( | ||
| atIndex trackIndex: Int64, completion: @escaping (Result<Void, Error>) -> Void | ||
| ) { | ||
| guard let currentItem = player.currentItem else { return } |
There was a problem hiding this comment.
Does this need to call the completion? Will it hang?
There was a problem hiding this comment.
Ugh, good catch. I forgot to re-audit all the early returns of the methods I made async.
Once this lands I'll try to fast-follow with a PR to switch to Pigeons async` Swift support to eliminate the possibility of this class of bugs.
| guard let currentItem = player.currentItem else { return } | ||
| let asset = currentItem.asset | ||
|
|
||
| asset.loadMediaSelectionGroup(for: .audible) { audioGroup, _ in |
There was a problem hiding this comment.
Does this need to be in a Task { @MainActor like getVideoTracks?
There was a problem hiding this comment.
Fixed and converted to async version as above.
| let asset = currentItem.asset | ||
|
|
||
| // Get tracks from media selection (for HLS streams) | ||
| asset.loadMediaSelectionGroup(for: .audible) { audioGroup, _ in |
There was a problem hiding this comment.
Same, does this need to be in a Task { @MainActor ?
There was a problem hiding this comment.
Somehow I had it in my head that all these AV* classes were main-thread only, so assumed this callback would be on the main thread since it's passing in an AV class, but apparently I had at some point incorrectly extrapolated that from AVPlayer/AVPlayerItem. So yes, this is potentially doing AVPlayerItem access from a random background thread 😬
It turns out there's an async variant of this method, so I'll switch to that (in a @MainActor Task) so it's easier to reason about everything.
There was a problem hiding this comment.
Fixed.
(The Task adds a little boilerplate over the callback version, but that will go away as soon as these are async Pigeon methods.)
| /// Note: Apple does not provide a constant for this key. | ||
| /// AVURLAsset.variants is documented via Apple's current async-loading API docs: | ||
| /// https://developer.apple.com/documentation/avfoundation/avpartialasyncproperty/variants | ||
| private let assetVariantsKey = "variants" |
| let currentItem = player.currentItem | ||
| assert( | ||
| currentItem?.status == .readyToPlay, | ||
| "reportInitializedIfReadyToPlay was called when the item wasn't ready to play.") |
There was a problem hiding this comment.
Same, this is the old objc name, use #function or hardcode
| assert(!isInitialized, "reportInitializedIfReadyToPlay should only be called once.") | ||
| isInitialized = true | ||
| eventListener?.videoPlayerDidInitialize( | ||
| withDuration: duration(), size: currentItem?.presentationSize ?? CGSize.zero) |
There was a problem hiding this comment.
| withDuration: duration(), size: currentItem?.presentationSize ?? CGSize.zero) | |
| withDuration: duration(), size: currentItem?.presentationSize ?? .zero) |
| } | ||
|
|
||
| /// Protocol for abstracting access to an AVAsset, to enable unit testing. | ||
| protocol WrappedAVAsset: AnyObject { |
There was a problem hiding this comment.
Does this need to be a AnyObject or can WrappedAVAsset become a struct?
There was a problem hiding this comment.
Technically only WrappedAVPlayerItem needs this (see below) currently, but I added it to all of them for consistency. I can remove it from most of them if you prefer though.
| } | ||
|
|
||
| /// Protocol for abstracting access to an AVPlayerItem, to enable unit testing. | ||
| protocol WrappedAVPlayerItem: AnyObject { |
There was a problem hiding this comment.
Same, does this need to be a AnyObject or can WrappedAVPlayerItem become a struct?
There was a problem hiding this comment.
This was needed because if WrappedAVPlayerItem doesn't have class semantics, then anything using the protocol has to follow struct semantics, which means that a let variable--say, the player item passed to VideoPlayer's init--becomes fully immutable rather than just not being reassignable as a variable. So playerItem.videoComposition = someNewComposition wouldn't work on a passed-in wrapped player item, even though it would on an actual player item.
| "\(#function) was called when the item wasn't ready to play.") | ||
| assert(!isInitialized, "\(#function) should only be called once.") | ||
| isInitialized = true | ||
| Task { @MainActor in |
There was a problem hiding this comment.
I think this changes event ordering. isInitialized is set, reportStatus calls updatePlayingState()-> player.play() -> rate KVO -> videoPlayerDidSetPlaying synchronously, and initialized only goes out when this task runs. The dart side can see isPlaying before initialized. In theory if dispose() runs first, currentItem is nil, duration() returns 0, then initialized would be sent after videoPlayerWasDisposed.
Since it's .readyToPlay duration should already be loaded. if case .loaded(let d) = asset.status(of: .duration) makes this synchronous.
There was a problem hiding this comment.
From the docs it doesn't seem like we can assume that specific properties are loaded just because it's ready to play, so I think we would still need to handle the non-loaded cases even though they would probably be rarely (if ever) called in practice.
I went the other direction instead and have more comprehensively adopted async, including at the Pigeon API level, so that we don't have sync method deferring work with Tasks, and instead can await as necessary to handle ordering. (There are potentially some ordering changes around KVO callbacks, which become async instead of sync, but I don't think we should be relying on exact call timing of those anyway.)
I have that pretty much done, but I need to iron out the unit tests that use autoreleasepool blocks to force deallocation at specific times, because they don't support async calls. Hopefully I'll have a new version up tomorrow.
| } | ||
|
|
||
| func seek(to position: Int64, completion: @escaping (Result<Void, Error>) -> Void) { | ||
| Task { @MainActor in |
There was a problem hiding this comment.
The seek used to be issued synchronously from pigeon, but now it's issued after an await. seekTo(x) followed by play() from dart would reach AVPlayer as play-then-seek.
Same as previous comment in reportInitialized, I think you can sync status(of: .duration) the duration instead of loading it.
There was a problem hiding this comment.
The seek used to be issued synchronously from pigeon, but now it's issued after an await.
seekTo(x)followed byplay()from dart would reachAVPlayeras play-then-seek.
This is true, but seekTo and play are async on the Dart side, so if you write seekTo(x); play() instead of await seekTo(x); await play() you are already playing with fire. Even though the method was returning right away on the native side, the Dart Future wouldn't complete until the completion(...) call at the end of the Task, so correct Dart code would work.
This should be clearer in the new code, where seek is an anync method, rather than a synchronous method that dispatches to a Task that calls a completion handler.
| } | ||
| completion(.success(trackData)) | ||
| } catch { | ||
| completion(.success([])) |
There was a problem hiding this comment.
Is the catch here really a success that looks like no tracks? Maybe at least log the error?
There was a problem hiding this comment.
Now that these functions are all async, I made them async throws and let any failures fall up into the Pigeon handler that will turn them into channel errors.
| currentItem.select(option, in: audioGroup) | ||
| } | ||
| } | ||
| } catch {} |
There was a problem hiding this comment.
Same, should this catch be a .failure instead of a success that looks like it was selected?
There was a problem hiding this comment.
Fixed as above.
| mediaSelectionTracks: mediaSelectionTracks))) | ||
| } catch { | ||
| completion( | ||
| .success( |
There was a problem hiding this comment.
Same, is this catch really a success?
There was a problem hiding this comment.
Fixed as above.
| // Fixes https://github.com/flutter/flutter/issues/48670 | ||
| if time.isIndefinite { return -9_223_372_036_854_775_807 } | ||
| if time.timescale == 0 { return 0 } | ||
| return CMTimeConvertScale(time, timescale: 1000, method: .roundHalfAwayFromZero).value |
There was a problem hiding this comment.
I think you want to round towards 0 to keep it the same ms.
| return CMTimeConvertScale(time, timescale: 1000, method: .roundHalfAwayFromZero).value | |
| return CMTimeConvertScale(time, timescale: 1000, method: .roundTowardZero).value |
There was a problem hiding this comment.
I was assuming that the old behavior was for expediency rather than a specific intent to truncate, but changed to roundTowardZero to preserve behavior.
| if let currentItem = player.currentItem { | ||
| switch currentItem.asset.status(of: .tracks) { | ||
| case .failed(let nsError): | ||
| error = nsError | ||
| case .notYetLoaded, .loading, .loaded(_): | ||
| break | ||
| } | ||
| } |
There was a problem hiding this comment.
nit, Swift cleverness (feel free to ignore any of these that aren't behavioral btw)
| if let currentItem = player.currentItem { | |
| switch currentItem.asset.status(of: .tracks) { | |
| case .failed(let nsError): | |
| error = nsError | |
| case .notYetLoaded, .loading, .loaded(_): | |
| break | |
| } | |
| } | |
| if case .failed(let trackError)? = player.currentItem?.asset.status(of: .tracks) { | |
| error = trackError | |
| } |
There was a problem hiding this comment.
Nice! if case is a pattern I am vaguely aware exists in both Dart and Swift, but haven't internalized yet. Changed.
| /// Wraps the AVAudioSession method of the same name. | ||
| func setCategory( | ||
| _ category: AVAudioSession.Category, | ||
| with options: AVAudioSession.CategoryOptions) throws |
There was a problem hiding this comment.
AVAudioSession doesn't have the with
| with options: AVAudioSession.CategoryOptions) throws | |
| options: AVAudioSession.CategoryOptions) throws |
| func setCategory( | ||
| _ category: AVAudioSession.Category, with options: AVAudioSession.CategoryOptions | ||
| ) throws { | ||
| return try AVAudioSession.sharedInstance().setCategory(category, options: options) |
There was a problem hiding this comment.
| return try AVAudioSession.sharedInstance().setCategory(category, options: options) | |
| try AVAudioSession.sharedInstance().setCategory(category, options: options) |
|
No longer left for later in the description:
|
| // - https://github.com/flutter/flutter/issues/124937 | ||
| // | ||
| // Failing to de-register results in a crash in [AVPlayer willChangeValueForKey:]. | ||
| @Test func doesNotCrashOnRateObservationAfterDisposal() async throws { |
There was a problem hiding this comment.
The reason this test is deleted instead of having the autoreleasepool code adjusted like the test below is that when I went to update it, I realized the test was a lie now anyways; leaving the KVO observers registered won't be a crash any more anyway, because in the new block-based version the behavior would be that the block is called, it finds that self is nil, and then it no-ops. So this test would never fail even with the bug present (exhibit A, the earlier iteration of this patch that did in fact leave them registered), and since it's a no-op anyway now I don't see how we could actually test it (nor is it critical to test like it was when it would crash).
jmagman
left a comment
There was a problem hiding this comment.
The last few things are very minor nits, feel free to ignore.
Thank you so much for working on this @stuartmorgan!
| playToEndObserver = NotificationCenter.default.addObserver( | ||
| forName: .AVPlayerItemDidPlayToEndTime, object: item, queue: .main | ||
| ) { [weak self] notification in | ||
| Task { @MainActor in |
There was a problem hiding this comment.
nit, I think this is already on the main queue a few lines above so you don't need the extra hop:
| Task { @MainActor in | |
| MainActor.assumeIsolated { |
There was a problem hiding this comment.
Fixed; I forgot this one had that argument.
(We could probably get away with doing the same thing in the KVO callbacks too, since they fire synchronously on the calling thread and player and player item should be MainActor-only, but it felt a bit risky which is why I used the Task instead. If there ever are ordering issues with these, that could be revisited. I'll actually add a comment about that just in case.)
|
|
||
| // MARK: FlutterTexture | ||
|
|
||
| func copyPixelBuffer() -> Unmanaged<CVPixelBuffer>? { |
There was a problem hiding this comment.
fyi I think this will need to become nonisolated when we start enforcing swift 6 (I believe it's run on the raster thread?), but not worth changing for this PR.
There was a problem hiding this comment.
Yes, this class is split over several threads, so will need work to fully annotate. I punted on everything but the overridden methods for now since there wasn't a bunch of new async stuff in this class.
| } | ||
| } | ||
|
|
||
| @MainActor func setEventListener(_ eventListener: FVPVideoEventListener) async { |
There was a problem hiding this comment.
nit since VideoPlayer is @MainActor the funcs don't need to be annotated as well.
There was a problem hiding this comment.
Oops, I thought I did a pass over the file when I switched from trying to do individual annotations to making the whole class MainActor, but apparently not since there were a bunch of them left. Removed everywhere.
| private var keyValueObservations: [NSKeyValueObservation] = [] | ||
| private var playToEndObserver: NSObjectProtocol? | ||
|
|
||
| /// Initializes a new instance of FVPVideoPlayer with the given AVPlayerItem, AV factory, and view |
There was a problem hiding this comment.
| /// Initializes a new instance of FVPVideoPlayer with the given AVPlayerItem, AV factory, and view | |
| /// Initializes a new instance of VideoPlayer with the given AVPlayerItem, AV factory, and view |
| /// AVPlayerItem is ready to play and the event listener is set to a non-nil value, whichever occurs | ||
| /// last. | ||
| /// | ||
| /// This flag is set to YES when the "initialized" message is first sent, and is never set to NO |
There was a problem hiding this comment.
| /// This flag is set to YES when the "initialized" message is first sent, and is never set to NO | |
| /// This flag is set to true when the "initialized" message is first sent, and is never set to false |
flutter/packages@1383427...93baed9 2026-10-22 [email protected] [material_ui] Port flutter/flutter flutter#186777 MaterialColor test (flutter/packages#12858) 2026-10-21 [email protected] [rfw] Migrate to material_ui (flutter/packages#12811) 2026-10-21 [email protected] [cupertino_ui] Fix the installing command of the cupertino_ui package (flutter/packages#13141) 2026-10-21 [email protected] [cupertino_ui] Unskip web tests that pass on CanvasKit and Skwasm: date_picker_test, spell_check_suggestions_toolbar_test (flutter/packages#13367) 2026-10-21 [email protected] [video_player] Convert player classes to Swift (flutter/packages#13197) 2026-10-21 [email protected] [camera_windows] Fall back to a physical stream for cameras that reject preferred-stream selectors (flutter/packages#12748) 2026-10-21 [email protected] [google_sign_in_ios, video_player] Specify types for pattern variable declarations (flutter/packages#13362) 2026-10-21 [email protected] [google_maps_flutter_web] Issue 64073 implement my location (flutter/packages#12186) 2026-10-21 [email protected] [material_ui] Unskip web tests that pass on CanvasKit and Skwasm: app_bar_theme_test, input_decorator_test, paginated_data_table_test, spell_check_suggestions_toolbar_test (flutter/packages#13352) If this roll has caused a breakage, revert this CL and set the roller to dry run mode using the controls here: https://autoroll.skia.org/r/flutter-packages-flutter-autoroll Please CC [email protected] on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
flutter/packages@1383427...93baed9 2026-10-22 [email protected] [material_ui] Port flutter/flutter flutter#186777 MaterialColor test (flutter/packages#12858) 2026-10-21 [email protected] [rfw] Migrate to material_ui (flutter/packages#12811) 2026-10-21 [email protected] [cupertino_ui] Fix the installing command of the cupertino_ui package (flutter/packages#13141) 2026-10-21 [email protected] [cupertino_ui] Unskip web tests that pass on CanvasKit and Skwasm: date_picker_test, spell_check_suggestions_toolbar_test (flutter/packages#13367) 2026-10-21 [email protected] [video_player] Convert player classes to Swift (flutter/packages#13197) 2026-10-21 [email protected] [camera_windows] Fall back to a physical stream for cameras that reject preferred-stream selectors (flutter/packages#12748) 2026-10-21 [email protected] [google_sign_in_ios, video_player] Specify types for pattern variable declarations (flutter/packages#13362) 2026-10-21 [email protected] [google_maps_flutter_web] Issue 64073 implement my location (flutter/packages#12186) 2026-10-21 [email protected] [material_ui] Unskip web tests that pass on CanvasKit and Skwasm: app_bar_theme_test, input_decorator_test, paginated_data_table_test, spell_check_suggestions_toolbar_test (flutter/packages#13352) If this roll has caused a breakage, revert this CL and set the roller to dry run mode using the controls here: https://autoroll.skia.org/r/flutter-packages-flutter-autoroll Please CC [email protected] on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
Converts
VideoPlayerandTextureBasedVideoPlayerto Swift. Also convertsAVAssetTrackUtilssince it simplifies calling from Swift, and that's a very simple conversion, as well as theAV*class wrappers, in order to replace deprecated methods with newer, Swift-friendlier alternatives. As part of the conversion, adopts Swift Pigeon for the instance-level APIs, so that all of the Pigeon generation in Swift-based (and combines the Pigeon definition files now that they can share a single config). Also adopts Pigeon's newasyncsupport to make handling async code simpler, so does require some test changes for the API surface change.Intentionally left to later PRs, to limit scope:
video_player_avfoundation_objcsubtarget (although these should be relatively simple)video_player_avfoundation_iosandvideo_player_avfoundation_macosAs part of the conversion, the minimum Flutter version has been changed to 3.47, which allows dropping versions of iOS and macOS that are no longer supported in 3.47, and thus avoiding the need to convert legacy codepaths that only existing for those no-longer-supported versions.
Updates CI's iOS version to target for deprecation checks to iOS 15, rather than 14, to allow using things that require iOS 15 without failing that check (which overrides the deployment version, but is intended to raise it, not lower it).
Also replaces a number of methods that are deprecated in iOS 16, but for which the replacements were added in iOS 15.
Part of flutter/flutter#119105
Pre-Review Checklist
[shared_preferences]///).Footnotes
Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling. ↩ ↩2