AVPlayer開發音樂模塊詳解(一)

前言:

? ? ? ? ? ? ?在iOS中無論哪個框架,只要能把它的屬性、方法、代理方法搞清楚,就基本上能夠進行簡單的開發。當然如果想更高性能更好體驗的話就需要了解每個屬性和方法的內在關系,以及加上自身掌握的處理事件的經驗。無論哪一種情況,我建議大家講框架的類全部整理出來,將每一個類的屬性和方法整理出來,iOS的API因為命名的規范大都能夠通過字面意思了解,但是其他的我建議仔細翻譯,或者去文檔中讀取英文注釋。 ?言歸正傳,下面我給大家介紹一下AVPlayer開發音樂模塊。


音樂概述:

先給大家擼兩張圖片,然后我仔細介紹一下:

當前主流的音樂開發框架
框架用到的主要類

1、AudioToolbox.framework的音頻播放時間不能超過30s,數據必須是PCM或者IMA4格式,音頻文件必須打包成.caf、.aif、.wav中的一種(注意這是官方文檔的說法,實際測試發現一些.mp3也可以播放. 它的主要用途可以用作app的音效(不是背景音).

2、MediaPlayer.framework框架下有兩個常用的系統封裝好的播放器:MPMoviePlayController 和 MPMoviePlayViewController, 二者的區別在于, 后者的視頻圖像需要一張View視圖作為載體, 你可以自己創建這個View, 那么也可以自由的控制它. 最明顯的例子就是你可以用它做個浮窗播放器.

3、AVFoundation.framework 目前被AVKit框架替代了, 但是我沒有跟進, 我就用它:

? ? ? AVAudioRecorder播放器, 提供錄音, 錄音的的代碼加起來沒你jj長.AVPlayer播放器, 一個能 ? ? 播放網絡和本地視頻/音頻的播放器, 和MediaPlayer.framework框架下的兩個播放器不同, 系統并未提供它的UI界面, 我們需要自己實現, 往好聽了說: 這是一個可以高度自定義的播放器.

AVAudioPlayer與 AVPlayer播放器的區別在于, 這貨只能播放本地音樂.


好了讓我開始音樂播放的開發之旅吧!?。?/h1>


1、框架的設計。

? ? ? ? 因為我們模塊需要播放本地音樂、網絡音樂播放、藍牙播放、車載SD卡播放,所以綜合情況比一般的音樂播放要麻煩很多。但是呢,大家也不用怕啦,只要框架設計的好就很大程度上減少我們的工作量。

? ? ? 所以呢,我把本地音樂和網絡音樂放到一個管理類(MusicPlayer)里面,然后藍牙播放和車載SD卡播放放到另外一個管理類(BLEMusicPlayer)里,兩個管理類都是繼承BasePlayer.

#importtypedef enum : NSInteger {

PLAYER_ORDER,? ? ? // 順序

PLAYER_REPEAT,? ? // 循環

PLAYER_RANDOM,? ? // 隨機

} PLAYERTYPEMODE;

@interface BasePlayer : NSObject

- (void)play;

- (void)pause;

- (void)stop;

- (void)next;

- (void)previous;

- (float)getSystomVolume;

- (void)adjustVolumeOfplayer:(float)value;

- (void)selectTypeMode:(PLAYERTYPEMODE)typeMode;

#import "BasePlayer.h"

@implementation BasePlayer

- (void)play{}

- (void)pause{}

- (void)stop{}

- (void)next{}

- (void)previous{}

- (float)getSystomVolume{

return 0;

}

- (void)adjustVolumeOfplayer:(float)value{}

- (void)selectTypeMode:(PLAYERTYPEMODE)typeMode{}

@end

可能名字起得不是太規范,大家見諒我語文老師是數學老師教的(別當真哈)!!

下面是MusicPlayer類,這個類我是封裝的AVPlayer,將播放的狀態、索引、歌曲緩存進度、播放進度、item等通過代理的方式全部傳出去。下面我只提供一些核心代碼,如果有不明白的地方大家可以在下面提問,或者直接看我的代碼。

NSKeyValueObservingOptions opations = NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew;

// 查看播放狀態

[self.player.currentItem addObserver:self forKeyPath:@"status" options:opations context:nil];

// 查看加載進度

[self.player.currentItem addObserver:self forKeyPath:@"loadedTimeRanges" options:opations context:nil];

// 監聽音樂是否完成

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(musicPlayDidFinished) name:AVPlayerItemDidPlayToEndTimeNotification object:self.player.currentItem];

// 監聽音樂是否跳躍

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(musicPlayJumped) name:AVPlayerItemTimeJumpedNotification object:self.player.currentItem];

// 監聽時間進度

__weak typeof(self) wself = self;

self.timeObserver = [self.player addPeriodicTimeObserverForInterval:CMTimeMake(1, 1) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {

// TODO

float current = CMTimeGetSeconds(time);

float total = CMTimeGetSeconds(wself.player.currentItem.duration);

float progress = current/total;

if (current) {

//? ? ? ? ? ? NSLog(@"\n 音樂加載的進度:%f \n 當前的時間:%f \n 總時間:%f" ,current/total,current,total);

if (wself.delegate&&[wself.delegate respondsToSelector:@selector(updataPlayProgress:currentTime:totalTime:)]) {

[wself.delegate updataPlayProgress:progress currentTime:current totalTime:total];

}

}

}];


#pragma mark --- KVO

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void *)context{? ? ? ?

AVPlayerItem *item = (AVPlayerItem*)object;? ?

if ([keyPath isEqualToString:@"status"]) {? ? ? ?

switch ([change[@"new"] integerValue]) {? ? ? ? ? ?

case AVPlayerItemStatusReadyToPlay: ? ? ? ? ?

{

[self play];? ? ? ? ? ? ? ?

if ([self.delegate respondsToSelector:@selector(playerStartPlayWithPlayer:)]) { ? ? ?[self.delegate playerStartPlayWithPlayer:self.player];? ? ? ? ? ? ? ?

} ? ? ? ? ? } ? ? ? ? ? ? ?

break; ? ? ? ? ??

case AVPlayerItemStatusFailed:? ? ? ? ? ? {? ? ? ? ? ? ? ?

NSLog(@"播放失敗");? ? ? ? ? ? ? ?

self.playing = NO;? ? ? ? ? ? ? ?

if ([self.delegate respondsToSelector:@selector(player:failure:)]) {? ? ? ? ? ? ? ? ? ? [self.delegate player:self.player failure:nil];? ? ? ? ? ? ? ? }? ? ? ? ? ? }? ? ? ? ? ? ? ?

break;? ? ? ? ? ?

case AVPlayerItemStatusUnknown:? ? ? ? ? ? {? ? ? ? ? ? ? ? NSLog(@"出現未知的原因");? ? ? ? ? ? ? ? self.playing = NO;? ? ? ? ? ? }? ? ? ? ? ? ? ?

break;? ? ? ? ? ?

default:? ? ? ? ? ? ? ? break;? ? ? ?

}?

? }? ? ?

? if ([keyPath isEqualToString:@"loadedTimeRanges"]) {? ? ? ? ? ? ? ?

? NSArray*array = item.loadedTimeRanges;

CMTimeRange timeRange = [array.firstObject CMTimeRangeValue];? //本次緩沖的時間范圍

float startSeconds = CMTimeGetSeconds(timeRange.start);

float durationSeconds = CMTimeGetSeconds(timeRange.duration);

float totalDuration = CMTimeGetSeconds(self.player.currentItem.asset.duration);

float bufferProgerss = durationSeconds / totalDuration;

NSTimeInterval totalBuffer = startSeconds + durationSeconds;

//? ? ? ? NSLog(@"\n音樂緩存的進度:%f \n 當前時間:%f \n 總的時間:%f",bufferProgerss ,durationSeconds,totalBuffer);

if ([self.delegate respondsToSelector:@selector(updataBufferProgress:)]) {

[self.delegate updataBufferProgress:bufferProgerss];

}

}

if ([keyPath isEqualToString:@"rate"]) {

// 0~1 表示正在播放 , 1 表示播放

float rate = self.player.rate;

if (self.delegate && [self.delegate respondsToSelector:@selector(player:rateOfPlayer:)]) {

[self.delegate player:self.player rateOfPlayer:rate];

}

}

if ([keyPath isEqualToString:@"currentItem"]) {

if (self.delegate && [self.delegate respondsToSelector:@selector(playerCurrentItemOfPlayerChanged:)]) {

[self.delegate playerCurrentItemOfPlayerChanged:self.player];

}

}

}

播放 ,暫停實現很簡單,下面我說一下音樂播放列表、上一首、下一首、播放模式的切換的基本思路。

在進入播放界面的時候,隨之傳遞的是一大溜的序列化的音樂數據,也就是你放到數組的模型類,因為我們沒有后臺,用的是第三方的LeanCloud儲存數據,因此表格需要我自己建,在這里呢我給大家的建議是:將 我的收藏、喜歡等屬于個人的屬性全部歸屬到一個表中,這樣查詢的時候也是方便的。上面的上一首、下一首、播放模式其實都是操作的索引值。

切換歌曲用這個方法:

[self.player replaceCurrentItemWithPlayerItem:item];

拖拽進度用這個方法:

[self.player seekToTime:seekTime toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero completionHandler:^(BOOL finished) {}];

好了,上面的只要研讀AVPlayer 、AVPlayerItem、AVAsset的方法和屬性不難實現,下篇文章我來專門講一下上面這幾個類,然后是緩存,在然后就是整個的藍牙模塊啦。

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

推薦閱讀更多精彩內容