#import <Foundation/Foundation.h>
/**
* 正在播放回调
* @param currentTime 时间显示 label.text
* @param currentProgress 播放进度 slider.value
*/
typedef void(^OnPlayingBlock)(Float64 currentTime, CGFloat currentProgress);
/**
* 准备播放回调
* @param totalDuration 音频总时长
*/
typedef void(^PrepareToPlayBlock)(CGFloat totalDuration);
/**
* 正在缓冲回调
* @param bufferDuration 已缓冲的时长
*/
typedef void(^OnBufferingBlock)(CGFloat bufferDuration);
/**
* 播放完成回调
* @param flag `YES`播放完成, `NO`播放失败.
*/
typedef void(^CompletePlayingBlock)(BOOL flag);
/**
* 缓存自动暂停回调, 用于更改播放按钮的样式
* @param isPlaying 是否正在播放, 如果没有播放表示正在缓冲
*/
typedef void(^IsPlayingBlock)(BOOL isPlaying);
@interface LilyNetPlayer : NSObject
@property (nonatomic, assign) CGFloat volume; //音量大小
@property (nonatomic, assign) Float64 changedTime; //改变播放进度
@property (nonatomic, assign) Float64 timeOffset; //之前播放的进度
@property (nonatomic, copy) OnPlayingBlock playingBlock;
@property (nonatomic, copy) PrepareToPlayBlock prepareToPlayBlock;
@property (nonatomic, copy) OnBufferingBlock bufferingBlock;
@property (nonatomic, copy) CompletePlayingBlock completePlayBlock;
@property (nonatomic, copy) IsPlayingBlock isPlayingBlock;
@property (nonatomic, copy) void(^playerRateStatus)(float rate);
//初始化播放器
-(void)createAudioPlayerWith:(NSString*)audioPath;
//开始播放
- (void)play;
//暂停播放
- (void)pause;
//停止播放
- (void)stop;
@end
#import "LilyNetPlayer.h"
#import <AVFoundation/AVFoundation.h>
@interface LilyNetPlayer ()
@property (nonatomic, strong) AVPlayer * player;
@property (nonatomic, strong) id periodicTimeObserver;
@property (nonatomic, assign) Float64 currentTime; //播放时间
@property (nonatomic, assign) Float64 totalDuration; //音频总时长
@end
@implementation LilyNetPlayer
#pragma mark - 初始化播放器
-(void)createAudioPlayerWith:(NSString*)audioPath
{
NSURL * url = [NSURL fileURLWithPath:audioPath];
AVPlayerItem * item = [AVPlayerItem playerItemWithURL:url];
_player = [[AVPlayer alloc] initWithPlayerItem:item];
AVAudioSession * session = [AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryPlayback error:nil];
[session setActive:YES error:nil];
[self addObserverForPlayer];
[self addObserverForPlayItem];
}
#pragma mark - 开始播放
- (void)play {
[self.player play];
}
#pragma mark - 暂停播放
- (void)pause {
[self.player pause];
}
#pragma mark - 停止播放
- (void)stop {
[self destroyPlayer];//释放 播放器
if (self.completePlayBlock) {
self.completePlayBlock(YES);
}
}
#pragma mark - 释放播放器
- (void)releasePlayer {
[self destroyPlayer];
}
#pragma mark - 改变播放进度
- (void)setChangedTime:(Float64)changedTime {
_changedTime = changedTime;
if (changedTime>0) {
CMTime time = CMTimeMakeWithSeconds(changedTime, 1 * NSEC_PER_SEC);
[self.player seekToTime:time toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
[self.player play];
}
}
#pragma mark - 音量大小
- (void)setVolume:(CGFloat)volume {
_volume = volume;
self.player.volume = volume;
}
#pragma mark - Notification
//播放完毕
- (void)addObserverForPlayer {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(audioPlayCompletion:) name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
}
//播放完毕
- (void)audioPlayCompletion:(NSNotification *)notification {
Float64 timeInterval = fabs(self.currentTime - self.totalDuration);
if (timeInterval > 1.0) {
if (self.isPlayingBlock) self.isPlayingBlock(NO);
return; // 下载完的音频已经播放完但是整个音频没播完
}
_timeOffset = 0.0;
__weak typeof(self) weakSelf = self;
if (self.player.currentItem.status == AVPlayerItemStatusReadyToPlay) {
[self.player seekToTime:kCMTimeZero completionHandler:^(BOOL finished) {
if (weakSelf.completePlayBlock) weakSelf.completePlayBlock(YES);
}];
}
[self destroyPlayer];//释放 播放器
}
#pragma mark - Observer
//观察 加载状态 加载进度
- (void)addObserverForPlayItem {
[self.player.currentItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
[self.player.currentItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
}
#pragma mark - AVPlayerItemObserver
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
AVPlayerItem * playerItem = object;
if ([keyPath isEqualToString:@"status"]) {
AVPlayerStatus status = [[change objectForKey:@"new"] intValue];
if (status == AVPlayerStatusUnknown) {
} else if (status == AVPlayerStatusReadyToPlay){//准备播放
_totalDuration = CMTimeGetSeconds(playerItem.duration);
_totalDuration = isnan(_totalDuration) ? 0.0 : _totalDuration;
if (self.prepareToPlayBlock) {
self.prepareToPlayBlock(_totalDuration);
}
if (_timeOffset > 0) {//接着之前的进度播放
CMTime time = CMTimeMakeWithSeconds(_timeOffset, 1 * NSEC_PER_SEC);
[self.player seekToTime:time toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
}
[self updatePlayProgress];//播放进度
} else if ([playerItem status] == AVPlayerStatusFailed) {//播放失败
if (self.completePlayBlock) {
self.completePlayBlock(NO);
}
[self destroyPlayer];//释放 播放器
}
} else if([keyPath isEqualToString:@"loadedTimeRanges"]){//获取最新缓存的区间
NSTimeInterval bufferInterval = [self bufferedDuration];
if (self.bufferingBlock) {
self.bufferingBlock(bufferInterval);
}
if (self.isPlayingBlock) {
if (bufferInterval > self.currentTime + 1.0) {
self.isPlayingBlock(YES);
} else if (self.player.rate == 0.0) {
self.isPlayingBlock(NO);
}
// 处理缓冲结束时,正好由于缓存而自动暂停的问题.
if (bufferInterval >= _totalDuration) {
self.isPlayingBlock(YES);
}
}
}
}
//播放进度
- (void)updatePlayProgress {
__weak typeof(self) weakSelf = self;
dispatch_queue_t queue = dispatch_get_main_queue();
CMTime timeInterval = CMTimeMakeWithSeconds(1.0f, NSEC_PER_SEC);
self.periodicTimeObserver = [self.player addPeriodicTimeObserverForInterval:timeInterval queue:queue usingBlock:^(CMTime time) {
weakSelf.currentTime = CMTimeGetSeconds(time);
if (weakSelf.currentTime && weakSelf.playingBlock) {
weakSelf.playingBlock(weakSelf.currentTime, weakSelf.currentTime);
}
if (weakSelf.playerRateStatus) {
weakSelf.playerRateStatus(weakSelf.player.rate);
}
}];
}
// 计算缓冲进度
- (NSTimeInterval)bufferedDuration {
NSArray * array = [self.player.currentItem loadedTimeRanges];
CMTimeRange timeRange = [array.firstObject CMTimeRangeValue];
float startSeconds = CMTimeGetSeconds(timeRange.start);
float durationSeconds = CMTimeGetSeconds(timeRange.duration);
NSTimeInterval totalBuffer = startSeconds + durationSeconds;
return totalBuffer;
}
#pragma mark - 释放播放器
- (void)destroyPlayer {
if (!_player) return;
[self.player pause];
self.timeOffset = 0.0;
[self.player.currentItem cancelPendingSeeks];
[self.player.currentItem.asset cancelLoading];
[self removeObserverFromPlayer];
}
- (void)removeObserverFromPlayer {
@try {
[self.player.currentItem removeObserver:self forKeyPath:@"status"];
[self.player.currentItem removeObserver:self forKeyPath:@"loadedTimeRanges"];
[self.player removeTimeObserver:self.periodicTimeObserver];
[self setPeriodicTimeObserver:nil]; // must set it nil value.
[self.player replaceCurrentItemWithPlayerItem:nil];
} @catch(NSException *__unused exception) { }
}
- (void)dealloc {
if (_player != nil) {
[self destroyPlayer];//释放 播放器
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
}
}
@end
//网络播放器
LilyNetPlayer * netPlayer = [[LilyNetPlayer alloc] init];
[netPlayer createAudioPlayerWith:@"网络音频路径"];
#pragma mark - PlayerBlock
- (void)addNetPlayerBlock {
// 准备播放时更新UI
netPlayer.prepareToPlayBlock = ^(CGFloat totalDuration) {
NSLog(@"音频总时长---%@",convertTime(totalDuration));
};
// 缓冲时更新UI
netPlayer.bufferingBlock = ^(CGFloat bufferDuration) {
NSLog(@"缓冲时长---%@",convertTime(bufferDuration));
};
// 播放时更新UI
netPlayer.playingBlock = ^(Float64 currentTime, CGFloat currentProgress) {
NSLog(@"播放时长---%@",convertTime(roundf(currentTime)));
NSLog(@"播放进度---%f",currentProgress);
};
// 播放完成更新UI
netPlayer.completePlayBlock = ^(BOOL flag) {
};
}
static inline NSString* convertTime(int totalSeconds) {
int seconds = totalSeconds % 60;
int minutes = (totalSeconds / 60) % 60;
NSString *time = [NSString stringWithFormat:@"%02d:%02d", minutes, seconds];
return time;
}
//开始播放
[netPlayer play];
//暂停播放
[netPlayer pause];
//停止播放
[netPlayer stop];
//修改音量大小
netPlayer.volume = 1;
//修改播放进度
netPlayer.changedTime = slider.value