iOS_AVAudioPlayer播放并实现了后台播放和远程控制

2022-07-20 13:30:58 浏览数 (1)

代码语言:javascript复制
//  ViewController.h
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h> 

@class AVAudioPlayer;

@interface PlayViewController : UIViewController<AVAudioPlayerDelegate>

@property(nonatomic, strong) AVAudioPlayer *player;

  (PlayViewController *)defaultPlayVC; //将该播放页面定义成单例

@end

代码语言:javascript复制
//  ViewController.m
#import "PlayViewController.h"

@interface PlayViewController ()

@property (retain, nonatomic) IBOutlet UIButton *suspendBtn;  //暂停按钮
@property (retain, nonatomic) IBOutlet UIButton *formerBtn;   //上一首按钮
@property (retain, nonatomic) IBOutlet UIButton *nextBtn; //下一首按钮
@property (retain, nonatomic) IBOutlet UISlider *slider;  //滑块
@property (retain, nonatomic) NSArray *musicUrlArray; //音乐网址数组
@property (retain, nonatomic) NSArray *musicAllTimeArray; //音乐时间数组
@property (retain, nonatomic) NSTimer *timer;  //计时器
@property (assign, nonatomic) int currentIndex; //当前音频下标

@end

@implementation PlayViewController
代码语言:javascript复制
#pragma mark 单例初始化方法
  (PlayViewController *)defaultPlayVC {
    static PlayViewController *playVC = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        playVC = [[PlayViewController alloc] init];
    });
    return playVC;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    self.currentIndex = 0;
    self.musicUrlArray = [NSArray arrayWithObjects:@"http://fdfs.xmcdn.com/group10/M08/D4/2E/wKgDaVZ9EYaQ-j48AFIOaAXL-wM019.mp3",
                          @"http://fdfs.xmcdn.com/group13/M09/D4/17/wKgDXVZ6fR2wR1E8AFuO_VClRZA066.mp3",
                          @"http://fdfs.xmcdn.com/group16/M0A/D5/16/wKgDbFZ89SqyFGWGAHoEKd7yPsw709.mp3",
                          @"http://fdfs.xmcdn.com/group8/M01/D4/34/wKgDYFZ6y1nR6QpZAEDVy7rUTTc247.mp3",
                          @"http://fdfs.xmcdn.com/group9/M00/D4/EF/wKgDZlZ7aLGCQEEDAHMsQsurq_Y170.mp3",
                          @"http://fdfs.xmcdn.com/group12/M07/D4/C4/wKgDW1Z70rWxoDA7ABAp1lwS5nI990.mp3", nil];
    self.musicAllTimeArray = [NSArray arrayWithObjects:@(1344.36), @(1500.04), @(1999.06), @(1062.22), @(1886.96), @(264.76), nil];
    
    //滑块
    self.slider.minimumValue = 0;
    [self.slider addTarget:self action:@selector(changValue:) forControlEvents:UIControlEventValueChanged];  //添加滑动事件
    [self.slider setThumbImage:[UIImage imageNamed:@"playProcessDot_n"]forState:UIControlStateNormal];

    //播放音频之前先要设置AVAudioSession模式 是它可以后台播放
    //并且要在plist文件中 添加required background modes这个key项,并选择"App plays audio or streams audio/video using AirPlay"这个value项。
    AVAudioSession *session = [AVAudioSession sharedInstance];
    [session setCategory:AVAudioSessionCategoryPlayback error:nil];
    [session setActive:YES error:nil];
    
    [self playerCurrentMusic];
}

-(void)playerCurrentMusic {

    //创建串行队列 (放入线程中,免得页面假死)
    dispatch_queue_t serialQueue = dispatch_queue_create("com.audio.www", DISPATCH_QUEUE_SERIAL);
    dispatch_async(serialQueue, ^{

        NSString *url = self.musicUrlArray[self.currentIndex];
        NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:url]];
        self.player = [[AVAudioPlayer alloc] initWithData:data error:nil];
        self.player.delegate = self;
        [self.player prepareToPlay];
        [self.player play];

        //滑块
        CGFloat allTime = [self.musicAllTimeArray[self.currentIndex] floatValue];
        self.slider.maximumValue = allTime;
        
        //计时器只能在主线程中
        dispatch_async(dispatch_get_main_queue(), ^{
            self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(reloadAction) userInfo:nil repeats:YES];
        });
    });
}

#pragma mark -- 每1秒 给进度条赋一次值
-(void)reloadAction {
    if ([self.player isPlaying]) {
        self.slider.value = self.player.currentTime;
    }
}

#pragma mark 点击暂停
- (IBAction)suspendAction:(UIButton *)sender {
    sender.selected = !sender.selected;
    if (sender.selected) {
        [self.player pause]; //暂停
        [sender setTitle:@"播放" forState:UIControlStateSelected];
    } else {
        [self.player play];  //播放
        [sender setTitle:@"暂停" forState:UIControlStateNormal];
    }
}

#pragma mark 点击上一首
- (IBAction)formerAction:(UIButton *)sender {
    [self myPlayFormer]; //播放上一首
}

#pragma mark 点击下一首
- (IBAction)nextAction:(UIButton *)sender {
    [self myPlayNext];  //播放下一首
}

#pragma mark 上一首
-(void)myPlayFormer {
    if (self.currentIndex > 0) {
        [self.player pause];
        self.currentIndex--;
        [self playerCurrentMusic];
    }
}
#pragma mark 下一首
-(void)myPlayNext {
    if (self.currentIndex < self.musicUrlArray.count - 1) {
        [self.player pause];
        self.currentIndex  ;
        [self playerCurrentMusic];
    }
}

/*****************   控制台   *****************/
#pragma mark -- 声明App接收远程控制事件
-(void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
    [self becomeFirstResponder];
}

#pragma mark -- App结束声明接收远程控制事件
- (void) viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    [[UIApplication sharedApplication] endReceivingRemoteControlEvents];
    [self resignFirstResponder];
}

#pragma mark -- 接受控制台的控制事件
- (void)remoteControlReceivedWithEvent: (UIEvent *) receivedEvent {
    if (receivedEvent.type == UIEventTypeRemoteControl) {
        //判断点击按钮的类型
        switch (receivedEvent.subtype) {
                
            case UIEventSubtypeRemoteControlPlay:
                [self.player play];  //播放
                break;
            case UIEventSubtypeRemoteControlPause:
                [self.player pause]; //暂停
                break;
            case UIEventSubtypeRemoteControlPreviousTrack:
                [self myPlayFormer]; //播放上一首
                break;
            case UIEventSubtypeRemoteControlNextTrack:
                [self myPlayNext];  //播放下一首
                break;
            default:
                break;
        }
    }
}

#pragma mark 实现后台播放完一首后, 继续播放下一首
- (BOOL)canBecomeFirstResponder {
    return YES;
}

/*****************   AVAudioPlayer 代理   *****************/
#pragma mark -- 后台播放被打断, 继续恢复播放 (比如打电话...)
- (void)audioPlayerEndInterruption:(AVAudioPlayer *)player withOptions:(NSUInteger)flags {
    [self.player play];
}
#pragma mark -- 播放完当前声音后,播放下一首
-(void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag {
    if (self.currentIndex < self.musicUrlArray.count - 1) {
        self.currentIndex  ;
        [self playerCurrentMusic];
    }
}

#pragma mark -- 滑块滑动改变播放进度(代理方法)
-(void)changValue:(UISlider *)slider {
    //拖动滑块时, 停止计时器
    [self.timer invalidate];
    self.player.currentTime = slider.value;
    //开始计时器
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(reloadAction) userInfo:nil repeats:YES];
}

想要实现后台播放(即程序进入后台,音乐继续播放)需要修改plist文件:

代码语言:javascript复制
1.添加required background modes这个key项,并选择"App plays audio or streams audio/video using AirPlay"这个value项。

2.XCode7.1以上获取网络数据,需要修改plist文件

在Info.plist中添加App Transport Security Settings类型Dictionary。

在App Transport Security Settings下添加Allow Arbitrary Loads类型Boolean,值设为YES

需要在后台实现播放页的各种上一首,下一首...各种功能, 还需要在Appdelegate.m的background方法里:

代码语言:javascript复制
- (void)applicationDidEnterBackground:(UIApplication *)application {
    
    //程序进入后台后, 为了让音乐继续播放, 在此方法里创建该代理播放页, 并指定它播放器的代理
    PlayViewController *player = [PlayViewController defaultPlayVC];
    player.player.delegate = player;
}

当前工程Demo地址: http://download.csdn.net/detail/margaret_mo/9412069

0 人点赞