? ? ? ? ?項(xiàng)目上線音頻播放后,仍然滿足不了廣大學(xué)員的需求,學(xué)員想要的是音頻能夠在鎖屏狀態(tài)下也能夠播放。于是和公司領(lǐng)導(dǎo)討論完成音頻的后臺播放功能,現(xiàn)在就將我所實(shí)現(xiàn)音頻鎖屏播放的過程和遇到的坑和大家分享一下。
1、首先是集成音頻播放器:(我把這個播放器放在了一個單利里面)
實(shí)現(xiàn)的代碼如下:
#pragma mark --添加在線播放器
- (void)addOnLinePlayer {
? ? WS(weakSelf)
? ? [[SKAVPlayerManager PlayerManager] replaceItemWithUrlString:self.onLinePlayerUrl type:@"zaixian"];
? ? __weak typeof(SKAVPlayerManager *) manager = [SKAVPlayerManager PlayerManager];
? ? [SKAVPlayerManager PlayerManager].statusBlock = ^(BOOL success) {
? ? ? ? if (success) {
? ? ? ? ? ? // 如果是暫停狀態(tài)則不進(jìn)行播放
? ? ? ? ? ? if (weakSelf.playerButton.selected != YES) {
? ? ? ? ? ? ? ? [manager playerPlay];
? ? ? ? ? ? ? ? [SKAVPlayerManager PlayerManager].player.rate = weakSelf.playerRate;
? ? ? ? ? ? }
? ? ? ? } else {
? ? ? ? }
? ? };
? ? // 添加時間監(jiān)聽
? ? self.myTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
}
// 播放一段音頻
- (void)replaceItemWithUrlString:(NSString *)urlString type:(NSString *)readType {
? ? self.ccid = urlString;
? ? [_playerItem removeObserver:self forKeyPath:@"status"];
? ? _playerItem = nil;
? ? if ([readType isEqualToString:@"zaixian"]) {
? ? ? ? _playerItem = [[AVPlayerItem alloc] initWithURL:[NSURL URLWithString:urlString]];
? ? } else if ([readType isEqualToString:@"bendi"]){
? ? ? ? NSURL *audioURL = [NSURL fileURLWithPath:urlString];
? ? ? ? _playerItem = [[AVPlayerItem alloc] initWithURL:audioURL];
? ? }
? ? [_playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
? ? [self.player replaceCurrentItemWithPlayerItem:_playerItem];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
? ? if ([keyPath isEqualToString:@"status"]) {
? ? ? ? self.isNotify = YES;
? ? ? ? switch (self.player.status) {
? ? ? ? ? ? case AVPlayerStatusUnknown:
? ? ? ? ? ? ? ? // KVO:未知狀態(tài),此時不能播放
? ? ? ? ? ? ? ? if (self.statusBlock) {
? ? ? ? ? ? ? ? ? ? self.statusBlock(NO);
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? break;
? ? ? ? ? ? case AVPlayerStatusReadyToPlay:
? ? ? ? ? ? ? ? //KVO:準(zhǔn)備完畢,可以播放
? ? ? ? ? ? ? ? if (self.statusBlock) {
? ? ? ? ? ? ? ? ? ? self.statusBlock(YES);
? ? ? ? ? ? ? ? }
//? ? ? ? ? ? ? ? [self playerPlay];
//? ? ? ? ? ? ? ? [[NSNotificationCenter defaultCenter] postNotificationName:PlayerStatusNormal object:nil];
? ? ? ? ? ? ? ? break;
? ? ? ? ? ? case AVPlayerStatusFailed:
? ? ? ? ? ? ? ? // KVO:加載失敗,網(wǎng)絡(luò)或者服務(wù)器出現(xiàn)問題
? ? ? ? ? ? ? ? if (self.statusBlock) {
? ? ? ? ? ? ? ? ? ? self.statusBlock(NO);
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? break;
? ? ? ? ? ? default:
? ? ? ? ? ? ? ? break;
? ? ? ? }
? ? }
}
2、時間進(jìn)度的控制如下以及進(jìn)度條的更新:
- (void)timerAction {
? ? if ([SKAVPlayerManager PlayerManager].player.currentTime.timescale == 0 || [SKAVPlayerManager PlayerManager].player.currentItem.duration.timescale == 0) {
? ? ? ? return;
? ? }
? ? // 獲得音樂總時長
? ? long long int totalTime = [SKAVPlayerManager PlayerManager].player.currentItem.duration.value / [SKAVPlayerManager PlayerManager].player.currentItem.duration.timescale;
? ? // 獲得當(dāng)前時間
? ? long long int currentTime = [SKAVPlayerManager PlayerManager].player.currentTime.value / [SKAVPlayerManager PlayerManager].player.currentTime.timescale;
? ? currentTimeLable.text = [NSString stringWithFormat:@"%02lld:%02lld", currentTime / 60, currentTime % 60];
? ? orignalTimeLable.text = [NSString stringWithFormat:@"%02lld:%02lld", totalTime / 60, totalTime % 60];
? ? audioSlider.maximumValue = totalTime;
? ? audioSlider.minimumValue = 0;
? ? audioSlider.value = currentTime;
? ? if (currentTime == totalTime) {
? ? ? ? // 進(jìn)行下一曲播放
? ? ? ? [self nextButtonAction:[UIButton new]];
? ? }
? ? if (@available(iOS 11.0, *)) {
? ? } else {
? ? ? ? if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {
? ? ? ? ? ? [self onLineRemoteControl];
? ? ? ? }
? ? }
}
3、這個時候音頻就能正常播放了,在保證音頻能正常播放的對工程進(jìn)行設(shè)置:
先要注冊后臺播放:

然后在info.plist文件里面進(jìn)行設(shè)置:
App plays audio or streams audio/video using AirPlay

4、當(dāng)程序進(jìn)入后臺和前臺的時候通過通知監(jiān)聽所處的狀態(tài):
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didEnterBackground) name:UIApplicationWillResignActiveNotification object:nil];
? ? [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didBecomeActive) name:UIApplicationDidBecomeActiveNotification object:nil];
- (void)didEnterBackground {
????????[self onLineRemoteControl];
}
// 在線后臺播放
- (void)onLineRemoteControl {
? ? ? ? MPNowPlayingInfoCenter *center = [MPNowPlayingInfoCenter defaultCenter];
? ? ? ? UIImage *lockImage = [UIImage imageNamed:@"photo1.png"];
? ? ? ? MPMediaItemArtwork *artwork =
? ? ? ? [[MPMediaItemArtwork alloc] initWithImage:lockImage];
? ? ? ? NSDictionary *mediaDict =
? ? ? ? @{
? ? ? ? ? MPMediaItemPropertyTitle: self.model.title,
? ? ? ? ? MPMediaItemPropertyMediaType: @(MPMediaTypeAnyAudio),
? ? ? ? ? MPMediaItemPropertyPlaybackDuration:
? ? ? ? ? ? ? @([SKAVPlayerManager PlayerManager].player.currentItem.duration.value / [SKAVPlayerManager PlayerManager].player.currentItem.duration.timescale),
? ? ? ? ? MPNowPlayingInfoPropertyPlaybackRate: @(1.0),
? ? ? ? ? MPNowPlayingInfoPropertyElapsedPlaybackTime:
? ? ? ? ? ? ? @([SKAVPlayerManager PlayerManager].player.currentTime.value / [SKAVPlayerManager PlayerManager].player.currentTime.timescale),
? ? ? ? ? MPMediaItemPropertyAlbumArtist:self.subscribeModel.title,
? ? ? ? ? MPMediaItemPropertyArtist:self.subscribeModel.title,
? ? ? ? ? MPMediaItemPropertyArtwork: artwork};
? ? ? ? [center setNowPlayingInfo:mediaDict];
? ? ? ? //讓app支持接受遠(yuǎn)程控制事件
? ? ? ? [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
}
- (void)?didBecomeActive {
//讓app結(jié)束接受遠(yuǎn)程控制事件
? ? [[UIApplication sharedApplication] endReceivingRemoteControlEvents];
}
5、這個時候發(fā)現(xiàn)是可以進(jìn)行后臺播放的,但是進(jìn)度條無法調(diào)整,為了能夠調(diào)整進(jìn)度條需要添加遠(yuǎn)程控制命令中心:
#pragma mark --鎖屏界面開啟和監(jiān)控遠(yuǎn)程控制事件
- (void)createRemoteCommandCenter{
? ? //遠(yuǎn)程控制命令中心 iOS 7.1 之后? 詳情看官方文檔:https://developer.apple.com/documentation/mediaplayer/mpremotecommandcenter
? ? MPRemoteCommandCenter *commandCenter = [MPRemoteCommandCenter sharedCommandCenter];
? ? //? ? commandCenter.togglePlayPauseCommand 耳機(jī)線控的暫停/播放
? ? [commandCenter.pauseCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
? ? ? ? return MPRemoteCommandHandlerStatusSuccess;
? ? }];
? ? [commandCenter.playCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
? ? ? ? return MPRemoteCommandHandlerStatusSuccess;
? ? }];
? ? [commandCenter.previousTrackCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
? ? ? ? NSLog(@"上一首");
? ? ? ? return MPRemoteCommandHandlerStatusSuccess;
? ? }];
? ? [commandCenter.nextTrackCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
? ? ? ? NSLog(@"下一首");
? ? ? ? return MPRemoteCommandHandlerStatusSuccess;
? ? }];
? ? //在控制臺拖動進(jìn)度條調(diào)節(jié)進(jìn)度(仿QQ音樂的效果)
? ? [commandCenter.changePlaybackPositionCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
? ? ? ? ? ? CMTime totlaTime = [SKAVPlayerManager PlayerManager].player.currentItem.duration;
? ? ? ? ? ? MPChangePlaybackPositionCommandEvent * playbackPositionEvent = (MPChangePlaybackPositionCommandEvent *)event;
? ? ? ? ? ? [[SKAVPlayerManager PlayerManager].player seekToTime:CMTimeMake(totlaTime.value*playbackPositionEvent.positionTime/CMTimeGetSeconds(totlaTime), totlaTime.timescale) completionHandler:^(BOOL finished) {
? ? ? ? ? ? }];
? ? ? ? return MPRemoteCommandHandlerStatusSuccess;
? ? }];
}
這個時候就都實(shí)現(xiàn)了所有的功能了我們所做的有如下截圖:

最后說下需要注意的幾點(diǎn):
1、使用changePlaybackPositionCommand進(jìn)行seekTime時候,控制中心的播放進(jìn)度條停止了下來 使用帶handler的回調(diào),在回調(diào)處再次對info進(jìn)行進(jìn)度條的更新
2、一定要注意block的循環(huán)引用問題,不然會引起內(nèi)存暴漲。