iOS音频播放之AVAudioPlayer,AVPlayer,AVQueuePlayer

本文以婚语APP为例,来讲解集体使用方法。
在婚语APP中,分别使用了AVAudioPlayer,AVPlayer,AVQueuePlayer来实现音频播放功能,下面以婚语的实际需求分别介绍它们的使用方法和区别。

需求1
档期备忘:用户新建档期记录时,可以进行录音备忘,录音完成后可直接播放,保存档期时将录音文件上传到服务器。

分析1:因为录音备忘一般时长较短文件较小,所以录音完将录音文件上传到服务器的同时,本地也保留录音文件,用户查看档期并点击播放语音备忘时,先读取本地录音文件,找不到时再到服务器下载保存到本地,然后再使用AVAudioPlayer实现本地音频播放。

需求2
婚礼音乐播放:如图,用户可以在线试听一些婚礼现场使用的背景音乐,只能选中某一首背景音乐进行播放,播放完成后停止,不能自动播放下一首。


iOS音频播放之AVAudioPlayer,AVPlayer,AVQueuePlayer_第1张图片
image

分析2:因为试听的音乐是在服务器上,而AVAudioPlayer只能播放本地音乐文件,所以需要使用支持在线音乐的AVPlayer进行播放。

需求3 婚语开场白:如图,在需求2的前提下,支持列表自动播放,类似于网易音乐。同时支持后台播放、锁屏歌曲信息显示和控制、耳机控制等

iOS音频播放之AVAudioPlayer,AVPlayer,AVQueuePlayer_第2张图片
image

分析3:此时可以使用AVPlayer的子类AVQueuePlayer进行列表播放。AVAudioPlayer,AVPlayer,AVQueuePlayer都支持后台播放、锁屏信息、耳机控制等

接下来,详细讲解一下这几个类:

一、 AVAudioPlayer

主要提供以下了几种播放音频的方法:

  1. System Sound Services

System Sound Services是最底层也是最简单的声音播放服务,调用 AudioServicesPlaySystemSound 这个方法就可以播放一些简单的音频文件,使用此方法只适合播放一些很小的提示或者警告音,因为它有很多限制:

■ 声音长度要小于 30 秒
■ In linear PCM 或者 IMA4 (IMA/ADPCM) 格式的
■ 打包成 .caf, .aif, 或者 .wav 的文件
■ 不能控制播放的进度
■ 调用方法后立即播放声音
■ 没有循环播放和立体声控制

另外,它还可以调用系统的震动功能,方法也很简单。具体的代码可以参考官方的示例 SysSound
,但是官方的示例只有一些简单的用法,从文档中我们发现可以通过 AudioServicesAddSystemSoundCompletion 方法为音频播放添加CallBack 函数,有了 CallBack 函数我们可以解决不少问题,比如可以克服 System Sound Services本身不支持循环播放的问题。

AVAudioPlayer 是 AVFoundation.framework 中定义的一个类,所以使用要先在工程中引入AVFoundation.framework。我们可以把 AVAudioPlayer看作是一个高级的播放器,它支持广泛的音频格式,主要是以下这些格式:

■ AAC
■ AMR(AdaptiveMulti-Rate, aformatforspeech)
■ ALAC(AppleLossless)
■ iLBC(internetLowBitrateCodec, anotherformatforspeech)
■ IMA4(IMA/ADPCM)
■ linearPCM(uncompressed)
■ µ-lawanda-law
■ MP3(MPEG-1audiolayer3

AVAudioPlayer可以播放任意长度的音频文件、支持循环播放、可以同步播放多个音频文件、控制播放进度以及从音频文件的任意一点开始播放等,更高级的功能可以参考 AVAudioPlayer的文档 。要使用 AVAudioPlayer的对象播放文件,你只需为其指定一个音频文件并设定一个实现了 AVAudioPlayerDelegate协议的 delegate 对象。

只要将 AVAudioPlayer 的 numberOfLoops属性设为负数,音频文件就会一直循环播放直到调用 stop 方法。

虽然 AVAudioPlayer可以播放很多格式,但是我们在实际开发过程中还是最好使用一些没有压缩的格式,比如 WAVE文件,这样可以减少系统处理单元的资源占用,以便更好的完成程序的其他功能。另外,在使用 AVAudioPlayer 连续播放 mp3这类经过压缩的音频文件时,在连接处可能出现一定的间隔时间。

如果以上两种音频播放的解决方案都无法满足你的需求,那么我想你肯定需要使用 Audio QueueServices。使用 Audio Queue Services对音频进行播放,你可以完全实现对声音的控制。例如,你可以在声音数据从文件读到内存缓冲区后对声音进行一定处理再进行播放,从而实现对音频的快速/慢速播放的功能。

因为 Audio Queue Services 相对复杂很多,Apple官方已经把它整理为一本书了,具体可以参考 Audio QueueServices ProgrammingGuide 和 SpeakHere 的程序示例。

OpenAL 是一套跨平台的开源的音频处理接口,与图形处理的 OpenGL类似,它为音频播放提供了一套更加优化的方案。它最适合开发游戏的音效,用法也与其他平台下相同。

AVAudioPlayer类封装了播放单个声音的能力。播放器可以用NSURL或者NSData来初始化,要注意的是NSURL不可以是网络url而必须是本地文件url,因为AVAudioPlayer不具备播放网络音频的能力。

一个AVAudioPlayer只能播放一个音频,如果你想混音你可以创建多个AVAudioPlayer实例,每个相当于混音板上的一个轨道。

可以通过音频的NSData或者本地音频文件的url,来创建一个AVAudioPlayer实例,如加载本地的music.mp3的音频文件:

NSURL *fileUrl = [[NSBundle mainBundle] URLForResource:@"music" withExtension:@"mp3"];
self.player = [[AVAudioPlayer alloc] initWithContentsOfURL:fileUrl error:nil];

if (self.player) {
    [self.player prepareToPlay];
}

加载音频文件后,可以调用prepareToPlay方法,这样可以提前获取需要的硬件支持,并加载音频到缓冲区。在调用play方法时,减少开始播放的延迟。

当调用play方法后,开始播放音乐:

[self.player play];
1
可以调用pause或stop来暂停播放,这里的stop方法的效果也只是暂停播放,不同之处是stop会撤销prepareToPlay方法所做的准备。

[self.player stop];
1
另外,我们可以进行更多的操作:

单独设置音乐的音量(默认1.0,可设置范围为0.0至1.0,两个极端为静音、系统音量):

self.player.volume = 0.5;
1
修改左右声道的平衡(默认0.0,可设置范围为-1.0至1.0,两个极端分别为只有左声道、只有右声道):

self.player.pan = -1;
1
设置播放速度(默认1.0,可设置范围为0.5至2.0,两个极端分别为一半速度、两倍速度):

self.player.rate = 0.5;
1
设置循环播放(默认1,若设置值大于0,则为相应的循环次数,设置为-1可以实现无限循环):

self.player.numberOfLoops = -1;

这个类对应的AVAudioPlayerDelegater的委托方法。audioPlayerDidFinishPlaying:successfully:当音频播放完成之后触发。当播放完成之后,可以处理一些事情

- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag
{
   //播放结束时执行的动作
}
- (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)playererror:(NSError *)error;
{
    //解码错误执行的动作
}
- (void)audioPlayerBeginInterruption:(AVAudioPlayer *)player;
{
    //处理中断的代码
}
audioPlayerEndInterruption:,当程序被应用外部打断之后,重新回到应用程序的时候触发。在这里当回到此应用程序的时候,继续播放音乐。
- (void)audioPlayerEndInterruption:(AVAudioPlayer *)player
{
   [audioPlayer play];
}

二、 AVPlayer

AVPlayer支持播放本地、分步下载、或在线流媒体音视频,不仅可以播放音频,配合AVPlayerLayer类可实现视频播放。另外支持播放进度监听。

1.AVPlayer需要通过AVPlayerItem来关联需要播放的媒体。

#import 

AVPlayerItem *item = [[AVPlayerItem alloc] initWithURL:[NSURL URLWithString:urlStr]];
AVPlayer *player = [[AVPlayer alloc] initWithPlayerItem:item];


2.在准备播放前,通过KVO添加播放状态改变监听
[self.player.currentItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];

处理KVO回调事件:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if ([keyPath isEqualToString:@"status"]) {
        switch (self.player.status) {
            case AVPlayerStatusUnknown:
            {
                NSLog(@"未知转态");
            }
                break;
            case AVPlayerStatusReadyToPlay:
            {
                NSLog(@"准备播放");
            }
                break;
            case AVPlayerStatusFailed:
            {
                NSLog(@"加载失败");
            }
                break;

            default:
                break;
        }

    }
}

3.KVO监听音乐缓冲状态:
[self.player.currentItem addObserver:self
                          forKeyPath:@"loadedTimeRanges"
                             options:NSKeyValueObservingOptionNew
                             context:nil];


-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context

{
    //...
    if ([keyPath isEqualToString:@"loadedTimeRanges"]) {

        NSArray * timeRanges = self.player.currentItem.loadedTimeRanges;
        //本次缓冲的时间范围
        CMTimeRange timeRange = [timeRanges.firstObject CMTimeRangeValue];
        //缓冲总长度
        NSTimeInterval totalLoadTime = CMTimeGetSeconds(timeRange.start) + CMTimeGetSeconds(timeRange.duration);
        //音乐的总时间
        NSTimeInterval duration = CMTimeGetSeconds(self.player.currentItem.duration);
        //计算缓冲百分比例
        NSTimeInterval scale = totalLoadTime/duration;
        //更新缓冲进度条
        //        self.loadTimeProgress.progress = scale;
    }
}
##注意:kvo添加之后,在使用结束之后,记得移除!!!!
4.开始播放后,通过KVO添加播放结束事件监听

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(playFinished:)
                                             name:AVPlayerItemDidPlayToEndTimeNotification
                                           object:_player.currentItem];

5.开始播放时,通过AVPlayer的方法监听播放进度,并更新进度条(定期监听的方法),注意:有时候,我们点击播放了,但是半天还没有播放,那么究竟什么时候才真正开始播放,能不能检测到?就用下面这个方法,经过验证,当开始执行这个方法的时候,就真正开始播放了:
(1)方法传入一个CMTime结构体,每到一定时间都会回调一次,包括开始和结束播放
(2)如果block里面的操作耗时太长,下次不一定会收到回调,所以尽量减少block的操作耗时
(3)方法会返回一个观察者对象,当播放完毕时需要移除这个观察者

__weak typeof(self) weakSelf = self;
id timeObserve =[self.player addPeriodicTimeObserverForInterval:CMTimeMake(1.0, 1.0) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
    //当前播放的时间
    float current = CMTimeGetSeconds(time);
    //总时间
    float total = CMTimeGetSeconds(item.duration);
    if (current) {
        float progress = current / total;
        //更新播放进度条
        weakSelf.playSlider.value = progress;
    }
}];
##使用结束之后移除观察者:
if (timeObserve) {
        [player removeTimeObserver:_timeObserve];
        timeObserve = nil;
    }

6.用户拖动进度条,修改播放进度
- (void)playSliderValueChange:(UISlider *)sender
{
    //根据值计算时间
    float time = sender.value * CMTimeGetSeconds(self.player.currentItem.duration);
    //跳转到当前指定时间
    [self.player seekToTime:CMTimeMake(time, 1)];
}
7.上一首、下一首:这里我们有两种方式可以实现,
一种是由你自行控制下一首歌曲的item,将其替换到当前播放的item

[player replaceCurrentItemWithPlayerItem:songItem];

另一种是使用AVPlayer的子类AVQueuePlayer来播放多个item,调用advanceToNextItem来播放下一首音乐

NSArray * items = @[item1, item2, item3 ....];
AVQueuePlayer * queuePlayer = [[AVQueuePlayer alloc]initWithItems:items];

8.监听AVPlayer播放完成通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playbackFinished:) name:AVPlayerItemDidPlayToEndTimeNotification object:songItem];

- (void)playbackFinished:(NSNotification *)notice {    
    BASE_INFO_FUN(@"播放完成");    
    [self playNext];
}
9.播放完毕后,一般都会进行播放下一首的操作。
播放下一首前,别忘了移除这个item的观察者:
[[NSNotificationCenter defaultCenter] removeObserver:self];

10.AVPlayer的内存的释放
用完是需要注意要对其进行释放:写在你退出的点击事件当中,比如说要pop视图了,另外:注意:没有释放播放器的playeritem 所以还在缓冲 释放播放器时加上两句
self.player=nil;  
[playerItem cancelPendingSeeks];
[playerItem.asset cancelLoading];
[player.currentItem cancelPendingSeeks];
[player.currentItem.asset cancelLoading];

三、 AVQueuePlayer

AVPlayer只支持单个媒体资源的播放,我们可以使用AVPlayer的子类AVQueuePlayer实现列表播放。音频或者视频一个接一个的无缝连续播放,因为AVQueuePlayer是AVPlayer的子类,所以AVQueuePlayer可以使用AVPlayer的所有方法。,虽然AVPlayer通过监听播放状态也可以做到视频结束后的自动切换,但是使用AVQueuePlayer加载会快很多。应该是AVFoundation框架对AVQueuePlayer进行了优化,排队的视频会进行预加载在AVPlayer的基础上,增加以下方法:
我根据官方文档简单进行翻译下,就不上代码了,因为文档很简单,功能很直白

AVQueuePlayer is a subclass of AVPlayer used to play a number of items in sequence. Using this class you can create and manage a queue of player items comprised of local or progressively downloaded file-based media, such as QuickTime movies or MP3 audio files, as well as media served using HTTP Live Streaming.
AVQueuePlayer是AVPlayer被用来依次播放的子类。用这个类你能够创建和管理一个播放或文件下载的队列,例如QuickTime格式的视频或MP3音频文件,同样还支持流媒体的使用。

创建队列对象的方法有两种,都是根据数组创建的,数组元素类型是AVPlayerItem:

+ (instancetype)queuePlayerWithItems:(NSArray *)items;

- (AVQueuePlayer *)initWithItems:(NSArray *)items;


获取当前存在于队列里元素的方法

- (NSArray *)items;


结束当前播放并播放下一集(将当前item从队列中移除)

- (void)advanceToNextItem;


判断是否能够在队列中追加播放资源(需要注意的是,不支持队列中存在多个相同的播放资源,即AVPlayerItem*)

- (BOOL)canInsertItem:(AVPlayerItem *)item afterItem:(nullable AVPlayerItem *)afterItem;


在指定位置插入一条播放资源(如果元素是空的则自动会过滤掉)

- (void)insertItem:(AVPlayerItem *)item afterItem:(nullable AVPlayerItem *)afterItem;


从队列中移除播放资源(如果移除的是当前正在播放的元素,则会自动执行advanceToNextItem播放下一集)

- (void)removeItem:(AVPlayerItem *)item;


清除所有播放资源(当执行这一操作后,将会暂停播放)

- (void)removeAllItems;

##例如:
- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    //缓冲进度条
    UIProgressView *loadingProgressView = [[UIProgressView alloc] initWithFrame:CGRectMake(20,200,self.view.bounds.size.width-40,10.0)];
    loadingProgressView.progress = 0.0;
    loadingProgressView.trackTintColor = [UIColor darkGrayColor];
    loadingProgressView.progressTintColor= [UIColor greenColor];
    self.loadingProgressView = loadingProgressView;
    [self.view addSubview:loadingProgressView];
    
    //播放进度条
    UIProgressView *playingProgressView = [[UIProgressView alloc] initWithFrame:loadingProgressView.frame];
    playingProgressView.progress = 0.0;
    playingProgressView.trackTintColor = [UIColor clearColor];
    playingProgressView.progressTintColor= [UIColor redColor];
    self.playingProgressView = playingProgressView;
    [self.view addSubview:playingProgressView];
    NSArray *musicStringArray = @[@"https://m8.music.126.net/20190809180313/9d97c837f0a68356767e2e03544808e7/ymusic/5353/0358/560b/d96f7c1f09427862f62baaacf4c96306.mp3",@"https://m7.music.126.net/20190809180340/849c2b5bd3e434cbb8384412dac27785/ymusic/550f/0208/535a/be283811795b87e04c13d0b3712c1953.mp3"];
    
    NSMutableArray *tempArray = [NSMutableArray array];
    for (NSString *musicUrl in musicStringArray) {
        AVPlayerItem *itme = [AVPlayerItem playerItemWithURL:[NSURL URLWithString:musicUrl]];
        [tempArray addObject:itme];
    }
    self.musics = [tempArray copy];
    self.player = [[AVQueuePlayer alloc] initWithItems:self.musics];
    [self.player play];
    NSLog(@"播放单元==%lu",(unsigned long)self.player.items.count);
    UIButton *next=[[UIButton alloc]initWithFrame:CGRectMake(40, 100, 80, 40)];
    [next setTitle:@"下一首" forState:UIControlStateNormal];
    next.backgroundColor=[UIColor redColor];
    [self.view addSubview:next];
    [next addTarget:self action:@selector(next_song) forControlEvents:UIControlEventTouchUpInside];
    
    UIButton *insert=[[UIButton alloc]initWithFrame:CGRectMake(140, 100, 80, 40)];
    [insert setTitle:@"播放其他" forState:UIControlStateNormal];
    insert.backgroundColor=[UIColor greenColor];
    [self.view addSubview:insert];
    [insert addTarget:self action:@selector(insert_song) forControlEvents:UIControlEventTouchUpInside];
    [self.player.currentItem addObserver:self
                              forKeyPath:@"status"
                                 options:NSKeyValueObservingOptionNew
                                 context:nil];
    [self.player.currentItem addObserver:self
                              forKeyPath:@"loadedTimeRanges"
                                 options:NSKeyValueObservingOptionNew
                                 context:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(playbackFinished:)
                                                 name:AVPlayerItemDidPlayToEndTimeNotification
                                               object:_player.currentItem];

    __weak typeof(self) weakSelf = self;
    _timeObserver = [self.player addPeriodicTimeObserverForInterval:CMTimeMake(1.0, 30) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
        //当前播放的时间
        float current = CMTimeGetSeconds(time);
        //总时间
        float total = CMTimeGetSeconds(weakSelf.player.currentItem.duration);
        if (current) {
            float progress = current / total;
            //更新播放进度条
            weakSelf.playingProgressView.progress = progress;
           // [weakSelf configNowPlayingCenter];
        }
    }];
    
}
//播放下一首
- (void)next_song {
    [self.player advanceToNextItem];
}
//在当前播放队列里面加入其他的播放源
-(void)insert_song{
    //首先判断能不能加入
    AVPlayerItem *itme = [AVPlayerItem playerItemWithURL:[NSURL URLWithString:@"https://m7.music.126.net/20190809180401/045989a0cf1e493fd4ad30235bf30ce3/ymusic/07fa/a2a1/35ea/732937117d6d0a8c13a81bb40184662e.mp3"]];

    if([self.player canInsertItem:itme afterItem:self.player.currentItem]){
        [self.player insertItem:itme afterItem:self.player.currentItem];
        [self.player advanceToNextItem];
    }else{
        NSLog(@"不能插入===");
    }
    
}

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if ([keyPath isEqualToString:@"status"]) {
        switch (self.player.status) {
            case AVPlayerStatusUnknown:
            {
                NSLog(@"未知转态");
            }
                break;
            case AVPlayerStatusReadyToPlay:
            {
                NSLog(@"准备播放");
                
            }
                break;
            case AVPlayerStatusFailed:
            {
                NSLog(@"加载失败");
            }
                break;
                
            default:
                break;
        }
    }
    if ([keyPath isEqualToString:@"loadedTimeRanges"]) {
        
        NSArray * timeRanges = self.player.currentItem.loadedTimeRanges;
        //本次缓冲的时间范围
        CMTimeRange timeRange = [timeRanges.firstObject CMTimeRangeValue];
        //缓冲总长度
        NSTimeInterval totalLoadTime = CMTimeGetSeconds(timeRange.start) + CMTimeGetSeconds(timeRange.duration);
        //音乐的总时间
        NSTimeInterval duration = CMTimeGetSeconds(self.player.currentItem.duration);
        //计算缓冲百分比例
        NSTimeInterval scale = totalLoadTime/duration;
        //更新缓冲
        self.loadingProgressView.progress = scale;
    }
}
-(void)playbackFinished:(NSNotification *)notice {
    NSLog(@"播放完成");
  
}

//移除监听音乐播放进度
-(void)removeTimeObserver
{
    if (self.timeObserver) {
        [self.player removeTimeObserver:self.timeObserver];
        self.timeObserver = nil;
    }
}

//移除监听音乐缓冲状态
-(void)removePlayLoadTime
{
    if (self.player.currentItem != nil){
        [self.player.currentItem removeObserver:self forKeyPath:@"loadedTimeRanges"];
    }
}

//移除监听播放器状态
-(void)removePlayStatus
{
    if (self.player.currentItem != nil){
        [self.player.currentItem removeObserver:self forKeyPath:@"status"];
    }
}

-(void)dealloc{
    [self.player pause];
    [self removeTimeObserver];
//    [self removePlayLoadTime];
//    [self removePlayStatus];
    self.player = nil;
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    NSLog(@"%s",__func__);
}

和AVPlayer一样,直接调用play方法来播放,queue player顺序播放队列中的item,
如果想要跳过一个item,播放下一个item,可以调用方法advanceToNextItem。

可以对队列进行插入和删除操作,调用方法insertItem:afterItem: , removeItem: , 
和removeAllItems。正常情况下当插入一个item之前,应该检查是否可以插入,
通过使用canInsertItem:afterItem:方法,第二个参数传nil

四、 后台播放

当 App 退到后台时,会进入 suspend 状态,若此时在播放视频或者音频,则会自动暂停。我们需要实现的效果是,当 App 退到后台时,视频中的声音还能继续播放。另外,我们还同时实现视频的连续播放功能,和在锁屏界面控制视频播放的功能。具体怎么做:。

要实现后台播放视频功能,首先需要实现后台播放音频功能。实现后台播放音频很简单,只要简单配置一下就可以了。总共有三步:

  1. 修改 Info.plist
    Info.plist中添加 Required background modes,并在下面添加一项 App plays audio or streams audio/video using AirPlay。如图所示:
iOS音频播放之AVAudioPlayer,AVPlayer,AVQueuePlayer_第3张图片
image
  1. 修改 Capabilities

Capabilities中开启 Background Modes。如图所示:

iOS音频播放之AVAudioPlayer,AVPlayer,AVQueuePlayer_第4张图片
image
  1. 修改 AppDelegate
    在 AppDelegate 的 application: didFinishLaunchingWithOptions: 方法中,添加以下代码:
// 告诉app支持后台播放
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
[audioSession setCategory:AVAudioSessionCategoryPlayback error:nil];
[audioSession setActive:YES error:nil];

至此就实现了后台播放音频的功能.

五、 锁屏显示(Now Playing Center)播放信息与耳机控制

#首先加入头文件
#import 

##在在播放界面介入以下代码
-(void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
    // 开始接受远程控制
    [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
    [self resignFirstResponder];
}
- (void)viewWillDisappear:(BOOL)animated
{
    // 接触远程控制
    [[UIApplication sharedApplication] endReceivingRemoteControlEvents];
    [self becomeFirstResponder];
}
// 重写父类成为响应者方法
- (BOOL)canBecomeFirstResponder
{
    return YES;
}
//重写父类方法,接受外部事件的处理,
##对远程控制事件作出相应的操作
- (void) remoteControlReceivedWithEvent: (UIEvent *) receivedEvent {
    NSLog(@"remote");
    if (receivedEvent.type == UIEventTypeRemoteControl) {
        
        switch (receivedEvent.subtype) { // 得到事件类型
                
            case UIEventSubtypeRemoteControlTogglePlayPause: // 暂停 ios6
                [self.player pause]; // 调用你所在项目的暂停按钮的响应方法 下面的也是如此
                break;
                
            case UIEventSubtypeRemoteControlPreviousTrack:  // 上一首连按三下
                NSLog(@"播放上一首");
                break;
                
            case UIEventSubtypeRemoteControlNextTrack: // 下一首,连按两下
                NSLog(@"播放下一首");
                break;
                
            case UIEventSubtypeRemoteControlPlay: //播放
                [self.player play]; // 调用你所在项目的暂停按钮的响应方法 下面的也是如此
                break;
                
            case UIEventSubtypeRemoteControlPause: // 暂停 ios7
                [self.player pause]; // 调用你所在项目的暂停按钮的响应方法 下面的也是如此
                NSLog(@"暂停");
                break;
                
            default:
                break;
        }
    }
}
##该方法是设置锁屏的时候,显示的歌词信息的方法,里面的对应的信息,要换成自己的数据,在在使用的地方调用该方法就可以了
- (void)configNowPlayingCenter {
    NSLog(@"锁屏设置");
    // BASE_INFO_FUN(@"配置NowPlayingCenter");
    NSMutableDictionary * info = [NSMutableDictionary dictionary];
    //音乐的标题
    [info setObject:@"席之位" forKey:MPMediaItemPropertyTitle];
    //音乐的艺术家
    //NSString *author= [[self.playlistArr[self.currentNum] valueForKey:@"songinfo"] valueForKey:@"author"];
    [info setObject:@"薛之谦" forKey:MPMediaItemPropertyArtist];
    //音乐的播放时间
    [info setObject:@(CMTimeGetSeconds(self.player.currentTime)) forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime];
    //音乐的播放速度
    [info setObject:@(1) forKey:MPNowPlayingInfoPropertyPlaybackRate];
    //音乐的总时间
    [info setObject:@(CMTimeGetSeconds(self.player.currentItem.duration)) forKey:MPMediaItemPropertyPlaybackDuration];
    //音乐的封面
    MPMediaItemArtwork * artwork = [[MPMediaItemArtwork alloc] initWithImage:[UIImage imageNamed:@"0.jpg"]];
    [info setObject:artwork forKey:MPMediaItemPropertyArtwork];
    //完成设置
    [[MPNowPlayingInfoCenter defaultCenter]setNowPlayingInfo:info];
}

大概总结一下音乐播放器的播放逻辑:
(1) 初始化播放界面
(2)从接口获取播放列表、选择第一首为当前播放歌曲
(3)根据当前歌曲初始化播放器 、同步歌曲信息到播放界面(此时播放界面应展示歌曲信息,但是播放按钮应不可用且有loading之类的提示表示正在加载歌曲)、同步歌曲信息到Now Playing Center
(4)当播放器的status变为ReadyToPlay时,播放歌曲、同步播放信息到播放界面(播放时间、总时间、进度条等等)、同步播放信息到Now Playing Center
(5)当用户进行暂停操作时,刷新播放界面
(6)当用户进行下一首、上一首操作时,或完成某一首歌曲的播放时,将对应的歌曲设置为当前播放歌曲,重复3-5步骤
(7)由于网络情况不好造成播放器自动暂停播放时,应刷新播放界面
(8)由于网络情况不好造成播放器不能进入播放状态时,应有所处理(比如提示耐心等待或者播放本地离线的歌曲

参考文章:https://blog.csdn.net/dolacmeng/article/details/77430108

你可能感兴趣的:(iOS音频播放之AVAudioPlayer,AVPlayer,AVQueuePlayer)