本人不才备图,在項目中寫了個FM,也類似于播放器吧愉镰!為了減少代碼,我封裝了幾個類來解決這些問題髓窜。寫音樂播放器首先你要想你需要那幾個類。音樂播放器你得需要,向客戶展示播放暫停等View你的需要吧,在此我又封裝了一個解析數(shù)據(jù)的類寄纵。廢話不多說開始吧鳖敷!
向大家展示一下寫項目之前調(diào)研的界面吧!程拭!
一.首先我們先寫音樂播放器類(JYJMusicPlayerTools)
1..h中
class JYJProgramModel;
@protocol JYJMusicPlayerToolsDelegate <NSObject>
// 通過代理返回當前歌曲播放的進度
- (void)getCurTime:(NSString *)curTime totle:(NSString *)totle progress:(CGFloat)progress;
// 播放結(jié)束后定踱,如何操作由外部決定
- (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;
// 跳轉(zhuǎn)
- (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變?yōu)檫@種狀態(tài),才會真正的播放.
[self playMusic];
break;
case AVPlayerItemStatusFailed:
// mini設(shè)備不插耳機或者某些耳機會導致準備失敗.
NSLog(@"準備失敗");
if ([self.delegate respondsToSelector:@selector(playFail)]) {
[self.delegate playFail];
}
break;
default:
break;
}
}
}
// 播放
- (void)playMusic {
//如果定時器已經(jīng)存在,說明已經(jīng)在播放中恃鞋,直接返回
// 對于已經(jīng)存在的定時器崖媚,只有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 {
//!!!!!!!!計時器的處理方法中,不斷的調(diào)用代理方法恤浪,將音樂播放器的進度返回出去
[self.delegate getCurTime:[self valueToString:[self getCurrentTime]] totle:[self valueToString:[self getTotalTime]] progress:[self getProgress]];
}
// 獲取當前時間 (3┭啤!K伞\拧!就是這個算法 別問我為什么 規(guī)定的)
- (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];
}
// 將整數(shù)轉(zhuǎn)化為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];
}
// 跳轉(zhuǎn)方法
- (void)seekToTimeWithValue:(CGFloat)value {
// 跳轉(zhuǎn)之前先停止
[self.player pause];
//跳轉(zhuǎn)
[self.player seekToTime:CMTimeMake(value * [self getTotalTime], 1) completionHandler:^(BOOL finished) {
if (finished == YES) {
[self.player play];
}
}];
}
二. 我們將我們需要的音樂播放器類已經(jīng)建完 我們講一下數(shù)據(jù)解析類吧I翱汀(JYJDataTools)
1.h 中
@class JYJProgramModel;
typedef void(^PassValue)(NSArray *array);
@interface JYJDataTools : NSObject
@property (nonatomic, strong) NSMutableArray *dataArray;// 接受數(shù)據(jù)的數(shù)組
// 單例
+ (instancetype)shareDataTools;
- (void)postDataWithURL:(NSString *)URL andBody:(id)body passValue:(PassValue)passvalue;
- (JYJProgramModel *)getModelWithIndex:(NSInteger)index;// 第幾個歌曲
@property (nonatomic, strong) JYJProgramModel *programModel;
2.m中
// 單例創(chuàng)建
+ (instancetype)shareDataTools {
static JYJDataTools *dataTools = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
dataTools = [[JYJDataTools alloc]init];
});
return dataTools;
}
// 因為我的數(shù)據(jù)是post請求的 網(wǎng)址的url和body 體是不一樣的而我的數(shù)據(jù)頭是一樣的泥张,所以我只需要將網(wǎng)址的url和body和需要的數(shù)據(jù)封裝在一起。
- (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];
}
// >现怠;帷!3菟摺筝野!block回調(diào)
passvalue(self.dataArray);
} andFailureBlock:^(NSError *error) {
}];
}
- (JYJProgramModel *)getModelWithIndex:(NSInteger)index {
return self.dataArray[index];
}
三.大體需要的類已經(jīng)準備完畢,第三個類就需要自己來完成了粤剧,也就是鋪帶有暫停歇竟、播放、上一曲抵恋、下一曲焕议、進度條的頁面啦。
四.這時候我就當你已經(jīng)鋪好頁面了弧关。我們還的需要一個最重要的類了盅安,也就是音樂播放控制器。當然前面也只是做一下鋪墊世囊,沒有前面的他們我們什么也完不成1鸩t。?br> 我們需要從有TableView的VC跳轉(zhuǎn)到這個播放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對應(yīng)的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:@"節(jié)目加載失敗" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *action = [UIAlertAction actionWithTitle:@"確認" style: UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
}];
[alert addAction:action];
[self presentViewController:alert animated:YES completion:nil];
}