iOS實(shí)現(xiàn)播放遠(yuǎn)程網(wǎng)絡(luò)音樂(lè)的核心技術(shù)點(diǎn)總結(jié)
一、前言
這兩天做了個(gè)小項(xiàng)目涉及到了遠(yuǎn)程音樂(lè)播放,因?yàn)榈谝淮巫鲞@種音樂(lè)項(xiàng)目,邊查資料邊做,其中涉及到主要技術(shù)點(diǎn)有:
- 如何播放遠(yuǎn)程網(wǎng)絡(luò)音樂(lè)
- 如何切換當(dāng)前正在播放中的音樂(lè)資源
- 如何監(jiān)聽(tīng)音樂(lè)播放的各種狀態(tài)(播放器狀態(tài)、播放的進(jìn)度、緩沖的進(jìn)度,播放完成)
- 如何手動(dòng)操控播放進(jìn)度
- 如何在后臺(tái)模式或者鎖屏情況下正常播放音樂(lè)
- 如何在鎖屏模式下顯示音樂(lè)播放信息和遠(yuǎn)程操控音樂(lè)
如果您對(duì)一塊技術(shù)點(diǎn)有興趣或者正在尋找相關(guān)資料,那么本篇或許能提供一些參考或啟發(fā)。
二、 網(wǎng)絡(luò)音樂(lè)播放的核心技術(shù)點(diǎn)
根據(jù)自己的經(jīng)驗(yàn)和查了一些音樂(lè)播放的相關(guān)資料,最簡(jiǎn)單和最易上手的的技術(shù)方案我想應(yīng)該是采用ios系統(tǒng)自帶的AVFoundation框架。
我們知道AVFoundation框架是蘋果專門為多媒體打造的一個(gè)庫(kù),這個(gè)庫(kù)非常強(qiáng)大,專門用來(lái)處理音視頻等復(fù)雜的多媒體技術(shù),而本篇要講的所有技術(shù)點(diǎn)就是基于AVFoundation框架中的一個(gè)類——AVPlayer。
那么AVPlayer是什么?
你可以把他看成是一個(gè)已經(jīng)封裝好的播放器,它的作用是用來(lái)播放遠(yuǎn)程的或本地的視頻和音頻。因?yàn)楸镜氐囊粢曨l的播放比較簡(jiǎn)單,這里就不做講述,本編主要是講遠(yuǎn)程音樂(lè)播放,因?yàn)槎际腔贏VPlayer同一套API,所以掌握遠(yuǎn)程音樂(lè)播放其實(shí)就是相當(dāng)于掌握遠(yuǎn)程視頻播放。好了廢話就不多說(shuō)了,下面開始上菜。
1、導(dǎo)入AVFoundation框架,創(chuàng)建AVPlayer播放器
-(AVPlayer *)player { if (_player == nil) { // AVPlayerItem是一個(gè)包裝音樂(lè)資源的類,初始化時(shí)可以傳入一個(gè)音樂(lè)的url AVPlayerItem *item = [[AVPlayerItem alloc] initWithURL:[NSURL URLWithString:@"http://xxxxxxxx"]]; //通過(guò)AVPlayerItem初始化player _player = [[AVPlayer alloc] initWithPlayerItem:item]; } return _player; }
此處懶加載創(chuàng)建,讓播放器成為控制器的全局屬性,注意需要強(qiáng)引用,否則回收釋放掉了就無(wú)法播放。
2、播放或停止音樂(lè)
//開始播放 [self.player play]; //停止播放 [self.player pause];
這個(gè)沒(méi)什么好講的,只要調(diào)用AVPlayer的兩個(gè)實(shí)例方法
3、切換當(dāng)前正在播放中的音樂(lè)資源
//創(chuàng)建需要播放的AVPlayerItem AVPlayerItem *item = [[AVPlayerItem alloc] initWithURL:[NSURL URLWithString:model.url]]; //替換當(dāng)前音樂(lè)資源 [self.player replaceCurrentItemWithPlayerItem:item];
這個(gè)可以用于歌曲的切換,如上一首、下一首。
4、通過(guò)KVO監(jiān)聽(tīng)播放器的狀態(tài)
[self.player.currentItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
拿到播放器的currentItem,注冊(cè)當(dāng)前對(duì)象為觀察者,監(jiān)聽(tīng)它的status屬性。status屬性是AVPlayerItemStatus類型,它是一個(gè)枚舉類型,如下:
typedef NS_ENUM(NSInteger, AVPlayerItemStatus) { AVPlayerItemStatusUnknown,//未知狀態(tài) AVPlayerItemStatusReadyToPlay,//準(zhǔn)備播放 AVPlayerItemStatusFailed//加載失敗 };
當(dāng)status屬性值發(fā)生改變時(shí),就會(huì)觸發(fā)觀察者方法的回調(diào),如下:
//觀察者回調(diào) -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context { //注意這里查看的是self.player.status屬性 if ([keyPath isEqualToString:@"status"]) { switch (self.player.status) { case AVPlayerStatusUnknown: { NSLog(@"未知轉(zhuǎn)態(tài)"); } break; case AVPlayerStatusReadyToPlay: { NSLog(@"準(zhǔn)備播放"); } break; case AVPlayerStatusFailed: { NSLog(@"加載失敗"); } break; default: break; } } }
當(dāng) self.player.status == AVPlayerStatusReadyToPlay
時(shí),音樂(lè)就會(huì)開始正常播放,另外兩種狀態(tài)音樂(lè)是無(wú)法播放的,可以在上面方法相應(yīng)狀態(tài)里給出提示。這里需要特別強(qiáng)調(diào)一點(diǎn)的是觀察者監(jiān)聽(tīng)的對(duì)象是 self.player.currentItem
,而不是 self.player
,而當(dāng)監(jiān)聽(tīng)的屬性發(fā)生改變時(shí),觀察者回調(diào)的方法里需要查看的是 self.player.status
。當(dāng)然,你也可以不這么干,但是我嘗試過(guò)好幾次,不這么干的后果是無(wú)法監(jiān)聽(tīng)到 self.player.status
屬性的改變。
當(dāng)音樂(lè)播放完成,或者切換下一首歌曲時(shí),請(qǐng)務(wù)必記得移除觀察者,否則會(huì)crash。操作如下:
//移除觀察者 [self.player.currentItem removeObserver:self forKeyPath:@"status"];
5、監(jiān)聽(tīng)音樂(lè)的緩沖進(jìn)度
這個(gè)也是通過(guò)KVO監(jiān)聽(tīng)播放器當(dāng)前播放的音樂(lè)資源AVPlayerItem的loadedTimeRanges屬性。我們先看監(jiān)聽(tīng),如下:
//KVO監(jiān)聽(tīng)音樂(lè)緩沖狀態(tài) [self.player.currentItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
當(dāng) loadedTimeRanges
屬性發(fā)生改變時(shí),回調(diào)如下:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context { if ([keyPath isEqualToString:@"loadedTimeRanges"]) { NSArray * timeRanges = self.player.currentItem.loadedTimeRanges; //本次緩沖的時(shí)間范圍 CMTimeRange timeRange = [timeRanges.firstObject CMTimeRangeValue]; //緩沖總長(zhǎng)度 NSTimeInterval totalLoadTime = CMTimeGetSeconds(timeRange.start) + CMTimeGetSeconds(timeRange.duration); //音樂(lè)的總時(shí)間 NSTimeInterval duration = CMTimeGetSeconds(self.player.currentItem.duration); //計(jì)算緩沖百分比例 NSTimeInterval scale = totalLoadTime/duration; //更新緩沖進(jìn)度條 self.loadTimeProgress.progress = scale; } }
loadedTimeRanges這個(gè)屬性是一個(gè)數(shù)組,里面裝的是本次緩沖的時(shí)間范圍,這個(gè)范圍是用一個(gè)結(jié)構(gòu)體 CMTimeRange
表示,當(dāng)然在oc中結(jié)構(gòu)體是不能直接存放數(shù)組的,所以它被包裝成了oc對(duì)象 NSValue
。
我們來(lái)看下這個(gè)結(jié)構(gòu)體:
typedef struct { CMTime start; CMTime duration; } CMTimeRange;
start表示本次緩沖時(shí)間的起點(diǎn),duratin表示本次緩沖持續(xù)的時(shí)間范圍,具體詳細(xì)的計(jì)算方法可以看上面方法的實(shí)現(xiàn)。
當(dāng)音樂(lè)播放完成,或者切換下一首歌曲時(shí),請(qǐng)務(wù)必記得移除觀察者,否則會(huì)crash。操作如下:
[self.player.currentItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
6、監(jiān)聽(tīng)音樂(lè)播放的進(jìn)度
這個(gè)不是通過(guò)KVO了,AVPlayer專門提供了下面這個(gè)api用來(lái)監(jiān)聽(tīng)播放的進(jìn)度:
/** 監(jiān)聽(tīng)音樂(lè)播放進(jìn)度 @param interval 監(jiān)聽(tīng)的時(shí)間間隔,用來(lái)設(shè)置多長(zhǎng)時(shí)間回調(diào)一次 @param queue 隊(duì)列,一般傳主隊(duì)列 @param block 回調(diào)的block,會(huì)把當(dāng)前的播放時(shí)間傳遞過(guò)來(lái) @return 監(jiān)聽(tīng)的對(duì)象 */ - (id)addPeriodicTimeObserverForInterval:(CMTime)interval queue:(nullable dispatch_queue_t)queue usingBlock:(void (^)(CMTime time))block;
操作如下:
__weak typeof(self) weakSelf = self; self.timeObserver = [self.player addPeriodicTimeObserverForInterval:CMTimeMake(1.0, 1.0) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) { //當(dāng)前播放的時(shí)間 float current = CMTimeGetSeconds(time); //總時(shí)間 float total = CMTimeGetSeconds(item.duration); if (current) { float progress = current / total; //更新播放進(jìn)度條 weakSelf.playSlider.value = progress; weakSelf.currentTime.text = [weakSelf timeFormatted:current]; } }];
我們可以這個(gè)block里面拿到當(dāng)前播放時(shí)間,根據(jù)總時(shí)間計(jì)算出當(dāng)前播放所占的時(shí)間比例,最后更新播放進(jìn)度條。這里又涉及到了一個(gè)數(shù)據(jù)類類型CMTime,它也是一個(gè)結(jié)構(gòu)體,用來(lái)作為時(shí)間的格式,定義如下:
typedef struct CMTimeValue value; CMTimeScale timescale; CMTimeFlags flags; CMTimeEpoch epoch; } CMTime;
CMTime是以分?jǐn)?shù)的形式表示時(shí)間,value表示分子,timescale表示分母,flags是位掩碼,表示時(shí)間的指定狀態(tài)。所以我們要獲得時(shí)間的秒數(shù)需要分子除以分母。當(dāng)然你還可以用下面這個(gè)函數(shù)來(lái)獲取時(shí)間的秒數(shù):
Float64 CMTimeGetSeconds(CMTime time)
最后,當(dāng)音樂(lè)播放完成或者切換音樂(lè)時(shí),依然需要移除監(jiān)聽(tīng):
if (self.timeObserver) { [self.player removeTimeObserver:self.timeObserver]; self.timeObserver = nil; }
7、手動(dòng)超控(移動(dòng)滑塊)播放進(jìn)度
這是一個(gè)播放音視頻很常見(jiàn)的功能,所以強(qiáng)大的AVPlayer理所當(dāng)然的提供了幾個(gè)api,下面只講述其中最簡(jiǎn)單的一個(gè):
/** 定位播放時(shí)間 @param time 指定的播放時(shí)間 */ - (void)seekToTime:(CMTime)time; 具體使用如下: //移動(dòng)滑塊調(diào)整播放進(jìn)度 - (IBAction)playSliderValueChange:(UISlider *)sender { //根據(jù)值計(jì)算時(shí)間 float time = sender.value * CMTimeGetSeconds(self.player.currentItem.duration); //跳轉(zhuǎn)到當(dāng)前指定時(shí)間 [self.player seekToTime:CMTimeMake(time, 1)]; }
8、監(jiān)聽(tīng)音樂(lè)播放完成
一般音視頻播放完成時(shí)我們或多或少的都要處理一些業(yè)務(wù),比如循環(huán)播放,播完退出界面等等。下面看下如何監(jiān)聽(tīng)AVPlayer的播放完成。
//給AVPlayerItem添加播放完成通知 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playFinished:) name:AVPlayerItemDidPlayToEndTimeNotification object:_player.currentItem];
這里是采用注冊(cè)監(jiān)聽(tīng)AVPlayerItemDidPlayToEndTimeNotification通知,當(dāng)AVPlayer一播放完成時(shí),便會(huì)發(fā)出這個(gè)通知,我們收到通知后進(jìn)行處理即可
9、設(shè)置音樂(lè)后臺(tái)播放
我們知道運(yùn)行在ios系統(tǒng)下的程序一旦進(jìn)入后臺(tái)就會(huì)處于休眠狀態(tài),程序停止運(yùn)行了,也就播放不了什么音樂(lè)了。但是有一些特定功能的app還是處于可以后臺(tái)運(yùn)行的,比如音樂(lè)類型的app正處于這個(gè)范疇。但是,并不是說(shuō)你在應(yīng)用中播放音樂(lè)就能后臺(tái)高枕無(wú)憂的運(yùn)行了,你依然需要做如下幾步操作:
(1)開啟后臺(tái)模式
target ->capabilities-> Background modes ->打開開關(guān) ->勾選第一個(gè)選項(xiàng)
(2)程序啟動(dòng)時(shí)設(shè)置音頻會(huì)話
//一般在方法:application: didFinishLaunchingWithOptions:設(shè)置 //獲取音頻會(huì)話 AVAudioSession *session = [AVAudioSession sharedInstance]; //設(shè)置類型是播放。 [session setCategory:AVAudioSessionCategoryPlayback error:nil]; //激活音頻會(huì)話。 [session setActive:YES error:nil];
以上兩步設(shè)置無(wú)誤,程序進(jìn)入后臺(tái)模式,便可以進(jìn)行音樂(lè)播放
10、如何設(shè)置音樂(lè)鎖頻信息
我們看百度音樂(lè)鎖頻時(shí),也依然能在屏幕上展示歌曲的信息,以及切換歌曲等。下面看看這個(gè)功能是如何實(shí)現(xiàn)的:
//音樂(lè)鎖屏信息展示 - (void)setupLockScreenInfo { // 1.獲取鎖屏中心 MPNowPlayingInfoCenter *playingInfoCenter = [MPNowPlayingInfoCenter defaultCenter]; //初始化一個(gè)存放音樂(lè)信息的字典 NSMutableDictionary *playingInfoDict = [NSMutableDictionary dictionary]; // 2、設(shè)置歌曲名 if (self.currentModel.name) { [playingInfoDict setObject:self.currentModel.name forKey:MPMediaItemPropertyAlbumTitle]; } // 設(shè)置歌手名 if (self.currentModel.artist) { [playingInfoDict setObject:self.currentModel.artist forKey:MPMediaItemPropertyArtist]; } // 3設(shè)置封面的圖片 UIImage *image = [self getMusicImageWithMusicId:self.currentModel]; if (image) { MPMediaItemArtwork *artwork = [[MPMediaItemArtwork alloc] initWithImage:image]; [playingInfoDict setObject:artwork forKey:MPMediaItemPropertyArtwork]; } // 4設(shè)置歌曲的總時(shí)長(zhǎng) [playingInfoDict setObject:self.currentModel.detailDuration forKey:MPMediaItemPropertyPlaybackDuration]; //音樂(lè)信息賦值給獲取鎖屏中心的nowPlayingInfo屬性 playingInfoCenter.nowPlayingInfo = playingInfoDict; // 5.開啟遠(yuǎn)程交互,只有開啟這個(gè)才能進(jìn)行遠(yuǎn)程操控 [[UIApplication sharedApplication] beginReceivingRemoteControlEvents]; }
這里設(shè)置圖片時(shí)需要注意下,異步加載網(wǎng)絡(luò)圖片后再設(shè)置是無(wú)效的,所以圖片信息最好是先請(qǐng)求下來(lái)后再進(jìn)行設(shè)置。
遠(yuǎn)程超控的回調(diào)如下:
//監(jiān)聽(tīng)遠(yuǎn)程交互方法 - (void)remoteControlReceivedWithEvent:(UIEvent *)event { switch (event.subtype) { //播放 case UIEventSubtypeRemoteControlPlay:{ [self.player play]; } break; //停止 case UIEventSubtypeRemoteControlPause:{ [self.player pause]; } break; //下一首 case UIEventSubtypeRemoteControlNextTrack: [self nextBtnAction:nil]; break; //上一首 case UIEventSubtypeRemoteControlPreviousTrack: [self lastBtnAction:nil]; break; default: break; } }
三、總結(jié)
最后,畫了一張圖總結(jié)下播放遠(yuǎn)程網(wǎng)絡(luò)音樂(lè)的流程:
根據(jù)QQ音樂(lè)的界面做了個(gè)小demo,下面是demo的真機(jī)前臺(tái)和后臺(tái)播放的運(yùn)行效果:
四、結(jié)束語(yǔ)
播放遠(yuǎn)程網(wǎng)絡(luò)音樂(lè)的核心技術(shù)點(diǎn)基本上已經(jīng)寫完,當(dāng)然AVPlayer還有很多強(qiáng)大的功能沒(méi)有寫出來(lái),有興趣的可以進(jìn)一步挖掘。寫到這里已經(jīng)疲倦至極,后續(xù)會(huì)持續(xù)更新一些精彩的技術(shù)點(diǎn),也希望大家多多支持腳本之家。
- Nagios遠(yuǎn)程監(jiān)控安裝與配置詳解圖文
- iOS10 適配遠(yuǎn)程推送功能實(shí)現(xiàn)代碼
- iOS實(shí)現(xiàn)遠(yuǎn)程推送原理及過(guò)程
- iOS10最新實(shí)現(xiàn)遠(yuǎn)程通知的開發(fā)教程詳解
- iOS開發(fā)之運(yùn)動(dòng)事件和遠(yuǎn)程控制
- 詳解iOS本地推送與遠(yuǎn)程推送
- iOS消息遠(yuǎn)程推送通知
- iOS實(shí)時(shí)監(jiān)控網(wǎng)絡(luò)狀態(tài)的改變
- iOS實(shí)現(xiàn)實(shí)時(shí)檢測(cè)網(wǎng)絡(luò)狀態(tài)的示例代碼
- iOS中的實(shí)時(shí)遠(yuǎn)程配置全紀(jì)錄
相關(guān)文章
簡(jiǎn)單好用可任意定制的iOS Popover氣泡效果
Popover(氣泡彈出框/彈出式氣泡/氣泡)是由一個(gè)矩形和三角箭頭組成的彈出窗口,箭頭指向的地方通常是導(dǎo)致Popover彈出的控件或區(qū)域。本文通過(guò)實(shí)例代碼給大家介紹了iOS Popover氣泡效果,需要的朋友參考下吧2017-12-12C++ 中exit(),_exit(),return,abort()函數(shù)的區(qū)別
這篇文章主要介紹了C++ 中exit(),_exit(),return,abort()函數(shù)的區(qū)別的相關(guān)資料,需要的朋友可以參考下2016-12-12iOS開發(fā)使用XML解析網(wǎng)絡(luò)數(shù)據(jù)
XML解析其實(shí)這個(gè)概念出現(xiàn)了算夠久了,以前javaweb什么到處都在用。這邊我們主要大致介紹下,然后在在ios編程如何使用。2016-02-02iOS App開發(fā)中Core Data框架基本的數(shù)據(jù)管理功能小結(jié)
除了使用SQL關(guān)系型數(shù)據(jù)庫(kù),我們還可以使用Xcode中提供的Core Data來(lái)進(jìn)行表結(jié)構(gòu)數(shù)據(jù)處理,這里我們就來(lái)初步整理iOS App開發(fā)中Core Data框架基本的數(shù)據(jù)管理功能小結(jié):2016-06-06IOS 長(zhǎng)鏈接與短鏈接之間的轉(zhuǎn)換
這篇文章主要介紹了IOS 長(zhǎng)鏈接與短鏈接之間的轉(zhuǎn)換的相關(guān)資料,需要的朋友可以參考下2017-06-06iOS開發(fā)中控制屏幕旋轉(zhuǎn)的編寫方法小結(jié)
這篇文章主要介紹了iOS開發(fā)中控制屏幕旋轉(zhuǎn)的編寫方法小結(jié),包括橫豎屏切換時(shí)視圖所出現(xiàn)的問(wèn)題等經(jīng)常需要注意的地方,需要的朋友可以參考下2015-10-10IOS-MVC層讀取服務(wù)器接口JSON數(shù)據(jù)
這篇文章主要為大家詳細(xì)介紹了IOS-MVC層讀取服務(wù)器接口JSON數(shù)據(jù),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-12-12iOS視頻壓縮存儲(chǔ)至本地并上傳至服務(wù)器實(shí)例代碼
本篇文章主要介紹了iOS視頻壓縮存儲(chǔ)至本地并上傳至服務(wù)器實(shí)例代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04IOS 避免self循環(huán)引用的方法的實(shí)例詳解
這篇文章主要介紹了IOS 避免self循環(huán)引用的方法的實(shí)例詳解的相關(guān)資料,希望通過(guò)本文能幫助到大家,需要的朋友可以參考下2017-09-09