Skip to content

Commit 2703b67

Browse files
authored
[video_player_avfoundation] send video load failure even when eventsink was initialized late (#7194)
Event `initialized` is sent from `onListenWithArguments` by calling `setupEventSinkIfReadyToPlay` if event sink was `nil` during `observeValueForKeyPath`. This change also sends failure in a similar way. Adds more details to the error message returned by `VideoPlayerController.initialize()`. - flutter/flutter#151475 - flutter/flutter#147707 - flutter/flutter#56665 - now error message may look like: `Failed to load video: Operation Stopped: The server is not correctly configured.: The operation couldn�t be completed. (CoreMediaErrorDomain error -12939 - byte range and no content length - error code is 200)` Tests `testSeekToWhilePausedStartsDisplayLinkTemporarily` and `testSeekToWhilePlayingDoesNotStopDisplayLink` are failing on the main branch on ios. Seems position is correctly set to 1234 in `seekTo` completion handler but right after `[mockDisplayLink setRunning:YES]` is set again to 0 and after some time back to 1234 so those two tests sometimes fail (seems more often when running all tests).
1 parent 1cd499e commit 2703b67

File tree

4 files changed

+73
-8
lines changed

4 files changed

+73
-8
lines changed

packages/video_player/video_player_avfoundation/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 2.6.3
2+
3+
* Fixes VideoPlayerController.initialize() future never resolving with invalid video file.
4+
* Adds more details to the error message returned by VideoPlayerController.initialize().
5+
16
## 2.6.2
27

38
* Updates Pigeon for non-nullable collection type support.

packages/video_player/video_player_avfoundation/darwin/RunnerTests/VideoPlayerTests.m

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -790,6 +790,39 @@ - (void)testPublishesInRegistration {
790790
XCTAssertTrue([publishedValue isKindOfClass:[FVPVideoPlayerPlugin class]]);
791791
}
792792

793+
- (void)testFailedToLoadVideoEventShouldBeAlwaysSent {
794+
NSObject<FlutterPluginRegistrar> *registrar =
795+
[GetPluginRegistry() registrarForPlugin:@"testFailedToLoadVideoEventShouldBeAlwaysSent"];
796+
FVPVideoPlayerPlugin *videoPlayerPlugin =
797+
[[FVPVideoPlayerPlugin alloc] initWithRegistrar:registrar];
798+
FlutterError *error;
799+
800+
[videoPlayerPlugin initialize:&error];
801+
802+
FVPCreationOptions *create = [FVPCreationOptions makeWithAsset:nil
803+
uri:@""
804+
packageName:nil
805+
formatHint:nil
806+
httpHeaders:@{}];
807+
NSNumber *textureId = [videoPlayerPlugin createWithOptions:create error:&error];
808+
FVPVideoPlayer *player = videoPlayerPlugin.playersByTextureId[textureId];
809+
XCTAssertNotNil(player);
810+
811+
[self keyValueObservingExpectationForObject:(id)player.player.currentItem
812+
keyPath:@"status"
813+
expectedValue:@(AVPlayerItemStatusFailed)];
814+
[self waitForExpectationsWithTimeout:10.0 handler:nil];
815+
816+
XCTestExpectation *failedExpectation = [self expectationWithDescription:@"failed"];
817+
[player onListenWithArguments:nil
818+
eventSink:^(FlutterError *event) {
819+
if ([event isKindOfClass:FlutterError.class]) {
820+
[failedExpectation fulfill];
821+
}
822+
}];
823+
[self waitForExpectationsWithTimeout:10.0 handler:nil];
824+
}
825+
793826
#if TARGET_OS_IOS
794827
- (void)validateTransformFixForOrientation:(UIImageOrientation)orientation {
795828
AVAssetTrack *track = [[FakeAVAssetTrack alloc] initWithOrientation:orientation];

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

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -345,13 +345,7 @@ - (void)observeValueForKeyPath:(NSString *)path
345345
AVPlayerItem *item = (AVPlayerItem *)object;
346346
switch (item.status) {
347347
case AVPlayerItemStatusFailed:
348-
if (_eventSink != nil) {
349-
_eventSink([FlutterError
350-
errorWithCode:@"VideoError"
351-
message:[@"Failed to load video: "
352-
stringByAppendingString:[item.error localizedDescription]]
353-
details:nil]);
354-
}
348+
[self sendFailedToLoadVideoEvent];
355349
break;
356350
case AVPlayerItemStatusUnknown:
357351
break;
@@ -406,6 +400,32 @@ - (void)updatePlayingState {
406400
_displayLink.running = _isPlaying || self.waitingForFrame;
407401
}
408402

403+
- (void)sendFailedToLoadVideoEvent {
404+
if (_eventSink == nil) {
405+
return;
406+
}
407+
// Prefer more detailed error information from tracks loading.
408+
NSError *error;
409+
if ([self.player.currentItem.asset statusOfValueForKey:@"tracks"
410+
error:&error] != AVKeyValueStatusFailed) {
411+
error = self.player.currentItem.error;
412+
}
413+
__block NSMutableOrderedSet<NSString *> *details =
414+
[NSMutableOrderedSet orderedSetWithObject:@"Failed to load video"];
415+
void (^add)(NSString *) = ^(NSString *detail) {
416+
if (detail != nil) {
417+
[details addObject:detail];
418+
}
419+
};
420+
NSError *underlyingError = error.userInfo[NSUnderlyingErrorKey];
421+
add(error.localizedDescription);
422+
add(error.localizedFailureReason);
423+
add(underlyingError.localizedDescription);
424+
add(underlyingError.localizedFailureReason);
425+
NSString *message = [details.array componentsJoinedByString:@": "];
426+
_eventSink([FlutterError errorWithCode:@"VideoError" message:message details:nil]);
427+
}
428+
409429
- (void)setupEventSinkIfReadyToPlay {
410430
if (_eventSink && !_isInitialized) {
411431
AVPlayerItem *currentItem = self.player.currentItem;
@@ -587,6 +607,13 @@ - (FlutterError *_Nullable)onListenWithArguments:(id _Nullable)arguments
587607
// This line ensures the 'initialized' event is sent when the event
588608
// 'AVPlayerItemStatusReadyToPlay' fires before _eventSink is set (this function
589609
// onListenWithArguments is called)
610+
// and also send error in similar case with 'AVPlayerItemStatusFailed'
611+
// https://github.com/flutter/flutter/issues/151475
612+
// https://github.com/flutter/flutter/issues/147707
613+
if (self.player.currentItem.status == AVPlayerItemStatusFailed) {
614+
[self sendFailedToLoadVideoEvent];
615+
return nil;
616+
}
590617
[self setupEventSinkIfReadyToPlay];
591618
return nil;
592619
}

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.6.2
5+
version: 2.6.3
66

77
environment:
88
sdk: ^3.3.0

0 commit comments

Comments
 (0)