IOS APP内后台音乐播放

版权声明:文章为本人个人总结,如需转载请声明 https://blog.csdn.net/weixin_39624536/article/details/89716896

写在开头

最近做了IOS音频、音乐后台播放相关的需求,这里主要总结一下支持后台播放以及远程控制的一些用法,在这个过程中也遇到了一些问题,会在另一个博客里面进行总结。

一、后台权限申请

1、在Info.plist文件中里设置选项Required background modes ,然后添加item0:App plays audio or streams audio/video using AirPlay 

2、设置Capabilities -> Background Modes -> 勾选 Audio,AirPlay,and Picture in Picture

3、在AppDelegate.m中添加代码

- (void)applicationWillResignActive:(UIApplication *)application {
    // *让app接受远程事件控制,及锁屏是控制版会出现播放按钮
    [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
    // *后台播放代码
    AVAudioSession*session=[AVAudioSession sharedInstance];
    [session setActive:YES error:nil];
    [session setCategory:AVAudioSessionCategoryPlayback error:nil];
}

二、添加控制代码

我们在这里使用MPRemoteCommandCenter添加远程控制代码,使用MPNowPlayingInfoCenter更新通知中心控制台的媒体信息。

关于这个两个控件的使用,可以在网上查询一些教程,我这里做简单的介绍,并添加一下使用过程中需要的注意事项

1、MPRemoteCommandCenter

(1)是一个响应系统外部附件(耳机)以及系统控件发出的运程控制事件对象。

(2)在使用的过程中,不可以多次调用(最好只调用一次,主要是addTarget不能多次调用),因为调用多次以后,会多次响应远程事件

- (void)setupLockScreenControlInfo {
    
    MPRemoteCommandCenter *commandCenter = [MPRemoteCommandCenter sharedCommandCenter];
    
    // 锁屏播放    
    MPRemoteCommand *playCommand = commandCenter.playCommand;
    playCommand.enabled = YES;
    [playCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
        NSLog(@"锁屏暂停后点击播放");
        if (!self.isPlaying) {
            [self playMusic];
        }
        return MPRemoteCommandHandlerStatusSuccess;
    }];

    // 锁屏暂停
    MPRemoteCommand *pauseCommand = commandCenter.playCommand;
    pauseCommand.enabled = YES;
    [pauseCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
        NSLog(@"锁屏正在播放点击后暂停");
        
        if (self.isPlaying) {
            [self pauseMusic];
        }
        return MPRemoteCommandHandlerStatusSuccess;
    }];
    
    
    MPRemoteCommand *stopCommand = commandCenter.playCommand;
    stopCommand.enabled = YES;
    [stopCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
        [self stopMusic];
        
        return MPRemoteCommandHandlerStatusSuccess;
    }];
    
    // 播放和暂停按钮(耳机控制)
    MPRemoteCommand *playPauseCommand = commandCenter.togglePlayPauseCommand;
    playPauseCommand.enabled = YES;

    [playPauseCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
        
        if (self.isPlaying) {
            [self pauseMusic];
        }else {
            [self playMusic];
        }
        
        return MPRemoteCommandHandlerStatusSuccess;
    }];


/*    这部分功能没有用到,就暂时先放在这里
    // 上一曲
    MPRemoteCommand *previousCommand = commandCenter.previousTrackCommand;
    [previousCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
        
        [self playPrevMusic];
        
        return MPRemoteCommandHandlerStatusSuccess;
    }];
    
    // 下一曲
    MPRemoteCommand *nextCommand = commandCenter.nextTrackCommand;
    nextCommand.enabled = YES;
    [nextCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
        
        self.isAutoPlay = NO;
        
        if (self.isPlaying) {
            [self stopMusic];
        }
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            
            
            [self playNextMusic];
        });
        
        return MPRemoteCommandHandlerStatusSuccess;
    }];
    
    // 快进
    MPRemoteCommand *forwardCommand = commandCenter.seekForwardCommand;
    forwardCommand.enabled = YES;
    [forwardCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
        
        MPSeekCommandEvent *seekEvent = (MPSeekCommandEvent *)event;
        if (seekEvent.type == MPSeekCommandEventTypeBeginSeeking) {
            [self seekingForwardStart];
        }else {
            [self seekingForwardStop];
        }
        
        return MPRemoteCommandHandlerStatusSuccess;
    }];
    
    // 快退
    MPRemoteCommand *backwardCommand = commandCenter.seekBackwardCommand;
    backwardCommand.enabled = YES;
    [backwardCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
        
        MPSeekCommandEvent *seekEvent = (MPSeekCommandEvent *)event;
        if (seekEvent.type == MPSeekCommandEventTypeBeginSeeking) {
            [self seekingBackwardStart];
        }else {
            [self seekingBackwardStop];
        }
        
        return MPRemoteCommandHandlerStatusSuccess;
    }];
*/
    
    // 拖动进度条
    if (@available(iOS 9.1, *)) {
        
        
        MPRemoteCommand *changePlaybackPositionCommand = commandCenter.changePlaybackPositionCommand;
        changePlaybackPositionCommand.enabled = YES;
        [changePlaybackPositionCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
            self.isDraging = YES;
            NSLog(@"self.isDraging = %d",self.isDraging);
            MPChangePlaybackPositionCommandEvent *positionEvent = (MPChangePlaybackPositionCommandEvent *)event;

                self.positionTime = positionEvent.positionTime;
                NSLog(@"positionTime = %f",self.positionTime);
                //业务逻辑如下:
                self.currentTime = (float)self.positionTime * 1000 >= self.courseItem.duration.integerValue * 1000 ? self.courseItem.duration.integerValue * 1000 : (float)self.positionTime * 1000;
                
                CGFloat value = self.currentTime / self.duration;
                if (self.isPlaying) {
                    [kAudioPlayer setPlayerProgress:value];
                }else {
                    if (value == 0) {
                        value = 0.001;
                    }
                    self.seekProgress = value;
                }
            self.updataMediaCount = 0;
            NSLog(@"self.isDraging = %d",self.isDraging);
            return MPRemoteCommandHandlerStatusSuccess;
        }];
    } else {
        // Fallback on earlier versions
    }
}

2、MPNowPlayingInfoCenter

使用MPNowPlayingInfoCenter更新通知中心控制台的媒体信息,当然,我们这里只是更新了我们需要的一信息

扫描二维码关注公众号,回复: 6082294 查看本文章
//更新通知中心控制台媒体信息
- (void)setupLockScreenMediaInfo {
    
    MPNowPlayingInfoCenter *playingCenter = [MPNowPlayingInfoCenter defaultCenter];
    
    NSMutableDictionary *playingInfo = [NSMutableDictionary new];
    //标题
    playingInfo[MPMediaItemPropertyTitle]      = self.courseItem.title;
    //封面图片
    UIImage *coverImage = nil;
    if ([NSString isEmpty:self.courseItem.cover_url]) {
        coverImage = [UIImage imageNamed:@"loftyAppLogo"];
    }else {
        UIImageView *coverImageView = [[UIImageView alloc] init];
        [coverImageView sd_setImageWithURL:[NSURL URLWithString:self.courseItem.cover_url]];
        coverImage = coverImageView.image;
        if (coverImage == nil) {
            coverImage = [UIImage imageNamed:@"loftyAppLogo"];
        }
    }
    MPMediaItemArtwork *artwork = [[MPMediaItemArtwork alloc] initWithImage:coverImage];
    playingInfo[MPMediaItemPropertyArtwork] = artwork;
    
    // 当前播放的时间
    NSLog(@"playingInfo . currentTime = %f",self.currentTime / 1000);
    playingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = [NSNumber numberWithFloat:self.currentTime / 1000];
    // 进度的速度
    playingInfo[MPNowPlayingInfoPropertyPlaybackRate] = [NSNumber numberWithFloat:1.0];
    // 总时间
    playingInfo[MPMediaItemPropertyPlaybackDuration] = [NSNumber numberWithFloat:self.duration / 1000];
    if (@available(iOS 10.0, *)) {
        playingInfo[MPNowPlayingInfoPropertyPlaybackProgress] = [NSNumber numberWithFloat:self.audioControlView.progress];
    } else {
        // Fallback on earlier versions
    }
    [playingCenter setNowPlayingInfo:playingInfo];
}

关于MPNowPlayingInfoCenter设置关键字解析的链接:关键字语意解析

 3、其他的一些控制:耳机、电话打断

#pragma mark - Notifications
- (void)addNotifications {
    
    [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
    // 插拔耳机
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(audioSessionRouteChange:) name:AVAudioSessionRouteChangeNotification object:nil];
    // 播放打断,如电话打入
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(audioSessionInterruption:) name:AVAudioSessionInterruptionNotification object:nil];
}

- (void)audioSessionRouteChange:(NSNotification *)notify {
    
    NSDictionary *interuptionDict = notify.userInfo;
    
    NSInteger routeChangeReason = [[interuptionDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue];
    switch (routeChangeReason) {
        case AVAudioSessionRouteChangeReasonNewDeviceAvailable:
            NSLog(@"耳机插入");
            // 继续播放音频,什么也不用做
            break;
        case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:
        {
            NSLog(@"耳机拔出");
            // 注意:拔出耳机时系统会自动暂停你正在播放的音频,因此只需要改变UI为暂停状态即可
            if (self.isPlaying) {
                [self pauseMusic];
            }
        }
            break;   
        default:
            break;
    }
}

- (void)audioSessionInterruption:(NSNotification *)notify {
    
    NSDictionary *interuptionDict = notify.userInfo;
    NSInteger interruptionType = [[interuptionDict valueForKey:AVAudioSessionInterruptionTypeKey] integerValue];
    NSInteger interruptionOption = [[interuptionDict valueForKey: AVAudioSessionInterruptionOptionKey] integerValue];
    switch (interruptionType) {
            
        case AVAudioSessionInterruptionTypeBegan:
        {
            // 收到播放中断的通知,暂停播放
            if (self.isPlaying) {
                self.isInterrupt = YES;
                [self pauseMusic];
                self.isPlaying = NO;
            }
            
        }
            break;
        case AVAudioSessionInterruptionTypeEnded:
        {
            if (self.isInterrupt) {
                self.isInterrupt = NO;
                // 中断结束,判断是否需要恢复播放
                if (interruptionOption == AVAudioSessionInterruptionOptionShouldResume) {
                    if (!self.isPlaying) {
                        [self playMusic];
                        self.isPlaying = YES;
                    }
                }
            }
            
        }
            break;
        default:
            break;
    }
    
}

当然,把这些都加进去,只是完成了音频后台播放相关的设置和代码

如果,APP内还有视频,那么申请了后台播放权限,你会发现会有很多有(头)趣(疼)的问题

特别是在音频视频切换播放时,通知中心音乐控制台的显示,是一个让我很难忘的问题!

关于这方面的问题解决,请阅读这篇博客:支持后台播放的音频、视频开发中遇到的问题

猜你喜欢

转载自blog.csdn.net/weixin_39624536/article/details/89716896