本人不才,在项目中写了个FM,也类似于播放器吧!为了减少代码,我封装了几个类来解决这些问题。写音乐播放器首先你要想你需要那几个类。音乐播放器你得需要,向客户展示播放暂停等View你的需要吧,在此我又封装了一个解析数据的类。废话不多说开始吧!
向大家展示一下写项目之前调研的界面吧!!
1.gif
一.首先我们先写音乐播放器类(JYJMusicPlayerTools)
1..h中
class JYJProgramModel;
@protocol JYJMusicPlayerToolsDelegate <NSObject>
// 通过代理返回当前歌曲播放的进度
- (void)getCurTime:(NSString *)curTime totle:(NSString *)totle progress:(CGFloat)progress;
// 播放结束后,如何操作由外部决定
- (void)endPlayAction;
//播放失败后
- (void)playFail;
@end
@interface JYJMusicPlayerTools : NSObject
@property (nonatomic, strong) AVPlayer *player;
@property (nonatomic, strong) AVPlayerItem *playerItem;
@property (nonatomic, strong) JYJProgramModel *programModel;
@property (nonatomic, weak) id<JYJMusicPlayerToolsDelegate>delegate;
//单例方法
+ (instancetype)shareMusicPlayerTools;
// 播放音乐
- (void)playMusic;
//暂停音乐
- (void)pauseMusic;
// 准备播放
- (void)preparePlayMusic;
// 跳转
- (void)seekToTimeWithValue:(CGFloat)value;
2..m中
@property (nonatomic, strong) NSTimer *timer;// 滑动条定时器l;
@end
@implementation JYJMusicPlayerTools
//单例
+ (instancetype)shareMusicPlayerTools {
static JYJMusicPlayerTools *mpTools = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
mpTools = [[JYJMusicPlayerTools alloc]init];
});
return mpTools;
}
- (instancetype)init {
self = [super init];
if (self) {
self.player = [[AVPlayer alloc]init];
//为播放完毕添加观察者 播放完毕的操作 有外边决定
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(endPlay:) name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
}
return self;
}
// 播放完毕
- (void)endPlay:(NSNotification *)nati {
//播放完毕先让音乐停止
[self pauseMusic];
if ([self.delegate respondsToSelector:@selector(endPlayAction)]) {
[self.delegate endPlayAction];
}
}
// 准备播放
- (void)preparePlayMusic {
// 只要AVPlayer有currentTime,那么一定有观察者
// 所以直接移除
if (self.player.currentItem) {
[self.player.currentItem removeObserver:self forKeyPath:@"status"];
}
self.playerItem= [[AVPlayerItem alloc]initWithURL:[NSURL URLWithString:self.programModel.music_media]];
//为item的status添加观察者
[self.playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
//用新建的item,替换AVPlayer之前的item,新的item添加了观察者
[self.player replaceCurrentItemWithPlayerItem:self.playerItem];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
if ([keyPath isEqualToString:@"status"]) {
switch ([[change valueForKey:@"new"] integerValue]) {
case AVPlayerItemStatusUnknown:
NSLog(@"不知道什么错误");
break;
case AVPlayerItemStatusReadyToPlay:
// 只有观察到status变为这种状态,才会真正的播放.
[self playMusic];
break;
case AVPlayerItemStatusFailed:
// mini设备不插耳机或者某些耳机会导致准备失败.
NSLog(@"准备失败");
if ([self.delegate respondsToSelector:@selector(playFail)]) {
[self.delegate playFail];
}
break;
default:
break;
}
}
}
// 播放
- (void)playMusic {
//如果定时器已经存在,说明已经在播放中,直接返回
// 对于已经存在的定时器,只有pauseMusic方法才会使之停止和销毁
if (self.timer != nil) {
return;
}
// 播放后开启定时器
self.timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(timerAction:) userInfo:nil repeats:YES];
[self.player play];
}
- (void)timerAction:(NSTimer *)timer {
//!!!!!!!!计时器的处理方法中,不断的调用代理方法,将音乐播放器的进度返回出去
[self.delegate getCurTime:[self valueToString:[self getCurrentTime]] totle:[self valueToString:[self getTotalTime]] progress:[self getProgress]];
}
// 获取当前时间 (!!!!!就是这个算法 别问我为什么 规定的)
- (NSInteger)getCurrentTime {
if (self.player.currentItem) {
// 当前的时间value / 当前时间的时间比例
return self.player.currentTime.value / self.player.currentTime.timescale;
}
return 0;
}
- (NSInteger)getTotalTime {
CMTime totalTime = [self.player.currentItem duration];
if (totalTime.timescale == 0) {
return 1;
}else {
return totalTime.value / totalTime.timescale;
}
}
// 获取当前播放进度
- (CGFloat)getProgress {
return (CGFloat) [self getCurrentTime] / (CGFloat)[self getTotalTime];
}
// 将整数转化为00:00格式
- (NSString *)valueToString:(NSInteger)value {
return [NSString stringWithFormat:@"%.2ld:%.2ld",value / 60,value % 60];
}
- (void)pauseMusic {
[self.timer invalidate];
self.timer = nil;
[self.player pause];
}
// 跳转方法
- (void)seekToTimeWithValue:(CGFloat)value {
// 跳转之前先停止
[self.player pause];
//跳转
[self.player seekToTime:CMTimeMake(value * [self getTotalTime], 1) completionHandler:^(BOOL finished) {
if (finished == YES) {
[self.player play];
}
}];
}
二. 我们将我们需要的音乐播放器类已经建完 我们讲一下数据解析类吧!(JYJDataTools)
1.h 中
@class JYJProgramModel;
typedef void(^PassValue)(NSArray *array);
@interface JYJDataTools : NSObject
@property (nonatomic, strong) NSMutableArray *dataArray;// 接受数据的数组
// 单例
+ (instancetype)shareDataTools;
- (void)postDataWithURL:(NSString *)URL andBody:(id)body passValue:(PassValue)passvalue;
- (JYJProgramModel *)getModelWithIndex:(NSInteger)index;// 第几个歌曲
@property (nonatomic, strong) JYJProgramModel *programModel;
2.m中
// 单例创建
+ (instancetype)shareDataTools {
static JYJDataTools *dataTools = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
dataTools = [[JYJDataTools alloc]init];
});
return dataTools;
}
// 因为我的数据是post请求的 网址的url和body 体是不一样的而我的数据头是一样的,所以我只需要将网址的url和body和需要的数据封装在一起。
- (void)postDataWithURL:(NSString *)URL andBody:(id)body passValue:(PassValue)passvalue {
[JYJNetWorkParser POST:URL andBody:body andBodyStyle:JYJBodyString andHeader:HEADDIC andResponse:JYJJSON andSuccessBlock:^(id result) {
self.dataArray = [NSMutableArray array];
NSArray *dataArray = [result objectForKey:@"data"];
for (NSDictionary *dic in dataArray) {
JYJProgramModel*model = [JYJProgramModel modelWithDic:dic];
[self.dataArray addObject:model];
}
// !!!!!block回调
passvalue(self.dataArray);
} andFailureBlock:^(NSError *error) {
}];
}
- (JYJProgramModel *)getModelWithIndex:(NSInteger)index {
return self.dataArray[index];
}
三.大体需要的类已经准备完毕,第三个类就需要自己来完成了,也就是铺带有暂停、播放、上一曲、下一曲、进度条的页面啦。
四.这时候我就当你已经铺好页面了。我们还的需要一个最重要的类了,也就是音乐播放控制器。当然前面也只是做一下铺垫,没有前面的他们我们什么也完不成!!
我们需要从有TableView的VC跳转到这个播放VC(JYJMusicController)
1.TableViewVC
.m 中
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationController.navigationBar.translucent = NO;
[self createView];
[self handleData];
// Do any additional setup after loading the view.
}
- (void)handleData {
NSString *string = @"json=%7B%22session%22%3A%7B%22uid%22%3A%2233368%22%2C%22sid%22%3A%22d0f457f15aab43579fc6c37a4dce426c%22%7D%2C%22item_id%22%3A%2213241%22%2C%22keyword%22%3A%22%22%2C%22orderBy%22%3A2%2C%22recomm%22%3A0%2C%22pagination%22%3A%7B%22count%22%3A25%2C%22page%22%3A1%7D%7D";
[[JYJDataTools shareDataTools]postDataWithURL:@"http://www.aiyinsitanfm.com/mobile/channel/musiclist.jhtml" andBody:string passValue:^(NSArray __kindof *array) {
self.marrOfModel = array;
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}];
}
- (void)createView {
self.tableView = [[UITableView alloc]initWithFrame:self.view.bounds style:UITableViewStylePlain];
[self.view addSubview:_tableView];
self.tableView.delegate = self;
self.tableView.dataSource = self;
[self.tableView registerClass:[CustomTableViewCell class] forCellReuseIdentifier:@"pool"];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.modelOfArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
CustomTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"pool" forIndexPath:indexPath];
ModelOfFirst *model = self.modelOfArray[indexPath.row];
[cell setModel:model];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
JYJMusicController*vc = [JYJMusicController shareVC];
vc.index = indexPath.row;
vc.model = self.modelOfArray[indexPath.row];
[self.navigationController pushViewController:vc animated:YES];
}
2.JYJMusicController
.h中
@property (nonatomic, assign) NSInteger index;
@property (nonatomic, strong) ModelOfFirst *model;
+ (instancetype)shareVC;
.m中
@interface ViewController ()<MusicPlayToolsDelegate>
@property (nonatomic, strong) MusicPlayView *viewContairner;
@property (nonatomic, strong) UIImageView *backImageView;
@property (nonatomic, strong) MusicPlayTools *aa;
@property (nonatomic, strong) UILabel *labelOfPaoma;
@property (nonatomic, strong) LSPaoMaView *paoma;
@end
@implementation ViewController
+ (instancetype)shareVC {
static ViewController *vc = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
vc = [[ViewController alloc]init];
});
return vc;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.aa = [MusicPlayTools shareMusicPlayTools];
self.aa.delegate = self;
self.viewContairner = [[MusicPlayView alloc]initWithFrame:self.view.bounds];
[self.view addSubview:self.viewContairner];
[self.viewContairner play:^(MyButton *play) {
[[MusicPlayTools shareMusicPlayTools] musicPause];
} pause:^(MyButton *play) {
[[MusicPlayTools shareMusicPlayTools]musicPlay];
} lastSong:^(MyButton *play) {
if (self.index > 0) {
self.index --;
}else{
self.index = [GetDataTools shareGetData].dataArray.count - 1;
}
[self p_play];
} nextSong:^(MyButton *play) {
if (self.index == [GetDataTools shareGetData].dataArray.count -1) {
self.index = 0;
}else
{
self.index ++;
}
[self p_play];
}];
[self.viewContairner slider:^(UISlider *slider) {
[[MusicPlayTools shareMusicPlayTools] seekToTimeWithValue:slider.value];
} ];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.backImageView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, 300)];
[self.view addSubview:_backImageView];
[_backImageView sd_setImageWithURL:[NSURL URLWithString:self.model.music_img] placeholderImage:[UIImage imageNamed:@"zhanwei"]];
[self p_play];
}
- (void)p_play {
// 判断当前播放器的model 和 点击cell的index对应的model,是不是同一个.
// 如果是同一个,说明正在播放的和我们点击的是同一个, 这个时候不需要重新播放.直接返回就行了.
if ([[MusicPlayTools shareMusicPlayTools].model isEqual:[[GetDataTools shareGetData] getModelWithIndex:self.index]]) {
return;
}
// 如果播放中和我们点击的不是同一个,那么替换当前播放器的model.
// 然后重新准备播放.
[MusicPlayTools shareMusicPlayTools].model = [[GetDataTools shareGetData] getModelWithIndex:self.index];
//背景图片
[self.backImageView sd_setImageWithURL:[NSURL URLWithString:[MusicPlayTools shareMusicPlayTools].model.music_img] placeholderImage:[UIImage imageNamed:@"zhanwei"]];
//self.labelOfPaoma.text = [MusicPlayTools shareMusicPlayTools].model.title;
self.paoma = [LSPaoMaView shareLsinitWithFrame:CGRectZero title:[MusicPlayTools shareMusicPlayTools].model.music_title BackColor:[UIColor redColor] textColor:[UIColor whiteColor] textFont:25];
NSLog(@"%@",[MusicPlayTools shareMusicPlayTools].model.music_title);
// 注意这里准备播放 不是播放!!!
[[MusicPlayTools shareMusicPlayTools] musicPrePlay];
}
-(void)getCurTime:(NSString *)curTime Totle:(NSString *)totle Progress:(CGFloat)progress {
self.viewContairner.leftLabelTime.text = curTime;
self.viewContairner.rightLabelTime.text = totle;
self.viewContairner.slider.value = progress;
}
- (void)endOfPlayAction {
if (self.index == [GetDataTools shareGetData].dataArray.count -1) {
self.index = 0;
}else
{
self.index ++;
}
[self p_play];
}
- (void)playFail {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"提示" message:@"节目加载失败" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *action = [UIAlertAction actionWithTitle:@"确认" style: UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
}];
[alert addAction:action];
[self presentViewController:alert animated:YES completion:nil];
}