Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
459e7f8
feat(video_player_avfoundation): implement audio track selection
nateshmbhat Nov 7, 2025
272ff1d
chore(video_player): bump minimum OS versions for iOS and macOS
nateshmbhat Nov 7, 2025
6905f0a
Merge branch 'main' into 28-oct-platform-avfoundation
nateshmbhat Nov 10, 2025
b253fa4
Merge branch 'main' into 28-oct-platform-avfoundation
nateshmbhat Nov 19, 2025
1bfefef
Merge branch 'main' into 28-oct-platform-avfoundation
nateshmbhat Nov 24, 2025
32ab95d
Update packages/video_player/video_player_avfoundation/darwin/video_p…
nateshmbhat Nov 26, 2025
9f13dc2
Merge branch 'main' into 28-oct-platform-avfoundation
nateshmbhat Nov 26, 2025
dcb886d
refactor(ios): remove audio selection options caching
nateshmbhat Nov 26, 2025
cb061e8
refactor(ios): simplify audio track metadata extraction
nateshmbhat Nov 27, 2025
3c8b5d4
Merge branch 'main' into 28-oct-platform-avfoundation
nateshmbhat Nov 27, 2025
c972c37
refactor(ios): improve error handling and code formatting in video pl…
nateshmbhat Nov 27, 2025
aee5360
Merge branch '28-oct-platform-avfoundation' of github.com:nateshmbhat…
nateshmbhat Nov 27, 2025
2071972
refactor(ios): replace error handling with assertions in audio track …
nateshmbhat Dec 3, 2025
4d78fb2
Merge branch 'main' into 28-oct-platform-avfoundation
nateshmbhat Dec 3, 2025
b77c751
test(ios): remove redundant nil-check tests for getAudioTracks
nateshmbhat Dec 4, 2025
31858c4
chore(video_player_avfoundation): bump version to 2.9.0
nateshmbhat Dec 4, 2025
d2f6def
Merge branch '28-oct-platform-avfoundation' of github.com:nateshmbhat…
nateshmbhat Dec 4, 2025
f17d787
Merge branch 'main' into 28-oct-platform-avfoundation
nateshmbhat Dec 9, 2025
c9e9d42
fixed lint warning
nateshmbhat Dec 10, 2025
4c7c534
Merge branch '28-oct-platform-avfoundation' of github.com:nateshmbhat…
nateshmbhat Dec 10, 2025
c60befb
Merge branch 'main' into 28-oct-platform-avfoundation
nateshmbhat Dec 10, 2025
93989eb
Format avfoundation_video_player.dart
nateshmbhat Dec 10, 2025
731400a
Merge branch 'main' into 28-oct-platform-avfoundation
nateshmbhat Dec 11, 2025
9d91faf
test: remove audio track test cases from `VideoPlayerTests.m`
nateshmbhat Dec 25, 2025
4c07e9e
Merge branch '28-oct-platform-avfoundation' of github.com:nateshmbhat…
nateshmbhat Dec 25, 2025
524223d
style: reformat `NSURL` initializations in tests and update Xcode pro…
nateshmbhat Dec 25, 2025
27d5b20
Merge branch 'main' into 28-oct-platform-avfoundation
nateshmbhat Dec 25, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/video_player/.fvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"flutter": "3.38.0"
}
3 changes: 3 additions & 0 deletions packages/video_player/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

# FVM Version Cache
.fvm/
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>12.0</string>
<string>13.0</string>
</dict>
</plist>
2 changes: 1 addition & 1 deletion packages/video_player/video_player/example/ios/Podfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '12.0'
# platform :ios, '13.0'

# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
1F784D8C27C8AC72541E3F4C /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
Expand Down Expand Up @@ -205,6 +206,23 @@
/* End PBXResourcesBuildPhase section */

/* Begin PBXShellScriptBuildPhase section */
1F784D8C27C8AC72541E3F4C /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
Expand Down Expand Up @@ -335,7 +353,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
Expand Down Expand Up @@ -414,7 +432,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
Expand Down Expand Up @@ -465,7 +483,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
Expand Down
2 changes: 1 addition & 1 deletion packages/video_player/video_player/example/macos/Podfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
platform :osx, '10.14'
platform :osx, '10.15'

# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@
33CC10EB2044A3C60003C045 /* Resources */,
33CC110E2044A8840003C045 /* Bundle Framework */,
3399D490228B24CF009A79C7 /* ShellScript */,
C0B5FBA873B9089B9B9062E0 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
Expand Down Expand Up @@ -306,6 +307,23 @@
shellPath = /bin/sh;
shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
};
C0B5FBA873B9089B9B9062E0 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
D3E396DFBCC51886820113AA /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
Expand Down Expand Up @@ -402,7 +420,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MACOSX_DEPLOYMENT_TARGET = 10.15;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
Expand Down Expand Up @@ -481,7 +499,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MACOSX_DEPLOYMENT_TARGET = 10.15;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
Expand Down Expand Up @@ -528,7 +546,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MACOSX_DEPLOYMENT_TARGET = 10.15;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 2.9.0

* Implements `getAudioTracks()` and `selectAudioTrack()` methods.
* Updates minimum supported SDK version to Flutter 3.29/Dart 3.7.

## 2.8.8

* Refactors Dart internals for maintainability.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1051,4 +1051,165 @@ - (nonnull AVPlayerItem *)playerItemWithURL:(NSURL *)url {
return [AVPlayerItem playerItemWithAsset:[AVURLAsset URLAssetWithURL:url options:nil]];
}

#pragma mark - Audio Track Tests

// Tests getAudioTracks with a regular MP4 video file using real AVFoundation.
// The bee.mp4 video has a single audio track.
- (void)testGetAudioTracksWithRealMP4Video {
FVPVideoPlayer *player =
[[FVPVideoPlayer alloc] initWithPlayerItem:[self playerItemWithURL:self.mp4TestURL]
avFactory:[[FVPDefaultAVFactory alloc] init]
viewProvider:[[StubViewProvider alloc] initWithView:nil]];
XCTAssertNotNil(player);

XCTestExpectation *initializedExpectation = [self expectationWithDescription:@"initialized"];
StubEventListener *listener =
[[StubEventListener alloc] initWithInitializationExpectation:initializedExpectation];
player.eventListener = listener;
[self waitForExpectationsWithTimeout:30.0 handler:nil];

// Now test getAudioTracks
FlutterError *error = nil;
FVPNativeAudioTrackData *result = [player getAudioTracks:&error];

XCTAssertNil(error);
XCTAssertNotNil(result);

// For regular MP4 files, we expect asset tracks (not media selection tracks)
// bee.mp4 has at least one audio track
if (result.assetTracks) {
XCTAssertGreaterThanOrEqual(result.assetTracks.count, 1);
// First track should be selected by default
if (result.assetTracks.count > 0) {
FVPAssetAudioTrackData *firstTrack = result.assetTracks[0];
XCTAssertTrue(firstTrack.isSelected);
XCTAssertGreaterThan(firstTrack.trackId, 0);
}
}
// mediaSelectionTracks should be nil for regular MP4 files
XCTAssertNil(result.mediaSelectionTracks);

[player disposeWithError:&error];
}

// Tests getAudioTracks with an HLS stream using real AVFoundation.
// HLS streams use media selection groups for audio track selection.
- (void)testGetAudioTracksWithRealHLSStream {
NSURL *hlsURL = [NSURL
URLWithString:@"https://flutter.github.io/assets-for-api-docs/assets/videos/hls/bee.m3u8"];
XCTAssertNotNil(hlsURL);

FVPVideoPlayer *player =
[[FVPVideoPlayer alloc] initWithPlayerItem:[self playerItemWithURL:hlsURL]
avFactory:[[FVPDefaultAVFactory alloc] init]
viewProvider:[[StubViewProvider alloc] initWithView:nil]];
XCTAssertNotNil(player);

XCTestExpectation *initializedExpectation = [self expectationWithDescription:@"initialized"];
StubEventListener *listener =
[[StubEventListener alloc] initWithInitializationExpectation:initializedExpectation];
player.eventListener = listener;
[self waitForExpectationsWithTimeout:30.0 handler:nil];

// Now test getAudioTracks
FlutterError *error = nil;
FVPNativeAudioTrackData *result = [player getAudioTracks:&error];

XCTAssertNil(error);
XCTAssertNotNil(result);

// For HLS streams, the result depends on whether the stream has multiple audio options.
// The bee.m3u8 stream may or may not have multiple audio tracks.
// We verify the method returns valid data without crashing.
if (result.mediaSelectionTracks) {
// If media selection tracks exist, they should have valid structure
for (FVPMediaSelectionAudioTrackData *track in result.mediaSelectionTracks) {
XCTAssertNotNil(track.displayName);
XCTAssertGreaterThanOrEqual(track.index, 0);
}
} else if (result.assetTracks) {
// Falls back to asset tracks if no media selection group
for (FVPAssetAudioTrackData *track in result.assetTracks) {
XCTAssertGreaterThan(track.trackId, 0);
}
}

[player disposeWithError:&error];
}

// Tests that getAudioTracks returns valid data for audio-only files.
- (void)testGetAudioTracksWithRealAudioFile {
NSURL *audioURL = [NSURL
URLWithString:@"https://flutter.github.io/assets-for-api-docs/assets/audio/rooster.mp3"];
XCTAssertNotNil(audioURL);

FVPVideoPlayer *player =
[[FVPVideoPlayer alloc] initWithPlayerItem:[self playerItemWithURL:audioURL]
avFactory:[[FVPDefaultAVFactory alloc] init]
viewProvider:[[StubViewProvider alloc] initWithView:nil]];
XCTAssertNotNil(player);

XCTestExpectation *initializedExpectation = [self expectationWithDescription:@"initialized"];
StubEventListener *listener =
[[StubEventListener alloc] initWithInitializationExpectation:initializedExpectation];
player.eventListener = listener;
[self waitForExpectationsWithTimeout:30.0 handler:nil];

// Now test getAudioTracks
FlutterError *error = nil;
FVPNativeAudioTrackData *result = [player getAudioTracks:&error];

XCTAssertNil(error);
XCTAssertNotNil(result);

// Audio files should have at least one audio track
if (result.assetTracks) {
XCTAssertGreaterThanOrEqual(result.assetTracks.count, 1);
}

[player disposeWithError:&error];
}

// Tests that getAudioTracks works correctly through the plugin API with a real video.
- (void)testGetAudioTracksViaPluginWithRealVideo {
NSObject<FlutterPluginRegistrar> *registrar = OCMProtocolMock(@protocol(FlutterPluginRegistrar));
FVPVideoPlayerPlugin *videoPlayerPlugin =
[[FVPVideoPlayerPlugin alloc] initWithRegistrar:registrar];

FlutterError *error;
[videoPlayerPlugin initialize:&error];
XCTAssertNil(error);

FVPCreationOptions *create = [FVPCreationOptions
makeWithUri:@"https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4"
httpHeaders:@{}];
FVPTexturePlayerIds *identifiers = [videoPlayerPlugin createTexturePlayerWithOptions:create
error:&error];
XCTAssertNil(error);
XCTAssertNotNil(identifiers);

FVPVideoPlayer *player = videoPlayerPlugin.playersByIdentifier[@(identifiers.playerId)];
XCTAssertNotNil(player);

// Wait for player item to become ready
AVPlayerItem *item = player.player.currentItem;
[self keyValueObservingExpectationForObject:(id)item
keyPath:@"status"
expectedValue:@(AVPlayerItemStatusReadyToPlay)];
[self waitForExpectationsWithTimeout:30.0 handler:nil];

// Now test getAudioTracks
FVPNativeAudioTrackData *result = [player getAudioTracks:&error];

XCTAssertNil(error);
XCTAssertNotNil(result);

// For regular MP4, expect asset tracks
if (result.assetTracks) {
XCTAssertGreaterThanOrEqual(result.assetTracks.count, 1);
}

[player disposeWithError:&error];
}

@end
Loading