Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 2.8.2

* Restructure internals of Dart notification of video player events.

## 2.8.1

* Restructures internal logic to move more code to Dart.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#import <OCMock/OCMock.h>
#import <video_player_avfoundation/AVAssetTrackUtils.h>
#import <video_player_avfoundation/FVPEventBridge.h>
#import <video_player_avfoundation/FVPNativeVideoViewFactory.h>
#import <video_player_avfoundation/FVPTextureBasedVideoPlayer_Test.h>
#import <video_player_avfoundation/FVPVideoPlayerPlugin_Test.h>
Expand Down Expand Up @@ -166,6 +167,55 @@ - (instancetype)init {

#pragma mark -

@interface StubEventListener : NSObject <FVPVideoEventListener>

@property(nonatomic) XCTestExpectation *initializationExpectation;
@property(nonatomic) int64_t initializationDuration;
@property(nonatomic) CGSize initializationSize;

- (instancetype)initWithInitializationExpectation:(XCTestExpectation *)expectation;

@end

@implementation StubEventListener

- (instancetype)initWithInitializationExpectation:(XCTestExpectation *)expectation {
self = [super init];
_initializationExpectation = expectation;
return self;
}

- (void)videoPlayerDidComplete {
}

- (void)videoPlayerDidEndBuffering {
}

- (void)videoPlayerDidErrorWithMessage:(NSString *)errorMessage {
}

- (void)videoPlayerDidInitializeWithDuration:(int64_t)duration size:(CGSize)size {
[self.initializationExpectation fulfill];
self.initializationDuration = duration;
self.initializationSize = size;
}

- (void)videoPlayerDidSetPlaying:(BOOL)playing {
}

- (void)videoPlayerDidStartBuffering {
}

- (void)videoPlayerDidUpdateBufferRegions:(NSArray<NSArray<NSNumber *> *> *)regions {
}

- (void)videoPlayerWasDisposed {
}

@end

#pragma mark -

@implementation VideoPlayerTests

- (void)testBlankVideoBugWithEncryptedVideoStreamAndInvertedAspectRatioBugForSomeVideoStream {
Expand Down Expand Up @@ -220,9 +270,9 @@ - (void)testPlayerForPlatformViewDoesNotRegisterTexture {
viewProvider:[[StubViewProvider alloc] initWithView:nil]
registrar:registrar];

FlutterError *initalizationError;
[videoPlayerPlugin initialize:&initalizationError];
XCTAssertNil(initalizationError);
FlutterError *initializationError;
[videoPlayerPlugin initialize:&initializationError];
XCTAssertNil(initializationError);
FVPCreationOptions *create = [FVPCreationOptions
makeWithUri:@"https://flutter.github.io/assets-for-api-docs/assets/videos/hls/bee.m3u8"
httpHeaders:@{}
Expand All @@ -248,9 +298,9 @@ - (void)testSeekToWhilePausedStartsDisplayLinkTemporarily {
viewProvider:[[StubViewProvider alloc] initWithView:nil]
registrar:registrar];

FlutterError *initalizationError;
[videoPlayerPlugin initialize:&initalizationError];
XCTAssertNil(initalizationError);
FlutterError *initializationError;
[videoPlayerPlugin initialize:&initializationError];
XCTAssertNil(initializationError);
FVPCreationOptions *create = [FVPCreationOptions
makeWithUri:@"https://flutter.github.io/assets-for-api-docs/assets/videos/hls/bee.m3u8"
httpHeaders:@{}
Expand Down Expand Up @@ -305,9 +355,9 @@ - (void)testInitStartsDisplayLinkTemporarily {
viewProvider:[[StubViewProvider alloc] initWithView:nil]
registrar:registrar];

FlutterError *initalizationError;
[videoPlayerPlugin initialize:&initalizationError];
XCTAssertNil(initalizationError);
FlutterError *initializationError;
[videoPlayerPlugin initialize:&initializationError];
XCTAssertNil(initializationError);
FVPCreationOptions *create = [FVPCreationOptions
makeWithUri:@"https://flutter.github.io/assets-for-api-docs/assets/videos/hls/bee.m3u8"
httpHeaders:@{}
Expand Down Expand Up @@ -351,9 +401,9 @@ - (void)testSeekToWhilePlayingDoesNotStopDisplayLink {
viewProvider:[[StubViewProvider alloc] initWithView:nil]
registrar:registrar];

FlutterError *initalizationError;
[videoPlayerPlugin initialize:&initalizationError];
XCTAssertNil(initalizationError);
FlutterError *initializationError;
[videoPlayerPlugin initialize:&initializationError];
XCTAssertNil(initializationError);
FVPCreationOptions *create = [FVPCreationOptions
makeWithUri:@"https://flutter.github.io/assets-for-api-docs/assets/videos/hls/bee.m3u8"
httpHeaders:@{}
Expand Down Expand Up @@ -406,9 +456,9 @@ - (void)testPauseWhileWaitingForFrameDoesNotStopDisplayLink {
viewProvider:[[StubViewProvider alloc] initWithView:nil]
registrar:registrar];

FlutterError *initalizationError;
[videoPlayerPlugin initialize:&initalizationError];
XCTAssertNil(initalizationError);
FlutterError *initializationError;
[videoPlayerPlugin initialize:&initializationError];
XCTAssertNil(initializationError);
FVPCreationOptions *create = [FVPCreationOptions
makeWithUri:@"https://flutter.github.io/assets-for-api-docs/assets/videos/hls/bee.m3u8"
httpHeaders:@{}
Expand Down Expand Up @@ -477,16 +527,19 @@ - (void)testBufferingStateFromPlayer {
AVPlayer *avPlayer = player.player;
[avPlayer play];

[player onListenWithArguments:nil
eventSink:^(NSDictionary<NSString *, id> *event) {
if ([event[@"event"] isEqualToString:@"bufferingEnd"]) {
XCTAssertTrue(avPlayer.currentItem.isPlaybackLikelyToKeepUp);
}

if ([event[@"event"] isEqualToString:@"bufferingStart"]) {
XCTAssertFalse(avPlayer.currentItem.isPlaybackLikelyToKeepUp);
}
}];
// TODO(stuartmorgan): Update this test to instead use a mock listener, and add separate unit
// tests of FVPEventBridge.
[(NSObject<FlutterStreamHandler> *)player.eventListener
onListenWithArguments:nil
eventSink:^(NSDictionary<NSString *, id> *event) {
if ([event[@"event"] isEqualToString:@"bufferingEnd"]) {
XCTAssertTrue(avPlayer.currentItem.isPlaybackLikelyToKeepUp);
}

if ([event[@"event"] isEqualToString:@"bufferingStart"]) {
XCTAssertFalse(avPlayer.currentItem.isPlaybackLikelyToKeepUp);
}
}];
XCTestExpectation *bufferingStateExpectation =
[self expectationWithDescription:@"bufferingState"];
NSTimeInterval timeout = 10;
Expand All @@ -498,39 +551,39 @@ - (void)testBufferingStateFromPlayer {
}

- (void)testVideoControls {
NSDictionary<NSString *, id> *videoInitialization =
StubEventListener *eventListener =
[self sanityTestURI:@"https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4"];
XCTAssertEqualObjects(videoInitialization[@"height"], @720);
XCTAssertEqualObjects(videoInitialization[@"width"], @1280);
XCTAssertEqualWithAccuracy([videoInitialization[@"duration"] intValue], 4000, 200);
XCTAssertEqual(eventListener.initializationSize.height, 720);
XCTAssertEqual(eventListener.initializationSize.width, 1280);
XCTAssertEqualWithAccuracy(eventListener.initializationDuration, 4000, 200);
}

- (void)testAudioControls {
NSDictionary<NSString *, id> *audioInitialization = [self
StubEventListener *eventListener = [self
sanityTestURI:@"https://flutter.github.io/assets-for-api-docs/assets/audio/rooster.mp3"];
XCTAssertEqualObjects(audioInitialization[@"height"], @0);
XCTAssertEqualObjects(audioInitialization[@"width"], @0);
XCTAssertEqual(eventListener.initializationSize.height, 0);
XCTAssertEqual(eventListener.initializationSize.width, 0);
// Perfect precision not guaranteed.
XCTAssertEqualWithAccuracy([audioInitialization[@"duration"] intValue], 5400, 200);
XCTAssertEqualWithAccuracy(eventListener.initializationDuration, 5400, 200);
}

- (void)testHLSControls {
NSDictionary<NSString *, id> *videoInitialization = [self
StubEventListener *eventListener = [self
sanityTestURI:@"https://flutter.github.io/assets-for-api-docs/assets/videos/hls/bee.m3u8"];
XCTAssertEqualObjects(videoInitialization[@"height"], @720);
XCTAssertEqualObjects(videoInitialization[@"width"], @1280);
XCTAssertEqualWithAccuracy([videoInitialization[@"duration"] intValue], 4000, 200);
XCTAssertEqual(eventListener.initializationSize.height, 720);
XCTAssertEqual(eventListener.initializationSize.width, 1280);
XCTAssertEqualWithAccuracy(eventListener.initializationDuration, 4000, 200);
}

- (void)testAudioOnlyHLSControls {
XCTSkip(@"Flaky; see https://github.com/flutter/flutter/issues/164381");

NSDictionary<NSString *, id> *videoInitialization =
StubEventListener *eventListener =
[self sanityTestURI:@"https://flutter.github.io/assets-for-api-docs/assets/videos/hls/"
@"bee_audio_only.m3u8"];
XCTAssertEqualObjects(videoInitialization[@"height"], @0);
XCTAssertEqualObjects(videoInitialization[@"width"], @0);
XCTAssertEqualWithAccuracy([videoInitialization[@"duration"] intValue], 4000, 200);
XCTAssertEqual(eventListener.initializationSize.height, 0);
XCTAssertEqual(eventListener.initializationSize.width, 0);
XCTAssertEqualWithAccuracy(eventListener.initializationDuration, 4000, 200);
}

#if TARGET_OS_IOS
Expand All @@ -555,6 +608,8 @@ - (void)testSeekToleranceWhenNotSeekingToEnd {
httpHeaders:@{}
avFactory:stubAVFactory
viewProvider:[[StubViewProvider alloc] initWithView:nil]];
NSObject<FVPVideoEventListener> *listener = OCMProtocolMock(@protocol(FVPVideoEventListener));
player.eventListener = listener;

XCTestExpectation *seekExpectation =
[self expectationWithDescription:@"seekTo has zero tolerance when seeking not to end"];
Expand All @@ -577,6 +632,8 @@ - (void)testSeekToleranceWhenSeekingToEnd {
httpHeaders:@{}
avFactory:stubAVFactory
viewProvider:[[StubViewProvider alloc] initWithView:nil]];
NSObject<FVPVideoEventListener> *listener = OCMProtocolMock(@protocol(FVPVideoEventListener));
player.eventListener = listener;

XCTestExpectation *seekExpectation =
[self expectationWithDescription:@"seekTo has non-zero tolerance when seeking to end"];
Expand All @@ -592,7 +649,9 @@ - (void)testSeekToleranceWhenSeekingToEnd {

/// Sanity checks a video player playing the given URL with the actual AVPlayer. This is essentially
/// a mini integration test of the player component.
- (NSDictionary<NSString *, id> *)sanityTestURI:(NSString *)testURI {
///
/// Returns the stub event listener to allow tests to inspect the call state.
- (StubEventListener *)sanityTestURI:(NSString *)testURI {
NSURL *testURL = [NSURL URLWithString:testURI];
XCTAssertNotNil(testURL);
FVPVideoPlayer *player =
Expand All @@ -603,15 +662,9 @@ - (void)testSeekToleranceWhenSeekingToEnd {
XCTAssertNotNil(player);

XCTestExpectation *initializedExpectation = [self expectationWithDescription:@"initialized"];
__block NSDictionary<NSString *, id> *initializationEvent;
[player onListenWithArguments:nil
eventSink:^(NSDictionary<NSString *, id> *event) {
if ([event[@"event"] isEqualToString:@"initialized"]) {
initializationEvent = event;
XCTAssertEqual(event.count, 4);
[initializedExpectation fulfill];
}
}];
StubEventListener *listener =
[[StubEventListener alloc] initWithInitializationExpectation:initializedExpectation];
player.eventListener = listener;
[self waitForExpectationsWithTimeout:30.0 handler:nil];

// Starts paused.
Expand All @@ -634,9 +687,7 @@ - (void)testSeekToleranceWhenSeekingToEnd {
XCTAssertNil(error);
XCTAssertEqual(avPlayer.volume, 0.1f);

[player onCancelWithArguments:nil];

return initializationEvent;
return listener;
}

// Checks whether [AVPlayer rate] KVO observations are correctly detached.
Expand Down Expand Up @@ -787,12 +838,15 @@ - (void)testFailedToLoadVideoEventShouldBeAlwaysSent {
[self waitForExpectationsWithTimeout:10.0 handler:nil];

XCTestExpectation *failedExpectation = [self expectationWithDescription:@"failed"];
[player onListenWithArguments:nil
eventSink:^(FlutterError *event) {
if ([event isKindOfClass:FlutterError.class]) {
[failedExpectation fulfill];
}
}];
// TODO(stuartmorgan): Update this test to instead use a mock listener, and add separate unit
// tests of FVPEventBridge.
[(NSObject<FlutterStreamHandler> *)player.eventListener
onListenWithArguments:nil
eventSink:^(FlutterError *event) {
if ([event isKindOfClass:FlutterError.class]) {
[failedExpectation fulfill];
}
}];
[self waitForExpectationsWithTimeout:10.0 handler:nil];
}

Expand All @@ -804,12 +858,9 @@ - (void)testUpdatePlayingStateShouldNotResetRate {
viewProvider:[[StubViewProvider alloc] initWithView:nil]];

XCTestExpectation *initializedExpectation = [self expectationWithDescription:@"initialized"];
[player onListenWithArguments:nil
eventSink:^(NSDictionary<NSString *, id> *event) {
if ([event[@"event"] isEqualToString:@"initialized"]) {
[initializedExpectation fulfill];
}
}];
StubEventListener *listener =
[[StubEventListener alloc] initWithInitializationExpectation:initializedExpectation];
player.eventListener = listener;
[self waitForExpectationsWithTimeout:10 handler:nil];

FlutterError *error;
Expand Down
Loading