.h文件
#import
#import
typedefNS_ENUM(NSInteger, XCMVPlayerStatus) {
XCMVPlayerStatusReadyToPlay =0,// 准备好播放
XCMVPlayerStatusLoadingVideo, // 加载视频
XCMVPlayerStatusPlayEnd, // 播放结束
XCMVPlayerStatusCacheData, // 缓冲视频
XCMVPlayerStatusCacheEnd, // 缓冲结束
XCMVPlayerStatusPlayStop, // 播放中断 (多是没网)
XCMVPlayerStatusItemFailed, // 视频资源问题
XCMVPlayerStatusEnterBack, // 进入后台
XCMVPlayerStatusBecomeActive, // 从后台返回
};
@protocolXCMusicManagerDelegate
@optional
// 数据刷新
- (void)refreshDataWith:(NSTimeInterval)totalTime CurrentTime:(NSTimeInterval)currentTime LoadRange:(NSTimeInterval)loadTime Progress:(CGFloat)progress;
// 状态/错误 提示
- (void)promptPlayerStatusOrErrorWith:(XCMVPlayerStatus)status;
@end
@interfaceXCMusicManager :NSObject
/** 播放器 */
@property (nonatomic, strong) AVPlayer *player;
/** 视频资源 */
@property (nonatomic, strong) AVPlayerItem *currentItem;
// 音频/视频总长度
@property (nonatomic, assign) NSTimeInterval totalTime;
// 缓存数据
@property (nonatomic, assign) NSTimeInterval loadRange;
+ (instancetype)manager;
- (void)setPlayerWith:(NSURL*)url;
/** 播放 */
- (void)play;
/** 暂停 */
- (void)pause;
/** 正在播放 */
- (BOOL)isPlay;
/** 拖动音频/视频进度 */
- (void)seekPlayerTimeTo:(NSTimeInterval)time;
//代理
@property(nonatomic,weak)id XCMusicManagerDelegate;
/** 快进 */
- (void)fastForward;
/** 快退 */
- (void)fastBack;
@end
.m文件
#import "XCMusicManager.h"
@interface XCMusicManager()
/** 播放器观察者 */
@property (nonatomic ,strong) id timeObser;
// 是否正在拖动
@property (nonatomic ,assign) BOOL isSeeking;
// 是否能正常播放(缓冲)
@property (nonatomic, assign) BOOL isCanPlay;
// 是否需要缓冲
@property (nonatomic, assign) BOOL needBuffer;
@end
@implementation XCMusicManager
+ (instancetype)manager {
staticXCMusicManager* player;
staticdispatch_once_tonceToken;
dispatch_once(&onceToken, ^{
player = [[XCMusicManageralloc]init];
});
returnplayer;
}
#pragma mark - 销毁 release
- (void)dealloc
{
NSLog(@"--- %@ --- 销毁了",[self class]);
[self removeNotification];
[self removePlayerObserver];
[self removeObserverWithPlayItem:self.player.currentItem];
}
#pragma mark - 属性和方法
- (NSTimeInterval)totalTime
{
return CMTimeGetSeconds(self.player.currentItem.duration);
}
- (void)setPlayerWith:(NSURL*)url{
//当前播放
self.currentItem= [selfgetPlayerItem:url];
//创建播放器
_player = [AVPlayer playerWithPlayerItem:self.currentItem];
//添加播放器监察者
[self addPlayerObserver];
//当前曲目添加监察者
[self addObserverWithPlayItem:self.currentItem];
//为播放器添加通知
[self addNotificatonForPlayer];
}
#pragma mark - 创建播放器
/**
获取播放item
@param videoURL 视频网址
@return AVPlayerItem
*/
- (AVPlayerItem*)getPlayerItem:(NSURL*)videoURL
{
// 转utf8 防止中文报错
// videoPath = [videoPath stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
// videoPath = [videoPath stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
//网络音乐 AVPlayerItem *item = [AVPlayerItem playerItemWithURL:url];
// NSString *path =[ [NSBundle mainBundle] pathForResource:@"avengers" ofType:@"mp3"];
// NSURL *url = [NSURL fileURLWithPath:path];
// NSLog(@"sss-%@-%@",path,url);
AVPlayerItem*item = [AVPlayerItemplayerItemWithURL:videoURL];
returnitem;
}
#pragma mark - 添加 监控
/** 给player 添加 time observer */
- (void)addPlayerObserver
{
__weak typeof(self)weakSelf = self;
_timeObser = [self.player addPeriodicTimeObserverForInterval:CMTimeMake(1.0, 1.0) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
AVPlayerItem*playerItem = weakSelf.player.currentItem;
floatcurrent =CMTimeGetSeconds(time);
floattotal =CMTimeGetSeconds([playerItemduration]);
if(weakSelf.isSeeking) {
return;
}
if(weakSelf.XCMusicManagerDelegate&& [weakSelf.XCMusicManagerDelegaterespondsToSelector:@selector(refreshDataWith:CurrentTime:LoadRange:Progress:)]) {
[weakSelf.XCMusicManagerDelegaterefreshDataWith:totalCurrentTime:currentLoadRange:weakSelf.loadRangeProgress:current/total];
}
// NSLog(@"当前播放进度 %.2f/%.2f.",current,total);
}];
}
/** 移除 time observer */
- (void)removePlayerObserver
{
[_player removeTimeObserver:_timeObser];
}
#pragma mark - 给当前播放的item 添加观察者
/** 给当前播放的item 添加观察者
需要监听的字段和状态
status : AVPlayerItemStatusUnknown,AVPlayerItemStatusReadyToPlay,AVPlayerItemStatusFailed
loadedTimeRanges : 缓冲进度
playbackBufferEmpty : seekToTime后,缓冲数据为空,而且有效时间内数据无法补充,播放失败
playbackLikelyToKeepUp : seekToTime后,可以正常播放,相当于readyToPlay,一般拖动滑竿菊花转,到了这个这个状态菊花隐藏
*/
- (void)addObserverWithPlayItem:(AVPlayerItem*)item
{
[itemaddObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
[itemaddObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
[itemaddObserver:self forKeyPath:@"playbackBufferEmpty" options:NSKeyValueObservingOptionNew context:nil];
[itemaddObserver:self forKeyPath:@"playbackLikelyToKeepUp" options:NSKeyValueObservingOptionNew context:nil];
}
/** 移除 item 的 observer */
- (void)removeObserverWithPlayItem:(AVPlayerItem*)item
{
[itemremoveObserver:self forKeyPath:@"status"];
[itemremoveObserver:self forKeyPath:@"loadedTimeRanges"];
[itemremoveObserver:self forKeyPath:@"playbackBufferEmpty"];
[itemremoveObserver:self forKeyPath:@"playbackLikelyToKeepUp"];
}
/** 数据处理 获取到观察到的数据 并进行处理 */
- (void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void*)context
{
AVPlayerItem*item = object;
if([keyPathisEqualToString:@"status"]) {// 播放状态
[self handleStatusWithPlayerItem:item];
}elseif([keyPathisEqualToString:@"loadedTimeRanges"]) {// 缓冲进度
[self handleLoadedTimeRangesWithPlayerItem:item];
}elseif([keyPathisEqualToString:@"playbackBufferEmpty"]) {// 跳转后没数据
// 转菊花
if(self.isCanPlay) {
NSLog(@"跳转后没数据");
self.needBuffer=YES;
[self useDelegateWith:XCMVPlayerStatusCacheData];
}
}else if ([keyPath isEqualToString:@"playbackLikelyToKeepUp"]) {// 跳转后有数据
// 隐藏菊花
if (self.isCanPlay && self.needBuffer) {
NSLog(@"跳转后有数据");
self.needBuffer=NO;
[self useDelegateWith:XCMVPlayerStatusCacheEnd];
}
}
}
/**
处理 AVPlayerItem 播放状态
AVPlayerItemStatusUnknown 状态未知
AVPlayerItemStatusReadyToPlay 准备好播放
AVPlayerItemStatusFailed 播放出错
*/
- (void)handleStatusWithPlayerItem:(AVPlayerItem*)item
{
AVPlayerItemStatus status = item.status;
switch(status) {
case AVPlayerItemStatusReadyToPlay: // 准备好播放
NSLog(@"AVPlayerItemStatusReadyToPlay");
self.isCanPlay=YES;
[self useDelegateWith:XCMVPlayerStatusReadyToPlay];
break;
case AVPlayerItemStatusFailed: // 播放出错
NSLog(@"AVPlayerItemStatusFailed");
[self useDelegateWith:XCMVPlayerStatusItemFailed];
break;
case AVPlayerItemStatusUnknown: // 状态未知
NSLog(@"AVPlayerItemStatusUnknown");
break;
default:
break;
}
}
/** 处理缓冲进度 */
- (void)handleLoadedTimeRangesWithPlayerItem:(AVPlayerItem*)item
{
NSArray*loadArray = item.loadedTimeRanges;
CMTimeRangerange = [[loadArrayfirstObject]CMTimeRangeValue];
floatstart =CMTimeGetSeconds(range.start);
floatduration =CMTimeGetSeconds(range.duration);
NSTimeIntervaltotalTime = start + duration;// 缓存总长度
_loadRange= totalTime;
// NSLog(@"缓冲进度 -- %.2f",totalTime);
}
/**
添加关键通知
AVPlayerItemDidPlayToEndTimeNotification 视频播放结束通知
AVPlayerItemTimeJumpedNotification 视频进行跳转通知
AVPlayerItemPlaybackStalledNotification 视频异常中断通知
UIApplicationDidEnterBackgroundNotification 进入后台
UIApplicationDidBecomeActiveNotification 返回前台
*/
- (void)addNotificatonForPlayer
{
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[centeraddObserver:self selector:@selector(videoPlayEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
// [center addObserver:self selector:@selector(videoPlayToJump:) name:AVPlayerItemTimeJumpedNotification object:nil];//没意义
[centeraddObserver:self selector:@selector(videoPlayError:) name:AVPlayerItemPlaybackStalledNotification object:nil];
if(@available(iOS8.2, *)) {
[centeraddObserver:selfselector:@selector(videoPlayEnterBack:)name:NSExtensionHostDidEnterBackgroundNotificationobject:nil];
}else{
// Fallback on earlier versions
}
if(@available(iOS8.2, *)) {
[centeraddObserver:selfselector:@selector(videoPlayBecomeActive:)name:NSExtensionHostDidBecomeActiveNotificationobject:nil];
}else{
// Fallback on earlier versions
}
}
/** 移除 通知 */
- (void)removeNotification
{
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[centerremoveObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
// [center removeObserver:self name:AVPlayerItemTimeJumpedNotification object:nil];
[centerremoveObserver:self name:AVPlayerItemPlaybackStalledNotification object:nil];
if(@available(iOS8.2, *)) {
[centerremoveObserver:self name:NSExtensionHostDidEnterBackgroundNotification object:nil];
}else{
// Fallback on earlier versions
}
if(@available(iOS8.2, *)) {
[centerremoveObserver:self name:NSExtensionHostDidBecomeActiveNotification object:nil];
}else{
// Fallback on earlier versions
}
[centerremoveObserver:self];
}
/** 视频播放结束 */
- (void)videoPlayEnd:(NSNotification*)notic
{
NSLog(@"视频播放结束");
[self useDelegateWith:XCMVPlayerStatusPlayEnd];
[self.player seekToTime:kCMTimeZero];
}
/** 视频异常中断 */
- (void)videoPlayError:(NSNotification*)notic
{
NSLog(@"视频异常中断");
[self useDelegateWith:XCMVPlayerStatusPlayStop];
}
/** 进入后台 */
- (void)videoPlayEnterBack:(NSNotification*)notic
{
NSLog(@"进入后台");
[self useDelegateWith:XCMVPlayerStatusEnterBack];
}
/** 返回前台 */
- (void)videoPlayBecomeActive:(NSNotification*)notic
{
NSLog(@"返回前台");
[self useDelegateWith:XCMVPlayerStatusBecomeActive];
}
/**
播放状态代理调用
@param status 播放状态
*/
- (void)useDelegateWith:(XCMVPlayerStatus)status
{
if(self.isCanPlay==NO) {
return;
}
if(self.XCMusicManagerDelegate&& [self.XCMusicManagerDelegaterespondsToSelector:@selector(promptPlayerStatusOrErrorWith:)]) {
[self.XCMusicManagerDelegate promptPlayerStatusOrErrorWith:status];
}
}
/** 播放 */
- (void)play
{
NSLog(@"播放");
[self.playerplay];
}
/** 暂停 */
- (void)pause
{
[self.playerpause];
}
/** 拖动视频进度 */
- (void)seekPlayerTimeTo:(NSTimeInterval)time
{
[selfpause];
[self startToSeek];
__weak typeof(self)weakSelf = self;
[self.player seekToTime:CMTimeMake(time, 1.0) completionHandler:^(BOOL finished) {
[weakSelfendSeek];
[weakSelfplay];
}];
}
/** 跳动中不监听 */
- (void)startToSeek
{
self.isSeeking = YES;
}
- (void)endSeek
{
self.isSeeking = NO;
}
/** 快进15s */
- (void)fastForward{
[selfpause];
[self startToSeek];
__weak typeof(self)weakSelf = self;
[self.player seekToTime:CMTimeMakeWithSeconds(self.currentItem.currentTime.value/self.currentItem.currentTime.timescale+15, self.currentItem.currentTime.timescale) completionHandler:^(BOOL finished) {
[weakSelfendSeek];
[weakSelfplay];
}];
}
/** 快退15s */
- (void)fastBack{
[selfpause];
[self startToSeek];
__weak typeof(self)weakSelf = self;
[self.player seekToTime:CMTimeMakeWithSeconds(self.currentItem.currentTime.value/self.currentItem.currentTime.timescale-15, self.currentItem.currentTime.timescale) completionHandler:^(BOOL finished) {
[weakSelfendSeek];
[weakSelfplay];
}];
}
/** 正在播放 */
- (BOOL)isPlay{
return _player.rate==1?YES:NO;
}
使用
NSString *path =[ [NSBundle mainBundle] pathForResource:@"avengers" ofType:@"mp3"];
NSURL *url = [NSURL fileURLWithPath:path];
[musicManager setPlayerWith:url];
musicManager.XCMusicManagerDelegate = self;
[musicManager play];