iOS開發之網絡音樂播放器(SC音樂)

iOS開發之網絡音樂播放器(SC音樂)

前言

一直都想做一款自己的網絡音樂播放器,兩個月前做了一個swift版的網絡音樂播放器,但是那個播放器數據來源于我自己用VPS和nginx搭建的服務器,所有的文件都要自己準備,包括mp3、歌詞、專輯圖片等,非常麻煩,有興趣的可以跟我要源碼。現在這款音樂播放器數據是來源于百度音樂,前前后后花了一個多星期搞定,網上有一些音樂網站的API,有興趣的同學可以去查一下。我這里貼一下我自己用到的百度音樂API:http://blog.csdn.net/zuiaisha1/article/details/61200422

正題

一、播放控制

SC音樂用的是AVPlayer,這個庫是蘋果自帶的視頻庫,也可以播放音頻,可以支持邊播放邊緩存,使用也比較簡單。詳細看蘋果官網介紹:https://developer.apple.com/documentation/avfoundation/avplayer。這里介紹一下要用到的東西。我們知道,播放器要有播放、暫停、上一曲、下一曲的功能,還要知道播放總時間,當前時間,播放狀態,能夠從歌曲的任意時間點開始播放。在AVPlayer庫中:

play ---- 播放

pause ---- 暫停

rate ---- 播放狀態(0.0代表當前狀態是暫停, 1.0代表當前狀態是播放)

seekToTime ---- 從某個時間點開始播放(拖動進度條用到)

duration ---- 歌曲總時間

currentTime ---- 當前播放時間

上一曲和下一曲可以通過改變歌曲url來實現。

初始化一個AVPlayer需要一個playItem,所以先初始化一個playItem,再用這個playItem去實例化一個play,具體代碼:

MusicPlayerManager.h

//

//? MusicPlayerManager.h

//? BaiduMusic

//

//? Created by 凌 ??????陳 on 8/21/17.

//? Copyright ? 2017 凌 ??????陳. All rights reserved.

//

#import

#import

@interface MusicPlayerManager : NSObject

typedef enum : NSUInteger {

RepeatPlayMode,

RepeatOnlyOnePlayMode,

ShufflePlayMode,

} ShuffleAndRepeatState;

@property (nonatomic,strong) AVPlayer *play;

@property (nonatomic,strong) AVPlayerItem *playItem;

@property (nonatomic,assign) ShuffleAndRepeatState shuffleAndRepeatState;

@property (nonatomic,assign) NSInteger playingIndex;

+ (MusicPlayerManager *)sharedManager;

-(void) setPlayItem: (NSString *)songURL;

-(void) setPlay;

-(void) startPlay;

-(void) stopPlay;

-(void) play: (NSString *)songURL;

@end

MusicPlayerManager.m

//

//? MusicPlayerManager.m

//? BaiduMusic

//

//? Created by 凌 ??????陳 on 8/21/17.

//? Copyright ? 2017 凌 ??????陳. All rights reserved.

//

#import "MusicPlayerManager.h"

@implementation MusicPlayerManager

static MusicPlayerManager *_sharedManager = nil;

+(MusicPlayerManager *)sharedManager {

@synchronized( [MusicPlayerManager class] ){

if(!_sharedManager)

_sharedManager = [[self alloc] init];

return _sharedManager;

}

return nil;

}

-(void) setPlayItem: (NSString *)songURL {

NSURL * url? = [NSURL URLWithString:songURL];

_playItem = [[AVPlayerItem alloc] initWithURL:url];

}

-(void) setPlay {

_play = [[AVPlayer alloc] initWithPlayerItem:_playItem];

}

-(void) startPlay {

[_play play];

}

-(void) stopPlay {

[_play pause];

}

-(void) play: (NSString *)songURL {

[self setPlayItem:songURL];

[self setPlay];

[self startPlay];

}

@end

將一首歌的url傳進play方法就可以實現播放音樂了。上一曲下一曲只是改變一下歌曲的url就可以實現。

歌曲總時長:

_play.currentItem.duration

當前播放時間:

_play.currentTime

從某個時間點開始播放:

//播放器定位到對應的位置

CMTime targetTime = CMTimeMake((int64_t)(currentTime), 1);

[musicPlayer.play seekToTime:targetTime];

播放狀態:

//播放或者暫停按鍵按下,要判斷播放狀態

if (_play.rate == 0) {

// 當前狀態為暫停

// 下面要執行播放的代碼

} else {

// 當前狀態為播放

// 下面要執行暫停的代碼

}

監管播放(更新播放進度條和當前時間):

_playerTimeObserver = [musicPlayer.play addPeriodicTimeObserverForInterval:CMTimeMake(1.0, 1.0) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {

// 這里一秒進來一次,可以更新時間、播放進度條和歌詞

// 需要注意的是_playerTimeObserver必須要每首歌播放結束后清掉,不清會有問題。

}

播放結束通知:

// 歌曲播放結束后會調用自定義的方法finishedPlaying

[[NSNotificationCenter defaultCenter] ?addObserver:self selector:@selector(finishedPlaying) name:AVPlayerItemDidPlayToEndTimeNotification object:_play.currentItem];

二、獲取百度音樂數據

百度音樂的全接口:http://tingapi.ting.baidu.com/v1/restserver/ting。所有的數據都是以這個為開頭,后面加一些其他東西。

可以請求到的數據有很多,這里只說幾個:

一、獲取歌曲列表(新歌榜、熱歌榜、經典老歌榜等)

例:method=baidu.ting.billboard.billList&type=1&size=10&offset=0

完整的請求地址:http://tingapi.ting.baidu.com/v1/restserver/ting?from=qianqian&version=2.1.0&method=baidu.ting.billboard.billList&format=json&type=1&offset=0&size=100 (前100熱門歌曲,要獲取哪個榜只需要改變一下type的值就行了)

參數: type = 1-新歌榜,2-熱歌榜,11-搖滾榜,12-爵士,16-流行,21-歐美金曲榜,22-經典老歌榜,23-情歌對唱榜,24-影視金曲榜,25-網絡歌曲榜

size = 10 //返回條目數量

offset = 0 //獲取偏移

獲取到的數據如截圖所示:

我們只需要“song_list”里面的數據,點開后發現“song_list“就是一個字典:

繼續點開[0],里面是一首歌的信息,包括歌名、歌手名、專輯名等等,但是并沒有歌曲url,別急這個要另外獲取,需要用到這里的"song_id".

二、獲取歌曲url

例:method=baidu.ting.song.lry&songid=877578

完整的請求地址:http://tingapi.ting.baidu.com/v1/restserver/ting?from=qianqian&version=2.1.0&method=baidu.ting.song.play&songid=877578

參數:songid = 877578 //假設這個是歌曲id

獲取到的數據如圖所示:

我們只關注”bitrate“中的數據,點開發現里面有”file_link“,這個就是歌曲的url:

網絡請求我用大名鼎鼎的AFNetworking,獲取到的數據解析我用MJExtension,主要將數據轉成NSArray,將這兩個庫拉入自己的工程,添加頭文件#import "AFNetworking.h" ? #import "MJExtension.h"即可。

// 獲取歌曲信息請求

// 新歌榜

- (void)loadNewSongs

{

AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];

NSString *path = @"http://tingapi.ting.baidu.com/v1/restserver/ting?from=qianqian&version=2.1.0&method=baidu.ting.billboard.billList&format=json&type=1&offset=0&size=100";//前100熱門歌曲

[manager GET:path parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {

if ([responseObject isKindOfClass:[NSDictionary class]])

{

NSArray *array = [responseObject objectForKey:@"song_list"];

songInfo.OMSongs = [OMHotSongInfo mj_objectArrayWithKeyValuesArray:array];

//? ? ? ? ? ? [self reloadTableView:_radioAndMusicTableView];

[_mytableView reloadData];

}

} failure:^(AFHTTPRequestOperation *operation, NSError *error) {

NSLog(@"error--%@",error);

}];

}

其它的榜單改一下type的值就可以了。

獲取歌曲url請求:

-(void)getSelectedSong: (NSString *)songID index: (long)index {

AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];

NSString *path = [@"http://tingapi.ting.baidu.com/v1/restserver/ting?from=qianqian&version=2.1.0&method=baidu.ting.song.play&songid="? stringByAppendingString:songID];

[manager GET:path parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {

if ([responseObject isKindOfClass:[NSDictionary class]])

{

NSDictionary *array = [responseObject objectForKey:@"bitrate"];

self.file_link = [array objectForKey:@"file_link"];

self.file_size = [array objectForKey:@"file_size"];

self.file_duration = [array objectForKey:@"file_duration"];

self.playSongIndex = index;

}

} failure:^(AFHTTPRequestOperation *operation, NSError *error) {

NSLog(@"error--%@",error);

}];

}

三、播放界面

1、圓形專輯圖片(原先是矩形的,需要處理一下)

.h文件

@property (nonatomic, strong) UIView *playControllerView;

@property (nonatomic, strong) UIImageView *currentPlaySongImage;

.m文件

// 專輯圖片

// 先將專輯圖片放到正方形UIImageView, 再將UIImageView圓角設置為正方形邊長的一半就得到圓形的UIImageView了

_currentPlaySongImage = [[SCImageView alloc] initWithFrame:CGRectMake(10, 10 , _playControllerView.frame.size.height - 20 , _playControllerView.frame.size.height - 20)];

_currentPlaySongImage.image = [UIImage imageNamed:@"album_default"];

_currentPlaySongImage.clipsToBounds = true;

_currentPlaySongImage.layer.cornerRadius = (_playControllerView.frame.size.height - 20) * 0.5;

[_playControllerView addSubview:_currentPlaySongImage];

2、專輯圖片旋轉

我封裝了一個UIImageView的旋轉動畫類,代碼如下:

SCImageView.h

//

//? SCImageView.h

//? BaiduMusic

//

//? Created by 凌 ??????陳 on 8/22/17.

//? Copyright ? 2017 凌 ??????陳. All rights reserved.

//

#import

@interface SCImageView : UIImageView

-(void) startRotating;

-(void) stopRotating;

-(void) resumeRotate;

@end

SCImageView.c

//

//? SCImageView.m

//? BaiduMusic

//

//? Created by 凌 ??????陳 on 8/22/17.

//? Copyright ? 2017 凌 ??????陳. All rights reserved.

//

#import "SCImageView.h"

@implementation SCImageView

// 開始旋轉

-(void) startRotating {

? ? CABasicAnimation* rotateAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];

? ? rotateAnimation.fromValue = [NSNumber numberWithFloat:0.0];

? ? rotateAnimation.toValue = [NSNumber numberWithFloat:M_PI * 2];? // 旋轉一周

? ? rotateAnimation.duration = 20.0;? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? // 旋轉時間20秒

? ? rotateAnimation.repeatCount = MAXFLOAT;? ? ? ? ? ? ? ? ? ? ? ? ? // 重復次數,這里用最大次數

? ? [self.layer addAnimation:rotateAnimation forKey:nil];

}

// 停止旋轉

-(void) stopRotating {

? ? CFTimeInterval pausedTime = [self.layer convertTime:CACurrentMediaTime() fromLayer:nil];

? ? self.layer.speed = 0.0;? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? // 停止旋轉

? ? self.layer.timeOffset = pausedTime;? ? ? ? ? ? ? ? ? ? ? ? ? ? ? // 保存時間,恢復旋轉需要用到

}

// 恢復旋轉

-(void) resumeRotate {

? ? If (self.layer.timeOffset == 0) {

? ? ? ? [self startRotating];

? ? ? ? return;

? ? }

? ? CFTimeInterval pausedTime = self.layer.timeOffset;

? ? self.layer.speed = 1.0;? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? // 開始旋轉

? ? self.layer.timeOffset = 0.0;

? ? self.layer.beginTime = 0.0;

? ? CFTimeInterval timeSincePause = [self.layer convertTime:CACurrentMediaTime() fromLayer:nil] - ? ?pausedTime;? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? // 恢復時間

? ? self.layer.beginTime = timeSincePause;? ? ? ? ? ? ? ? ? ? ? ? ? // 從暫停的時間點開始旋轉

}

@end

歌曲剛開始播放調用startRotating,開始旋轉,點擊暫停按鍵時調用stopRotating停止旋轉,點擊播放按鍵時調用resumeRotate恢復旋轉(如果調用startRotating則又從頭開始旋轉)。

3、歌詞解析和歌詞滾動

首先我們先來看一下lrc文件的格式,如圖:

不難發現,除去歌詞頭部信息后,正文前面[]里面是時間點,右邊才是歌詞,也就是一段歌詞對應一個時間點,這個時間點是開始點,也就是說當歌曲播放到00:49.65這個時間點的時候,歌詞應該滾動到“因為在 一千年以后”這段。知道原理就好辦了。先解析歌詞。

// 解析歌詞

.h文件

@property (nonatomic,strong) NSMutableDictionary *mLRCDictinary;

@property (nonatomic,strong) NSMutableArray *mTimeArray;

@property (nonatomic, assign) BOOL mIsLRCPrepared;

.m文件

-(void) AnalysisLRC: (NSString *)lrcStr {

NSString* contentStr = lrcStr;

NSArray *lrcArray = [contentStr componentsSeparatedByString:@"\n"];

[mLRCDictinary removeAllObjects];

[mTimeArray removeAllObjects];

for (NSString *line in lrcArray) {

// 首先處理歌詞中無用的東西

// [ti:][ar:][al:]這類的直接跳過

if ([line containsString:@"[0"] || [line containsString:@"[1"] || [line containsString:@"[2"] || [line containsString:@"[3"]) {

NSArray *lineArr = [line componentsSeparatedByString:@"]"];

NSString *str1 = [line substringWithRange:NSMakeRange(3, 1)];

NSString *str2 = [line substringWithRange:NSMakeRange(6, 1)];

if ([str1 isEqualToString:@":"] && [str2 isEqualToString:@"."]) {

NSString *lrcStr = lineArr[1];

NSString *timeStr = [lineArr[0] substringWithRange:NSMakeRange(1, 5)];

[songInfo.mLRCDictinary setObject:lrcStr forKey:timeStr];

[songInfo.mTimeArray addObject:timeStr];

}

} else {

continue;

}

}

_mIsLRCPrepared = true;

[self.tableView reloadData];

}

mLRCDictinary存放配對時間點和歌詞段,mTimeArray存放時間點,通過時間點來找到相應的歌詞段,不過這個時間點是NSString格式,需要轉成int(我的精度要求不高,只到秒,后面的小數沒要)

NSString轉int

-(int) stringToInt: (NSString *)timeString {

NSArray *strTemp = [timeString componentsSeparatedByString:@":"];

int time = [strTemp.firstObject intValue] * 60 + [strTemp.lastObject intValue];

return time;

}

int轉NSString(顯示當前播放時間要用到)

-(NSString *)intToString: (int)needTransformInteger {

//實現00:00這種格式播放時間

int wholeTime = needTransformInteger;

int min? = wholeTime / 60;

int sec = wholeTime % 60;

NSString *str = [NSString stringWithFormat:@"%02d:%02d", min , sec];

return str;

}

歌詞滾動:

// songInfo.lrcIndex記錄歌詞第幾行,用currentTime 和 mTimeArray中第幾行歌詞的時間相比較,大于那個時間歌詞tableView滾動到那一行。

if (songInfo.lrcIndex <= songInfo.mLRCDictinary.count - 1) {

if ((int)currentTime >= [songInfo stringToInt:songInfo.mTimeArray[songInfo.lrcIndex]]) {

_deliverView.midView.midLrcView.currentRow = songInfo.lrcIndex;

//

[_deliverView.midView.midLrcView.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:_deliverView.midView.midLrcView.currentRow inSection:0] atScrollPosition:UITableViewScrollPositionMiddle animated:YES];

[_deliverView.midView.midLrcView.tableView reloadData];

songInfo.lrcIndex = songInfo.lrcIndex + 1;

}

}

先寫到這里,后續還會補充鎖屏播放設置,后臺播放設置,手勢操作等。如果各位覺得還可以,別忘了加個星星哦!

CSDN地址:http://blog.csdn.net/u014636932/article/details/77622358

這里附上github地址:https://github.com/Mozartisnotmyname/SCMusic.git

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,835評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,676評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,730評論 0 380
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,118評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,873評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,266評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,330評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,482評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,036評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,846評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,025評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,575評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,279評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,684評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,953評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,751評論 3 394
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,016評論 2 375

推薦閱讀更多精彩內容