iOS开发拓展篇—音频处理(音乐播放器3)
iOS开发拓展篇—音频处理(音乐播放器3)
说明:这篇文章主要介绍音频工具类和播放工具类的封装。
一、控制器间数据传递
1.两个控制器之间数据的传递
第一种方法:self.parentViewController.music=self.music[indexPath.row];不能满足
第二种做法:把整个数组传递给它
第三种做法:设置一个数据源,设置播放控制器的数据源是这个控制器。self.parentViewController.dataSource=self;好处:没有耦合性,任何实现了协议的可以作为数据源。
第四种做法:把整个项目会使用到的音频资源交给一个工具类去管理,这样就不用传递过去了。直接向工具类索要资源就可以。
二、封装一个音频工具类
新建一个音频工具类,用来管理音乐数据(音乐模型)
工具类中的代码设计如下:
YYMusicTool.h文件
// // YYMusicTool.h // #import <Foundation/Foundation.h> @class YYMusicModel; @interface YYMusicTool : NSObject /** * 返回所有的歌曲 */ + (NSArray *)musics; /** * 返回正在播放的歌曲 */ + (YYMusicModel *)playingMusic; + (void)setPlayingMusic:(YYMusicModel *)playingMusic; /** * 下一首歌曲 */ + (YYMusicModel *)nextMusic; /** * 上一首歌曲 */ + (YYMusicModel *)previousMusic; @end
YYMusicTool.m文件
// // YYMusicTool.m // #import "YYMusicTool.h" #import "YYMusicModel.h" #import "MJExtension.h" @implementation YYMusicTool static NSArray *_musics; static YYMusicModel *_playingMusic; /** * @return 返回所有的歌曲 */ +(NSArray *)musics { if (_musics==nil) { _musics=[YYMusicModel objectArrayWithFilename:@"Musics.plist"]; } return _musics; } +(void)setPlayingMusic:(YYMusicModel *)playingMusic { /* *如果没有传入需要播放的歌曲,或者是传入的歌曲名不在音乐库中,那么就直接返回 如果需要播放的歌曲就是当前正在播放的歌曲,那么直接返回 */ if (!playingMusic || ![[self musics]containsObject:playingMusic]) return; if (_playingMusic == playingMusic) return; _playingMusic=playingMusic; } /** * 返回正在播放的歌曲 */ +(YYMusicModel *)playingMusic { return _playingMusic; } /** * 下一首歌曲 */ +(YYMusicModel *)nextMusic { //设定一个初值 int nextIndex = 0; if (_playingMusic) { //获取当前播放音乐的索引 int playingIndex = [[self musics] indexOfObject:_playingMusic]; //设置下一首音乐的索引 nextIndex = playingIndex+1; //检查数组越界,如果下一首音乐是最后一首,那么重置为0 if (nextIndex>=[self musics].count) { nextIndex=0; } } return [self musics][nextIndex]; } /** * 上一首歌曲 */ +(YYMusicModel *)previousMusic { //设定一个初值 int previousIndex = 0; if (_playingMusic) { //获取当前播放音乐的索引 int playingIndex = [[self musics] indexOfObject:_playingMusic]; //设置下一首音乐的索引 previousIndex = playingIndex-1; //检查数组越界,如果下一首音乐是最后一首,那么重置为0 if (previousIndex<0) { previousIndex=[self musics].count-1; } } return [self musics][previousIndex]; } @end
三、封装一个音乐播放工具类
该工具类中的代码设计如下:
YYAudioTool.h文件
// // YYAudioTool.h // #import <Foundation/Foundation.h> #import <AVFoundation/AVFoundation.h> @interface YYAudioTool : NSObject /** *播放音乐文件 */ +(BOOL)playMusic:(NSString *)filename; /** *暂停播放 */ +(void)pauseMusic:(NSString *)filename; /** *播放音乐文件 */ +(void)stopMusic:(NSString *)filename; /** *播放音效文件 */ +(void)playSound:(NSString *)filename; /** *销毁音效 */ +(void)disposeSound:(NSString *)filename; @end
YYAudioTool.m文件
// // YYAudioTool.m // #import "YYAudioTool.h" @implementation YYAudioTool /** *存放所有的音乐播放器 */ static NSMutableDictionary *_musicPlayers; +(NSMutableDictionary *)musicPlayers { if (_musicPlayers==nil) { _musicPlayers=[NSMutableDictionary dictionary]; } return _musicPlayers; } /** *存放所有的音效ID */ static NSMutableDictionary *_soundIDs; +(NSMutableDictionary *)soundIDs { if (_soundIDs==nil) { _soundIDs=[NSMutableDictionary dictionary]; } return _soundIDs; } /** *播放音乐 */ +(BOOL)playMusic:(NSString *)filename { if (!filename) return NO;//如果没有传入文件名,那么直接返回 //1.取出对应的播放器 AVAudioPlayer *player=[self musicPlayers][filename]; //2.如果播放器没有创建,那么就进行初始化 if (!player) { //2.1音频文件的URL NSURL *url=[[NSBundle mainBundle]URLForResource:filename withExtension:nil]; if (!url) return NO;//如果url为空,那么直接返回 //2.2创建播放器 player=[[AVAudioPlayer alloc]initWithContentsOfURL:url error:nil]; //2.3缓冲 if (![player prepareToPlay]) return NO;//如果缓冲失败,那么就直接返回 //2.4存入字典 [self musicPlayers][filename]=player; } //3.播放 if (![player isPlaying]) { //如果当前没处于播放状态,那么就播放 return [player play]; } return YES;//正在播放,那么就返回YES } +(void)pauseMusic:(NSString *)filename { if (!filename) return;//如果没有传入文件名,那么就直接返回 //1.取出对应的播放器 AVAudioPlayer *player=[self musicPlayers][filename]; //2.暂停 [player pause];//如果palyer为空,那相当于[nil pause],因此这里可以不用做处理 } +(void)stopMusic:(NSString *)filename { if (!filename) return;//如果没有传入文件名,那么就直接返回 //1.取出对应的播放器 AVAudioPlayer *player=[self musicPlayers][filename]; //2.停止 [player stop]; //3.将播放器从字典中移除 [[self musicPlayers] removeObjectForKey:filename]; } //播放音效 +(void)playSound:(NSString *)filename { if (!filename) return; //1.取出对应的音效 SystemSoundID soundID=[[self soundIDs][filename] unsignedIntegerValue]; //2.播放音效 //2.1如果音效ID不存在,那么就创建 if (!soundID) { //音效文件的URL NSURL *url=[[NSBundle mainBundle]URLForResource:filename withExtension:nil]; if (!url) return;//如果URL不存在,那么就直接返回 OSStatus status = AudioServicesCreateSystemSoundID((__bridge CFURLRef)(url), &soundID); NSLog(@"%ld",status); //存入到字典中 [self soundIDs][filename]=@(soundID); } //2.2有音效ID后,播放音效 AudioServicesPlaySystemSound(soundID); } //销毁音效 +(void)disposeSound:(NSString *)filename { //如果传入的文件名为空,那么就直接返回 if (!filename) return; //1.取出对应的音效 SystemSoundID soundID=[[self soundIDs][filename] unsignedIntegerValue]; //2.销毁 if (soundID) { AudioServicesDisposeSystemSoundID(soundID); //2.1销毁后,从字典中移除 [[self soundIDs]removeObjectForKey:filename]; } } @end
四、在音乐播放控制器中的代码处理
YYPlayingViewController.m文件
// // YYPlayingViewController.m // #import "YYPlayingViewController.h" #import "YYMusicTool.h" #import "YYMusicModel.h" #import "YYAudioTool.h" @interface YYPlayingViewController () @property (weak, nonatomic) IBOutlet UIImageView *iconView; @property (weak, nonatomic) IBOutlet UILabel *songLabel; @property (weak, nonatomic) IBOutlet UILabel *singerLabel; @property (weak, nonatomic) IBOutlet UILabel *durationLabel; @property(nonatomic,strong)YYMusicModel *playingMusic; - (IBAction)exit; @end @implementation YYPlayingViewController #pragma mark-公共方法 -(void)show { //1.禁用整个app的点击事件 UIWindow *window=[UIApplication sharedApplication].keyWindow; window.userInteractionEnabled=NO; //2.添加播放界面 //设置View的大小为覆盖整个窗口 self.view.frame=window.bounds; //设置view显示 self.view.hidden=NO; //把View添加到窗口上 [window addSubview:self.view]; //3.检测是否换了歌曲 if (self.playingMusic!=[YYMusicTool playingMusic]) { [self RresetPlayingMusic]; } //4.使用动画让View显示 self.view.y=self.view.height; [UIView animateWithDuration:0.25 animations:^{ self.view.y=0; } completion:^(BOOL finished) { //设置音乐数据 [self starPlayingMusic]; window.userInteractionEnabled=YES; }]; } #pragma mark-私有方法 //重置正在播放的音乐 -(void)RresetPlayingMusic { //1.重置界面数据 self.iconView.image=[UIImage imageNamed:@"play_cover_pic_bg"]; self.songLabel.text=nil; self.singerLabel.text=nil; //2.停止播放 [YYAudioTool stopMusic:self.playingMusic.filename]; } //开始播放音乐数据 -(void)starPlayingMusic { //1.设置界面数据 //取出当前正在播放的音乐 // YYMusicModel *playingMusic=[YYMusicTool playingMusic]; //如果当前播放的音乐就是传入的音乐,那么就直接返回 if (self.playingMusic==[YYMusicTool playingMusic]) return; //存取音乐 self.playingMusic=[YYMusicTool playingMusic]; self.iconView.image=[UIImage imageNamed:self.playingMusic.icon]; self.songLabel.text=self.playingMusic.name; self.singerLabel.text=self.playingMusic.singer; //2.开始播放 [YYAudioTool playMusic:self.playingMusic.filename]; } #pragma mark-内部的按钮监听方法 //返回按钮 - (IBAction)exit { //1.禁用整个app的点击事件 UIWindow *window=[UIApplication sharedApplication].keyWindow; window.userInteractionEnabled=NO; //2.动画隐藏View [UIView animateWithDuration:0.25 animations:^{ self.view.y=window.height; } completion:^(BOOL finished) { window.userInteractionEnabled=YES; //设置view隐藏能够节省一些性能 self.view.hidden=YES; }]; } @end
注意:先让用户看到界面上的所有东西后,再开始播放歌曲。
提示:一般的播放器需要做一个重置的操作。
当从一首歌切换到另外一首时,应该先把上一首的信息删除,因此在show动画显示之前,应该检测是否换了歌曲,如果换了歌曲,则应该做一次重置操作。
实现效果(能够顺利的切换和播放歌曲,下面是界面显示):
五、补充代码
YYMusicsViewController.m文件
// // YYMusicsViewController.m // #import "YYMusicsViewController.h" #import "YYMusicModel.h" #import "MJExtension.h" #import "YYMusicCell.h" #import "YYPlayingViewController.h" #import "YYMusicTool.h" @interface YYMusicsViewController () @property(nonatomic,strong)YYPlayingViewController *playingViewController; @end @implementation YYMusicsViewController #pragma mark-懒加载 -(YYPlayingViewController *)playingViewController { if (_playingViewController==nil) { _playingViewController=[[YYPlayingViewController alloc]init]; } return _playingViewController; } - (void)viewDidLoad { [super viewDidLoad]; } #pragma mark - Table view data source /** *一共多少组 */ -(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } /** *每组多少行 */ -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [YYMusicTool musics].count; } /** *每组每行的cell */ -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { YYMusicCell *cell=[YYMusicCell cellWithTableView:tableView]; cell.music=[YYMusicTool musics][indexPath.row]; return cell; } /** * 设置每个cell的高度 */ -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { return 70; } /** * cell的点击事件 */ -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { //1.取消选中被点击的这行 [tableView deselectRowAtIndexPath:indexPath animated:YES]; //2.设置正在播放的歌曲 [YYMusicTool setPlayingMusic:[YYMusicTool musics][indexPath.row]]; //调用公共方法 [self.playingViewController show]; // //执行segue跳转 // [self performSegueWithIdentifier:@"music2playing" sender:nil]; } @end