Projects/LoopPlayer/LoopPlayer/main.m
/* |
Copyright (C) 2016 Apple Inc. All Rights Reserved. |
See LICENSE.txt for this sample’s licensing information |
Abstract: |
Command line tool for playing audiovisual media in a loop. |
*/ |
@import Foundation; |
@import AVFoundation; |
@import CoreMedia; |
static void* const AVLoopPlayerQueuePlayerStatusObservationContext = (void*)&AVLoopPlayerQueuePlayerStatusObservationContext; |
static void* const AVLoopPlayerCurrentItemObservationContext = (void*)&AVLoopPlayerCurrentItemObservationContext; |
static void* const AVLoopPlayerCurrentItemStatusObservationContext = (void*)&AVLoopPlayerCurrentItemStatusObservationContext; |
@interface AVLoopPlayer : NSObject |
{ |
@private |
AVQueuePlayer *_queuePlayer; |
BOOL _addedObservers; |
} |
- (void)playbackInLoopWithURL:(NSURL *)URL; |
- (void)stopPlayback; |
@end |
@implementation AVLoopPlayer |
- (id)init |
{ |
self = [super init]; |
if (self) |
{ |
_queuePlayer = [[AVQueuePlayer alloc] init]; |
} |
return self; |
} |
- (void)startObservingPlayerAndItem |
{ |
if (_addedObservers == NO) |
{ |
[_queuePlayer addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:AVLoopPlayerQueuePlayerStatusObservationContext]; |
[_queuePlayer addObserver:self forKeyPath:@"currentItem" options:NSKeyValueObservingOptionOld context:AVLoopPlayerCurrentItemObservationContext]; |
[_queuePlayer addObserver:self forKeyPath:@"currentItem.status" options:NSKeyValueObservingOptionNew context:AVLoopPlayerCurrentItemStatusObservationContext]; |
_addedObservers = YES; |
} |
} |
- (void)stopObservingPlayerAndItem |
{ |
if (_addedObservers) |
{ |
[_queuePlayer removeObserver:self forKeyPath:@"status" context:AVLoopPlayerQueuePlayerStatusObservationContext]; |
[_queuePlayer removeObserver:self forKeyPath:@"currentItem" context:AVLoopPlayerCurrentItemObservationContext]; |
[_queuePlayer removeObserver:self forKeyPath:@"currentItem.status" context:AVLoopPlayerCurrentItemStatusObservationContext]; |
_addedObservers = NO; |
} |
} |
- (void)playbackInLoopWithURL:(NSURL *)URL |
{ |
AVURLAsset *asset = [AVURLAsset assetWithURL:URL]; |
[asset loadValuesAsynchronouslyForKeys:@[@"duration", @"playable"] completionHandler:^{ |
/* |
The asset invokes its completion handler on an arbitrary queue when |
loading is complete. Because we want to access our AVQueuePlayer in our |
ensuing set-up, we must dispatch our handler to the main queue. |
*/ |
dispatch_async(dispatch_get_main_queue(), ^{ |
NSError *durationError, *playableError; |
/* |
Check to make sure duration and playable properties are loaded |
before accessing them. |
*/ |
AVKeyValueStatus durationStatus = [asset statusOfValueForKey:@"duration" error:&durationError]; |
AVKeyValueStatus playableStatus = [asset statusOfValueForKey:@"playable" error:&playableError]; |
if (durationStatus == AVKeyValueStatusLoaded && playableStatus == AVKeyValueStatusLoaded ) |
{ |
if (CMTIME_COMPARE_INLINE([asset duration], >=, CMTimeMake(1,100)) && [asset isPlayable]) |
{ |
/* |
Based on the duration of the asset, we decide the number of |
player items to add to demonstrate gapless playback of the |
same asset. |
*/ |
NSUInteger countOfPlayerItems = (1.0 / CMTimeGetSeconds([asset duration])) + 2; |
for (NSUInteger idx = 0; idx < countOfPlayerItems; ++idx) |
{ |
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:asset]; |
if (playerItem) |
{ |
[_queuePlayer insertItem:playerItem afterItem:nil]; |
} |
} |
[self startObservingPlayerAndItem]; |
[_queuePlayer play]; |
} |
else |
{ |
NSLog(@"Can't loop. Asset duration too short(%1.3f sec) or not playable(isPlayable: %s)", |
CMTimeGetSeconds([asset duration]), ([asset isPlayable]?"YES":"NO")); |
} |
} |
else |
{ |
if (durationStatus == AVKeyValueStatusFailed) |
NSLog(@"Failed to load duration property for asset: %@ with error: %@", asset, durationError); |
if (playableStatus == AVKeyValueStatusFailed) |
NSLog(@"Failed to load playable property for asset: %@ with error: %@", asset, playableError); |
} |
}); |
}]; |
} |
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)changeDictionary context:(void *)context |
{ |
if (context == AVLoopPlayerQueuePlayerStatusObservationContext) |
{ |
AVPlayerStatus newPlayerStatus = (AVPlayerStatus)[[changeDictionary objectForKey:NSKeyValueChangeNewKey] unsignedIntegerValue]; |
if (newPlayerStatus == AVPlayerStatusFailed) { |
AVQueuePlayer *player = (AVQueuePlayer *)object; |
NSLog(@"End looping since player has failed with error %@", player.error); |
[self stopPlayback]; |
} |
} |
else if (context == AVLoopPlayerCurrentItemObservationContext) |
{ |
AVQueuePlayer *player = (AVQueuePlayer *)object; |
if ([[player items] count] == 0) |
{ |
NSLog(@"Play queue emptied out due to bad player item. End looping."); |
[self stopPlayback]; |
} |
else |
{ |
// Append the previous current item to the player's queue. |
AVPlayerItem *itemRemoved = changeDictionary[NSKeyValueChangeOldKey]; |
/* |
An initial change from a nil currentItem yields NSNull here. Check |
to make sure the class is AVPlayerItem before appending it to the |
end of the queue. |
*/ |
if ([itemRemoved isKindOfClass:[AVPlayerItem class]]) |
{ |
[itemRemoved seekToTime:kCMTimeZero]; |
[self stopObservingPlayerAndItem]; |
[player insertItem:itemRemoved afterItem:nil]; |
[self startObservingPlayerAndItem]; |
} |
} |
} |
else if (context == AVLoopPlayerCurrentItemStatusObservationContext) |
{ |
AVPlayerItemStatus newItemStatus = (AVPlayerItemStatus)[[changeDictionary objectForKey:NSKeyValueChangeNewKey] unsignedIntegerValue]; |
if (newItemStatus == AVPlayerItemStatusFailed) { |
AVQueuePlayer *player = (AVQueuePlayer *)object; |
NSLog(@"End looping since player item has failed with error %@", player.currentItem.error); |
[self stopPlayback]; |
} |
} |
} |
- (void)stopPlayback |
{ |
[_queuePlayer pause]; |
[self stopObservingPlayerAndItem]; |
[_queuePlayer removeAllItems]; |
} |
@end |
int main(int argc, const char * argv[]) |
{ |
@autoreleasepool |
{ |
if (argc != 2) |
{ |
NSLog(@"Usage: %s <path-to-movie>",argv[0]); |
return 1; |
} |
NSString *filePath = [[NSString alloc] initWithUTF8String:argv[1]]; |
NSURL *fileURL = [NSURL fileURLWithPath:filePath]; |
AVLoopPlayer *player = [[AVLoopPlayer alloc] init]; |
[player playbackInLoopWithURL:fileURL]; |
// Play for at least 3 seconds. |
NSDate *timeOut = [NSDate dateWithTimeIntervalSinceNow:3.0]; |
[[NSRunLoop mainRunLoop] runUntilDate:timeOut]; |
[player stopPlayback]; |
return 0; |
} |
return 0; |
} |
Copyright © 2016 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2016-09-13