音频播放的实现以及后台播放音频 iOS

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

以下代码实现的是如下图所示的效果,点击图标可以暂停或者继续播放:
音频播放的实现以及后台播放音频 iOS_第1张图片

需要的属性:

//
@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;
}

播放音频

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];

注:如果需要获取音频的时长等信息,必须使用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];
    }];

监听回调

//
#pragma 监听播放状态回调
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionaryid> *)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;
            }
        }
    }
}

时间显示

//
- (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;
}

注意:有的说是NSTimer 计时器计时应该写在子线程中,但是写在子线程中发现倒计时与音频播放不同步,出现倒计时已经结束,但是音频还没播放完,所以这里我就都写在了主线程。(有见解的伙伴欢迎提点哦)

(2)然后,就是要实现后台播放以及返回其他页面,音频继续播放的过程
首先开启,允许后台运行模式
音频播放的实现以及后台播放音频 iOS_第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;
}

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;
}

注:以上只是部分主要代码,并非完整代码。

附:

参考文章:https://www.jianshu.com/p/ab300ea6e90c

你可能感兴趣的:(总结,项目)