/* |
Copyright (C) 2014 Apple Inc. All Rights Reserved. |
See LICENSE.txt for this sample’s licensing information |
|
Abstract: |
|
|
Command line tool for playing audiovisual media in loop |
|
|
*/ |
|
@import Foundation; |
@import AVFoundation; |
@import CoreMedia; |
|
static void* const AVLoopPlayerCurrentItemObservationContext = (void*)&AVLoopPlayerCurrentItemObservationContext; |
|
@interface AVLoopPlayer : NSObject |
{ |
@private |
AVQueuePlayer *_queuePlayer; |
} |
|
- (void)playbackInLoopWithURL:(NSURL *)URL; |
- (void)stopPlayback; |
|
@end |
|
@implementation AVLoopPlayer |
|
- (id)init |
{ |
self = [super init]; |
if (self) |
{ |
_queuePlayer = [[AVQueuePlayer alloc] init]; |
} |
|
return self; |
} |
|
- (void)playbackInLoopWithURL:(NSURL *)URL |
{ |
AVURLAsset *asset = [AVURLAsset assetWithURL:URL]; |
|
[asset loadValuesAsynchronouslyForKeys:@[@"duration"] completionHandler:^{ |
NSError *error; |
// Check to make sure duration is loaded before accessing its value. |
AVKeyValueStatus durationStatus = [asset statusOfValueForKey:@"duration" error:&error]; |
|
switch (durationStatus) |
{ |
case AVKeyValueStatusLoaded: |
{ |
// 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(), ^{ |
// 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]; |
} |
} |
|
[_queuePlayer addObserver:self forKeyPath:@"currentItem" options:NSKeyValueObservingOptionOld context:AVLoopPlayerCurrentItemObservationContext]; |
[_queuePlayer play]; |
}); |
|
break; |
} |
case AVKeyValueStatusFailed: |
{ |
NSLog(@"Failed to load duration property for asset: %@ with error: %@", asset, error); |
break; |
} |
default: |
break; |
} |
}]; |
} |
|
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)changeDictionary context:(void *)context |
{ |
if (context == AVLoopPlayerCurrentItemObservationContext) |
{ |
AVQueuePlayer *player = (AVQueuePlayer *)object; |
|
// 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]; |
[player insertItem:itemRemoved afterItem:nil]; |
} |
} |
} |
|
- (void)stopPlayback |
{ |
[_queuePlayer pause]; |
[_queuePlayer removeObserver:self forKeyPath:@"currentItem" context:AVLoopPlayerCurrentItemObservationContext]; |
[_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 atleast 3 seconds |
NSDate *timeOut = [NSDate dateWithTimeIntervalSinceNow:3.0]; |
[[NSRunLoop mainRunLoop] runUntilDate:timeOut]; |
|
[player stopPlayback]; |
|
return 0; |
} |
return 0; |
} |