Skip to content

update: allow starting new views with videoChange without changing AVPlayerItem #258

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open
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
2 changes: 2 additions & 0 deletions MUXSDKStats/MUXSDKStats/MUXSDKPlayerBinding.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,12 @@ typedef NS_ENUM(NSUInteger, MUXSDKViewOrientation) {
- (void)dispatchPause;
- (void)dispatchTimeUpdateEvent:(CMTime)time;
- (void)dispatchError;
- (void)dispatchVideoChange;
- (void)dispatchViewEnd;
- (void)dispatchOrientationChange:(MUXSDKViewOrientation) orientation;
- (void)dispatchAdEvent:(MUXSDKPlaybackEvent *)event;
- (float)getCurrentPlayheadTimeMs;
- (NSString *)playerName;
- (void)dispatchRenditionChange;
- (void)setAdPlaying:(BOOL)isAdPlaying;
- (BOOL)setAutomaticErrorTracking:(BOOL)automaticErrorTracking;
Expand Down
81 changes: 52 additions & 29 deletions MUXSDKStats/MUXSDKStats/MUXSDKPlayerBinding.m
Original file line number Diff line number Diff line change
Expand Up @@ -403,38 +403,50 @@ - (void)detachAVPlayer {
}
}

- (void)dispatchVideoChange {
// end the current view if it's not already been ended
if (_state != MUXSDKPlayerStateViewEnd) {
[self dispatchViewEnd];
}

[self.playDispatchDelegate videoChangedForPlayer:_name];
// TODO: test that we have all the metadata we need on the subsequent view. On android we needed to catch up with the current state
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this refer to external stuff like MUXSDKCustomerData or something else?

Copy link
Collaborator Author

@daytime-em daytime-em Jan 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The customer data, and also more general things like the private stuff in the player binding

I saw some odd stuff with playhead time during the subsequent views after changing, so I think there might still be something to clear. It could be here or in the player binding (edit: oops, here or in the stores I mean), I'm not really sure


//
// Special case for AVQueuePlayer
// In a normal videoChange: world - the KVO for "rate" will fire - and
// subsequently after that this binding will dispatchPlay. In fact, any time
// an AVPlayer gets an item loaded into it the KVO for "rate" changes.
//
// However, in AVQueuePlayer world - the "rate" doesn't fire when the video is
// changed. I don't know why, but I guess that is the intended behavior. For that
// reason, if we're handling a videoChange event and we're dealing with AVQueuePlayer
// then we have to fire the play event here.
//
if (_shouldHandleAVQueuePlayerItem) {
_shouldHandleAVQueuePlayerItem = false;
[self dispatchPlay];
}
}

- (void)monitorAVPlayerItem {
if ((!_automaticVideoChange && !_didTriggerManualVideoChange) || _isAdPlaying) {
NSLog(@"MUXSDK-INFO - monitorAVPlayeritem: Called");
// The player item could be the the ad itself, and we monitor ads differentlty than main content
if (_isAdPlaying) {
return;
}

if (_playerItem) {
if (_didTriggerManualVideoChange) {
_didTriggerManualVideoChange = false;
}
[self dispatchViewEnd];
[self stopMonitoringAVPlayerItem];

if (_player.currentItem) {
[self.playDispatchDelegate videoChangedForPlayer:_name];
}

//
// Special case for AVQueuePlayer
// In a normal videoChange: world - the KVO for "rate" will fire - and
// subsequently after that this binding will dispatchPlay. In fact, any time
// an AVPlayer gets an item loaded into it the KVO for "rate" changes.
//
// However, in AVQueuePlayer world - the "rate" doesn't fire when the video is
// changed. I don't know why, but I guess that is the intended behavior. For that
// reason, if we're handling a videoChange event and we're dealing with AVQueuePlayer
// then we have to fire the play event here.
//
if (_shouldHandleAVQueuePlayerItem) {
_shouldHandleAVQueuePlayerItem = false;
[self dispatchPlay];
}
}
if (_player && _player.currentItem) {
// change the video automatically if enabled, unless the customer changed video manually already, and only if there's an item
NSLog(@"MUXSDK-INFO - monitorAVPlayeritem: checking to change video: %b, %lu, %b", _automaticVideoChange, (unsigned long)_state, _didTriggerManualVideoChange);
if (_automaticVideoChange && _state != MUXSDKPlayerStateReady && !_didTriggerManualVideoChange) {
NSLog(@"MUXSDK-INFO - automatically changing video");
[self dispatchVideoChange];
}

_playerItem = _player.currentItem;
[_playerItem addObserver:self
forKeyPath:@"status"
Expand All @@ -447,6 +459,9 @@ - (void)monitorAVPlayerItem {

[self dispatchSessionData];
}

// reset for the next media item
_didTriggerManualVideoChange = false;
}

- (void)dispatchSessionData {
Expand Down Expand Up @@ -488,13 +503,15 @@ - (void)stopMonitoringAVPlayerItem {
[self safelyRemovePlayerItemObserverForKeyPath:@"status"];
[self safelyRemovePlayerItemObserverForKeyPath:@"playbackBufferEmpty"];
_playerItem = nil;
if (!_isAdPlaying) {
[MUXSDKCore destroyPlayer: _name];
}
}

/// Cleans up our Core monitor. Call when we are detaching from an AVPlayer
- (void)destroyPlayer {
[MUXSDKCore destroyPlayer: _name];
}

- (void) programChangedForPlayer {
[self monitorAVPlayerItem];
[self dispatchVideoChange];
[self dispatchPlay];
[self dispatchPlaying];
}
Expand All @@ -510,6 +527,10 @@ - (CMTime)getTimeObserverInternal {
return CMTimeMakeWithSeconds(0.1, NSEC_PER_SEC);
}

- (NSString *)playerName {
return _name;
}

- (float)getCurrentPlayheadTimeMs {
return CMTimeGetSeconds([_player currentTime]) * 1000;
}
Expand Down Expand Up @@ -775,6 +796,7 @@ - (void)dispatchViewInit {
return;
}
[self resetVideoData];

MUXSDKPlayerData *playerData = [self getPlayerData];
MUXSDKViewInitEvent *event = [[MUXSDKViewInitEvent alloc] init];
[event setPlayerData:playerData];
Expand Down Expand Up @@ -1232,6 +1254,7 @@ - (BOOL) doubleValueIsEqual:(NSNumber *) x toOther:(NSNumber *) n {
}

- (void)didTriggerManualVideoChange {
NSLog(@"MUXSDK-INFO - didTriggerManualVideoChange");
_didTriggerManualVideoChange = true;
}
@end
Expand Down
30 changes: 25 additions & 5 deletions MUXSDKStats/MUXSDKStats/MUXSDKStats.m
Original file line number Diff line number Diff line change
Expand Up @@ -603,13 +603,10 @@ + (void)videoChangeForPlayer:(nonnull NSString *)name withCustomerData:(nullable
MUXSDKCustomerVideoData *videoData = [customerData customerVideoData];
MUXSDKCustomData *customData = [customerData customData];

if (!(videoData || viewData || customData)) {
return;
}
MUXSDKPlayerBinding *player = [_viewControllers valueForKey:name];
if (player) {
[player didTriggerManualVideoChange];
[player dispatchViewEnd];

if (videoData) {
[_customerVideoDataStore setVideoData:videoData forPlayerName:name];
}
Expand All @@ -622,6 +619,9 @@ + (void)videoChangeForPlayer:(nonnull NSString *)name withCustomerData:(nullable
if (customData) {
[_customerCustomDataStore setCustomData:customData forPlayerName:name];
}

[player dispatchVideoChange];
[player didTriggerManualVideoChange];
[player prepareForAvQueuePlayerNextItem];
}
}
Expand All @@ -630,8 +630,28 @@ + (void)videoChangeForPlayer:(nonnull NSString *)name withCustomerData:(nullable

+ (void)programChangeForPlayer:(nonnull NSString *)name
withCustomerData:(nullable MUXSDKCustomerData *)customerData {
[MUXSDKStats videoChangeForPlayer:name withCustomerData:customerData];
MUXSDKPlayerBinding *player = [_viewControllers valueForKey:name];
[player dispatchViewEnd];

if (customerData) {
MUXSDKCustomerPlayerData *playerData = customerData.customerPlayerData;
MUXSDKCustomerVideoData *videoData = customerData.customerVideoData;
MUXSDKCustomerViewData *viewData = customerData.customerViewData;
MUXSDKCustomData *customData = customerData.customData;
if (videoData) {
[_customerVideoDataStore setVideoData:videoData forPlayerName:name];
}
if (viewData) {
[_customerViewDataStore setViewData:viewData forPlayerName:name];
}
if (playerData) {
[_customerPlayerDataStore setPlayerData:playerData forPlayerName:name];
}
if (customData) {
[_customerCustomDataStore setCustomData:customData forPlayerName:name];
}
}

if (player) {
[player programChangedForPlayer];
}
Expand Down
4 changes: 3 additions & 1 deletion MUXSDKStats/MUXSDKStatsTests/MUXSDKPlayerBindingTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,9 @@ - (void)testPlayerBindingManagerStartsNewViews {
[self setupViewControllerPlayerBinding:name
softwareName:@"TestSoftware"
softwareVersion:@"0.1.0"];


NSDictionary *events = [MUXSDKCore capturedEventsForPlayer:name];

XCTAssertEqual(3, [MUXSDKCore eventsCountForPlayer:name]);
id<MUXSDKEventTyping> event0 = [MUXSDKCore eventAtIndex:0 forPlayer:name];
id<MUXSDKEventTyping> event1 = [MUXSDKCore eventAtIndex:1 forPlayer:name];
Expand Down
36 changes: 27 additions & 9 deletions MUXSDKStats/MUXSDKStatsTests/MUXSDKStatsTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,11 @@ - (void)testVideoChangeForAVPlayerViewControllerWithCustomData{
NSArray *expectedEventTypes = @[MUXSDKPlaybackEventViewInitEventType,
MUXSDKDataEventType,
MUXSDKPlaybackEventPlayerReadyEventType,
MUXSDKPlaybackEventViewEndEventType
MUXSDKPlaybackEventViewEndEventType,
MUXSDKPlaybackEventViewInitEventType,
MUXSDKDataEventType,
];

[self assertPlayer:playName dispatchedEventTypes:expectedEventTypes];
[self assertPlayer:playName dispatchedDataEventsAtIndex:1 withCustomData:@{@"c1": @"bar"}];
[MUXSDKStats destroyPlayer:playName];
Expand All @@ -206,10 +209,13 @@ - (void)testVideoChangeForAVPlayerViewControllerWithCustomerViewData{
XCTAssertNotNil(playerBinding, "expected monitorAVPlayerViewController to return a playerBinding");
[customerViewData setViewSessionId:@"bar"];
[MUXSDKStats videoChangeForPlayer:playName withCustomerData:customerData];
NSArray *expectedEventTypes = @[MUXSDKPlaybackEventViewInitEventType,
MUXSDKDataEventType,
MUXSDKPlaybackEventPlayerReadyEventType,
MUXSDKPlaybackEventViewEndEventType
NSArray *expectedEventTypes = @[MUXSDKPlaybackEventViewInitEventType, // from first view
MUXSDKDataEventType, // from first view
MUXSDKPlaybackEventPlayerReadyEventType, // from first view
MUXSDKPlaybackEventViewEndEventType, // changing video for second view
MUXSDKPlaybackEventViewInitEventType, // from the second view starting
MUXSDKDataEventType, // from the second view starting
MUXSDKPlaybackEventViewEndEventType, // from destroyPlayer
];
[MUXSDKStats destroyPlayer:playName];
[self assertPlayer:playName dispatchedEventTypes:expectedEventTypes];
Expand All @@ -233,7 +239,10 @@ - (void)testVideoChangeForAVPlayerViewController{
NSArray *expectedEventTypes = @[MUXSDKPlaybackEventViewInitEventType,
MUXSDKDataEventType,
MUXSDKPlaybackEventPlayerReadyEventType,
MUXSDKPlaybackEventViewEndEventType
MUXSDKPlaybackEventViewEndEventType,
MUXSDKPlaybackEventViewInitEventType,
MUXSDKDataEventType,
MUXSDKPlaybackEventViewEndEventType,
];
[MUXSDKStats destroyPlayer:playName];
[self assertPlayer:playName dispatchedEventTypes:expectedEventTypes];
Expand All @@ -258,7 +267,7 @@ - (void)testManualVideoChangeForAVPlayerViewController{

NSArray *expectedEventTypes = @[MUXSDKPlaybackEventViewInitEventType,
MUXSDKDataEventType,
MUXSDKPlaybackEventPlayerReadyEventType
MUXSDKPlaybackEventPlayerReadyEventType,
];
[self assertPlayer:playName dispatchedEventTypes:expectedEventTypes];
[self assertPlayer:playName dispatchedDataEventsAtIndex:1 withCustomerVideoData:@{@"vtt": @"01234"}];
Expand Down Expand Up @@ -329,7 +338,11 @@ - (void)testVideoChangeForAVPlayerLayerWithCustomerViewData API_UNAVAILABLE(visi
NSArray *expectedEventTypes = @[MUXSDKPlaybackEventViewInitEventType,
MUXSDKDataEventType,
MUXSDKPlaybackEventPlayerReadyEventType,
MUXSDKPlaybackEventViewEndEventType
MUXSDKPlaybackEventViewEndEventType, // from changing to the new view
MUXSDKPlaybackEventViewInitEventType, // from changing to the new view
MUXSDKDataEventType, // from changing to the new view
MUXSDKPlaybackEventViewEndEventType, // from destroying the player

];
[MUXSDKStats destroyPlayer:playName];
[self assertPlayer:playName dispatchedEventTypes:expectedEventTypes];
Expand All @@ -353,7 +366,10 @@ - (void)testVideoChangeForAVPlayerLayer API_UNAVAILABLE(visionos) {
NSArray *expectedEventTypes = @[MUXSDKPlaybackEventViewInitEventType,
MUXSDKDataEventType,
MUXSDKPlaybackEventPlayerReadyEventType,
MUXSDKPlaybackEventViewEndEventType
MUXSDKPlaybackEventViewEndEventType,
MUXSDKPlaybackEventViewInitEventType,
MUXSDKDataEventType,
MUXSDKPlaybackEventViewEndEventType,
];
[MUXSDKStats destroyPlayer:playName];
[self assertPlayer:playName dispatchedEventTypes:expectedEventTypes];
Expand Down Expand Up @@ -449,6 +465,8 @@ - (void)testProgramChangeForAVPlayerViewController{
MUXSDKDataEventType,
MUXSDKPlaybackEventPlayerReadyEventType,
MUXSDKPlaybackEventViewEndEventType,
MUXSDKPlaybackEventViewInitEventType,
MUXSDKDataEventType,
MUXSDKPlaybackEventPlayEventType,
MUXSDKPlaybackEventPlayingEventType,
MUXSDKPlaybackEventViewEndEventType,
Expand Down
1 change: 1 addition & 0 deletions MUXSDKStats/MUXSDKStatsTests/Utils/MUXSDKCore+Mock.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
+ (NSUInteger) eventsCountForPlayer:(NSString *)playerId;
+ (MUXSDKDataEvent *) globalEventAtIndex:(NSUInteger)index;
+ (NSUInteger) globalEventsCount;
+ (NSArray *) capturedEventsForPlayer: (NSString *)player;

@end

4 changes: 4 additions & 0 deletions MUXSDKStats/MUXSDKStatsTests/Utils/MUXSDKCore+Mock.m
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,8 @@ + (NSUInteger) globalEventsCount {
return globalEvents.count;
}

+ (NSArray *) capturedEventsForPlayer: (NSString *)player {
return [[events objectForKey:player] copy];
}

@end
Loading