IOS 音频实现以及后台播放音频

Posted Andy__Wu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了IOS 音频实现以及后台播放音频相关的知识,希望对你有一定的参考价值。

1、首先,音频播放的实现,我这里使用的是AVPlayer。 
AVAudioPlayer只能播放本地资源。当然还有别的播放方法这里就不列举了。

以下代码实现的是如下图所示的效果,点击图标可以暂停或者继续播放: 


需要的属性:
@property (nonatomic, strong) AVPlayer *player;
@property (nonatomic, strong) UIImageView *playerView;
@property (nonatomic, strong) UILabel *timeL;
@property (nonatomic, strong) UIImageView *animationV;//加载动画


初始化属性

//
- (AVPlayer *)player

    if (_player == nil)

        NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@",kVedioUrl,_playItemInfo[@"ypwj"]]];
        AVAsset *avset = [AVAsset assetWithURL:url] ;
        AVPlayerItem *item = [AVPlayerItem playerItemWithAsset:avset];
        _item = item;

        // 创建AVPlayer
        _player = [AVPlayer playerWithPlayerItem:_item];

        // 添加AVPlayerLayer
        AVPlayerLayer *layer = [AVPlayerLayer playerLayerWithPlayer:self.player];
        layer.frame = CGRectMake(self.view.bounds.size.width - 55, self.view.bounds.size.height - 100, 50, 50);
        [self.view.layer addSublayer:layer];

        //监听是否可播放
        [_item addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
        //监听缓存状态,可以添加加载动画
        [_item addObserver:self forKeyPath:@"playbackBufferEmpty" options:NSKeyValueObservingOptionNew context:nil];
        //监听是否播放完成
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playToTheEnd) name:AVPlayerItemDidPlayToEndTimeNotification object:_item];
        //监听播放状态,播放还是暂停
        [_player addObserver:self forKeyPath:@"timeControlStatus" options:NSKeyValueObservingOptionNew context:nil];

   
    return _player;

- (UIImageView *)playerView
    if (_playerView == nil)
        _playerView = [[UIImageView alloc] init];
        _playerView.image = [UIImage imageNamed:@"L0"];
        //播放动画
        NSMutableArray *imgArr = [NSMutableArray array];
        for (int i = 0; i<4; i ++)
            UIImage *image = [UIImage imageNamed:[NSString stringWithFormat:@"L%d",i]];
            [imgArr addObject:image];
       
        _playerView.animationImages = imgArr;
        _playerView.animationDuration = 2.0;
        _playerView.animationRepeatCount = 0;

        _playerView.userInteractionEnabled = YES;
        UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(playOrPause)];
        [_playerView addGestureRecognizer:tap];
   
    return _playerView;

- (UIImageView *)animationV
    //旋转动画
    if (_animationV == nil)
        _animationV  = [[UIImageView alloc] init];
        _animationV.image = [UIImage imageNamed:@"loadcc"];
        CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
        //默认是顺时针效果,若将fromValue和toValue的值互换,则为逆时针效果
        animation.fromValue = [NSNumber numberWithFloat:0.f];
        animation.toValue = [NSNumber numberWithFloat: M_PI *2];
        animation.duration = 2;
        animation.autoreverses = NO;
        animation.fillMode = kCAFillModeForwards;
        animation.repeatCount = MAXFLOAT; //如果这里想设置成一直自旋转,可以设置为MAXFLOAT,否则设置具体的数值则代表执行多少次
        [_animationV.layer addAnimation:animation forKey:nil];
   
    return _animationV;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
播放音频

NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@",kVedioUrl,_mp3Url]];
    AVAsset *avset = [AVAsset assetWithURL:url] ;
    CMTime audioDuration = avset.duration; //获取音频时长
    _countTime = CMTimeGetSeconds(audioDuration);

    AVPlayerItem *item = [AVPlayerItem playerItemWithAsset:avset];
    [self.player replaceCurrentItemWithPlayerItem:item];//替换当前播放的音频
    [self.player play];
1
2
3
4
5
6
7
8
注:如果需要获取音频的时长等信息,必须使用AVAsset,不需要的话,可以直接使用[AVPlayerItem playerItemWithURL:url]就可以了

显示音频所剩的播放时间

//
__weak typeof(self) weakSelf = self;
    [self.player addPeriodicTimeObserverForInterval:CMTimeMake(1, 1) queue:nil usingBlock:^(CMTime time)

        AVPlayerItem *item = weakSelf.item;
        //已播放时长
        NSInteger currentTime = item.currentTime.value/item.currentTime.timescale;
        //音频总时长
        NSInteger allTime = CMTimeGetSeconds(weakSelf.player.currentItem.duration);

        weakSelf.timeL.text = [weakSelf showPlayerTime:allTime - currentTime];
    ];
1
2
3
4
5
6
7
8
9
10
11
12
监听回调

//
#pragma 监听播放状态回调
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
    if ([object isKindOfClass:[AVPlayerItem class]])
        if ([keyPath isEqualToString:@"status"])
            switch (_item.status)
                case AVPlayerItemStatusReadyToPlay:
                    //推荐将视频播放在这里

                    break;

                case AVPlayerItemStatusUnknown:
                    NSLog(@"AVPlayerItemStatusUnknown");
                    break;

                case AVPlayerItemStatusFailed:
                    NSLog(@"AVPlayerItemStatusFailed");
                    break;

                default:
                    break;
           
        else if ([keyPath isEqualToString:@"playbackBufferEmpty"])
            if (_item.playbackBufferEmpty)
                _animationV.hidden = NO;
           
       
   
    if ([object isKindOfClass:[AVPlayer class]])
        if ([keyPath isEqualToString:@"timeControlStatus"])
            switch (_player.timeControlStatus)
                case AVPlayerTimeControlStatusPlaying:
                    _isPlaying = YES;
                    _animationV.hidden = YES;
                    break;

                case AVPlayerTimeControlStatusPaused:
                    _isPlaying = NO;
                    _animationV.hidden = YES;
                    [_playerView stopAnimating];
                    _playerView.image = [UIImage imageNamed:@"L0"];
                    break;

                case AVPlayerTimeControlStatusWaitingToPlayAtSpecifiedRate:
                    _animationV.hidden = NO;
                default:
                    break;
           
       
   

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
时间显示

//
- (NSString *)showPlayerTime:(NSInteger)countTime
    NSInteger minute = countTime / 60;
    NSInteger second = countTime % 60;
    NSString *str = @"";
    if (minute < 10)
        if (second < 10)
            str = [NSString stringWithFormat:@" 0%ld:0%ld",minute,second];

        else
            str = [NSString stringWithFormat:@" 0%ld:%ld",minute,second];
       
    else
        if (second < 10)
            str = [NSString stringWithFormat:@" %ld:0%ld",minute,second];
        else
            str = [NSString stringWithFormat:@" %ld:%ld",minute,second];
       
   
    return str;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
注意:有的说是NSTimer 计时器计时应该写在子线程中,但是写在子线程中发现倒计时与音频播放不同步,出现倒计时已经结束,但是音频还没播放完,所以这里我就都写在了主线程。(有见解的伙伴欢迎提点哦)

(2)然后,就是要实现后台播放以及返回其他页面,音频继续播放的过程 
首先开启,允许后台运行模式 


然后在APPDelegate 中:

- (void)applicationWillResignActive:(UIApplication *)application
    //开启后台处理多媒体事件
    [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
    AVAudiosession *session=[AVAudioSession sharedInstance];
    [session setActive:YES error:nil];
    //后台播放
    [session setCategory:AVAudioSessionCategoryPlayback error:nil];
    //这样做,可以在按home键进入后台后 ,播放一段时间,几分钟吧。但是不能持续播放,若需要持续播放,还需要申请后台任务id,具体做法是:
    _bgTaskId=[AppDelegate backgroundPlayerID:_bgTaskId];
    //其中的_bgTaskId是后台任务UIBackgroundTaskIdentifier _bgTaskId;

+(UIBackgroundTaskIdentifier)backgroundPlayerID:(UIBackgroundTaskIdentifier)backTaskId

    //设置并激活音频会话类别
    AVAudioSession *session=[AVAudioSession sharedInstance];
    [session setCategory:AVAudioSessionCategoryPlayback error:nil];
    [session setActive:YES error:nil];
    //允许应用程序接收远程控制
    [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
    //设置后台任务ID
    UIBackgroundTaskIdentifier newTaskId=UIBackgroundTaskInvalid;
    newTaskId=[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil];
    if(newTaskId!=UIBackgroundTaskInvalid&&backTaskId!=UIBackgroundTaskInvalid)
   
        [[UIApplication sharedApplication] endBackgroundTask:backTaskId];
   
    return newTaskId;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
But:我们会发现当实现当只实现以上的applicationWillResignActive方法,或者只开启图中的background modes ,做到这两个中的一个,就可以实现后台播放。。。

进入后台播放可以了,但是当我们返回到其他的页面,再次进入到这个页面时,会发现,音频又叠加了一个音频,而不是我们想要的当前音频正常的继续播放。所以…我们想到了,对,揍死它——单例。

将当前控制器设置成单例,这样每次进入这个页面,音频可以毫无影响的继续播放啦~

static MyController *instance;

+(id)shareInstance
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^
        if(instance == nil)
            instance = [[MyController alloc] init];
    );
    return instance;

1
2
3
4
5
6
7
8
9
10
注:以上只是部分主要代码,并非完整代码。

附:

参考文章:https://www.jianshu.com/p/ab300ea6e90c
--------------------- 
作者:Kaiccy 
来源:CSDN 
原文:https://blog.csdn.net/Kaiccy/article/details/81542353 
版权声明:本文为博主原创文章,转载请附上博文链接!

以上是关于IOS 音频实现以及后台播放音频的主要内容,如果未能解决你的问题,请参考以下文章

IOS后台运行 之 后台播放音乐

iphone sdk:是不是可以在 iOS 4 的后台播放音频播放列表

后台播放音乐

iOS4 和后台播放音频

如何在 iOS 后台播放音频?

iOS如何停止在后台播放音频?