代碼地址以下:<br>http://www.demodashi.com/demo/11944.htmlhtml
天道酬勤ios
前言
做爲一名iOS
開發者,每當使用APP
的時候,總不免會不由自主的去想一想,這個怎麼作的?該怎麼實現呢?好久以前,就想寫一個關於音樂方面的播放器,最近恰好得空,就趁機摸索着寫了下,寫的很差,還望多多指教。spring
前提準備
爲了可以有明確的思路來作這個demo
,我下載了QQ音樂
和網易雲音樂
,而後分別對比,最終選擇了QQ音樂
來參照,先是獲取了其中的全部資源文件,在這以後就是研究使用技術,這裏我選擇了FreeStreamer
,雖然系統也有,可是該框架可能更好用點。數組
實現部分
在這以前,先來看看大概效果圖吧 緩存
再看完效果圖以後,咱們就來看看這其中涉及到的幾個難點吧(在我看開~)session
- 一、先讓播放器跑起來 這裏我使用的是
pods
來管理三方庫,代碼以下
platform:ios,’8.0’ target "GLMusicBox" do pod 'FreeStreamer', '~> 3.7.3' pod 'SDWebImage', '~> 4.0.0’ pod 'MJRefresh', '~> 3.1.11’ pod 'Masonry', '~> 1.0.2' pod 'Reachability', '~> 3.2' pod 'AFNetworking', '~> 3.0' pod 'IQKeyboardManager', '~> 3.3.2’ end
針對FreeStreamer
我簡單進行了封裝下app
#import "FSAudioStream.h" @class GLMusicLRCModel; typedef NS_ENUM(NSInteger,GLLoopState){ GLSingleLoop = 0,//單曲循環 GLForeverLoop,//重複循環 GLRandomLoop,//隨機播放 GLOnceLoop//列表一次順序播放 }; @protocol GLMusicPlayerDelegate<NSObject> /** * 實時更新 * **/ - (void)updateProgressWithCurrentPosition:(FSStreamPosition)currentPosition endPosition:(FSStreamPosition)endPosition; - (void)updateMusicLrc; @end @interface GLMusicPlayer : FSAudioStream /** * 播放列表 * **/ @property (nonatomic,strong) NSMutableArray *musicListArray; /** 當前播放歌曲的歌詞 */ @property (nonatomic,strong) NSMutableArray <GLMusicLRCModel*>*musicLRCArray; /** * 當前播放 * **/ @property (nonatomic,assign,readonly) NSUInteger currentIndex; /** * 當前播放的音樂的標題 * **/ @property (nonatomic,strong) NSString *currentTitle; /** 是不是暫停狀態 */ @property (nonatomic,assign) BOOL isPause; @property (nonatomic,weak) id<GLMusicPlayerDelegate>glPlayerDelegate; //默認 重複循環 GLForeverLoop @property (nonatomic,assign) GLLoopState loopState; /** * 單例播放器 * **/ + (instancetype)defaultPlayer; /** 播放隊列中的指定的文件 @param index 序號 */ - (void)playMusicAtIndex:(NSUInteger)index; /** 播放前一首 */ - (void)playFont; /** 播放下一首 */ - (void)playNext; @end
這裏繼承了FSAudioStream
,而且採用了單例模式框架
+ (instancetype)defaultPlayer { static GLMusicPlayer *player = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ FSStreamConfiguration *config = [[FSStreamConfiguration alloc] init]; config.httpConnectionBufferSize *=2; config.enableTimeAndPitchConversion = YES; player = [[super alloc] initWithConfiguration:config]; player.delegate = (id)self; player.onFailure = ^(FSAudioStreamError error, NSString *errorDescription) { //播放錯誤 //有待解決 }; player.onCompletion = ^{ //播放完成 NSLog(@" 打印信息: 播放完成1"); }; player.onStateChange = ^(FSAudioStreamState state) { switch (state) { case kFsAudioStreamPlaying: { NSLog(@" 打印信息 playing....."); player.isPause = NO; [GLMiniMusicView shareInstance].palyButton.selected = YES; } break; case kFsAudioStreamStopped: { NSLog(@" 打印信息 stop.....%@",player.url.absoluteString); } break; case kFsAudioStreamPaused: { //pause player.isPause = YES; [GLMiniMusicView shareInstance].palyButton.selected = NO; NSLog(@" 打印信息: pause"); } break; case kFsAudioStreamPlaybackCompleted: { NSLog(@" 打印信息: 播放完成2"); [player playMusicForState]; } break; default: break; } }; //設置音量 [player setVolume:0.5]; //設置播放速率 [player setPlayRate:1]; player.loopState = GLForeverLoop; }); return player; }
而後實現了播放方法dom
- (void)playFromURL:(NSURL *)url { //根據地址 在本地找歌詞 NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"musiclist" ofType:@"plist"]]; for (NSString *playStringKey in dic.allKeys) { if ([[dic valueForKey:playStringKey] isEqualToString:url.absoluteString]) { self.currentTitle = playStringKey; break; } } [self stop]; if (![url.absoluteString isEqualToString:self.url.absoluteString]) { [super playFromURL:url]; }else{ [self play]; } NSLog(@" 當前播放歌曲:%@",self.currentTitle); [GLMiniMusicView shareInstance].titleLable.text = self.currentTitle; //獲取歌詞 NSString *lrcFile = [NSString stringWithFormat:@"%@.lrc",self.currentTitle]; self.musicLRCArray = [NSMutableArray arrayWithArray:[GLMusicLRCModel musicLRCModelsWithLRCFileName:lrcFile]]; if (![self.musicListArray containsObject:url]) { [self.musicListArray addObject:url]; } //更新主界面歌詞UI if (self.glPlayerDelegate && [self.glPlayerDelegate respondsToSelector:@selector(updateMusicLrc)]) { [self.glPlayerDelegate updateMusicLrc]; } _currentIndex = [self.musicListArray indexOfObject:url]; if (!_progressTimer) { _progressTimer = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateProgress)]; [_progressTimer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; } }
在上面的代碼中,有許多邏輯是後面加的,好比更新UI
界面,獲取歌詞等處理,若是要實現簡單的播放,則能夠不用重寫該方法,直接經過playFromURL
就能夠實現咱們的播放功能。iphone
- 二、更新UI 這裏的
UI
暫不包括歌詞的更新,而只是進度條的更新,要更新進度條,比不可少的是定時器,這裏我沒有選擇NSTimer
,而是選擇了CADisplayLink
,至於爲何,我想你們應該都比較瞭解,能夠這麼來對比,下面引用一段其餘博客的對比:iOS
設備的屏幕刷新頻率是固定的,CADisplayLink
在正常狀況下會在每次刷新結束都被調用,精確度至關高。NSTimer
的精確度就顯得低了點,好比NSTimer
的觸發時間到的時候,runloop
若是在阻塞狀態,觸發時間就會推遲到下一個runloop
週期。而且NSTimer
新增了tolerance
屬性,讓用戶能夠設置能夠容忍的觸發的時間的延遲範圍。CADisplayLink
使用場合相對專注,適合作UI
的不停重繪,好比自定義動畫引擎或者視頻播放的渲染。NSTimer
的使用範圍要普遍的多,各類須要單次或者循環定時處理的任務均可以使用。在UI
相關的動畫或者顯示內容使用CADisplayLink
比起用NSTimer
的好處就是咱們不須要在格外關心屏幕的刷新頻率了,由於它自己就是跟屏幕刷新同步的 使用方法
if (!_progressTimer) { _progressTimer = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateProgress)]; [_progressTimer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; }
更新進度
- (void)updateProgress { if (self.glPlayerDelegate && [self.glPlayerDelegate respondsToSelector:@selector(updateProgressWithCurrentPosition:endPosition:)]) { [self.glPlayerDelegate updateProgressWithCurrentPosition:self.currentTimePlayed endPosition:self.duration]; } [self showLockScreenCurrentTime:(self.currentTimePlayed.second + self.currentTimePlayed.minute * 60) totalTime:(self.duration.second + self.duration.minute * 60)]; }
在這裏有兩個屬性:currentTimePlayed
和duration
,分別保存着當前播放時間和總時間,是以下的結構體
typedef struct { unsigned minute; unsigned second; /** * Playback time in seconds. */ float playbackTimeInSeconds; /** * Position within the stream, where 0 is the beginning * and 1.0 is the end. */ float position; } FSStreamPosition;
咱們在更新UI
的時候,主要能夠根據其中的minute
和second
來,若是播放了90s
,那麼minute
就爲1
,而second
爲30
,因此咱們在計算的時候,應該是這樣的(self.currentTimePlayed.second + self.currentTimePlayed.minute * 60)
固然在更新進度條的時候,咱們也能夠經過position
直接來給slider
進行賦值,這表示當前播放的比例
#pragma mark == GLMusicPlayerDelegate - (void)updateProgressWithCurrentPosition:(FSStreamPosition)currentPosition endPosition:(FSStreamPosition)endPosition { //更新進度條 self.playerControlView.slider.value = currentPosition.position; self.playerControlView.leftTimeLable.text = [NSString translationWithMinutes:currentPosition.minute seconds:currentPosition.second]; self.playerControlView.rightTimeLable.text = [NSString translationWithMinutes:endPosition.minute seconds:endPosition.second]; //更新歌詞 [self updateMusicLrcForRowWithCurrentTime:currentPosition.position *(endPosition.minute *60 + endPosition.second)]; self.playerControlView.palyMusicButton.selected = [GLMusicPlayer defaultPlayer].isPause; }
本項目中,slider
控件沒有用系統的,而是簡單的寫了一個,大概以下
@interface GLSlider : UIControl //進度條顏色 @property (nonatomic,strong) UIColor *progressColor; //緩存條顏色 @property (nonatomic,strong) UIColor *progressCacheColor; //滑塊顏色 @property (nonatomic,strong) UIColor *thumbColor; //設置進度值 0-1 @property (nonatomic,assign) CGFloat value; //設置緩存進度值 0-1 @property (nonatomic,assign) CGFloat cacheValue; @end
static CGFloat const kProgressHeight = 2; static CGFloat const kProgressLeftPadding = 2; static CGFloat const kThumbHeight = 16; @interface GLSlider() //滑塊 默認 @property (nonatomic,strong) CALayer *thumbLayer; //進度條 @property (nonatomic,strong) CALayer *progressLayer; //緩存進度條 @property (nonatomic,strong) CALayer *progressCacheLayer; @property (nonatomic,assign) BOOL isTouch; @end @implementation GLSlider - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { [self addSubLayers]; } return self; } ....
這裏是添加了緩存進度條的,可是因爲時間關係,代碼中還未實時更新緩存進度
- 三、更新歌詞界面
說到歌詞界面,咱們看到QQ音樂
的效果是這樣的,逐行逐字進行更新,注意不是逐行更新。考慮到逐字進行更新,那麼咱們必需要對lable
進行乾點什麼,這裏對其進行了繼承,並添加了些方法
@interface GLMusicLrcLable : UILabel //進度 @property (nonatomic,assign) CGFloat progress; @end
#import "GLMusicLrcLable.h" @implementation GLMusicLrcLable - (void)setProgress:(CGFloat)progress { _progress = progress; //重繪 [self setNeedsDisplay]; } - (void)drawRect:(CGRect)rect { [super drawRect:rect]; CGRect fillRect = CGRectMake(0, 0, self.bounds.size.width * _progress, self.bounds.size.height); [UICOLOR_FROM_RGB(45, 185, 105) set]; UIRectFillUsingBlendMode(fillRect, kCGBlendModeSourceIn); } @end
注意UIRectFillUsingBlendMode
該方法可以實現逐字進行漸變的效果 逐字的問題解決了,那麼就剩下逐行問題了,逐行的問題應該不難,是的。咱們只須要在指定的時間內將其滾動就行,以下
[self.lrcTableView scrollToRowAtIndexPath:currentIndexPath atScrollPosition:UITableViewScrollPositionMiddle animated:YES]
可是這中要注意一個問題,那就是必須作到,在下一行進行展現的時候,取消上一行的效果,以下
//設置當前行的狀態 [currentCell reloadCellForSelect:YES]; //取消上一行的選中狀態 [previousCell reloadCellForSelect:NO];
- (void)reloadCellForSelect:(BOOL)select { if (select) { _lrcLable.font = [UIFont systemFontOfSize:17]; }else{ _lrcLable.font = [UIFont systemFontOfSize:14]; _lrcLable.progress = 0; } }
其中 _lrcLable.progress = 0;
必需要,不然咱們的文字顏色不會改變 在大問題已經解決的狀況下,咱們就須要關心另外一個重要的問題了,那就是歌詞。這裏先介紹一個網站,能夠獲取歌曲名和歌詞的 (找了很久....)歌曲歌詞獲取,不過好多好聽的歌曲竟然播放不了,你懂得,大天朝版權問題....找一首歌,播放就能看到看到歌詞了。關於歌詞,有許多格式,這裏我用的是lrc
格式,應該還算比較主流,格式大概以下
[ti:老人與海] [ar:海鳴威 ] [al:單曲] [by:www.5nd.com From 那時花開] [00:04.08]老人與海 海鳴威 [00:08.78]海鳴威 [00:37.06]秋天的夜凋零在漫天落葉裏面 [00:42.43]泛黃世界一點一點隨風而漸遠 [00:47.58]冬天的雪白色了你個人情人節 [00:53.24]消失不見 愛的碎片 [00:57.87]Rap: [00:59.32]翻開塵封的相片 [01:00.87]想起和你看過 的那些老舊默片 [01:02.50]老人與海的情節 [01:04.23]畫面中你卻依稀 在浮現
在有了格式後,咱們就須要一個模型,來分離歌曲信息了,下面是我建的模型
#import <Foundation/Foundation.h> @interface GLMusicLRCModel : NSObject //該段歌詞對應的時間 @property (nonatomic,assign) NSTimeInterval time; //歌詞 @property (nonatomic,strong) NSString *title; /** * 將特色的歌詞格式進行轉換 * **/ + (id)musicLRCWithString:(NSString *)string; /** * 根據歌詞的路徑返回歌詞模型數組 * **/ + (NSArray <GLMusicLRCModel *>*)musicLRCModelsWithLRCFileName:(NSString *)name; @end
#import "GLMusicLRCModel.h" @implementation GLMusicLRCModel +(id)musicLRCWithString:(NSString *)string { GLMusicLRCModel *model = [[GLMusicLRCModel alloc] init]; NSArray *lrcLines =[string componentsSeparatedByString:@"]"]; if (lrcLines.count == 2) { model.title = lrcLines[1]; NSString *timeString = lrcLines[0]; timeString = [timeString stringByReplacingOccurrencesOfString:@"[" withString:@""]; timeString = [timeString stringByReplacingOccurrencesOfString:@"]" withString:@""]; NSArray *times = [timeString componentsSeparatedByString:@":"]; if (times.count == 2) { NSTimeInterval time = [times[0] integerValue]*60 + [times[1] floatValue]; model.time = time; } }else if(lrcLines.count == 1){ } return model; } +(NSArray <GLMusicLRCModel *>*)musicLRCModelsWithLRCFileName:(NSString *)name { NSString *lrcPath = [[NSBundle mainBundle] pathForResource:name ofType:nil]; NSString *lrcString = [NSString stringWithContentsOfFile:lrcPath encoding:NSUTF8StringEncoding error:nil]; NSArray *lrcLines = [lrcString componentsSeparatedByString:@"\n"]; NSMutableArray *lrcModels = [NSMutableArray array]; for (NSString *lrcLineString in lrcLines) { if ([lrcLineString hasPrefix:@"[ti"] || [lrcLineString hasPrefix:@"[ar"] || [lrcLineString hasPrefix:@"[al"] || ![lrcLineString hasPrefix:@"["]) { continue; } GLMusicLRCModel *lrcModel = [GLMusicLRCModel musicLRCWithString:lrcLineString]; [lrcModels addObject:lrcModel]; } return lrcModels; } @end
在歌詞模型準備好以後,咱們要展現歌詞,這裏我選擇的是tableview
,經過每個cell
來加載不一樣的歌詞,而後經過歌詞的時間信息來更新和滾動
#pragma mark == UITableViewDataSource - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [GLMusicPlayer defaultPlayer].musicLRCArray.count; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { MusicLRCTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"musicLrc" forIndexPath:indexPath]; cell.selectionStyle = UITableViewCellSelectionStyleNone; cell.backgroundColor = [UIColor clearColor]; cell.contentView.backgroundColor = [UIColor clearColor]; cell.lrcModel = [GLMusicPlayer defaultPlayer].musicLRCArray[indexPath.row]; if (indexPath.row == self.currentLcrIndex) { [cell reloadCellForSelect:YES]; }else{ [cell reloadCellForSelect:NO]; } return cell; }
這裏面惟一比較麻煩的可能就是更新歌詞了,在上面的定時器中,咱們也經過代理來更新了進度條,因此我也將更新歌詞的部分放在了代理中,這樣能夠達到實時更新的目的,下面看看方法
//逐行更新歌詞 - (void)updateMusicLrcForRowWithCurrentTime:(NSTimeInterval)currentTime { for (int i = 0; i < [GLMusicPlayer defaultPlayer].musicLRCArray.count; i ++) { GLMusicLRCModel *model = [GLMusicPlayer defaultPlayer].musicLRCArray[i]; NSInteger next = i + 1; GLMusicLRCModel *nextLrcModel = nil; if (next < [GLMusicPlayer defaultPlayer].musicLRCArray.count) { nextLrcModel = [GLMusicPlayer defaultPlayer].musicLRCArray[next]; } if (self.currentLcrIndex != i && currentTime >= model.time) { BOOL show = NO; if (nextLrcModel) { if (currentTime < nextLrcModel.time) { show = YES; } }else{ show = YES; } if (show) { NSIndexPath *currentIndexPath = [NSIndexPath indexPathForRow:i inSection:0]; NSIndexPath *previousIndexPath = [NSIndexPath indexPathForRow:self.currentLcrIndex inSection:0]; self.currentLcrIndex = i; MusicLRCTableViewCell *currentCell = [self.lrcTableView cellForRowAtIndexPath:currentIndexPath]; MusicLRCTableViewCell *previousCell = [self.lrcTableView cellForRowAtIndexPath:previousIndexPath]; //設置當前行的狀態 [currentCell reloadCellForSelect:YES]; //取消上一行的選中狀態 [previousCell reloadCellForSelect:NO]; if (!self.isDrag) { [self.lrcTableView scrollToRowAtIndexPath:currentIndexPath atScrollPosition:UITableViewScrollPositionMiddle animated:YES]; } } } if (self.currentLcrIndex == i) { MusicLRCTableViewCell *cell = [self.lrcTableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]]; CGFloat totalTime = 0; if (nextLrcModel) { totalTime = nextLrcModel.time - model.time; }else{ totalTime = [GLMusicPlayer defaultPlayer].duration.minute * 60 + [GLMusicPlayer defaultPlayer].duration.second - model.time; } CGFloat progressTime = currentTime - model.time; cell.lrcLable.progress = progressTime / totalTime; } } }
到此爲止,咱們一個簡單的播放器就差很少實現了,可是這...並無完,相比QQ音樂
而言,它還差一個播放順序切換的功能和鎖屏播放功能
- 四、切換播放順序 這個比較簡單,只是須要注意在切換的時候,注意數組的越界和不一樣模式的處理 這裏,我定義了以下幾種模式
typedef NS_ENUM(NSInteger,GLLoopState){ GLSingleLoop = 0,//單曲循環 GLForeverLoop,//重複循環 GLRandomLoop,//隨機播放 GLOnceLoop//列表一次順序播放 };
切換代碼
//不一樣狀態下 播放歌曲 - (void)playMusicForState { switch (self.loopState) { case GLSingleLoop: { [self playMusicAtIndex:self.currentIndex]; } break; case GLForeverLoop: { if (self.currentIndex == self.musicListArray.count-1) { [self playMusicAtIndex:0]; }else{ [self playMusicAtIndex:self.currentIndex + 1]; } } break; case GLRandomLoop: { //取隨機值 int index = arc4random() % self.musicListArray.count; [self playMusicAtIndex:index]; } break; case GLOnceLoop: { if (self.currentIndex == self.musicListArray.count-1) { [self stop]; }else{ [self playMusicAtIndex:self.currentIndex + 1]; } } break; default: break; } }
- 五、鎖屏播放 就如上圖2中那樣,因爲在
iOS 11
中好像不能支持背景圖片和歌詞展現,多是爲了界面更加簡潔吧,因此我這裏也就沒有加該功功能,只是簡答的有個播放界面和幾個控制按鈕 首先須要在工程中這樣設置,保證在後臺播放而後就是在
appdelegate
中添加以下代碼
AVAudioSession *session = [AVAudioSession sharedInstance]; // [session setActive:YES error:nil]; [session setActive:YES withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:nil]; [session setCategory:AVAudioSessionCategoryPlayback error:nil]; [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
而且添加控制事件
#pragma mark == event response -(void)remoteControlReceivedWithEvent:(UIEvent *)event{ NSLog(@"%ld",event.subtype); if (event.type == UIEventTypeRemoteControl) { switch (event.subtype) { case UIEventSubtypeRemoteControlPlay: { //點擊播放按鈕或者耳機線控中間那個按鈕 [[GLMusicPlayer defaultPlayer] pause]; } break; case UIEventSubtypeRemoteControlPause: { //點擊暫停按鈕 [[GLMusicPlayer defaultPlayer] pause]; } break; case UIEventSubtypeRemoteControlStop : { //點擊中止按鈕 [[GLMusicPlayer defaultPlayer] stop]; } break; case UIEventSubtypeRemoteControlTogglePlayPause: { //點擊播放與暫停開關按鈕(iphone抽屜中使用這個) [[GLMusicPlayer defaultPlayer] pause]; } break; case UIEventSubtypeRemoteControlNextTrack: { //點擊下一曲按鈕或者耳機中間按鈕兩下 [[GLMusicPlayer defaultPlayer] playNext]; } break; case UIEventSubtypeRemoteControlPreviousTrack: { //點擊上一曲按鈕或者耳機中間按鈕三下 [[GLMusicPlayer defaultPlayer] playFont]; } break; case UIEventSubtypeRemoteControlBeginSeekingBackward: { //快退開始 點擊耳機中間按鈕三下不放開 } break; case UIEventSubtypeRemoteControlEndSeekingBackward: { //快退結束 耳機快退控制鬆開後 } break; case UIEventSubtypeRemoteControlBeginSeekingForward: { //開始快進 耳機中間按鈕兩下不放開 } break; case UIEventSubtypeRemoteControlEndSeekingForward: { //快進結束 耳機快進操做鬆開後 } break; default: break; } } }
beginReceivingRemoteControlEvents
爲容許傳遞遠程控制事件,remoteControlReceivedWithEvent
爲接收一個遠程控制事件,關於控制事件的類型,在代碼中,已經註釋過,這裏就再也不說了。 控制事件搞定了,剩下的就是界面的展現了,主要是歌曲信息的展現,經過以下的代碼就能實現
NSMutableDictionary *musicInfoDict = [[NSMutableDictionary alloc] init]; //設置歌曲題目 [musicInfoDict setObject:self.currentTitle forKey:MPMediaItemPropertyTitle]; //設置歌手名 [musicInfoDict setObject:@"" forKey:MPMediaItemPropertyArtist]; //設置專輯名 [musicInfoDict setObject:@"" forKey:MPMediaItemPropertyAlbumTitle]; //設置歌曲時長 [musicInfoDict setObject:[NSNumber numberWithFloat:totalTime] forKey:MPMediaItemPropertyPlaybackDuration]; //設置已經播放時長 [musicInfoDict setObject:[NSNumber numberWithFloat:currentTime] forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime]; [[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:musicInfoDict];
關於歌曲信息的設置,能夠不按照我這樣,定時器中時刻進行刷新,只須要在播放
、暫停
、快進快退
這些時間有變化的地方傳入當前歌曲的關鍵信息就能夠,系統會自動去根據播放狀況去更新鎖屏界面上的進度條,而不須要咱們時刻傳入當前播放時間。這裏我爲了偷懶,就加在裏面了。爲了防止頻繁操做,我採起了個方法,在其餘地方看到的,就是監聽鎖屏狀況
//監聽鎖屏狀態 lock=1則爲鎖屏狀態 uint64_t locked; __block int token = 0; notify_register_dispatch("com.apple.springboard.lockstate",&token,dispatch_get_main_queue(),^(int t){ }); notify_get_state(token, &locked); //監聽屏幕點亮狀態 screenLight = 1則爲變暗關閉狀態 uint64_t screenLight; __block int lightToken = 0; notify_register_dispatch("com.apple.springboard.hasBlankedScreen",&lightToken,dispatch_get_main_queue(),^(int t){ }); notify_get_state(lightToken, &screenLight);
經過該狀況來設置。
在上面鎖屏播放的過程當中,出現一個問題,就是當我切換歌曲的時候,不論是在鎖屏狀況下,仍是在app
內 經過各類查找,大概找到問題,首先在
appdelegate
中將[session setActive:YES error:nil]
改爲了[session setActive:YES withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:nil]
,而後再播放的地方加了一個[self stop]
,先中止播放
- (void)playFromURL:(NSURL *)url { //根據地址 在本地找歌詞 NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"musiclist" ofType:@"plist"]]; for (NSString *playStringKey in dic.allKeys) { if ([[dic valueForKey:playStringKey] isEqualToString:url.absoluteString]) { self.currentTitle = playStringKey; break; } } [self stop]; if (![url.absoluteString isEqualToString:self.url.absoluteString]) { [super playFromURL:url]; }else{ [self play]; }
到此爲止,一個簡單的播放器就差很少了,因爲時間關係,可能還有些bug
,但願你們能多多提出來,我好進行修正,後續我還將加一個功能,由於這兩天公司有個很老的項目,有個下載問題,有點蛋疼,因此準備些一個隊列下載,而後順便加到播放器上。
結構截圖
操做步驟
iOS之基於FreeStreamer的簡單音樂播放器(模仿QQ音樂)
代碼地址以下:<br>http://www.demodashi.com/demo/11944.html
注:本文著做權歸做者,由demo大師代發,拒絕轉載,轉載須要做者受權