diff --git a/packages/video_player/video_player/example/ios/Flutter/AppFrameworkInfo.plist b/packages/video_player/video_player/example/ios/Flutter/AppFrameworkInfo.plist
index 7c569640062..1dc6cf7652b 100644
--- a/packages/video_player/video_player/example/ios/Flutter/AppFrameworkInfo.plist
+++ b/packages/video_player/video_player/example/ios/Flutter/AppFrameworkInfo.plist
@@ -21,6 +21,6 @@
CFBundleVersion
1.0
MinimumOSVersion
- 12.0
+ 13.0
diff --git a/packages/video_player/video_player/example/ios/Podfile b/packages/video_player/video_player/example/ios/Podfile
index 01d4aa611bb..17adeb14132 100644
--- a/packages/video_player/video_player/example/ios/Podfile
+++ b/packages/video_player/video_player/example/ios/Podfile
@@ -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'
diff --git a/packages/video_player/video_player/example/ios/Runner.xcodeproj/project.pbxproj b/packages/video_player/video_player/example/ios/Runner.xcodeproj/project.pbxproj
index 2ab10fb9081..a003785afc3 100644
--- a/packages/video_player/video_player/example/ios/Runner.xcodeproj/project.pbxproj
+++ b/packages/video_player/video_player/example/ios/Runner.xcodeproj/project.pbxproj
@@ -140,6 +140,7 @@
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
+ 1F784D8C27C8AC72541E3F4C /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@@ -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;
@@ -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;
@@ -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;
@@ -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;
diff --git a/packages/video_player/video_player/example/macos/Podfile b/packages/video_player/video_player/example/macos/Podfile
index ae77cc1d426..66f6172bbb3 100644
--- a/packages/video_player/video_player/example/macos/Podfile
+++ b/packages/video_player/video_player/example/macos/Podfile
@@ -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'
diff --git a/packages/video_player/video_player/example/macos/Runner.xcodeproj/project.pbxproj b/packages/video_player/video_player/example/macos/Runner.xcodeproj/project.pbxproj
index e6fa40d2ed6..9869c74bb38 100644
--- a/packages/video_player/video_player/example/macos/Runner.xcodeproj/project.pbxproj
+++ b/packages/video_player/video_player/example/macos/Runner.xcodeproj/project.pbxproj
@@ -193,6 +193,7 @@
33CC10EB2044A3C60003C045 /* Resources */,
33CC110E2044A8840003C045 /* Bundle Framework */,
3399D490228B24CF009A79C7 /* ShellScript */,
+ C0B5FBA873B9089B9B9062E0 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@@ -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;
@@ -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;
@@ -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;
@@ -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;
diff --git a/packages/video_player/video_player_avfoundation/CHANGELOG.md b/packages/video_player/video_player_avfoundation/CHANGELOG.md
index 593df056f9c..b72d84b232c 100644
--- a/packages/video_player/video_player_avfoundation/CHANGELOG.md
+++ b/packages/video_player/video_player_avfoundation/CHANGELOG.md
@@ -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.9
* Resolve `tracksWithMediaType:` deprecations.
diff --git a/packages/video_player/video_player_avfoundation/darwin/RunnerTests/VideoPlayerTests.m b/packages/video_player/video_player_avfoundation/darwin/RunnerTests/VideoPlayerTests.m
index afcdfed3bc9..26bc4a50f41 100644
--- a/packages/video_player/video_player_avfoundation/darwin/RunnerTests/VideoPlayerTests.m
+++ b/packages/video_player/video_player_avfoundation/darwin/RunnerTests/VideoPlayerTests.m
@@ -1051,6 +1051,149 @@ - (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.
+// Regular MP4 files do not have media selection groups, so getAudioTracks returns an empty array.
+- (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;
+ NSArray *result = [player getAudioTracks:&error];
+
+ XCTAssertNil(error);
+ XCTAssertNotNil(result);
+
+ // Regular MP4 files do not have media selection groups for audio.
+ // getAudioTracks only returns selectable audio tracks from HLS streams.
+ XCTAssertEqual(result.count, 0);
+
+ [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;
+ NSArray *result = [player getAudioTracks:&error];
+
+ XCTAssertNil(error);
+ XCTAssertNotNil(result);
+
+ // For HLS streams with multiple audio options, we get media selection tracks.
+ // The bee.m3u8 stream may or may not have multiple audio tracks.
+ // We verify the method returns valid data without crashing.
+ for (FVPMediaSelectionAudioTrackData *track in result) {
+ XCTAssertNotNil(track.displayName);
+ XCTAssertGreaterThanOrEqual(track.index, 0);
+ }
+
+ [player disposeWithError:&error];
+}
+
+// Tests that getAudioTracks returns valid data for audio-only files.
+// Regular audio files do not have media selection groups, so getAudioTracks returns an empty array.
+- (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;
+ NSArray *result = [player getAudioTracks:&error];
+
+ XCTAssertNil(error);
+ XCTAssertNotNil(result);
+
+ // Regular audio files do not have media selection groups.
+ // getAudioTracks only returns selectable audio tracks from HLS streams.
+ XCTAssertEqual(result.count, 0);
+
+ [player disposeWithError:&error];
+}
+
+// Tests that getAudioTracks works correctly through the plugin API with a real video.
+// Regular MP4 files do not have media selection groups, so getAudioTracks returns an empty array.
+- (void)testGetAudioTracksViaPluginWithRealVideo {
+ NSObject *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
+ NSArray *result = [player getAudioTracks:&error];
+
+ XCTAssertNil(error);
+ XCTAssertNotNil(result);
+
+ // Regular MP4 files do not have media selection groups.
+ // getAudioTracks only returns selectable audio tracks from HLS streams.
+ XCTAssertEqual(result.count, 0);
+
+ [player disposeWithError:&error];
+}
+
- (void)testLoadTracksWithMediaTypeIsCalledOnNewerOS {
if (@available(iOS 15.0, macOS 12.0, *)) {
AVAsset *mockAsset = OCMClassMock([AVAsset class]);
diff --git a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPVideoPlayer.m b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPVideoPlayer.m
index 3c45daec018..5b79d4291c2 100644
--- a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPVideoPlayer.m
+++ b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPVideoPlayer.m
@@ -443,6 +443,70 @@ - (void)setPlaybackSpeed:(double)speed error:(FlutterError *_Nullable *_Nonnull)
[self updatePlayingState];
}
+- (nullable NSArray *)getAudioTracks:
+ (FlutterError *_Nullable *_Nonnull)error {
+ AVPlayerItem *currentItem = _player.currentItem;
+ NSAssert(currentItem, @"currentItem should not be nil");
+ AVAsset *asset = currentItem.asset;
+
+ // Get tracks from media selection (for HLS streams)
+ AVMediaSelectionGroup *audioGroup =
+ [asset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicAudible];
+
+ NSMutableArray *mediaSelectionTracks =
+ [[NSMutableArray alloc] init];
+
+ if (audioGroup.options.count > 0) {
+ AVMediaSelection *mediaSelection = currentItem.currentMediaSelection;
+ AVMediaSelectionOption *currentSelection =
+ [mediaSelection selectedMediaOptionInMediaSelectionGroup:audioGroup];
+
+ for (NSInteger i = 0; i < audioGroup.options.count; i++) {
+ AVMediaSelectionOption *option = audioGroup.options[i];
+ NSString *displayName = option.displayName;
+
+ NSString *languageCode = nil;
+ if (option.locale) {
+ languageCode = option.locale.languageCode;
+ }
+
+ NSArray *titleItems =
+ [AVMetadataItem metadataItemsFromArray:option.commonMetadata
+ withKey:AVMetadataCommonKeyTitle
+ keySpace:AVMetadataKeySpaceCommon];
+ NSString *commonMetadataTitle = titleItems.firstObject.stringValue;
+
+ BOOL isSelected = [currentSelection isEqual:option];
+
+ FVPMediaSelectionAudioTrackData *trackData =
+ [FVPMediaSelectionAudioTrackData makeWithIndex:i
+ displayName:displayName
+ languageCode:languageCode
+ isSelected:isSelected
+ commonMetadataTitle:commonMetadataTitle];
+
+ [mediaSelectionTracks addObject:trackData];
+ }
+ }
+
+ return mediaSelectionTracks;
+}
+
+- (void)selectAudioTrackAtIndex:(NSInteger)trackIndex
+ error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error {
+ AVPlayerItem *currentItem = _player.currentItem;
+ NSAssert(currentItem, @"currentItem should not be nil");
+ AVAsset *asset = currentItem.asset;
+
+ AVMediaSelectionGroup *audioGroup =
+ [asset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicAudible];
+
+ if (audioGroup && trackIndex >= 0 && trackIndex < (NSInteger)audioGroup.options.count) {
+ AVMediaSelectionOption *option = audioGroup.options[trackIndex];
+ [currentItem selectMediaOption:option inMediaSelectionGroup:audioGroup];
+ }
+}
+
#pragma mark - Private
- (int64_t)duration {
diff --git a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/messages.g.h b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/messages.g.h
index d06c3fd0179..01f584187b1 100644
--- a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/messages.g.h
+++ b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/messages.g.h
@@ -16,6 +16,7 @@ NS_ASSUME_NONNULL_BEGIN
@class FVPPlatformVideoViewCreationParams;
@class FVPCreationOptions;
@class FVPTexturePlayerIds;
+@class FVPMediaSelectionAudioTrackData;
/// Information passed to the platform view creation.
@interface FVPPlatformVideoViewCreationParams : NSObject
@@ -42,6 +43,22 @@ NS_ASSUME_NONNULL_BEGIN
@property(nonatomic, assign) NSInteger textureId;
@end
+/// Raw audio track data from AVMediaSelectionOption (for HLS streams).
+@interface FVPMediaSelectionAudioTrackData : NSObject
+/// `init` unavailable to enforce nonnull fields, see the `make` class method.
+- (instancetype)init NS_UNAVAILABLE;
++ (instancetype)makeWithIndex:(NSInteger)index
+ displayName:(nullable NSString *)displayName
+ languageCode:(nullable NSString *)languageCode
+ isSelected:(BOOL)isSelected
+ commonMetadataTitle:(nullable NSString *)commonMetadataTitle;
+@property(nonatomic, assign) NSInteger index;
+@property(nonatomic, copy, nullable) NSString *displayName;
+@property(nonatomic, copy, nullable) NSString *languageCode;
+@property(nonatomic, assign) BOOL isSelected;
+@property(nonatomic, copy, nullable) NSString *commonMetadataTitle;
+@end
+
/// The codec used by all APIs.
NSObject *FVPGetMessagesCodec(void);
@@ -78,6 +95,11 @@ extern void SetUpFVPAVFoundationVideoPlayerApiWithSuffix(
- (void)seekTo:(NSInteger)position completion:(void (^)(FlutterError *_Nullable))completion;
- (void)pauseWithError:(FlutterError *_Nullable *_Nonnull)error;
- (void)disposeWithError:(FlutterError *_Nullable *_Nonnull)error;
+/// @return `nil` only when `error != nil`.
+- (nullable NSArray *)getAudioTracks:
+ (FlutterError *_Nullable *_Nonnull)error;
+- (void)selectAudioTrackAtIndex:(NSInteger)trackIndex
+ error:(FlutterError *_Nullable *_Nonnull)error;
@end
extern void SetUpFVPVideoPlayerInstanceApi(id binaryMessenger,
diff --git a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/messages.g.m b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/messages.g.m
index 155ac2bacad..c421576564c 100644
--- a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/messages.g.m
+++ b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/messages.g.m
@@ -48,6 +48,12 @@ + (nullable FVPTexturePlayerIds *)nullableFromList:(NSArray *)list;
- (NSArray *)toList;
@end
+@interface FVPMediaSelectionAudioTrackData ()
++ (FVPMediaSelectionAudioTrackData *)fromList:(NSArray *)list;
++ (nullable FVPMediaSelectionAudioTrackData *)nullableFromList:(NSArray *)list;
+- (NSArray *)toList;
+@end
+
@implementation FVPPlatformVideoViewCreationParams
+ (instancetype)makeWithPlayerId:(NSInteger)playerId {
FVPPlatformVideoViewCreationParams *pigeonResult =
@@ -120,6 +126,43 @@ + (nullable FVPTexturePlayerIds *)nullableFromList:(NSArray *)list {
}
@end
+@implementation FVPMediaSelectionAudioTrackData
++ (instancetype)makeWithIndex:(NSInteger)index
+ displayName:(nullable NSString *)displayName
+ languageCode:(nullable NSString *)languageCode
+ isSelected:(BOOL)isSelected
+ commonMetadataTitle:(nullable NSString *)commonMetadataTitle {
+ FVPMediaSelectionAudioTrackData *pigeonResult = [[FVPMediaSelectionAudioTrackData alloc] init];
+ pigeonResult.index = index;
+ pigeonResult.displayName = displayName;
+ pigeonResult.languageCode = languageCode;
+ pigeonResult.isSelected = isSelected;
+ pigeonResult.commonMetadataTitle = commonMetadataTitle;
+ return pigeonResult;
+}
++ (FVPMediaSelectionAudioTrackData *)fromList:(NSArray *)list {
+ FVPMediaSelectionAudioTrackData *pigeonResult = [[FVPMediaSelectionAudioTrackData alloc] init];
+ pigeonResult.index = [GetNullableObjectAtIndex(list, 0) integerValue];
+ pigeonResult.displayName = GetNullableObjectAtIndex(list, 1);
+ pigeonResult.languageCode = GetNullableObjectAtIndex(list, 2);
+ pigeonResult.isSelected = [GetNullableObjectAtIndex(list, 3) boolValue];
+ pigeonResult.commonMetadataTitle = GetNullableObjectAtIndex(list, 4);
+ return pigeonResult;
+}
++ (nullable FVPMediaSelectionAudioTrackData *)nullableFromList:(NSArray *)list {
+ return (list) ? [FVPMediaSelectionAudioTrackData fromList:list] : nil;
+}
+- (NSArray *)toList {
+ return @[
+ @(self.index),
+ self.displayName ?: [NSNull null],
+ self.languageCode ?: [NSNull null],
+ @(self.isSelected),
+ self.commonMetadataTitle ?: [NSNull null],
+ ];
+}
+@end
+
@interface FVPMessagesPigeonCodecReader : FlutterStandardReader
@end
@implementation FVPMessagesPigeonCodecReader
@@ -131,6 +174,8 @@ - (nullable id)readValueOfType:(UInt8)type {
return [FVPCreationOptions fromList:[self readValue]];
case 131:
return [FVPTexturePlayerIds fromList:[self readValue]];
+ case 132:
+ return [FVPMediaSelectionAudioTrackData fromList:[self readValue]];
default:
return [super readValueOfType:type];
}
@@ -150,6 +195,9 @@ - (void)writeValue:(id)value {
} else if ([value isKindOfClass:[FVPTexturePlayerIds class]]) {
[self writeByte:131];
[self writeValue:[value toList]];
+ } else if ([value isKindOfClass:[FVPMediaSelectionAudioTrackData class]]) {
+ [self writeByte:132];
+ [self writeValue:[value toList]];
} else {
[super writeValue:value];
}
@@ -502,4 +550,49 @@ void SetUpFVPVideoPlayerInstanceApiWithSuffix(id binaryM
[channel setMessageHandler:nil];
}
}
+ {
+ FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc]
+ initWithName:[NSString stringWithFormat:@"%@%@",
+ @"dev.flutter.pigeon.video_player_avfoundation."
+ @"VideoPlayerInstanceApi.getAudioTracks",
+ messageChannelSuffix]
+ binaryMessenger:binaryMessenger
+ codec:FVPGetMessagesCodec()];
+ if (api) {
+ NSCAssert([api respondsToSelector:@selector(getAudioTracks:)],
+ @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to @selector(getAudioTracks:)",
+ api);
+ [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) {
+ FlutterError *error;
+ NSArray *output = [api getAudioTracks:&error];
+ callback(wrapResult(output, error));
+ }];
+ } else {
+ [channel setMessageHandler:nil];
+ }
+ }
+ {
+ FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc]
+ initWithName:[NSString stringWithFormat:@"%@%@",
+ @"dev.flutter.pigeon.video_player_avfoundation."
+ @"VideoPlayerInstanceApi.selectAudioTrack",
+ messageChannelSuffix]
+ binaryMessenger:binaryMessenger
+ codec:FVPGetMessagesCodec()];
+ if (api) {
+ NSCAssert([api respondsToSelector:@selector(selectAudioTrackAtIndex:error:)],
+ @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to "
+ @"@selector(selectAudioTrackAtIndex:error:)",
+ api);
+ [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) {
+ NSArray *args = message;
+ NSInteger arg_trackIndex = [GetNullableObjectAtIndex(args, 0) integerValue];
+ FlutterError *error;
+ [api selectAudioTrackAtIndex:arg_trackIndex error:&error];
+ callback(wrapResult(nil, error));
+ }];
+ } else {
+ [channel setMessageHandler:nil];
+ }
+ }
}
diff --git a/packages/video_player/video_player_avfoundation/example/ios/Podfile b/packages/video_player/video_player_avfoundation/example/ios/Podfile
index c9339a034eb..6eafd7e2e95 100644
--- a/packages/video_player/video_player_avfoundation/example/ios/Podfile
+++ b/packages/video_player/video_player_avfoundation/example/ios/Podfile
@@ -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'
diff --git a/packages/video_player/video_player_avfoundation/example/macos/Runner.xcodeproj/project.pbxproj b/packages/video_player/video_player_avfoundation/example/macos/Runner.xcodeproj/project.pbxproj
index 44df4b4d978..41178cae189 100644
--- a/packages/video_player/video_player_avfoundation/example/macos/Runner.xcodeproj/project.pbxproj
+++ b/packages/video_player/video_player_avfoundation/example/macos/Runner.xcodeproj/project.pbxproj
@@ -246,6 +246,7 @@
33CC10EB2044A3C60003C045 /* Resources */,
33CC110E2044A8840003C045 /* Bundle Framework */,
3399D490228B24CF009A79C7 /* ShellScript */,
+ 43465698DA6E8053DBCCE1D3 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@@ -373,6 +374,23 @@
shellPath = /bin/sh;
shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
};
+ 43465698DA6E8053DBCCE1D3 /* [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;
+ };
5121AE1943D8EE14C90ED8B7 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
diff --git a/packages/video_player/video_player_avfoundation/example/pubspec.yaml b/packages/video_player/video_player_avfoundation/example/pubspec.yaml
index cc176e75c3f..902bf087303 100644
--- a/packages/video_player/video_player_avfoundation/example/pubspec.yaml
+++ b/packages/video_player/video_player_avfoundation/example/pubspec.yaml
@@ -16,7 +16,7 @@ dependencies:
# The example app is bundled with the plugin so we use a path dependency on
# the parent directory to use the current plugin's version.
path: ../
- video_player_platform_interface: ^6.3.0
+ video_player_platform_interface: ^6.6.0
dev_dependencies:
flutter_test:
diff --git a/packages/video_player/video_player_avfoundation/lib/src/avfoundation_video_player.dart b/packages/video_player/video_player_avfoundation/lib/src/avfoundation_video_player.dart
index 834b36ed6b0..6684d9c4c65 100644
--- a/packages/video_player/video_player_avfoundation/lib/src/avfoundation_video_player.dart
+++ b/packages/video_player/video_player_avfoundation/lib/src/avfoundation_video_player.dart
@@ -178,6 +178,40 @@ class AVFoundationVideoPlayer extends VideoPlayerPlatform {
return _api.setMixWithOthers(mixWithOthers);
}
+ @override
+ Future> getAudioTracks(int playerId) async {
+ final List nativeData = await _playerWith(
+ id: playerId,
+ ).getAudioTracks();
+ final tracks = [];
+
+ for (final track in nativeData) {
+ final String? label = track.commonMetadataTitle ?? track.displayName;
+ tracks.add(
+ VideoAudioTrack(
+ id: track.index.toString(),
+ label: label,
+ language: track.languageCode,
+ isSelected: track.isSelected,
+ ),
+ );
+ }
+
+ return tracks;
+ }
+
+ @override
+ Future selectAudioTrack(int playerId, String trackId) {
+ final int trackIndex = int.parse(trackId);
+ return _playerWith(id: playerId).selectAudioTrack(trackIndex);
+ }
+
+ @override
+ bool isAudioTrackSupportAvailable() {
+ // iOS/macOS with AVFoundation supports audio track selection
+ return true;
+ }
+
@override
Widget buildView(int playerId) {
return buildViewWithOptions(VideoViewOptions(playerId: playerId));
@@ -249,6 +283,12 @@ class _PlayerInstance {
return Duration(milliseconds: await _api.getPosition());
}
+ Future> getAudioTracks() =>
+ _api.getAudioTracks();
+
+ Future selectAudioTrack(int trackIndex) =>
+ _api.selectAudioTrack(trackIndex);
+
Stream get videoEvents {
_eventSubscription ??= _eventChannel.receiveBroadcastStream().listen(
_onStreamEvent,
diff --git a/packages/video_player/video_player_avfoundation/lib/src/messages.g.dart b/packages/video_player/video_player_avfoundation/lib/src/messages.g.dart
index 9072c153f95..76b66c3ca4b 100644
--- a/packages/video_player/video_player_avfoundation/lib/src/messages.g.dart
+++ b/packages/video_player/video_player_avfoundation/lib/src/messages.g.dart
@@ -154,6 +154,69 @@ class TexturePlayerIds {
int get hashCode => Object.hashAll(_toList());
}
+/// Raw audio track data from AVMediaSelectionOption (for HLS streams).
+class MediaSelectionAudioTrackData {
+ MediaSelectionAudioTrackData({
+ required this.index,
+ this.displayName,
+ this.languageCode,
+ required this.isSelected,
+ this.commonMetadataTitle,
+ });
+
+ int index;
+
+ String? displayName;
+
+ String? languageCode;
+
+ bool isSelected;
+
+ String? commonMetadataTitle;
+
+ List