@@ -1024,4 +1024,362 @@ - (nonnull AVPlayerItem *)playerItemWithURL:(NSURL *)url {
10241024 return [AVPlayerItem playerItemWithAsset: [AVURLAsset URLAssetWithURL: url options: nil ]];
10251025}
10261026
1027+ #pragma mark - Audio Track Tests
1028+
1029+ - (void )testGetAudioTracksWithRegularAssetTracks {
1030+ // Create mocks
1031+ id mockPlayer = OCMClassMock ([AVPlayer class ]);
1032+ id mockPlayerItem = OCMClassMock ([AVPlayerItem class ]);
1033+ id mockAsset = OCMClassMock ([AVAsset class ]);
1034+ id mockAVFactory = OCMProtocolMock (@protocol (FVPAVFactory));
1035+ id mockViewProvider = OCMProtocolMock (@protocol (FVPViewProvider));
1036+
1037+ // Set up basic mock relationships
1038+ OCMStub ([mockPlayer currentItem ]).andReturn (mockPlayerItem);
1039+ OCMStub ([mockPlayerItem asset ]).andReturn (mockAsset);
1040+ OCMStub ([mockAVFactory playerWithPlayerItem: OCMOCK_ANY]).andReturn (mockPlayer);
1041+
1042+ // Create player with mocks
1043+ FVPVideoPlayer *player = [[FVPVideoPlayer alloc ] initWithPlayerItem: mockPlayerItem
1044+ avFactory: mockAVFactory
1045+ viewProvider: mockViewProvider];
1046+
1047+ // Create mock asset tracks
1048+ id mockTrack1 = OCMClassMock ([AVAssetTrack class ]);
1049+ id mockTrack2 = OCMClassMock ([AVAssetTrack class ]);
1050+
1051+ // Configure track 1
1052+ OCMStub ([mockTrack1 trackID ]).andReturn (1 );
1053+ OCMStub ([mockTrack1 languageCode ]).andReturn (@" en" );
1054+ OCMStub ([mockTrack1 estimatedDataRate ]).andReturn (128000 .0f );
1055+
1056+ // Configure track 2
1057+ OCMStub ([mockTrack2 trackID ]).andReturn (2 );
1058+ OCMStub ([mockTrack2 languageCode ]).andReturn (@" es" );
1059+ OCMStub ([mockTrack2 estimatedDataRate ]).andReturn (96000 .0f );
1060+
1061+ // Mock empty format descriptions to avoid Core Media crashes in test environment
1062+ OCMStub ([mockTrack1 formatDescriptions ]).andReturn (@[]);
1063+ OCMStub ([mockTrack2 formatDescriptions ]).andReturn (@[]);
1064+
1065+ // Mock the asset to return our tracks
1066+ NSArray *mockTracks = @[ mockTrack1, mockTrack2 ];
1067+ OCMStub ([mockAsset tracksWithMediaType: AVMediaTypeAudio]).andReturn (mockTracks);
1068+
1069+ // Mock no media selection group (regular asset)
1070+ OCMStub ([mockAsset mediaSelectionGroupForMediaCharacteristic: AVMediaCharacteristicAudible])
1071+ .andReturn (nil );
1072+
1073+ // Test the method
1074+ FlutterError *error = nil ;
1075+ FVPNativeAudioTrackData *result = [player getAudioTracks: &error];
1076+
1077+ // Verify results
1078+ XCTAssertNil (error);
1079+ XCTAssertNotNil (result);
1080+ XCTAssertNotNil (result.assetTracks );
1081+ XCTAssertNil (result.mediaSelectionTracks );
1082+ XCTAssertEqual (result.assetTracks .count , 2 );
1083+
1084+ // Verify first track
1085+ FVPAssetAudioTrackData *track1 = result.assetTracks [0 ];
1086+ XCTAssertEqual (track1.trackId , 1 );
1087+ XCTAssertEqualObjects (track1.language , @" en" );
1088+ XCTAssertTrue (track1.isSelected ); // First track should be selected
1089+ XCTAssertEqualObjects (track1.bitrate , @128000 );
1090+
1091+ // Verify second track
1092+ FVPAssetAudioTrackData *track2 = result.assetTracks [1 ];
1093+ XCTAssertEqual (track2.trackId , 2 );
1094+ XCTAssertEqualObjects (track2.language , @" es" );
1095+ XCTAssertFalse (track2.isSelected ); // Second track should not be selected
1096+ XCTAssertEqualObjects (track2.bitrate , @96000 );
1097+
1098+ [player disposeWithError: &error];
1099+ }
1100+
1101+ - (void )testGetAudioTracksWithMediaSelectionOptions {
1102+ // Create mocks
1103+ id mockPlayer = OCMClassMock ([AVPlayer class ]);
1104+ id mockPlayerItem = OCMClassMock ([AVPlayerItem class ]);
1105+ id mockAsset = OCMClassMock ([AVAsset class ]);
1106+ id mockAVFactory = OCMProtocolMock (@protocol (FVPAVFactory));
1107+ id mockViewProvider = OCMProtocolMock (@protocol (FVPViewProvider));
1108+
1109+ // Set up basic mock relationships
1110+ OCMStub ([mockPlayer currentItem ]).andReturn (mockPlayerItem);
1111+ OCMStub ([mockPlayerItem asset ]).andReturn (mockAsset);
1112+ OCMStub ([mockAVFactory playerWithPlayerItem: OCMOCK_ANY]).andReturn (mockPlayer);
1113+
1114+ // Create player with mocks
1115+ FVPVideoPlayer *player = [[FVPVideoPlayer alloc ] initWithPlayerItem: mockPlayerItem
1116+ avFactory: mockAVFactory
1117+ viewProvider: mockViewProvider];
1118+
1119+ // Create mock media selection group and options
1120+ id mockMediaSelectionGroup = OCMClassMock ([AVMediaSelectionGroup class ]);
1121+ id mockOption1 = OCMClassMock ([AVMediaSelectionOption class ]);
1122+ id mockOption2 = OCMClassMock ([AVMediaSelectionOption class ]);
1123+
1124+ // Configure option 1
1125+ OCMStub ([mockOption1 displayName ]).andReturn (@" English" );
1126+ id mockLocale1 = OCMClassMock ([NSLocale class ]);
1127+ OCMStub ([mockLocale1 languageCode ]).andReturn (@" en" );
1128+ OCMStub ([mockOption1 locale ]).andReturn (mockLocale1);
1129+
1130+ // Configure option 2
1131+ OCMStub ([mockOption2 displayName ]).andReturn (@" Español" );
1132+ id mockLocale2 = OCMClassMock ([NSLocale class ]);
1133+ OCMStub ([mockLocale2 languageCode ]).andReturn (@" es" );
1134+ OCMStub ([mockOption2 locale ]).andReturn (mockLocale2);
1135+
1136+ // Mock metadata for option 1
1137+ id mockMetadataItem = OCMClassMock ([AVMetadataItem class ]);
1138+ OCMStub ([mockMetadataItem commonKey ]).andReturn (AVMetadataCommonKeyTitle);
1139+ OCMStub ([mockMetadataItem stringValue ]).andReturn (@" English Audio Track" );
1140+ OCMStub ([mockOption1 commonMetadata ]).andReturn (@[ mockMetadataItem ]);
1141+
1142+ // Configure media selection group
1143+ NSArray *options = @[ mockOption1, mockOption2 ];
1144+ OCMStub ([(AVMediaSelectionGroup *)mockMediaSelectionGroup options ]).andReturn (options);
1145+ OCMStub ([[(AVMediaSelectionGroup *)mockMediaSelectionGroup options ] count ]).andReturn (2 );
1146+
1147+ // Mock the asset to return media selection group
1148+ OCMStub ([mockAsset mediaSelectionGroupForMediaCharacteristic: AVMediaCharacteristicAudible])
1149+ .andReturn (mockMediaSelectionGroup);
1150+
1151+ // Mock current selection for both iOS 11+ and older versions
1152+ id mockCurrentMediaSelection = OCMClassMock ([AVMediaSelection class ]);
1153+ OCMStub ([mockPlayerItem currentMediaSelection ]).andReturn (mockCurrentMediaSelection);
1154+ OCMStub (
1155+ [mockCurrentMediaSelection selectedMediaOptionInMediaSelectionGroup: mockMediaSelectionGroup])
1156+ .andReturn (mockOption1);
1157+
1158+ // Also mock the deprecated method for iOS < 11
1159+ OCMStub ([mockPlayerItem selectedMediaOptionInMediaSelectionGroup: mockMediaSelectionGroup])
1160+ .andReturn (mockOption1);
1161+
1162+ // Test the method
1163+ FlutterError *error = nil ;
1164+ FVPNativeAudioTrackData *result = [player getAudioTracks: &error];
1165+
1166+ // Verify results
1167+ XCTAssertNil (error);
1168+ XCTAssertNotNil (result);
1169+ XCTAssertNil (result.assetTracks );
1170+ XCTAssertNotNil (result.mediaSelectionTracks );
1171+ XCTAssertEqual (result.mediaSelectionTracks .count , 2 );
1172+
1173+ // Verify first option
1174+ FVPMediaSelectionAudioTrackData *option1Data = result.mediaSelectionTracks [0 ];
1175+ XCTAssertEqual (option1Data.index , 0 );
1176+ XCTAssertEqualObjects (option1Data.displayName , @" English" );
1177+ XCTAssertEqualObjects (option1Data.languageCode , @" en" );
1178+ XCTAssertTrue (option1Data.isSelected );
1179+ XCTAssertEqualObjects (option1Data.commonMetadataTitle , @" English Audio Track" );
1180+
1181+ // Verify second option
1182+ FVPMediaSelectionAudioTrackData *option2Data = result.mediaSelectionTracks [1 ];
1183+ XCTAssertEqual (option2Data.index , 1 );
1184+ XCTAssertEqualObjects (option2Data.displayName , @" Español" );
1185+ XCTAssertEqualObjects (option2Data.languageCode , @" es" );
1186+ XCTAssertFalse (option2Data.isSelected );
1187+
1188+ [player disposeWithError: &error];
1189+ }
1190+
1191+ - (void )testGetAudioTracksWithNoCurrentItem {
1192+ // Create mocks
1193+ id mockPlayer = OCMClassMock ([AVPlayer class ]);
1194+ id mockPlayerItem = OCMClassMock ([AVPlayerItem class ]);
1195+ id mockAVFactory = OCMProtocolMock (@protocol (FVPAVFactory));
1196+ id mockViewProvider = OCMProtocolMock (@protocol (FVPViewProvider));
1197+
1198+ // Set up basic mock relationships
1199+ OCMStub ([mockAVFactory playerWithPlayerItem: OCMOCK_ANY]).andReturn (mockPlayer);
1200+
1201+ // Create player with mocks
1202+ FVPVideoPlayer *player = [[FVPVideoPlayer alloc ] initWithPlayerItem: mockPlayerItem
1203+ avFactory: mockAVFactory
1204+ viewProvider: mockViewProvider];
1205+
1206+ // Mock player with no current item
1207+ OCMStub ([mockPlayer currentItem ]).andReturn (nil );
1208+
1209+ // Test the method
1210+ FlutterError *error = nil ;
1211+ FVPNativeAudioTrackData *result = [player getAudioTracks: &error];
1212+
1213+ // Verify results
1214+ XCTAssertNil (error);
1215+ XCTAssertNotNil (result);
1216+ XCTAssertNil (result.assetTracks );
1217+ XCTAssertNil (result.mediaSelectionTracks );
1218+
1219+ [player disposeWithError: &error];
1220+ }
1221+
1222+ - (void )testGetAudioTracksWithNoAsset {
1223+ // Create mocks
1224+ id mockPlayer = OCMClassMock ([AVPlayer class ]);
1225+ id mockPlayerItem = OCMClassMock ([AVPlayerItem class ]);
1226+ id mockAVFactory = OCMProtocolMock (@protocol (FVPAVFactory));
1227+ id mockViewProvider = OCMProtocolMock (@protocol (FVPViewProvider));
1228+
1229+ // Set up basic mock relationships
1230+ OCMStub ([mockPlayer currentItem ]).andReturn (mockPlayerItem);
1231+ OCMStub ([mockAVFactory playerWithPlayerItem: OCMOCK_ANY]).andReturn (mockPlayer);
1232+
1233+ // Create player with mocks
1234+ FVPVideoPlayer *player = [[FVPVideoPlayer alloc ] initWithPlayerItem: mockPlayerItem
1235+ avFactory: mockAVFactory
1236+ viewProvider: mockViewProvider];
1237+
1238+ // Mock player item with no asset
1239+ OCMStub ([mockPlayerItem asset ]).andReturn (nil );
1240+
1241+ // Test the method
1242+ FlutterError *error = nil ;
1243+ FVPNativeAudioTrackData *result = [player getAudioTracks: &error];
1244+
1245+ // Verify results
1246+ XCTAssertNil (error);
1247+ XCTAssertNotNil (result);
1248+ XCTAssertNil (result.assetTracks );
1249+ XCTAssertNil (result.mediaSelectionTracks );
1250+
1251+ [player disposeWithError: &error];
1252+ }
1253+
1254+ - (void )testGetAudioTracksCodecDetection {
1255+ // Create mocks
1256+ id mockPlayer = OCMClassMock ([AVPlayer class ]);
1257+ id mockPlayerItem = OCMClassMock ([AVPlayerItem class ]);
1258+ id mockAsset = OCMClassMock ([AVAsset class ]);
1259+ id mockAVFactory = OCMProtocolMock (@protocol (FVPAVFactory));
1260+ id mockViewProvider = OCMProtocolMock (@protocol (FVPViewProvider));
1261+
1262+ // Set up basic mock relationships
1263+ OCMStub ([mockPlayer currentItem ]).andReturn (mockPlayerItem);
1264+ OCMStub ([mockPlayerItem asset ]).andReturn (mockAsset);
1265+ OCMStub ([mockAVFactory playerWithPlayerItem: OCMOCK_ANY]).andReturn (mockPlayer);
1266+
1267+ // Create player with mocks
1268+ FVPVideoPlayer *player = [[FVPVideoPlayer alloc ] initWithPlayerItem: mockPlayerItem
1269+ avFactory: mockAVFactory
1270+ viewProvider: mockViewProvider];
1271+
1272+ // Create mock asset track with format description
1273+ id mockTrack = OCMClassMock ([AVAssetTrack class ]);
1274+ OCMStub ([mockTrack trackID ]).andReturn (1 );
1275+ OCMStub ([mockTrack languageCode ]).andReturn (@" en" );
1276+
1277+ // Mock empty format descriptions to avoid Core Media crashes in test environment
1278+ OCMStub ([mockTrack formatDescriptions ]).andReturn (@[]);
1279+
1280+ // Mock the asset
1281+ OCMStub ([mockAsset tracksWithMediaType: AVMediaTypeAudio]).andReturn (@[ mockTrack ]);
1282+ OCMStub ([mockAsset mediaSelectionGroupForMediaCharacteristic: AVMediaCharacteristicAudible])
1283+ .andReturn (nil );
1284+
1285+ // Test the method
1286+ FlutterError *error = nil ;
1287+ FVPNativeAudioTrackData *result = [player getAudioTracks: &error];
1288+
1289+ // Verify results
1290+ XCTAssertNil (error);
1291+ XCTAssertNotNil (result);
1292+ XCTAssertNotNil (result.assetTracks );
1293+ XCTAssertEqual (result.assetTracks .count , 1 );
1294+
1295+ FVPAssetAudioTrackData *track = result.assetTracks [0 ];
1296+ XCTAssertEqual (track.trackId , 1 );
1297+ XCTAssertEqualObjects (track.language , @" en" );
1298+
1299+ [player disposeWithError: &error];
1300+ }
1301+
1302+ - (void )testGetAudioTracksWithEmptyMediaSelectionOptions {
1303+ // Create mocks
1304+ id mockPlayer = OCMClassMock ([AVPlayer class ]);
1305+ id mockPlayerItem = OCMClassMock ([AVPlayerItem class ]);
1306+ id mockAsset = OCMClassMock ([AVAsset class ]);
1307+ id mockAVFactory = OCMProtocolMock (@protocol (FVPAVFactory));
1308+ id mockViewProvider = OCMProtocolMock (@protocol (FVPViewProvider));
1309+
1310+ // Set up basic mock relationships
1311+ OCMStub ([mockPlayer currentItem ]).andReturn (mockPlayerItem);
1312+ OCMStub ([mockPlayerItem asset ]).andReturn (mockAsset);
1313+ OCMStub ([mockAVFactory playerWithPlayerItem: OCMOCK_ANY]).andReturn (mockPlayer);
1314+
1315+ // Create player with mocks
1316+ FVPVideoPlayer *player = [[FVPVideoPlayer alloc ] initWithPlayerItem: mockPlayerItem
1317+ avFactory: mockAVFactory
1318+ viewProvider: mockViewProvider];
1319+
1320+ // Create mock media selection group with no options
1321+ id mockMediaSelectionGroup = OCMClassMock ([AVMediaSelectionGroup class ]);
1322+ OCMStub ([(AVMediaSelectionGroup *)mockMediaSelectionGroup options ]).andReturn (@[]);
1323+ OCMStub ([[(AVMediaSelectionGroup *)mockMediaSelectionGroup options ] count ]).andReturn (0 );
1324+
1325+ // Mock the asset
1326+ OCMStub ([mockAsset mediaSelectionGroupForMediaCharacteristic: AVMediaCharacteristicAudible])
1327+ .andReturn (mockMediaSelectionGroup);
1328+ OCMStub ([mockAsset tracksWithMediaType: AVMediaTypeAudio]).andReturn (@[]);
1329+
1330+ // Test the method
1331+ FlutterError *error = nil ;
1332+ FVPNativeAudioTrackData *result = [player getAudioTracks: &error];
1333+
1334+ // Verify results - should fall back to asset tracks
1335+ XCTAssertNil (error);
1336+ XCTAssertNotNil (result);
1337+ XCTAssertNotNil (result.assetTracks );
1338+ XCTAssertNil (result.mediaSelectionTracks );
1339+ XCTAssertEqual (result.assetTracks .count , 0 );
1340+
1341+ [player disposeWithError: &error];
1342+ }
1343+
1344+ - (void )testGetAudioTracksWithNilMediaSelectionOption {
1345+ // Create mocks
1346+ id mockPlayer = OCMClassMock ([AVPlayer class ]);
1347+ id mockPlayerItem = OCMClassMock ([AVPlayerItem class ]);
1348+ id mockAsset = OCMClassMock ([AVAsset class ]);
1349+ id mockAVFactory = OCMProtocolMock (@protocol (FVPAVFactory));
1350+ id mockViewProvider = OCMProtocolMock (@protocol (FVPViewProvider));
1351+
1352+ // Set up basic mock relationships
1353+ OCMStub ([mockPlayer currentItem ]).andReturn (mockPlayerItem);
1354+ OCMStub ([mockPlayerItem asset ]).andReturn (mockAsset);
1355+ OCMStub ([mockAVFactory playerWithPlayerItem: OCMOCK_ANY]).andReturn (mockPlayer);
1356+
1357+ // Create player with mocks
1358+ FVPVideoPlayer *player = [[FVPVideoPlayer alloc ] initWithPlayerItem: mockPlayerItem
1359+ avFactory: mockAVFactory
1360+ viewProvider: mockViewProvider];
1361+
1362+ // Create mock media selection group with nil option
1363+ id mockMediaSelectionGroup = OCMClassMock ([AVMediaSelectionGroup class ]);
1364+ NSArray *options = @[ [NSNull null ] ]; // Simulate nil option
1365+ OCMStub ([(AVMediaSelectionGroup *)mockMediaSelectionGroup options ]).andReturn (options);
1366+ OCMStub ([[(AVMediaSelectionGroup *)mockMediaSelectionGroup options ] count ]).andReturn (1 );
1367+
1368+ // Mock the asset
1369+ OCMStub ([mockAsset mediaSelectionGroupForMediaCharacteristic: AVMediaCharacteristicAudible])
1370+ .andReturn (mockMediaSelectionGroup);
1371+
1372+ // Test the method
1373+ FlutterError *error = nil ;
1374+ FVPNativeAudioTrackData *result = [player getAudioTracks: &error];
1375+
1376+ // Verify results - should handle nil option gracefully
1377+ XCTAssertNil (error);
1378+ XCTAssertNotNil (result);
1379+ XCTAssertNotNil (result.mediaSelectionTracks );
1380+ XCTAssertEqual (result.mediaSelectionTracks .count , 0 ); // Should skip nil options
1381+
1382+ [player disposeWithError: &error];
1383+ }
1384+
10271385@end
0 commit comments