ios 简单的本地音乐播放器
一、导入资源文件
二、新建一个控制器,继承于UITableViewController,用来展示播放列表
1、播放列表的实现
@interface MusicListViewController (){ //定义一个播放列表数组 NSMutableArray *musicList; } - (void)viewDidLoad { [super viewDidLoad]; //调用解析文件类方法,得到播放列表 musicList=[MusicModel allMusics]; } - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return musicList.count; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath]; MusicModel *model=musicList[indexPath.row]; //调用画图的分类方法,使歌手头像变成圆形 cell.imageView.image=[UIImage circleImageWithName:model.singerIcon borderWidth:5 borderColor:[UIColor redColor]]; cell.textLabel.text=model.name; return cell; } -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{ return 80; } //点击了对应的cell后执行下面代码 -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{ MusicModel *model=musicList[indexPath.row]; [self.playerVC showWithPlayingMusic:model]; }
2、添加一个UIimageView的分类,调整歌手的头像(正方形——>圆形)
分类的实现代码如下:
UIImage+YY.h文件
+(instancetype)circleImageWithName:(NSString *)name borderWidth:(CGFloat)borderWidth borderColor:(UIColor *)borderColor{ //1、拿到要处理的图片 UIImage *oldImg=[UIImage imageNamed:name]; //2、你要绘图,首先要创建上下文(你可以指定上下文的区域) CGFloat imgW=oldImg.size.width+borderWidth*2; CGFloat imgH=oldImg.size.height+borderWidth*2; UIGraphicsBeginImageContext(CGSizeMake(imgW, imgH)); //3、拿到所创建的上下文,设置填充颜色 CGContextRef ctx=UIGraphicsGetCurrentContext(); [borderColor set]; //4、画大圆(边框颜色以及宽度) CGFloat radius=imgW/2.0;//大圆的半径 CGFloat cenX=radius; CGFloat cenY=radius; CGContextAddArc(ctx, cenX, cenY, radius, 0, 2*M_PI, 0); CGContextFillPath(ctx); //5、画小圆(把图片画上去) CGFloat smallRadius=radius-borderWidth; CGContextAddArc(ctx, cenX, cenY, smallRadius, 0, 2*M_PI, 0); //裁剪 CGContextClip(ctx); //6、画图 [oldImg drawInRect:CGRectMake(borderWidth, borderWidth, oldImg.size.width, oldImg.size.height)]; //7、从当前上下文中拿到一张新图片 UIImage *newImage=UIGraphicsGetImageFromCurrentImageContext(); //8、关闭上下文 UIGraphicsEndImageContext(); return newImage; }
3、解析歌曲信息,获得歌曲模型
xxxx.h
@interface MusicModel : NSObject //歌曲名称 @property(nonatomic,strong)NSString *name; //歌曲链接 @property(nonatomic,strong)NSString *filename; //歌词名称 @property(nonatomic,strong)NSString *lrcname; //歌手 @property(nonatomic,strong)NSString *singer; //歌手头像 @property(nonatomic,strong)NSString *singerIcon; //封面照片 @property(nonatomic,strong)NSString *icon; //返回所有音乐模型 +(NSMutableArray *)allMusics; @end
xxxx.m
-(instancetype)initWithDict:(NSDictionary *)dict{ if (self=[super init]) { [self setValuesForKeysWithDictionary:dict]; } return self; } //返回了所有音乐模型 +(NSMutableArray *)allMusics{ NSMutableArray *arrM=[[NSMutableArray alloc]init]; NSString *path=[[NSBundle mainBundle]pathForResource:@"Musics.plist" ofType:nil]; NSArray *tmpArr=[NSArray arrayWithContentsOfFile:path]; for (NSDictionary *tmpDic in tmpArr) { MusicModel *model=[[MusicModel alloc]initWithDict:tmpDic]; [arrM addObject:model]; } return arrM; }
三、点击了对应的歌曲后,会跳转到另一个控制器:这里用xib做,继承与UIViewController
界面部分如下:
在上一个播放列表界面,点击对应歌曲后执行的方法如下:
@interface MusicPlayerController : UIViewController @property(nonatomic,strong)NSArray *musicList; @property(nonatomic,strong)AVAudioPlayer *player; @property(nonatomic,strong)MusicModel *playIngModel;//当前正在播放的音乐模型 //写一个方法来显示界面 -(void)showWithPlayingMusic:(MusicModel *)model; @end
@interface MusicPlayerController ()<AVAudioPlayerDelegate>{ NSTimer *t; LrcView *lrcView; } //返回到上一层界面 - (IBAction)exitBtnPressed:(id)sender; //上一曲按钮 - (IBAction)previousBtnPressed:(id)sender; //播放暂停按钮 - (IBAction)playBtnPressed:(id)sender; //下一曲按钮 - (IBAction)nextBtnPressed:(id)sender; //进度条 @property (weak, nonatomic) IBOutlet UISlider *slider; - (IBAction)sliderChanged:(id)sender; @property (weak, nonatomic) IBOutlet UILabel *songName; @property (weak, nonatomic) IBOutlet UILabel *singer; @property (weak, nonatomic) IBOutlet UIImageView *imgView; @end
这里既不用push也不用model,因为当返回到上一个界面的时候不能让这个界面销毁
//显示界面 -(void)showWithPlayingMusic:(MusicModel *)model{ //1、拿到window UIWindow *window=[UIApplication sharedApplication].windows[0]; //2、将当前控制器的view添加到window上面 [window addSubview:self.view]; //3、指定self.view的frame self.view.frame=CGRectMake(0, WinHeight, WinWidth, WinHeight); //4、动画,让当前view从下方显示出来 [UIView animateWithDuration:1.0 animations:^{ self.view.frame=window.frame; }]; //5、播放音乐 //当前所播放的音乐和外面传进来的音乐不一样的时候,才需要重新播放 if (![self.playIngModel isEqual:model]) { [self resertPlayingModel:model]; } }
//重写PlayingModel方法(当前播放歌曲改变调用此方法) -(void)resertPlayingModel:(MusicModel *)model{ _playIngModel=model; self.imgView.image=[UIImage imageNamed:model.icon]; //播放音乐 NSURL *url=[[NSBundle mainBundle]URLForResource:model.filename withExtension:nil]; //声明会话类型,后台播放步骤2 AVAudioSession *session = [AVAudioSession sharedInstance]; [session setActive:YES error:nil]; [session setCategory:AVAudioSessionCategoryPlayback error:nil]; self.player=[[AVAudioPlayer alloc]initWithContentsOfURL:url error:nil]; self.player.delegate=self; [self.player prepareToPlay]; [self.player play]; //指定silder的信息 self.slider.minimumValue=0; self.slider.value=0; self.slider.maximumValue=self.player.duration; self.songName.text=model.name; self.singer.text=model.singer; t=[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(updateSlider:) userInfo:nil repeats:YES]; }
//刷新slider的进度
-(void)updateSlider:(NSTimer *)t{
self.slider.value=self.player.currentTime;
}
- (IBAction)sliderChanged:(id)sender {
self.player.currentTime=self.slider.value;
}
- (IBAction)playBtnPressed:(id)sender { UIButton *btn=sender; btn.selected=!btn.selected; //如果当前播放器正在播放音乐,则暂停 if ([self.player isPlaying]) { [self.player pause]; } //否则继续播放 else{ [self.player play]; } } - (IBAction)previousBtnPressed:(id)sender { //1、拿到当前正在播放的音乐对应数组的index NSInteger currentIndex=[self.musicList indexOfObject:_playIngModel]; //2、让currentIndex-- currentIndex--; if (currentIndex<0) { currentIndex=self.musicList.count-1; } MusicModel *model=self.musicList[currentIndex]; [self resertPlayingModel:model]; } - (IBAction)nextBtnPressed:(id)sender { //1、拿到当前正在播放的音乐对应数组的index NSInteger currentIndex=[self.musicList indexOfObject:_playIngModel]; //2、让currentIndex++ currentIndex++; if (currentIndex>=self.musicList.count) { currentIndex=0; } MusicModel *model=self.musicList[currentIndex]; [self resertPlayingModel:model]; } //显示界面 -(void)showWithPlayingMusic:(MusicModel *)model{ //1、拿到window UIWindow *window=[UIApplication sharedApplication].windows[0]; //2、将当前控制器的view添加到window上面 [window addSubview:self.view]; //3、指定self.view的frame self.view.frame=CGRectMake(0, WinHeight, WinWidth, WinHeight); //4、动画,让当前view从下方显示出来 [UIView animateWithDuration:1.0 animations:^{ self.view.frame=window.frame; }]; //5、播放音乐 //当前所播放的音乐和外面传进来的音乐不一样的时候,才需要重新播放 if (![self.playIngModel isEqual:model]) { [self resertPlayingModel:model]; } } - (IBAction)exitBtnPressed:(id)sender { [UIView animateWithDuration:1.0 animations:^{ self.view.frame=CGRectMake(0, WinHeight, WinWidth, WinHeight); }]; } #pragma mark AVAudioPlayerDelegate //当播放完一首歌之后自动跳转到下一首 - (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag{ [t invalidate]; [self nextBtnPressed:nil]; }
五、歌词部分 :这里把它分装出来了,xib方式,继承于UIView
往控件上托一个UITableView,继承于UITableViewDataSource,UITableViewDelegate
首次先传递歌词界面及信息:
-(void)viewDidLoad{ [super viewDidLoad]; //初始化显示歌词的view lrcView=[LrcView lrcView]; [self.view insertSubview:lrcView aboveSubview:self.imgView]; } -(void)viewWillLayoutSubviews{ [super viewWillLayoutSubviews]; lrcView.frame=CGRectMake(0, 0, WinWidth, WinHeight-100); lrcView.backgroundColor=[UIColor clearColor]; } //重写PlayingModel方法(当前播放歌曲改变调用此方法) -(void)resertPlayingModel:(MusicModel *)model{ _playIngModel=model; self.imgView.image=[UIImage imageNamed:model.icon]; //播放音乐 NSURL *url=[[NSBundle mainBundle]URLForResource:model.filename withExtension:nil]; AVAudioSession *session = [AVAudioSession sharedInstance]; [session setActive:YES error:nil]; [session setCategory:AVAudioSessionCategoryPlayback error:nil]; self.player=[[AVAudioPlayer alloc]initWithContentsOfURL:url error:nil]; self.player.delegate=self; [self.player prepareToPlay]; [self.player play]; //传递歌词信息 lrcView.lrcName=model.lrcname; lrcView.player=self.player; //指定silder的信息 self.slider.minimumValue=0; self.slider.value=0; self.slider.maximumValue=self.player.duration; self.songName.text=model.name; self.singer.text=model.singer; t=[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(updateSlider:) userInfo:nil repeats:YES]; }
@interface LrcView : UIView<UITableViewDataSource,UITableViewDelegate>{ NSTimer *t; } @property (weak, nonatomic) IBOutlet UITableView *tableView; @property(nonatomic,assign)NSInteger selectIndex; //当前所播放的歌词的名称 @property(nonatomic,strong)NSString *lrcName; @property(nonatomic,strong)AVAudioPlayer *player; +(instancetype)lrcView; @property(nonatomic,strong)NSMutableArray *contentList; @end
#import "LrcView.h" #import "LrcLine.h" @implementation LrcView -(void)awakeFromNib{ //取消tableView的分割线 self.tableView.separatorStyle=UITableViewCellSeparatorStyleNone; self.tableView.contentInset=UIEdgeInsetsMake(self.tableView.frame.size.height*0.5, 0, 0, 0); } -(void)layoutSubviews{ [super layoutSubviews]; } //拿到歌词名称了 -(void)setLrcName:(NSString *)lrcName{ _lrcName=lrcName; //初始化一个歌词名(10405520.lrc)对应的所有歌词信息 _contentList=[LrcLine allLrcLineModelsWithFileName:lrcName]; [self.tableView reloadData]; //每隔0.5秒执行刷新歌词的方法 t=[NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(updateLrc) userInfo:nil repeats:YES]; } -(void)updateLrc{ //当前播放器的时间 CGFloat currentTime=self.player.currentTime;//80.89秒 int minute=currentTime/60; int second=(int)currentTime%60; int msecond=(currentTime-(int)currentTime)*100; //把当前播放器的播放时间转换为字符串 NSString *currentTimeStr=[NSString stringWithFormat:@"%02d:%02d.%02d",minute,second,msecond]; //遍历每一个歌词 for (int i=0; i<self.contentList.count; i++) { LrcLine *model=self.contentList[i]; NSInteger nextIndex=i+1; //最后一个歌词 if (nextIndex<self.contentList.count) { LrcLine *nextModel=self.contentList[nextIndex]; if ([currentTimeStr compare:model.time]!=NSOrderedAscending &&[currentTimeStr compare:nextModel.time]==NSOrderedAscending &&_selectIndex!=i) {//_selectIndex!=i 第一次滚动到对应的行后,定时器0.5s一刷新,tableView在第i行结束前就不用重复滚动和刷新了 //刷新指定行的内容 NSArray *reloadRows=@[[NSIndexPath indexPathForRow:_selectIndex inSection:0],[NSIndexPath indexPathForRow:i inSection:0]]; _selectIndex=i; [self.tableView reloadRowsAtIndexPaths:reloadRows withRowAnimation:UITableViewRowAnimationNone]; //满足条件,就让tableView滚动到对应的行 NSIndexPath *indexPath=[NSIndexPath indexPathForRow:i inSection:0]; [self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:YES]; } } } } +(instancetype)lrcView{ return [[[NSBundle mainBundle]loadNibNamed:@"LrcView" owner:nil options:nil] lastObject]; } -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{ return self.contentList.count; } -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ static NSString *identifier=@"cell"; UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:identifier]; if (cell==nil) { cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier]; cell.backgroundColor=[UIColor clearColor]; cell.selectionStyle=UITableViewCellSelectionStyleNone; cell.textLabel.textColor=[UIColor whiteColor]; cell.textLabel.textAlignment=NSTextAlignmentCenter; } if (indexPath.row==_selectIndex) { cell.textLabel.textColor=[UIColor redColor]; cell.textLabel.font=[UIFont systemFontOfSize:20]; } else{ cell.textLabel.textColor=[UIColor whiteColor]; cell.textLabel.font=[UIFont systemFontOfSize:16]; } LrcLine *model=self.contentList[indexPath.row]; cell.textLabel.text=model.word; return cell; } @end
解析歌词:
//返回一个歌词名对应的所有的model信息 +(NSMutableArray *)allLrcLineModelsWithFileName:(NSString *)lrcName{ NSMutableArray *arrM=[[NSMutableArray alloc]init]; NSURL *url=[[NSBundle mainBundle]URLForResource:lrcName withExtension:nil]; //把歌词信息转换字符串 NSString *lrcStr=[NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:nil]; NSArray *lrcComps=[lrcStr componentsSeparatedByString:@"\n"]; //转换成model for (NSString *tmpStr in lrcComps) { LrcLine *model=[[LrcLine alloc]init]; [arrM addObject:model]; if (![tmpStr hasPrefix:@"["]) { continue ;//跳出当前循环一次 } if ([tmpStr hasPrefix:@"[ti:"] ||[tmpStr hasPrefix:@"[ar:"]||[tmpStr hasPrefix:@"[al:"]) { NSString *word=[[tmpStr componentsSeparatedByString:@":"] lastObject]; model.word=[word substringToIndex:word.length-1]; } else{ NSArray *array=[tmpStr componentsSeparatedByString:@"]"]; model.time=[[array firstObject] substringFromIndex:1]; model.word=[array lastObject]; } } return arrM; }
六、后台播放:
开启后台模式步骤 1、infoPlist 添加 Required background modes 说明要支持的后台类型 2、要支持音乐的后台播放,需要声明会话类型 AVAudioSession *session = [AVAudioSession sharedInstance]; [session setActive:YES error:nil]; [session setCategory:AVAudioSessionCategoryPlayback error:nil]; 3、添加代码 -(BOOL)canBecomeFirstResponder{ return YES; } //远程控制器事件 -(void)remoteControlReceivedWithEvent:(UIEvent *)event{ } 4、注册对应的通知,并且实现通知规定的方法 [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(willResignActive) name:UIApplicationWillResignActiveNotification object:nil]; [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(willEnterForeground) name:UIApplicationWillEnterForegroundNotification object:nil]; -(void)willResignActive{ [self becomeFirstResponder]; //开始接受远程控制事件 [[UIApplication sharedApplication]beginReceivingRemoteControlEvents]; } //当应用程序将要进入前台调用该方法 -(void)willEnterForeground{ [self resignFirstResponder]; //取消接受远程控制事件 [[UIApplication sharedApplication]endReceivingRemoteControlEvents]; }
//远程控制器事件 -(void)remoteControlReceivedWithEvent:(UIEvent *)event{ if (event.type==UIEventTypeRemoteControl) { switch (event.subtype) { case UIEventSubtypeMotionShake:// 摇晃事件 break; case UIEventSubtypeRemoteControlPause://暂停事件 [_player pause]; break; case UIEventSubtypeRemoteControlPlay://播放事件 [_player play]; break; case UIEventSubtypeRemoteControlStop://停止事件 break; //播放或暂停切换【操作:播放或暂停状态下,按耳机线控中间按钮一下】 case UIEventSubtypeRemoteControlTogglePlayPause: if ([_player isPlaying]) { [_player pause]; } else{ [_player play]; } break; case UIEventSubtypeRemoteControlNextTrack://下一曲 [self nextBtnPressed:nil];; break; case UIEventSubtypeRemoteControlPreviousTrack://上一曲 [self previousBtnPressed:nil]; break; case UIEventSubtypeRemoteControlBeginSeekingForward://快进开始 NSLog(@"Begin seek forward..."); break; case UIEventSubtypeRemoteControlEndSeekingForward://快进结束 NSLog(@"End seek forward..."); break; case UIEventSubtypeRemoteControlBeginSeekingBackward://快退开始 NSLog(@"Begin seek backward..."); break; case UIEventSubtypeRemoteControlEndSeekingBackward://快退结束 NSLog(@"End seek backward..."); break; default: break; } } }