头文件
@interface ZNAudioPlayerUtil : NSObject
//播放一组网络音频文件
+ (void)playAudios:(NSArray*)audioArr;
//终止播放
+ (void)stopPlayer;
//暂停播放
+ (void)pausePlayer;
//恢复播放
+ (void)resumePlayer;
//拖动到某个时间点
+ (void)seekToTime:(CMTime)time;
//当前播放时间
+ (CGFloat)currentTime;
//总时长
+ (CGFloat)totalTime;
@end
.m
static void* const AVLoopPlayerQueuePlayerStatusObservationContext = (void*)&AVLoopPlayerQueuePlayerStatusObservationContext;
static void* const AVLoopPlayerCurrentItemObservationContext = (void*)&AVLoopPlayerCurrentItemObservationContext;
static void* const AVLoopPlayerCurrentItemStatusObservationContext = (void*)&AVLoopPlayerCurrentItemStatusObservationContext;
@interface ZNAudioPlayerUtil(){
BOOL _addedObservers;
}
@property (nonatomic, strong) AVQueuePlayer *queuePlayer;
@property (nonatomic, assign) CGFloat totalDuration;
- (void)playbackInLoopWithURL:(NSString *)urlStr;
- (void)stopPlayback;
@end
@implementation ZNAudioPlayerUtil
(instancetype)shareInstance{
static ZNAudioPlayerUtil *player = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
player = [[ZNAudioPlayerUtil alloc]init];
});
return player;
}(void)playAudios:(NSArray *)audioArr{
[[ZNAudioPlayerUtil shareInstance] stopPlayback];
for (NSString *audioID in audioArr) {
NSString *urlStr = [NSString stringWithFormat:@”%@%@?id=%@”,EvHRServerHost,kHR_DownloadAudioFileUrl,audioID];
[[ZNAudioPlayerUtil shareInstance] playbackInLoopWithURL:urlStr];
}
}(void)stopPlayer{
[[ZNAudioPlayerUtil shareInstance] stopPlayback];
}(instancetype)init{
if (self = [super init]) {
_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:(NSString *)urlStr
{
AVURLAsset *asset = [AVURLAsset assetWithURL:[NSURL URLWithString:urlStr]];[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
{
return;
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];
}
pragma mark - public methods
(void)pausePlayer{
AVQueuePlayer *queuePalyer = [ZNAudioPlayerUtil shareInstance].queuePlayer;
if (queuePalyer.timeControlStatus == AVPlayerTimeControlStatusPlaying) {
[queuePalyer pause];
}
}(void)resumePlayer{
AVQueuePlayer *queuePalyer = [ZNAudioPlayerUtil shareInstance].queuePlayer;
if (queuePalyer.timeControlStatus == AVPlayerTimeControlStatusPaused) {
[queuePalyer play];
}
}(void)seekToTime:(CMTime)time{
AVQueuePlayer *queuePlayer = [ZNAudioPlayerUtil shareInstance].queuePlayer;
if (queuePlayer) {
[queuePlayer seekToTime:time];
}
}(CGFloat)currentTime{
AVQueuePlayer *queuePlayer = [ZNAudioPlayerUtil shareInstance].queuePlayer;
if (queuePlayer) {
return [ZNAudioPlayerUtil totalTime] - CMTimeGetSeconds(queuePlayer.currentItem.currentTime);
}
return 0;
}(CGFloat)totalTime{
AVQueuePlayer *queuePlayer = [ZNAudioPlayerUtil shareInstance].queuePlayer;
if (queuePlayer) {
CGFloat totalDuration = 0;
for (AVPlayerItem *item in queuePlayer.items) {
CGFloat duration = CMTimeGetSeconds(item.duration);
totalDuration += duration;
}
return totalDuration;
}
return 0;
}
@end