最近一直在做音頻播放器的改版重構(gòu),開發(fā)測試到最后發(fā)布用時差不多要一個月,功能上可能并不是很多,跟大多數(shù)音樂播放器一樣,但是要注意的細(xì)節(jié)倒是不少。今天將播放器整理一下,做個儲備。
播放器懸浮窗篇

播放器功能結(jié)構(gòu)
- 從視圖上看整個播放器從上往下分為六個功能結(jié)構(gòu)
- 音頻詳情:當(dāng)前音頻的詳情數(shù)據(jù)
- 專欄詳情:當(dāng)前專欄的詳情
- 倍速定時控制:倍速和定時列表選項(xiàng)
- 控制條:進(jìn)度控制
- 播放控制:音頻播放控制
- 業(yè)務(wù)相關(guān):音頻列表、下載、點(diǎn)贊、進(jìn)文章頁
從功能結(jié)構(gòu)上劃分,可以對播放器的視圖做一個結(jié)構(gòu)拆分,這里是將播放控制和業(yè)務(wù)相關(guān)進(jìn)行了一個自定義view,可以簡單的降低了視圖的復(fù)雜度。
播放器數(shù)據(jù)結(jié)構(gòu)
開發(fā)模式采用了MVVM+RAC,無論是MVC還是MVVM、MVCS說白了都是數(shù)據(jù)流式架構(gòu),控制了數(shù)據(jù)的在代碼結(jié)構(gòu)中的走向,所以項(xiàng)目中只要能約定好一個固定的數(shù)據(jù)流的走向,分層結(jié)構(gòu)明確,就能在開發(fā)中降低耦合度,提升代碼質(zhì)量。
我們這里的VM是拆分成了store 和 service,store層負(fù)責(zé)基礎(chǔ)數(shù)據(jù)的管理,包括從網(wǎng)絡(luò)層和數(shù)據(jù)庫層上來的數(shù)據(jù),service負(fù)責(zé)從store層拿數(shù)據(jù),然后通過RAC提供給view層,所以播放器這里的view,只提供了一個入口函數(shù),外邊調(diào)用播放器,只需要傳遞專欄詳情和列表數(shù)據(jù)以及播放位置,而播放器的當(dāng)前播放數(shù)據(jù)都存放在service里。
//播放器的入口函數(shù),viewcontroller提供
- (void)addPlayList:(NSArray *)playList playIndex:(NSInteger)index detail:(NSDictionary *)detailColumn;
// 播放器的當(dāng)前播放數(shù)據(jù),service持有
@property (nonatomic, strong) Model currentAudioModel; //當(dāng)前播放的audio
@property (nonatomic, assign) NSInteger currentAudioIndex; //當(dāng)前播放的位置
@property (nonatomic, strong) Model *detailRecord; //詳情
@property (nonatomic, strong) NSArray *playList;//播放列表
@property (nonatomic, assign) PlayStatus currentAudioPlayStatus; //當(dāng)前播放狀態(tài)
@property (nonatomic, strong) NSString *playTime; //播放時間
@property (nonatomic, strong) NSNumber *hadLiked; //是否點(diǎn)了贊
@property (nonatomic, strong) NSString *audioSpeed;//當(dāng)前播放速度
@property (nonatomic, strong) RACSubject *rac_shareIcon;
播放器核心控制
播放器開發(fā)通常采用的都是AVPlayer,功能豐富,自定制比較強(qiáng)。這里簡單的比較一下系統(tǒng)提供的音頻播放功能:
- AudioToolbox.framework提供的系統(tǒng)聲音服務(wù)(System Sound Service),
通常用來播放鈴聲,點(diǎn)擊聲音,推送聲
音效播放時間不能超過30s
數(shù)據(jù)必須是PCM或者IMA4格式
音頻文件必須打包成.caf , .aif, .wav中的一種(這是官方的說法,實(shí)際測試一些.aac .mp3格式的也可以播放) - AVFoundation.framework中的AVAudioPlayer
通過音頻的NSData或者本地音頻文件的url,來創(chuàng)建一個AVAudioPlayer實(shí)例,如加載本地的music.mp3的音頻 - AVQueuePlayer
AVPlayer的子類可以實(shí)現(xiàn)列表播放。因?yàn)槭顷犃泄芾恚詻]有提供上一首的方法,不滿足需求。 - AVPlayer
支持播放本地、分步下載、或在線流媒體音視頻,不僅可以播放音頻,配合AVPlayerLayer類可實(shí)現(xiàn)視頻播放。另外支持播放進(jìn)度監(jiān)聽。
AVPlayer的使用
- 設(shè)置播放器URL
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:playerUrl options:nil];
AVPlayerItem *item = [AVPlayerItem playerItemWithAsset:asset];
self.player = [AVPlayer playerWithPlayerItem:item];
- 添加播放器監(jiān)聽
這里監(jiān)聽的播放器器屬性包含了四個
- status:我在AVPlayerItemStatusReadyToPlay的狀態(tài)下,取CMTimeGetSeconds(playerItem.duration)的值進(jìn)行了播放總時間的更新,因?yàn)楹笈_給的音頻總時間不一定是很準(zhǔn)確的,可能會丟失一點(diǎn)精度。
- loadedTimeRanges:網(wǎng)絡(luò)音頻的的緩沖進(jìn)度,這里需要注意的是,本地數(shù)據(jù)不會調(diào)用,所以在設(shè)置的時候本地數(shù)據(jù)不能依賴這里
- playbackBufferEmpty:緩沖是否為空,這個值為YES,視頻就會暫停,加載圈就會啟動
- playbackLikelyToKeepUp:視頻是否可以正常播放,這個值為YES,加載圈就會消失,這里不包含本地音頻播放的情況
- name:AVPlayerItemDidPlayToEndTimeNotification:播放結(jié)束通知
- addPeriodicTimeObserverForInterval 播放進(jìn)度監(jiān)聽
[self.player.currentItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
[self.player.currentItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
[self.player.currentItem addObserver:self forKeyPath:@"playbackBufferEmpty" options:NSKeyValueObservingOptionNew context:nil];
[self.player.currentItem addObserver:self forKeyPath:@"playbackLikelyToKeepUp" options:NSKeyValueObservingOptionNew context:nil];
//
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playFinished:) name:AVPlayerItemDidPlayToEndTimeNotification object:self.player.currentItem];
- (id)addPeriodicTimeObserverForInterval:(CMTime)interval queue:(nullable dispatch_queue_t)queue usingBlock:(void (^)(CMTime time))block;
- 鎖屏狀態(tài)信息
//音樂鎖屏信息展示
- (void)setupLockScreenInfo
{
// 1.獲取鎖屏中心
MPNowPlayingInfoCenter *playingInfoCenter = [MPNowPlayingInfoCenter defaultCenter];
//初始化一個存放音樂信息的字典
NSMutableDictionary *playingInfoDict = [NSMutableDictionary dictionary];
// 2、設(shè)置歌曲名
[playingInfoDict setObject:self.currentModel.title forKey:MPMediaItemPropertyTitle];
// 設(shè)置歌手名
[playingInfoDict setObject:self.currentModel.artist forKey:MPMediaItemPropertyArtist];
// 3設(shè)置封面的圖片
MPMediaItemArtwork *artwork = [[MPMediaItemArtwork alloc] initWithImage:image];
[playingInfoDict setObject:artwork forKey:MPMediaItemPropertyArtwork];
// 4設(shè)置歌曲的總時長
[playingInfoDict setObject:self.currentModel.time?:@0 forKey:MPMediaItemPropertyPlaybackDuration];
//音樂信息賦值給獲取鎖屏中心的nowPlayingInfo屬性
playingInfoCenter.nowPlayingInfo = playingInfoDict;
// 5.開啟遠(yuǎn)程交互
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
}
設(shè)備控制之AVAudioSession
只要是app使用到了音頻處理的相關(guān)硬件,就需要對AVAudioSession進(jìn)行設(shè)置,來管理多個APP對音頻硬件設(shè)備(麥克風(fēng),揚(yáng)聲器)的資源使用。
AUAvudioSession能做很多事:
- 設(shè)置自己的APP是否和其他APP音頻同時存在,還是中斷其他APP聲音
- 在手機(jī)調(diào)到靜音模式下,自己的APP音頻是否可以播放出聲音
- 電話或者其他APP中斷自己APP的音頻的事件處理
- 指定音頻輸入和輸出的設(shè)備(比如是聽筒輸出聲音,還是揚(yáng)聲器輸出聲音)
- 是否支持錄音,錄音同時是否支持音頻播放
1. AVAudioSession的Categoary設(shè)置
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:&error]
上述代碼是一般音樂播放器程序中的category,代表了只支持音頻播放,通過設(shè)置可以不支持與其他app進(jìn)行混音。iOS一共提供了七種category來進(jìn)行開發(fā)中的相應(yīng)配置,每種配置都提供了四種能力,
四種能力:
- 是否打斷不支持混音播放的APP
- 是否會響應(yīng)手機(jī)靜音鍵開關(guān)
- 是否支持音頻錄制
- 是否支持音頻播放

- AVAudioSessionCategoryAmbient,只支持音頻播放。這個 Category,音頻會被靜音鍵和鎖屏鍵靜音。并且不會打斷其他應(yīng)用的音頻播放。
- AVAudioSessionCategorySoloAmbient,這個是系統(tǒng)默認(rèn)使用的 Category,只支持音頻播放。音頻會被靜音鍵和鎖屏鍵靜音。和AVAudioSessionCategoryAmbient不同的是,這個會打斷其他應(yīng)用的音頻播放
- AVAudioSessionCategoryPlayback,只支持音頻播放。你的音頻不會被靜音鍵和鎖屏鍵靜音。適用于音頻是主要功能的APP,像網(wǎng)易云這些音樂app,鎖屏后依然可以播放。
需要注意一下,選擇支持在靜音鍵切到靜音狀態(tài)以及鎖屏鍵切到鎖屏狀態(tài)下仍然可以播放音頻 Category 時,必須在應(yīng)用中開啟支持后臺音頻功能,詳見 UIBackgroundModes。
- AVAudioSessionCategoryRecord,只支持音頻錄制。不支持播放。
- AVAudioSessionCategoryPlayAndRecord,支持音頻播放和錄制。音頻的輸入和輸出不需要同步進(jìn)行,也可以同步進(jìn)行。需要音頻通話類應(yīng)用,可以使用這個 Category。
- AVAudioSessionCategoryAudioProcessing,只支持本地音頻編解碼處理。不支持播放和錄制。
- AVAudioSessionCategoryMultiRoute,支持音頻播放和錄制。允許多條音頻流的同步輸入和輸出。(比如USB連接外部揚(yáng)聲器輸出音頻,藍(lán)牙耳機(jī)同時播放另一路音頻這種特殊需求)
我們也可以通過AVAudioSession的屬性來讀取當(dāng)前設(shè)備支持的Category,這樣可以保證設(shè)備兼容性。
@property(readonly) NSArray<NSString *> *availableCategories;
2. AVAudioSession的Mode和Options設(shè)置
上邊的Category是七種大類別,Mode和Options是小類別調(diào)整,一些情況下我們可能并不需要調(diào)整。
- Mode
我們通過讀取下面這條屬性獲取當(dāng)前設(shè)備支持的Mode
@property(readonly) NSArray<NSString *> *availableModes;
iOS下有七種mode來定制我們的Category行為

- AVAudioSessionModeDefault,默認(rèn)模式,與所有的 Category 兼容
- AVAudioSessionModeVoiceChat,適用于VoIP 類型的應(yīng)用。只能是 AVAudioSessionCategoryPlayAndRecord Category下。在這個模式系統(tǒng)會自動配置AVAudioSessionCategoryOptionAllowBluetooth 這個選項(xiàng)。系統(tǒng)會自動選擇最佳的內(nèi)置麥克風(fēng)組合支持語音聊天。
- AVAudioSessionModeVideoChat,用于視頻聊天類型應(yīng)用,只能是 AVAudioSessionCategoryPlayAndRecord Category下。適在這個模式系統(tǒng)會自動配置 AVAudioSessionCategoryOptionAllowBluetooth 和 AVAudioSessionCategoryOptionDefaultToSpeaker 選項(xiàng)。系統(tǒng)會自動選擇最佳的內(nèi)置麥克風(fēng)組合支持視頻聊天。
- AVAudioSessionModeGameChat,適用于游戲類應(yīng)用。使用 GKVoiceChat 對象的應(yīng)用會自動設(shè)置這個模式和AVAudioSessionCategoryPlayAndRecord Category。實(shí)際參數(shù)和AVAudioSessionModeVideoChat一致
- AVAudioSessionModeVideoRecording,適用于使用攝像頭采集視頻的應(yīng)用。只能是 AVAudioSessionCategoryPlayAndRecord 和 - AVAudioSessionCategoryRecord 這兩個 Category下。這個模式搭配 AVCaptureSession API 結(jié)合來用可以更好地控制音視頻的輸入輸出路徑。(例如,設(shè)置 automaticallyConfiguresApplicationAudioSession 屬性,系統(tǒng)會自動選擇最佳輸出路徑。
- AVAudioSessionModeMeasurement,最小化系統(tǒng)。只用于 AVAudioSessionCategoryPlayAndRecord、AVAudioSessionCategoryRecord、AVAudioSessionCategoryPlayback 這幾種 Category。
- AVAudioSessionModeMoviePlayback,適用于播放視頻的應(yīng)用。只用于 AVAudioSessionCategoryPlayback 這個Category。
-
Options
使用options去微調(diào)Category行為,如下表
Options
調(diào)優(yōu)我們的Category
通過Category和合適的Mode和Options的搭配我們可以調(diào)優(yōu)出我們的效果,下面舉兩個應(yīng)用場景:
用過高德地圖的都知道,在后臺播放QQ音樂的時候,如果導(dǎo)航語音出來,QQ音樂不會停止,而是被智能壓低和混音,等導(dǎo)航語音播報完后,QQ音樂正常播放,這里我們需要后臺播放音樂,所以Category使用AVAudioSessionCategoryPlayback,需要混音和智能壓低其他APP音量,所以O(shè)ptions選用 AVAudioSessionCategoryOptionMixWithOthers和AVAudioSessionCategoryOptionDuckOthers
代碼示例如下
BOOL isSuccess = [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback
withOptions:AVAudioSessionCategoryOptionMixWithOthers | AVAudioSessionCategoryOptionDuckOthers
error:&setCategoryError];
又或者我希望AVAudioSessionCategoryPlayAndRecord這個Category默認(rèn)的音頻由揚(yáng)聲器播放,那么可以調(diào)用這個接口去調(diào)整Category
- (BOOL)setCategory:(NSString *)category
withOptions:(AVAudioSessionCategoryOptions)options
error:(NSError **)outError
通過選擇合適和Category,mode和options,就可以調(diào)優(yōu)音頻的輸入輸出,來滿足日常開發(fā)需求(需要注意的是Category,mode,option是搭配使用的,而不是簡單組合,也就是說某種Category支持某些mode和option,從上面的表中也可以看出這一點(diǎn))
3. 播放器中斷處理
其他APP或者電話會中斷我們的APP音頻,所以相應(yīng)的我們要做出處理。
我們可以通過監(jiān)聽AVAudioSessionInterruptionNotification這個key獲取音頻中斷事件
回調(diào)回來Userinfo有鍵值
AVAudioSessionInterruptionTypeKey:
取值A(chǔ)VAudioSessionInterruptionTypeBegan表示中斷開始
取值A(chǔ)VAudioSessionInterruptionTypeEnded表示中斷結(jié)束
中斷開始:我們需要做的是保存好播放狀態(tài),上下文,更新用戶界面等
中斷結(jié)束:我們要做的是恢復(fù)好狀態(tài)和上下文,更新用戶界面,根據(jù)需求準(zhǔn)備好之后選擇是否激活我們session。
選擇不同的音頻播放技術(shù),處理中斷方式也有差別,具體如下:
- System Sound Services:使用 System Sound Services 播發(fā)音頻,系統(tǒng)會自動處理,不受APP控制,當(dāng)中斷發(fā)生時,音頻播放會靜音,當(dāng)中斷結(jié)束后,音頻播放會恢復(fù)。
- AV Foundation framework:AVAudioPlayer 類和 AVAudioRecorder 類提供了中斷開始和結(jié)束的 Delegate 回調(diào)方法來處理中斷。中斷發(fā)生,系統(tǒng)會自動停止播放,需要做的是記錄播放時間等狀態(tài),更新用戶界面,等中斷結(jié)束后,再次調(diào)用播放方法,系統(tǒng)會自動激活session。
- Audio Queue Services, I/O audio unit:使用aduio unit這些技術(shù)需要處理中斷,需要做的是記錄播放或者錄制的位置,中斷結(jié)束后自己恢復(fù)audio session。
- OpenAL:使用 OpenAL 播放時,同樣需要自己監(jiān)聽中斷。管理 OpenAL上下文,用戶中斷結(jié)束后恢復(fù)audio session。
需要注意的是:
- 有中斷開始事件,不一定對應(yīng)有中斷結(jié)束事件,所以需要在用戶進(jìn)入前臺,點(diǎn)擊UI操作的時候,需要保存好播放狀態(tài)和對Audio Session管理,以便不影響APP的音頻功能。
- 接受到中斷結(jié)束時候,需要判斷是否需要進(jìn)行繼續(xù)播放,未放置在之前沒有播放時候也接受到了中斷結(jié)束事件,所以需要增加之前是否播放的判斷。
- 音頻資源競爭上,一定是電話優(yōu)先。
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleInterreption:)
name:AVAudioSessionInterruptionNotification
object:nil];
- (void)handleInterreption:(NSNotification *)notificaiton
{
DDLogDebug(@"handleInterreption:%@", notificaiton.userInfo);
AVAudioSessionInterruptionType interuptionType = [notificaiton.userInfo[AVAudioSessionInterruptionTypeKey] intValue];
switch (interuptionType) {
case AVAudioSessionInterruptionTypeBegan: {
DDLogDebug(@"收到中斷,停止音頻播放");
[self pause];
break;
}
case AVAudioSessionInterruptionTypeEnded: {
DDLogDebug(@"系統(tǒng)中斷結(jié)束");
AVAudioSessionInterruptionOptions options = [notificaiton.userInfo[AVAudioSessionInterruptionOptionKey] intValue];
NSError *error = nil;
if (options == AVAudioSessionInterruptionOptionShouldResume) {
[self play:nil];
}
if (error) {
DDLogDebug(@"AVAudioSessionInterruptionOptionShouldResume失敗:%@",[error localizedDescription]);
}
break;
}
}
}
3. AVAudioSession之 setActive
setActive方法有一下幾個功能:
- 設(shè)置在靜音模式下播放
- 設(shè)置之后可以使app拿到第一響應(yīng)者,同時會取消別的音樂播放器播放狀態(tài)
- 取消設(shè)置時候設(shè)置withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation,可以放棄相應(yīng),使對方收到中斷通知,對方app可以繼續(xù)播放。
這里有一個問題,在app剛啟動時候,設(shè)置active為yes,然后立刻鎖屏,app會有幾率收到中斷通知,所以我們上邊提到,收到中斷喚醒時候需要判斷是否可以進(jìn)行播放。
耳機(jī)監(jiān)聽
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(audioSessionRouteChange:)
name:AVAudioSessionRouteChangeNotification
object:nil];
- (void)audioSessionRouteChange:(NSNotification *)notify
{
NSDictionary *interuptionDict = notify.userInfo;
NSInteger routeChangeReason = [[interuptionDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue];
switch (routeChangeReason) {
case AVAudioSessionRouteChangeReasonNewDeviceAvailable:
DDLogVerbose(@"耳機(jī)插入");
// 繼續(xù)播放音頻,什么也不用做
break;
case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:
{
DDLogVerbose(@"耳機(jī)拔出");
// 拔出耳機(jī)時系統(tǒng)會自動暫停你正在播放的音頻,因此只需要改變UI為暫停狀態(tài)即可
if ([GKTAudioManager sharedManager].currentAudioPlayStatus == GKTPlayStatusPlaying) {
[[GKTAudioManager sharedManager] pause];
}
}
break;
default:
break;
}
}
遠(yuǎn)程控制,耳機(jī)和鎖屏狀態(tài)
//鎖屏界面開啟和監(jiān)控遠(yuǎn)程控制事件
+ (void)configRemoteCommandCenter{ /**/
//遠(yuǎn)程控制命令中心 iOS 7.1 之后 詳情看官方文檔:https://developer.apple.com/documentation/mediaplayer/mpremotecommandcenter
MPRemoteCommandCenter *commandCenter = [MPRemoteCommandCenter sharedCommandCenter];
// MPFeedbackCommand對象反映了當(dāng)前App所播放的反饋狀態(tài). MPRemoteCommandCenter對象提供feedback對象用于對媒體文件進(jìn)行喜歡, 不喜歡, 標(biāo)記的操作. 效果類似于網(wǎng)易云音樂鎖屏?xí)r的效果
//添加喜歡按鈕
// MPFeedbackCommand *likeCommand = commandCenter.likeCommand;
// likeCommand.enabled = YES;
// likeCommand.localizedTitle = @"喜歡";
// [likeCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) { NSLog(@"喜歡"); return MPRemoteCommandHandlerStatusSuccess;
// }]; //添加不喜歡按鈕,假裝是“上一首”
// MPFeedbackCommand *dislikeCommand = commandCenter.dislikeCommand;
// dislikeCommand.enabled = YES;
// dislikeCommand.localizedTitle = @"上一首";
// [dislikeCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
// NSLog(@"上一首");
// [[GKTAudioManager sharedManager] previous:^(id error, id result) {
//
// }];
// return MPRemoteCommandHandlerStatusSuccess;
// }];
[commandCenter.previousTrackCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
NSLog(@"上一首");
[[GKTAudioManager sharedManager] previous];
return MPRemoteCommandHandlerStatusSuccess;
}];
//標(biāo)記
// MPFeedbackCommand *bookmarkCommand = commandCenter.bookmarkCommand;
// bookmarkCommand.enabled = YES;
// bookmarkCommand.localizedTitle = @"標(biāo)記";
// [bookmarkCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) { NSLog(@"標(biāo)記"); return MPRemoteCommandHandlerStatusSuccess;
// }];
// commandCenter.togglePlayPauseCommand 耳機(jī)線控的暫停/播放
commandCenter.togglePlayPauseCommand.enabled = YES;
[commandCenter.togglePlayPauseCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
if ([GKTAudioManager sharedManager].currentAudioPlayStatus == GKTPlayStatusPlaying) {
[[GKTAudioManager sharedManager] pause];
} else {
[[GKTAudioManager sharedManager] play:^(id error, id result) {
}];
}
return MPRemoteCommandHandlerStatusSuccess;
}];
[commandCenter.pauseCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
[[GKTAudioManager sharedManager] pause];
return MPRemoteCommandHandlerStatusSuccess;
}];
[commandCenter.playCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
[[GKTAudioManager sharedManager] play:^(id error, id result) {
}];
// [self.player setRate:self.rateValue];
return MPRemoteCommandHandlerStatusSuccess;
}]; // [commandCenter.previousTrackCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
// NSLog(@"上一首");
// return MPRemoteCommandHandlerStatusSuccess;
// }];
[commandCenter.nextTrackCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) { NSLog(@"下一首");
[[GKTAudioManager sharedManager] next:^(id error, id result) {
}];
// [self.player setRate:self.rateValue];
return MPRemoteCommandHandlerStatusSuccess;
}];
//快進(jìn)
// MPSkipIntervalCommand *skipBackwardIntervalCommand = commandCenter.skipForwardCommand;
// skipBackwardIntervalCommand.preferredIntervals = @[@(54)];
// skipBackwardIntervalCommand.enabled = YES;
// [skipBackwardIntervalCommand addTarget:self action:@selector(skipBackwardEvent:)];
//在控制臺拖動進(jìn)度條調(diào)節(jié)進(jìn)度(仿QQ音樂的效果)
[commandCenter.changePlaybackPositionCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) { CMTime totlaTime = [GKTAudioManager sharedManager].player.currentItem.duration;
MPChangePlaybackPositionCommandEvent * playbackPositionEvent = (MPChangePlaybackPositionCommandEvent *)event;
@try {
[[GKTAudioManager sharedManager].player seekToTime:CMTimeMake(totlaTime.value*playbackPositionEvent.positionTime/CMTimeGetSeconds(totlaTime), totlaTime.timescale) completionHandler:^(BOOL finished) {
}];
} @catch (NSException *exception) {} @finally {}
return MPRemoteCommandHandlerStatusSuccess;
}];
}
-(void)skipBackwardEvent: (MPSkipIntervalCommandEvent *)skipEvent
{ NSLog(@"快進(jìn)了 %f秒", skipEvent.interval);
}
總結(jié):
播放器的總結(jié)大概就到這里,在開發(fā)的過程中,我們可能更關(guān)注一個基礎(chǔ)功能的開發(fā),但更要注重細(xì)節(jié)上東西,比如上邊提到的AVAudioSession對 setActive的設(shè)置,大概率在發(fā)布之前,大家更注重的是功能上的完整性,而推到了線上,用戶在使用時候,對細(xì)節(jié)的感知更明顯。
