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播放器的区别在于, 这货只能播放本地音乐.


好了让我开始音乐播放的开发之旅吧!!!


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阅读 202,009评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,808评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,891评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,283评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,285评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,409评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,809评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,487评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,680评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,499评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,548评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,268评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,815评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,872评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,102评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,683评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,253评论 2 341

推荐阅读更多精彩内容