基于系統(tǒng)AVplayer的音頻播放實現1.0-2.0倍速播放,鎖屏播放,記憶播放,斷點播放

音頻播放主要是基于系統(tǒng)的AVPlayerAVPlayerItem,不像視頻需要顯示畫面需要AVPlayerLayer

實現思路:

建一個繼承于NSObject類.h .m文件,或許你疑問為什么不寫在view或者viewcontrol中,因為音頻播放你只需要處理數據就行了,比如做快進快退操作,下一曲上一曲操作,只需要把數據傳給它做數據操作就行了.下面看代碼;

建一個繼承與NSobject類起名叫MAudioPlayer的文件

導入

#import <AVFoundation/AVFoundation.h>
#import <MediaPlayer/MediaPlayer.h>

在.h中添加一些屬性,用于外部訪問,比如當前播放器播放的model等,再寫一些方法用于外部調用,比如暫停,播放等

@property (nonatomic ,strong,readonly) AVPlayer *player;
@property (nonatomic, assign, readonly) NSInteger currentTime;/*!*當前播放器時間*/
@property (nonatomic, assign, readonly) NSInteger totalTime;/*!*當前播放器總時間*/
@property (nonatomic, assign,readonly) MAudioPlayState playerState;/*用枚舉定義了播放器狀態(tài)*/
@property (nonatomic, assign) float ratevalue;/*!*倍速*/

定義的枚舉類型,可自定義添加狀態(tài)

typedef NS_ENUM(NSUInteger, MAudioPlayState) {
MAudioStatePlaying = 1,
MAudioStatePaused,
MAudioStateWaiting,
};

還可以加一個model,在播放的時候將當前播放的model傳給播放器,下面代碼中,在播放器代理里面有使用到這個model

/*
 * 當前音頻模型
 */
@property (nonatomic, strong) DetailCourseListModel *currentAudioModel;

定義了一個播放器代理,實現兩個方法,一個是實時回傳播放器時間刷新UI,一個是播放完畢,做暫?;蛘呦乱磺僮?/p>

@protocol MAudioPlayerDelegate <NSObject>

- (void)audioUpdateWith:(float)time Totaltime:(float)totalTime;

-(void)audioPlayEnd;

@end

在.m中聲明AVPlayerItem對象

@property (nonatomic ,strong) AVPlayerItem *playerItem;

再添加一個時間觀察,用于實時刷新UI數據
@property (nonatomic, strong) id timeObserve;// 時間觀察
還可以再加一些輔助對象,比如:電話監(jiān)聽,耳機拔插監(jiān)聽,來電前播放狀態(tài),鎖屏播放的存儲的字典等
@property (nonatomic, strong) CTCallCenter *callCenter ;/*!*監(jiān)聽電話*/
@property (nonatomic, assign) BOOL isPlay;/*!*播放或者暫停*/
@property (nonatomic, strong) NSMutableDictionary *imageSpaceDict;/*!*存圖片字典*/

用單例初始化保證全局只有一個播放器
+ (instancetype)sharedMPlayer

預留一些播放暫停,播放,切換下一曲等操作方法

初始化播放器方法

/**
初始化播放器

 @param url 播放地址
 @param recordTime 指定時間播放
 @param ratevalue 倍速,1.0~2.0倍速播放
*/
- (void)initWithUrl:(NSString *)url seekTotime:(NSInteger)recordTime rateValue:(float)ratevalue;

- (void)playerPaused;//暫停播放方法
- (void)playerPlay//開啟播放
- (void)closePlayer//移除播放器
-(void)SetlockScreenInformation:(DetailCourseListModel *)model;//鎖屏方法,model根據需求自定義

現在看一下.m中的代碼

單例初始化對象,保證整個app只存在一個播放器

+ (instancetype)sharedMPlayer
{
    static MAudioPlayer *audioPlayer = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
     audioPlayer = [[MAudioPlayer alloc] init];
    
 });
    return audioPlayer;
}

初始化播放器

- (void)initWithUrl:(NSString *)url seekTotime :(NSInteger)recordTime rateValue:(float)ratevalue{
    //[self callStatCenter];//監(jiān)聽電話
   //[self audioRouteChangeListener];//拔插耳機
   self.playerItem = [[AVPlayerItem alloc] initWithURL:[NSURL URLWithString:localString]];
    _ratevalue = ratevalue;//倍速
    //AVPlayer
    _player = [AVPlayer playerWithPlayerItem:self.playerItem];
    [_player play];
    //指定時間播放,類似記憶播放,拖動進度條也用這個方法
    [_player seekToTime:CMTimeMake(recordTime, 1)];
}

因為self.playerItem用的是懶加載,看一下,item怎么初始化,做了哪些操作

/**
 *  根據playerItem,來添加移除觀察者
 *
 *  @param playerItem playerItem
 */
- (void)setPlayerItem:(AVPlayerItem *)playerItem
{
    //如果初始化的item與當前item相等,則不做操作
    if (_playerItem == playerItem) {return;}
    //如果當前item不為空,移除里面的屬性觀察
    if (_playerItem) {
        [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:_playerItem];
        [_playerItem removeObserver:self forKeyPath:@"status"];
    }
    _playerItem = playerItem;
    if (playerItem) {
    //當前音頻播放完畢監(jiān)聽,我這里寫的代理,方便數據傳遞
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(moviePlayDidEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:playerItem];
        //監(jiān)聽播放器狀態(tài)
        [playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
      //除了播放器狀態(tài),還可以監(jiān)聽緩沖狀態(tài):無緩沖playbackBufferEmpty,緩沖足夠可以播放:playbackBufferEmpty等,具體狀態(tài)可以百度查找 
    }
 }

增加一個觀察,用來觀察播放器,暫停,播放等狀態(tài),方便刷新UI

//觀察播放器狀態(tài)
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSString*, id> *)change context:(nullable void *)context {
    
    if ([keyPath isEqualToString:@"status"]) {
        AVPlayerItemStatus status = _playerItem.status;
        switch (status) {
            case AVPlayerItemStatusReadyToPlay:
            {
                self.playerItem.audioTimePitchAlgorithm = AVAudioTimePitchAlgorithmTimeDomain;
                 [_player play];
                 //如果想實現倍速播放,必須調用此方法
                [self enableAudioTracks:YES inPlayerItem:_playerItem];
                self.player.rate = _ratevalue;
                //增加一個時間觀察,為了實時拿到當前播放時間,刷新UI,鎖屏操作等
                [self addTimeObserve];
            }
                break;
            case AVPlayerItemStatusUnknown:
            {
                BLLog(@"AVPlayerItemStatusUnknown");
            }
                break;
            case AVPlayerItemStatusFailed:
            {
                BLLog(@"AVPlayerItemStatusFailed");
                BLLog(@"%@",_playerItem.error);
            }
                break;
                
            default:
                break;
        }
    }

}

每一秒刷新UI

- (void)addTimeObserve{
    __weak typeof(self) weakSelf = self;
    self.timeObserve = [self.player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(1, 1) queue:nil usingBlock:^(CMTime time){
    AVPlayerItem *currentItem = weakSelf.playerItem;
     NSArray *loadedRanges = currentItem.seekableTimeRanges;
    NSInteger currentTime = (NSInteger)CMTimeGetSeconds([currentItem currentTime]);
    CGFloat totalTime = (CGFloat)currentItem.duration.value / currentItem.duration.timescale;
    if (self.delegateM && [self.delegateM respondsToSelector:@selector(audioUpdateWith:Totaltime:)]) {
 //播放器時間代理
    [weakSelf.delegateM audioUpdateWith:currentTime Totaltime:totalTime];
        }
        //根據系統(tǒng)方法來判斷播放器狀態(tài),供外部屬性調用實時刷新UI,比如:外部播放器按鈕狀態(tài)可根據可狀態(tài)播放,點擊播放還是暫停,也可以通過此狀態(tài)判斷
        if (self.player.timeControlStatus == AVPlayerTimeControlStatusPlaying) {
            _playerState = MAudioStatePlaying;
        }
        if (self.player.timeControlStatus == AVPlayerTimeControlStatusPaused) {
            _playerState = MAudioStatePaused;
        }
        if (self.player.timeControlStatus == AVPlayerTimeControlStatusWaitingToPlayAtSpecifiedRate) {
            _playerState = MAudioStateWaiting;
        }
        if (loadedRanges.count > 0 && currentItem.duration.timescale != 0) {
            _totalTime = totalTime;
            
            _currentTime = currentTime;
        }
        
    }];
}

倍速切換方法

- (void)enableAudioTracks:(BOOL)enable inPlayerItem:(AVPlayerItem*)playerItem
{
    for (AVPlayerItemTrack *track in playerItem.tracks)
    {
        if ([track.assetTrack.mediaType isEqual:AVMediaTypeAudio])
        {
            track.enabled = enable;
        }
    }
}

下面就是實現播放器播放,暫停等方法了

//播放暫停
- (void)playerPaused {
    [self.player pause];
    
}
//播放繼續(xù)
- (void)playerPlay {
    [self.player play];
    self.player.rate = _ratevalue;
}

關閉播放器,記得移除通知,置空播放器

- (void)closePlayer{
    [self.player.currentItem cancelPendingSeeks];
    [self.player.currentItem.asset cancelLoading];
    self.playerItem = nil;
    [self.player replaceCurrentItemWithPlayerItem:nil];
    _player = nil;
    self.ratevalue = 1.0;
    self.callCenter = nil;
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    
}

對,還有播放器鎖屏的實現方法,可在.h里面寫一個鎖屏方法供外部調用,鎖屏方法就是在外部播放器代理里面調用,如果鎖屏中加載圖片為網絡圖片的話,最好做一個字典通過key-value來存儲

-(void)SetlockScreenInformation:(DetailCourseListModel *)model{
    //model是項目中用到的,可根據自己需求定義
    NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithDictionary:[[MPNowPlayingInfoCenter defaultCenter] nowPlayingInfo]];
    
    [dict setObject:model.teacherName==nil?@"1":model.name forKey:MPMediaItemPropertyTitle];
    //此表現形式 name-title 副標題
    //    [dict setObject:recordUploadModel.teacherName==nil?@"1":recordUploadModel.teacherName forKey:MPMediaItemPropertyArtist];
    
    [dict setObject:model.name==nil?@"1":model.name forKey:MPMediaItemPropertyAlbumTitle];
    NSString *imageUrl;
    //判斷當前是個鏈接還是上傳路徑
    if ([model.imgUrl hasPrefix:@"http"]) {
        imageUrl = model.imgUrl;
    }else{
    //拼接圖片url
       imageUrl = [NSString stringWithFormat:@"%@%@", imageUrlString, model.imgUrl];
    }
    
    
    if (![self.imageSpaceDict objectForKey:imageUrl]) {
        NSLog(@"走了幾次啊");
        
        UIImage *imageM = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:imageUrl]]];
        [self.imageSpaceDict setValue:imageM forKey:imageUrl];
    }
    UIImage *tempImage = [UIImage imageNamed:@"加載中"];
    
    [dict setObject:[[MPMediaItemArtwork alloc] initWithImage:[self.imageSpaceDict objectForKey:imageUrl] == nil?tempImage:[self.imageSpaceDict objectForKey:imageUrl]] forKey:MPMediaItemPropertyArtwork];
    //當前已經過時間
    [dict setObject:[NSNumber numberWithDouble:_currentTime] forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime];
    //總時間
    [dict setObject:[NSNumber numberWithDouble:_totalTime] forKey:MPMediaItemPropertyPlaybackDuration];
    [[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:dict];
}

寫了那么多了,估計看的也沒有耐心了.鎖屏后怎么在鎖屏界面或控制中心對播放器做暫停,上一曲下一曲等操作呢,可以在控制器中加入以下代碼:

-(void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
    // 開始接受遠程控制
    [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
    //成為第一響應者
    [self becomeFirstResponder];
    //  開啟界面常亮
    [[UIApplication sharedApplication] setIdleTimerDisabled:YES];
}
  
-(void)viewWillDisappear:(BOOL)animated{
    [super viewWillDisappear:animated];
    //接觸遠程控制
    [[UIApplication sharedApplication] endReceivingRemoteControlEvents];
    [self resignFirstResponder];
    //  關閉界面常亮
    [[UIApplication sharedApplication] setIdleTimerDisabled:NO];

}

重寫父類成為響應者方法

// 重寫父類成為響應者方法
- (BOOL)canBecomeFirstResponder
{
    return YES;
}
//重寫父類方法,接受外部事件的處理
- (void)remoteControlReceivedWithEvent: (UIEvent *) receivedEvent {
    if (receivedEvent.type == UIEventTypeRemoteControl) {
        
        switch (receivedEvent.subtype) { // 得到事件類型
                
            case UIEventSubtypeRemoteControlTogglePlayPause: // 暫停ios6
              
            break;
                
            case UIEventSubtypeRemoteControlPreviousTrack:  // 上一首
                
            break;
                
            case UIEventSubtypeRemoteControlNextTrack: // 下一首
               
            break;
                
            case UIEventSubtypeRemoteControlPlay: //播放
            break;
                
            case UIEventSubtypeRemoteControlPause: // 暫停 ios7
                
            break;
                
            default:
            break;
        }
    }
}

給鎖屏界面實時傳數據呢,就在播放器代理里面

#pragma mark - 播放器代理時間
- (void)audioUpdateWith:(float)time Totaltime:(float)totalTime{
    //滑動進度條
    _slider.value = time/totalTime;
    //刷新當前時間,通過擴展方法轉化成00:00:00格式
    _currentTimeLB.text = [NSString timeTransformString:(float)time];
    //刷新總時間
    _totalTimeLB.text = [NSString timeTransformString:(float)totalTime];
    //當前標題,就是model里面的
    _titleLB.text = [NSString stringWithFormat:@"%@",MAudioPlay.currentAudioModel.name];
    //在代理里面調用鎖屏方法,傳數據
    [MAudioPlay SetlockScreenInformation:MAudioPlay.currentAudioModel];
}

秒轉換成00:00:00格式,我寫的是NSString的擴展屬性,此方法也是在網上找的,用到手動釋放,需要在工程TARGETS->Build Phases 找到你寫此方法的文件,雙擊加入-fno-objc-arc 方法如下:

+(NSString *)timeTransformString:(unsigned long)ms
{
    unsigned long seconds, h, m, s;
    char buff[128] = { 0 };
    NSString *time = nil;
    
    seconds = ms ;
    h = seconds / 3600;
    m = (seconds - h * 3600) / 60;
    s = seconds - h * 3600 - m * 60;
    snprintf(buff, sizeof(buff), "%02ld:%02ld:%02ld", h, m, s);
    time = [[[NSString alloc] initWithCString:buff
                                      encoding:NSUTF8StringEncoding] autorelease];
    
    return time;
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容