最近在写关于音视频播放的案例,所以就趁机会研究了一下AVPlayer的内容。我封装的目前只能播放网络音视频。还未添加缓存,以后找机会研究一下再更新。代码中提供了音视频的上一曲、下一曲、暂停、开始、停止、单曲播放、顺序播放、随机播放等功能。代码写的不好,仅供参考~
代码接口文件
#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
NS_ASSUME_NONNULL_BEGIN
// 当前播放器的播放形式
typedef NS_ENUM(NSInteger, MediaPlayType) {
MediaPlayTypeCycle, ///< 顺序
MediaPlayTypeSingle, ///< 单曲循环
MediaPlayTypeRandom ///< 随机播放
};
// 当前播放器的播放状态
typedef NS_ENUM(NSInteger, MediaPlayStatus) {
MediaPlayStatusStop, ///< 停止播放
MediaPlayStatusPause, ///< 暂停播放
MediaPlayStatusPlaying ///< 正在播放
};
// 媒体加载状态
typedef NS_ENUM(NSInteger, MediaLoadStatus) {
MediaLoadStatusReadyToPlay, ///< 准备播放
MediaLoadStatusUnknown, ///< 未知
MediaPlayStatusFailed ///< 失败
};
@class MediaPlyerManager;
@protocol MediaPlyerManagerDelegate <NSObject>
@optional
// 数据加载状态 根据状态进行播放或其他操作
- (void)MediaPlayer:(MediaPlyerManager *)playerManager playerItemStatus:(MediaLoadStatus)status;
// 缓冲进度
- (void)MediaPlayer:(MediaPlyerManager *)playerManager netBufferValue:(CGFloat)value;
// 缓冲是否足够播放
- (void)MediaPlayer:(MediaPlyerManager *)playerManager bufferHasEnough:(BOOL)enough;
// 当前播放的时间
- (void)MediaPlayer:(MediaPlyerManager *)playerManager currentPlayTime:(NSString *)time currentPlayTimeValue:(CGFloat)value;
// 播放总时间
- (void)MediaPlayer:(MediaPlyerManager *)playerManager mediaEndTime:(NSString *)time mediaEndTimeValue:(CGFloat)value;
// 播放结束
- (void)MediaPlayerCurrentMediaPlayFinish:(MediaPlyerManager *)playerManager;
// 播放状态
- (void)MediaPlayer:(MediaPlyerManager *)playerManager playeStatus:(MediaPlayStatus)status;
// 获取数据切换时获取正在播放的URL和当前的index
- (void)MediaPlayer:(MediaPlyerManager *)playerManager currentUrl:(NSString *)url currentIndex:(NSInteger)index;
// 为了配合手机后台播放 实时获取播放的进度,总的时间,当前的index<通过index获取图片等信息>
- (void)MediaPlayer:(MediaPlyerManager *)playerManager currentProgressValue:(CGFloat)value totalValue:(CGFloat)totalValue currentIndex:(NSInteger)index;
@end
typedef MediaPlyerManager *(^playerCurrentTime)(NSString *time);
@interface MediaPlyerManager : NSObject
@property (nonatomic, strong, readonly) AVPlayer *mediaPlayer; ///< 播放器
@property (nonatomic, strong, readonly) AVPlayerItem *meidaPlayerItem; ///< 播放器的CurrentItem
@property (nonatomic, strong, readonly) NSMutableArray<NSString*> *dataUrlArray; ///< 正在播放的列表数据
@property (nonatomic, assign, readonly) MediaPlayType playType; ///< 当前播放类型
@property (nonatomic, assign, readonly) MediaPlayStatus playStatus; ///< 当前播放状态
@property (nonatomic, assign, readonly) NSInteger currentIndex; ///< 当前播放的索引
@property (nonatomic, assign, readonly) BOOL isPlaying; ///< 是否在播放
@property (nonatomic, assign, readonly) CGFloat curentPlayTimeValue; ///< 当前播放时间值
@property (nonatomic, copy, readonly) NSString *curentPlayTime; ///< 当前播放时间
@property (nonatomic, assign, readonly) CGFloat endPlayTimeValue; ///< 当前播放时间值
@property (nonatomic, copy, readonly) NSString *endPlayTime; ///< 当前播放时间
+ (instancetype)defaultManager;
/**
列表播放 ⚠️<默认不自动播放>
@param urls 文件路径数组
@param delegate 回调代理
@return MediaPlyerManager
*/
- (MediaPlyerManager *)playerWithUrls:(NSArray<NSString *> *)urls actionWithDelegate:(id<MediaPlyerManagerDelegate>)delegate;
/**
单个音视频播放 ⚠️<默认不自动播放>
@param url 文件路径
@param delegate 回调代理
@return MediaPlyerManager
*/
- (MediaPlyerManager *)playerWithUrl:(NSString *)url actionWithDelegate:(id<MediaPlyerManagerDelegate>)delegate;
/**
开始播放
*/
- (void)play;
/**
暂停播放
*/
- (void)pause;
/**
停止播放
*/
- (void)stop;
/**
下一曲
*/
- (void)next;
/**
上一曲
*/
- (void)previous;
/**
指定进度开始播放
@param progress 进度百分比
*/
- (void)setupPlayerSeekToProgress:(CGFloat)progress;
/**
制定播放类型
@param type 类型
*/
- (void)setupMediaPlayerType:(MediaPlayType)type;
/**
指定播放的index
@param index 索引
*/
- (void)setupPlayerIndex:(NSInteger)index;
/**
添加数据
@param files 文件数组
@param index 索引
*/
- (void)insertMediaFile:(NSArray<NSString *> *)files atIndex:(NSInteger)index;
/**
移除全部数据
*/
- (void)removeAllFiles;
/**
移除索引中的单个数据
@param index 索引
*/
- (void)removeObjectAtIndex:(NSInteger)index;
/**
设置锁屏样式
@param coverImage 专辑图片
@param size 显示大小
@param title 标题
@param author 专辑作者
@param album 专辑名称
@param currentTime 当前播放时间
@param duration 播放总时长
*/
- (void)setupLockScreenPlayInfo:(UIImage *)coverImage
imageSize:(CGSize)size
title:(NSString *)title
ahthor:(NSString *)author
album:(NSString *)album
currentPlayTime:(CGFloat)currentTime
duration:(CGFloat)duration;
@end
NS_ASSUME_NONNULL_END
代码实现文件
#import "MediaPlyerManager.h"
#import <MediaPlayer/MediaPlayer.h>
@interface MediaPlyerManager ()
@property (nonatomic, strong, readwrite) NSMutableArray<NSString *> *dataUrlArray;
@property (nonatomic, strong, readwrite) AVPlayer *mediaPlayer;
@property (nonatomic, assign, readwrite) BOOL isPlaying;
@property (nonatomic, strong, readwrite) AVPlayerItem *meidaPlayerItem;
@property (nonatomic, assign, readwrite) MediaPlayType playeType;
@property (nonatomic, assign, readwrite) NSInteger currentIndex;
@property (nonatomic, assign, readwrite) MediaPlayStatus playStatus;
@property (nonatomic, assign, readwrite) CGFloat curentPlayTimeValue;
@property (nonatomic, copy, readwrite ) NSString *curentPlayTime;
@property (nonatomic, assign, readwrite) CGFloat endPlayTimeValue;
@property (nonatomic, copy, readwrite ) NSString *endPlayTime;
@property (nonatomic, weak ) id <MediaPlyerManagerDelegate> delegate;
@end
@implementation MediaPlyerManager
+ (instancetype)defaultManager {
static dispatch_once_t onceToken;
static MediaPlyerManager *manger;
dispatch_once(&onceToken, ^{
manger = [[MediaPlyerManager alloc] init];
});
return manger;
}
#pragma mark - 初始化
- (MediaPlyerManager *)playerWithUrl:(NSString *)url actionWithDelegate:(id<MediaPlyerManagerDelegate>)delegate {
[self playerWithUrls:@[url] actionWithDelegate:delegate];
return self;
}
- (MediaPlyerManager *)playerWithUrls:(NSArray<NSString *> *)urls actionWithDelegate:(id<MediaPlyerManagerDelegate>)delegate {
self.delegate = delegate;
self.currentIndex = 0;
self.dataUrlArray = [NSMutableArray array];
NSMutableArray *array = [NSMutableArray arrayWithCapacity:urls.count];
for (NSString *urlStr in urls) {
[array addObject:[self createPlayerItemWithUrl:urlStr]];
[self.dataUrlArray addObject:urlStr];
}
self.playeType = MediaPlayTypeCycle;
self.mediaPlayer = [[AVPlayer alloc] initWithPlayerItem:array.firstObject];
self.meidaPlayerItem = self.mediaPlayer.currentItem;
[self getCurrentIndex:self.currentIndex];
[self addObserver];
__weak typeof(self) weakself = self;
[self.mediaPlayer addPeriodicTimeObserverForInterval:CMTimeMake(1, 1) queue:NULL usingBlock:^(CMTime time) {
NSString *currentString = [weakself getStringFromCMTime:time];
weakself.curentPlayTime = currentString;
weakself.curentPlayTimeValue = (CGFloat)time.value/time.timescale;
#pragma mark - 获取当前播放时间
if (weakself.delegate && [weakself.delegate respondsToSelector:@selector(MediaPlayer:currentPlayTime:currentPlayTimeValue:)]) {
[weakself.delegate MediaPlayer:weakself currentPlayTime:currentString currentPlayTimeValue:(CGFloat)time.value/time.timescale];
}
#pragma mark - 实时获取播放信息
if (self.delegate && [self.delegate respondsToSelector:@selector(MediaPlayer:currentProgressValue:totalValue:currentIndex:)]) {
[weakself.delegate MediaPlayer:weakself currentProgressValue:weakself.curentPlayTimeValue totalValue:weakself.endPlayTimeValue currentIndex:weakself.currentIndex];
}
}];
return self;
}
#pragma mark - 播放结束
- (void)playFinish:(NSNotification *)notification {
if (self.delegate && [self.delegate respondsToSelector:@selector(MediaPlayerCurrentMediaPlayFinish:)]) {
[self.delegate MediaPlayerCurrentMediaPlayFinish:self];
}
if (self.playeType == MediaPlayTypeSingle) {
[self.mediaPlayer seekToTime:kCMTimeZero];
[self play];
} else {
if (self.currentIndex < self.dataUrlArray.count - 1) {
self.currentIndex += 1;
} else {
self.currentIndex = 0;
}
[self.mediaPlayer replaceCurrentItemWithPlayerItem:[self createPlayerItemWithUrl:self.dataUrlArray[self.currentIndex]]];
self.meidaPlayerItem = self.mediaPlayer.currentItem;
[self getCurrentIndex:self.currentIndex];
[self play];
}
}
#pragma mark - KVO
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey,id> *)change
context:(void *)context {
AVPlayerItem *playerItem = object;
if ([keyPath isEqualToString:@"status"]) {
MediaLoadStatus status = [change[@"new"] integerValue];
#pragma mark - 获取媒体加载状态
if (self.delegate && [self.delegate respondsToSelector:@selector(MediaPlayer:playerItemStatus:)]) {
[self.delegate MediaPlayer:self playerItemStatus:status];
}
} else if ([keyPath isEqualToString:@"loadedTimeRanges"]) {
NSArray * timeRanges = playerItem.loadedTimeRanges;
CMTimeRange timeRange = [timeRanges.firstObject CMTimeRangeValue];
NSTimeInterval totalLoadTime = CMTimeGetSeconds(timeRange.start) \
+ CMTimeGetSeconds(timeRange.duration);
NSTimeInterval duration = CMTimeGetSeconds(playerItem.duration);
NSTimeInterval scale = totalLoadTime/duration;
#pragma mark - 获取媒体总时间
if ((CGFloat)duration/scale >= 0) {
self.endPlayTime = [self getStringFromCMTime:playerItem.duration];
self.endPlayTimeValue = (CGFloat)duration/scale;
if (self.delegate && [self.delegate respondsToSelector:@selector(MediaPlayer:mediaEndTime:mediaEndTimeValue:)]) {
[self.delegate MediaPlayer:self mediaEndTime:[self getStringFromCMTime:playerItem.duration] mediaEndTimeValue:(CGFloat)duration/scale];
}
}
#pragma mark - 缓冲百分比
if (self.delegate && [self.delegate respondsToSelector:@selector(MediaPlayer:netBufferValue:)]) {
[self.delegate MediaPlayer:self netBufferValue:scale];
}
} else if ([keyPath isEqualToString:@"playbackBufferEmpty"]) {
#pragma mark - 缓冲不足够播放
if (self.delegate && [self.delegate respondsToSelector:@selector(MediaPlayer:bufferHasEnough:)]) {
[self.delegate MediaPlayer:self bufferHasEnough:false];
}
} else if ([keyPath isEqualToString:@"playbackLikelyToKeepUp"]) {
#pragma mark - 缓冲足够播放
if (self.delegate && [self.delegate respondsToSelector:@selector(MediaPlayer:bufferHasEnough:)]) {
[self.delegate MediaPlayer:self bufferHasEnough:true];
}
}
}
#pragma mark - 开始播放
- (void)play {
[self.mediaPlayer play];
[self getPlayStatus:MediaPlayStatusPlaying];
}
#pragma mark - 暂停播放
- (void)pause {
[self.mediaPlayer pause];
[self getPlayStatus:MediaPlayStatusPause];
}
#pragma mark - 停止播放
- (void)stop {
[self.mediaPlayer replaceCurrentItemWithPlayerItem:nil];
[self getPlayStatus:MediaPlayStatusStop];
[self removeObserver];
}
#pragma mark - 下一个
- (void)next {
if (self.playeType == MediaPlayTypeRandom) {
self.currentIndex = (NSInteger)arc4random_uniform((int32_t)(self.dataUrlArray.count - 1));
} else {
if (self.currentIndex == self.dataUrlArray.count - 1) {
self.currentIndex = 0;
} else {
self.currentIndex += 1;
}
}
[self.mediaPlayer replaceCurrentItemWithPlayerItem:[self createPlayerItemWithUrl:self.dataUrlArray[self.currentIndex]]];
self.meidaPlayerItem = self.mediaPlayer.currentItem;
[self getCurrentIndex:self.currentIndex];
[self addObserver];
}
#pragma mark - 上一个
- (void)previous {
if (self.playeType == MediaPlayTypeRandom) {
self.currentIndex = (NSInteger)arc4random_uniform((int32_t)(self.dataUrlArray.count - 1));
} else {
if (self.currentIndex == 0) {
self.currentIndex = self.dataUrlArray.count - 1;
} else {
self.currentIndex -= 1;
}
}
[self.mediaPlayer replaceCurrentItemWithPlayerItem:[self createPlayerItemWithUrl:self.dataUrlArray[self.currentIndex]]];
self.meidaPlayerItem = self.mediaPlayer.currentItem;
[self getCurrentIndex:self.currentIndex];
[self addObserver];
}
#pragma mark - 播放状态
- (void)getPlayStatus:(MediaPlayStatus)status {
if (self.delegate && [self.delegate respondsToSelector:@selector(MediaPlayer:playeStatus:)]) {
[self.delegate MediaPlayer:self playeStatus:status];
}
self.playStatus = status;
if (status == MediaPlayStatusPlaying) {
self.isPlaying = true;
} else {
self.isPlaying = false;
}
}
#pragma mark - 根据index进行回调
- (void)getCurrentIndex:(NSInteger)index {
if (self.delegate && [self.delegate respondsToSelector:@selector(MediaPlayer:currentUrl:currentIndex:)]) {
[self.delegate MediaPlayer:self currentUrl:self.dataUrlArray[index] currentIndex:index];
}
}
#pragma mark - 设置播放进度百分比
- (void)setupPlayerSeekToProgress:(CGFloat)progress {
float timeValue = progress * CMTimeGetSeconds(self.mediaPlayer.currentItem.duration);
[self.mediaPlayer seekToTime:CMTimeMake(timeValue, 1)];
}
#pragma mark - 设置播放形式
- (void)setupMediaPlayerType:(MediaPlayType)type {
self.playeType = type;
}
#pragma mark - 播放指定index的媒体
- (void)setupPlayerIndex:(NSInteger)index {
if (index > (self.dataUrlArray.count - 1)) {
@throw [NSException exceptionWithName:@"越界错误" reason:@"index 不能超出URL数组的长度" userInfo:nil];
return;
}
self.currentIndex = index;
[self.mediaPlayer replaceCurrentItemWithPlayerItem:[self createPlayerItemWithUrl:self.dataUrlArray[self.currentIndex]]];
self.meidaPlayerItem = self.mediaPlayer.currentItem;
[self getCurrentIndex:self.currentIndex];
}
#pragma mark - 插入数据
- (void)insertMediaFile:(NSArray<NSString *> *)files atIndex:(NSInteger)index {
for (NSString *urlStr in files) {
NSInteger i = [files indexOfObject:urlStr];
[self.dataUrlArray insertObject:urlStr atIndex:index + i];
}
if (index < self.currentIndex) {
self.currentIndex += 1;
}
}
#pragma mark - 删除数据
- (void)removeAllFiles {
[self stop];
[self.dataUrlArray removeAllObjects];
self.dataUrlArray = [NSMutableArray array];
self.currentIndex = 0;
}
- (void)removeObjectAtIndex:(NSInteger)index {
if (self.dataUrlArray.count == 1) {
[self removeAllFiles];
} else {
[self.dataUrlArray removeObjectAtIndex:index];
if (index == self.currentIndex) {
if (index == 0) {
self.currentIndex = 0;
[self next];
} else {
self.currentIndex -= 1;
}
} else {
if (self.currentIndex > index) {
self.currentIndex -= 1;
}
}
}
}
#pragma mark - Utils
- (NSString *)getStringFromCMTime:(CMTime)time {
float currentTimeValue = (CGFloat)time.value/time.timescale;
NSDate * currentDate = [NSDate dateWithTimeIntervalSince1970:currentTimeValue];
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
NSInteger unitFlags = NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;
NSDateComponents *components = [calendar components:unitFlags fromDate:currentDate];
if (currentTimeValue >= 3600 ) {
return [NSString stringWithFormat:@"%02ld:%02ld:%02ld", (long)components.hour, (long)components.minute, (long)components.second];
} else {
return [NSString stringWithFormat:@"%02ld:%02ld", (long)components.minute, (long)components.second];
}
}
- (void)addObserver {
// 监控状态属性
[self.meidaPlayerItem addObserver:self
forKeyPath:@"status"
options:(NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew)
context:nil];
// 监控缓冲加载情况属性
[self.meidaPlayerItem addObserver:self
forKeyPath:@"loadedTimeRanges"
options:(NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew)
context:nil];
// 监听缓冲不足够播放
[self.meidaPlayerItem addObserver:self
forKeyPath:@"playbackBufferEmpty"
options:NSKeyValueObservingOptionNew
context:nil];
// 监听缓冲足够播放
[self.meidaPlayerItem addObserver:self
forKeyPath:@"playbackLikelyToKeepUp"
options:NSKeyValueObservingOptionNew
context:nil];
// 获取是否播放结束
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(playFinish:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:self.meidaPlayerItem];
}
- (void)removeObserver {
[[NSNotificationCenter defaultCenter] removeObserver:self];
@try {
[self.meidaPlayerItem removeObserver:self forKeyPath:@"loadedTimeRanges"];
[self.meidaPlayerItem removeObserver:self forKeyPath:@"playbackBufferEmpty"];
[self.meidaPlayerItem removeObserver:self forKeyPath:@"playbackLikelyToKeepUp"];
[self.meidaPlayerItem removeObserver:self forKeyPath:@"status"];
}
@catch(NSException *exception) {
NSLog(@"%@", exception);
}
}
#pragma mark - 设置锁屏样式
- (void)setupLockScreenPlayInfo:(UIImage *)coverImage
imageSize:(CGSize)size
title:(NSString *)title
ahthor:(NSString *)author
album:(NSString *)album
currentPlayTime:(CGFloat)currentTime
duration:(CGFloat)duration {
Class playingInfoCenter = NSClassFromString(@"MPNowPlayingInfoCenter");
if (playingInfoCenter) {
NSMutableDictionary *songInfo = [[NSMutableDictionary alloc] init];
MPMediaItemArtwork *albumArt = [[MPMediaItemArtwork alloc] initWithBoundsSize:size requestHandler:^UIImage * _Nonnull(CGSize size) {
return coverImage;
}];
[songInfo setObject:title forKey:MPMediaItemPropertyTitle];
[songInfo setObject:author forKey:MPMediaItemPropertyArtist];
[songInfo setObject:album forKey:MPMediaItemPropertyAlbumTitle];
[songInfo setObject:albumArt forKey:MPMediaItemPropertyArtwork];
[songInfo setObject:[NSNumber numberWithDouble:currentTime] forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime];
[songInfo setObject:[NSNumber numberWithDouble:duration] forKey:MPMediaItemPropertyPlaybackDuration];
[[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:songInfo];
}
}
- (AVPlayerItem *)createPlayerItemWithUrl:(NSString *)url {
return [AVPlayerItem playerItemWithURL:[NSURL URLWithString:url]];
}
@end
提问
本来相同使用AVQueuePlayer来进行列表播放的,但是当做单曲循环的时候遇到问题:通过通知监听播放完成,在通知的方法里进行具体操作,但是设置无效,直接播放的还是下一个文件。如果有人知道如何解决,帮忙回复一下。