前言:
在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的方法和属性不难实现,下篇文章我来专门讲一下上面这几个类,然后是缓存,在然后就是整个的蓝牙模块啦。