美文网首页
AVPlayer封装

AVPlayer封装

作者: 今晚月色 | 来源:发表于2019-01-10 10:45 被阅读47次
    60920ddb4ccdca50074568b5ea54679f.jpg

    最近在写关于音视频播放的案例,所以就趁机会研究了一下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来进行列表播放的,但是当做单曲循环的时候遇到问题:通过通知监听播放完成,在通知的方法里进行具体操作,但是设置无效,直接播放的还是下一个文件。如果有人知道如何解决,帮忙回复一下。

    相关文章

      网友评论

          本文标题:AVPlayer封装

          本文链接:https://www.haomeiwen.com/subject/sdporqtx.html