QQMusic: 仿QQ音乐 |
您所在的位置:网站首页 › qq音乐界面视频 › QQMusic: 仿QQ音乐 |
QQ音乐
作者: Liwx
邮箱: [email protected]
目录 02.音视频播放 QQ音乐界面搭建 (自己整理) QQ音乐界面搭建 1.storyboard布局QQ音乐界面 QQ音乐主界面整体框图 界面控件设置 2.实现音乐的播放 封装WXAudioTool播放音效和播放音乐工具类 播放音效/音乐工具类实现步骤 封装WXMusicTool获取音乐列表工具类 在主控制器实现播放音乐 3.添加播放进度定时器,更新播放进度 实现更新播放进度功能和歌手图标旋转功能 实时更新播放进度信息 歌手图标旋转动画 4.处理滚动条 自定义滚动歌词的WXLrcScrollView,继承UIScrollView 5.播放/暂停,上一首,下一首功能实现,音乐播放完毕自动切换到下一首 播放/暂停,上一首,下一首功能实现 音乐播放完毕自动切换到下一首 6.设置进度条UISlider的处理 监听进度条的事件 进度条添加Tap敲击手势 7.歌词的解析 歌词模型类功能的实现 创建歌词文件处理WXLrcTool工具类 8.实现歌词ScrollView歌词的滚动功能 主界面控制器创建更新歌词的定时器CADisplayLink定时器 自定义显示歌词的WXLrcLabel 实现歌词的滚动,当前行歌词进度颜色填充效果 10.设置主界面的歌词 在主界面中设置主界面歌词 由lrcScrollView内部为主界面歌词内容和进度进行更新 11.实现锁屏界面信息展示和操作 锁屏界面项目配置 12.实现锁屏锁屏歌词展示 绘制锁屏封面和歌词 锁屏界面实现播放,暂停,上一首,下一首功能 QQ音乐界面搭建 QQ音乐运行效果(模拟器不能演示锁屏界面的功能,所以展示效果图没有锁屏界面功能展示)2.WXAudioTool工具类实现播放等API接口方法 实现播放音乐,暂停音乐,停止音乐和播放音效API接口方法播放音乐: + (AVAudioPlayer *)playMusicWithMusicName:(NSString *)musicName; 暂停音乐: + (void)pauseMusicWithMusicName:(NSString *)musicName; 停止音乐: + (void)stopMusicWithMusicName:(NSString *)musicName; 播放音效: + (void)playSoundWithSoundName:(NSString *)soundName; 3.WXAudioTool工具类实现代码 #import "WXAudioTool.h" #import @implementation WXAudioTool // SINGLE: 创建一个可变字典缓存音乐,字典只需创建一次,可以在initialize类方法中创建 static NSMutableDictionary *_soundIDs; // SINGLE: 创建音乐内存缓存,在initialize类方法中创建 static NSMutableDictionary *_players; #pragma mark - 初始化设置 /** 创建内存缓存 */ + (void)initialize { // 创建可变字典,用于存放播放音效SystemSoundID _soundIDs = [NSMutableDictionary dictionary]; // 创建可变字典,用于存放音乐播放器AVAudioPlayer _players = [NSMutableDictionary dictionary]; } #pragma mark - 播放音乐API(AVAudioPlayer) /** 播放音乐 */ + (AVAudioPlayer *)playMusicWithMusicName:(NSString *)musicName { // 1.先从内存字典中获取播放器AVAudioPlayer AVAudioPlayer *player = _players[musicName]; // 2.判断是否从内存获取到播放器,如果没有获取到,新建播放器 if (player == nil) { // 2.1 获取音乐文件的url NSURL *url = [[NSBundle mainBundle] URLForResource:musicName withExtension:nil]; if (url == nil) return nil; // 2.2 根据音频文件的url,创建播放器 player = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil]; // 2.3 保存到内存缓存_players [_players setObject:player forKey:musicName]; } // SINGLE: 3.播放音乐 [player play]; return player; } /** 暂停音乐 */ + (void)pauseMusicWithMusicName:(NSString *)musicName { // 1.从内存字典中取出播放器 AVAudioPlayer *player = _players[musicName]; // 2.如果内存中有获取到播放器,暂停播放 if (player) { // SINGLE: 暂停播放 [player pause]; } } /** 停止音乐 */ + (void)stopMusicWithMusicName:(NSString *)musicName { // 1.从内存字典中取出播放器 AVAudioPlayer *player = _players[musicName]; // 2.如果内存中有获取到播放器,停止播放 if (player) { // SINGLE: 2.1 停止播放 [player stop]; // 2.2 从内存字典中移除 [_players removeObjectForKey:musicName]; player = nil; } } #pragma mark - 播放短音效API(SystemSoundID) // REMARKS: 播放音效类方法 /** 播放音效 */ + (void)playSoundWithSoundName:(NSString *)soundName { // 1.先从内存缓存获取soundID SystemSoundID soundID = [_soundIDs[soundName] unsignedIntValue]; // 2.判断内存是否存在音效资源,内存没有音效资源,则创建 if (soundID == 0) { // 2.1 若不存在, 创建音效资源url CFURLRef url = (__bridge CFURLRef)[[NSBundle mainBundle] URLForResource:soundName withExtension:nil]; // 2.2 判断url是否为空,如果为空,说明资源不存在,直接退出 if (url == nil) return; // 2.3 生成SystemSoundID AudioServicesCreateSystemSoundID(url, &soundID); // 2.4 存入可变字典内存缓存 [_soundIDs setObject:@(soundID) forKey:soundName]; } // 3.播放音效 AudioServicesPlaySystemSound(soundID); } @end 封装WXMusicTool获取音乐列表工具类 1.创建WXMusicItem音乐模型 @interface WXMusicItem : NSObject /** 音乐名 */ @property (nonatomic ,copy)NSString *name; /** 音乐文件名 */ @property (nonatomic ,copy)NSString *filename; /** 歌词文件名 */ @property (nonatomic ,copy)NSString *lrcname; /** 歌手名 */ @property (nonatomic ,copy)NSString *singer; /** 歌手小图标 */ @property (nonatomic ,copy)NSString *singerIcon; /** 歌手大图标 */ @property (nonatomic ,copy)NSString *icon; @end2.WXMusicTool工具类实现API接口方法 实现播放音乐,暂停音乐,停止音乐和播放音效API接口方法 获取所有的音乐: + (NSArray *)musics; 获取正在播放的音乐: + (WXMusicItem *)playingMusic; 设置播放的音乐: + (void)setupMusic:(WXMusicItem *)music; 获取上一首音乐: + (WXMusicItem *)previous; 获取下一首音乐: + (WXMusicItem *)next;3.WXMusicTool工具类实现代码 #import "WXMusicTool.h" #import "WXMusicItem.h" #import @implementation WXMusicTool #pragma mark - 静态变量 /** 所有音乐 */ static NSArray *_musicItems; /** 当前播放的音乐 */ static WXMusicItem *_playingMusicItem; /** 获取所有音乐模型,设置当前默认音乐 */ + (void)initialize { // 从plist文件中获取所有音乐模型 _musicItems = [WXMusicItem mj_objectArrayWithFilename:@"Musics.plist"]; // 设置当前默认音乐 _playingMusicItem = _musicItems[4]; } #pragma mark - 播放音乐操作 /** 获取所有的音乐 */ + (NSArray *)musics { return _musicItems; } /** 获取正在播放的音乐(默认) */ + (WXMusicItem *)playingMusic { return _playingMusicItem; } /** 设置播放的音乐 */ + (void)setupMusic:(WXMusicItem *)music { _playingMusicItem = music; } /** 获取上一首音乐 */ + (WXMusicItem *)previous { // 1.获取当前音乐的下标值 NSInteger currentIndex = [_musicItems indexOfObject:_playingMusicItem]; // 2.获取上一首音乐的下标值,判断是否越界 NSInteger previousIndex = currentIndex - 1; if (previousIndex = _musicItems.count) { nextIndex = 0; } // 3.获取下一首的音乐 WXMusicItem *nextMusicItem = _musicItems[nextIndex]; // 4.返回下一首音乐 return nextMusicItem; } @end 在主控制器实现播放音乐 播放音乐实现 #pragma mark - 播放音乐 - (void)playingMusic { // 1.获取当前音乐 WXMusicItem *playerMusicItem = [WXMusicTool playingMusic]; // 2.更新子控件信息 self.albumView.image = [UIImage imageNamed:playerMusicItem.icon]; self.iconView.image = [UIImage imageNamed:playerMusicItem.icon]; self.songLabel.text = playerMusicItem.name; self.singerLabel.text = playerMusicItem.singer; // 3.开始播放音乐 AVAudioPlayer *currentPlayer = [WXAudioTool playMusicWithMusicName:playerMusicItem.filename]; // 3.0 设置代理,用来监听音乐播放完毕,实现自动切换到下一首的功能 currentPlayer.delegate = self; self.currentPlayer = currentPlayer; // 3.1 设置当前播放时间和音乐总时长 self.currentLabel.text = [NSString stringWithTime:currentPlayer.currentTime]; self.totalLabel.text = [NSString stringWithTime:currentPlayer.duration]; // 3.2 更新当前播放按钮的状态 self.playOrPauseBtn.selected = self.currentPlayer.isPlaying; // 3.3 设置当前播放的音乐的歌词 self.lrcScrollView.lrcFileName = playerMusicItem.lrcname; // 3.4 将当前播放的音乐的总时长传给lrcScrollView,用于做锁屏界面的总时长 self.lrcScrollView.duration = currentPlayer.duration; // 4.添加旋转动画 [self addIconViewAnimate]; // 5.添加定时器(需先移除定时器在添加,避免当前定时器还在运行,又开启新定时器) [self removeProgressTimer]; [self addProgressTimer]; // 6.添加更新歌词定时器 [self removeLrcTimer]; [self addLrcTimer]; // 7.设置默认当前音乐播放时间,总时长和进度条 [self updateProgressInfo]; } 3.添加播放进度定时器,更新播放进度1.添加定时器步骤,需先移除当前定时器,再添加定时器. 2.将定时器添加到NSRunLoop,并设置NSRunLoopCommonModes模式. 3.歌手图标旋转动画实现,使用基础核心动画CABasicAnimation,设置绕z轴旋转360°无限循环等动画属性配置. 实现更新播放进度功能和歌手图标旋转功能 播放进度定时器创建/移除方法 创建播放进度定时器: - (void)addProgressTimer; 移除播放进度定时器: - (void)removeProgressTimer; 实时更新播放进度信息 更新进度进度实现 - (void)updateProgressInfo { // 1.更新当前音乐播放时间和当前音乐总时长 self.currentLabel.text = [NSString stringWithTime:self.currentPlayer.currentTime]; self.totalLabel.text = [NSString stringWithTime:self.currentPlayer.duration]; // 2.更新进度条信息 self.progressSlider.value = self.currentPlayer.currentTime / self.currentPlayer.duration; } 歌手图标旋转动画 歌手图标旋转动画实现 - (void)addIconViewAnimate { // 1.创建核心动画,并设置期相关属性 CABasicAnimation *anim = [CABasicAnimation animation]; // 1.1 设置绕z轴旋转360°,无限循环等 anim.keyPath = @"transform.rotation.z"; anim.fromValue = @(0); anim.toValue = @(M_PI * 2); anim.repeatCount = CGFLOAT_MAX; anim.duration = 35; // SINGLE: 1.2 当进入后台,再进入前台时,核心动画会失效,需设置removedOnCompletion属性为NO,这样核心动画就不会失效 // removedOnCompletion: 设置为NO表示动画完成的时候不要移除. anim.removedOnCompletion = NO; // 2.添加动画到self.iconView.layer [self.iconView.layer addAnimation:anim forKey:nil]; } 4.处理滚动条 自定义滚动歌词的WXLrcScrollView,继承UIScrollView 1.在WXLrcScrollView初始化时,添加滚动歌词的tableView 在initWithCoder和initWithFrame方法中开启分页功能,初始化和添加tableView到WXLrcScrollView 设置子控件tableView的数据源和代理,实现数据源方法,为tableView提供显示的测试数据 在layoutSubviews中添加tableView的约束,并设置tableView背景颜色,分割线,内边距等,必须在layoutSubviews中设置,否则会tableView中的歌词播放不同步,背景为白色等问题. /** layoutSubviews中布局子控件tableView */ - (void)layoutSubviews { [super layoutSubviews]; // 1.布局tableView [self.tableView mas_makeConstraints:^(MASConstraintMaker *make) { make.top.equalTo(self.mas_top); make.bottom.equalTo(self.mas_bottom); make.height.equalTo(self.mas_height); make.left.equalTo(self.mas_left).offset(self.bounds.size.width); make.right.equalTo(self.mas_right); make.width.equalTo(self.mas_width); }]; // SINGLE: 2.清空tableView背景颜色,取消tableView的分割线,设置tableView的内边距 self.tableView.backgroundColor = [UIColor clearColor]; self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone; self.tableView.contentInset = UIEdgeInsetsMake(self.tableView.bounds.size.height * 0.5, 0, self.tableView.bounds.size.height * 0.5, 0); } 5.播放/暂停,上一首,下一首功能实现,音乐播放完毕自动切换到下一首在storyboard中设置播放按钮在Normal/Selected状态下的按钮显示的图片 播放/暂停,上一首,下一首功能实现播放/暂停功能实现 切换播放按钮的状态 判断是否正在播放,如果当前正在播放则暂停播放,反之则继续播放 暂停播放: 暂停播放,移除定时器,暂停动画(分类实现暂停和继续动画) 继续播放: 继续播放,开启定时器,继续动画上一首/下一首功能实现 使用WXMusicTool工具类获取上一首/下一首音乐 实现切换音乐的方法示例代码 /** 下一首 */ - (IBAction)nextMusic { // 1.获取下一首音乐 WXMusicItem *nextMusicItem = [WXMusicTool next]; // 2.播放下一首音乐 [self playMusic:nextMusicItem]; } /** 上一首 */ - (IBAction)previousMusic { // 1.获取上一首音乐 WXMusicItem *previousMusicItem = [WXMusicTool previous]; // 2.播放上一首音乐 [self playMusic:previousMusicItem]; } /** 切换播放的音乐 */ - (void)playMusic:(WXMusicItem *)music { // 1.获取当前音乐,并暂停播放 WXMusicItem *curMusicItem = [WXMusicTool playingMusic]; [WXAudioTool pauseMusicWithMusicName:curMusicItem.filename]; // 2.设置当前播放音乐music [WXMusicTool setupMusic:music]; // 3.开始播放音乐 [self playingMusic]; } 音乐播放完毕自动切换到下一首 主控制器的当前播放器遵守AVAudioPlayerDelegate代理协议 在播放器创建完成时设置代理,用来监听音乐播放完毕,实现自动切换到下一首的功能 self.currentPlayer.delegate = self; 实现代理方法audioPlayerDidFinishPlaying:successfully,当前音乐正常播放完成后调用. #pragma mark - 代理协议 // SINGLE: 当前音乐播放完成后调用,在代理协议中 - (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag { // flag == YES 表示音乐播放正常停止,播放完毕自动切换到下一首 if (flag) { // 音乐播放完毕自动切换下一首 [self nextMusic]; } } 6.设置进度条UISlider的处理 监听进度条的事件 监听进度条TouchDown事件,当UISlider监听到TouchDown事件时,移除进度条定时器 监听进度条TouchUpInside事件,当UISlider监听到TouchUpInside事件时,更新当前播放进度,并重新开启定时器 监听到进度条ValueChange事件,拖动UISlider时,使用UISlider的value属性和当前音乐总时长计算当前播放进度,并更新当前播放时间Label的文字信息. self.currentLabel.text = [NSString stringWithTime:self.progressSlider.value * self.currentPlayer.duration]; 进度条添加Tap敲击手势 在Main.storyboard给UISlider进度条添加点击Tap手势滚动tableView的方法, scrollPosition: UITableViewScrollPositionTop表示tableView滚动到顶部 *- (void)scrollToRowAtIndexPath:(NSIndexPath )indexPath atScrollPosition:(UITableViewScrollPosition)scrollPosition animated:(BOOL)animated; 计算当前行歌词的进度 获取当前行歌词进度 当前行歌词进度 = (当前播放的时间 - 当前行歌词的开始时间) / (下一行歌词的开始时间 - 当前行歌词的开始时间) 因该方法每秒执行60次,考虑到内部刷新列表操作和性能问题,判断如果当前行是正在播放的歌词,就无需刷新,通过self.currentIndex != i,如果不等于i,才进入刷新列表 设置当前行歌词后,要刷新当前行和上一行歌词的cell /** 重写当前播放时间set方法,该方法每秒会调用60次,因为外部用CADisplayLink定时器刷新歌词进度 */ - (void)setCurrentTime:(NSTimeInterval)currentTime { // 1.保存当前播放时间 _currentTime = currentTime; // 2.获取歌词的总数 NSInteger count = self.lrcList.count; // 3.遍历歌词数组 for (NSInteger i = 0; i Background modes ->Audio![]() 创建后台播放音视频的会话,并激活会话 // 1.创建会话 AVAudioSession *session = [AVAudioSession sharedInstance]; // 2.设置类别为后台播放 AVAudioSessionCategoryPlayback: 类别为后台播放,该常量字符串在AVAudioSession.h中 [session setCategory:AVAudioSessionCategoryPlayback error:nil]; // 3.激活会话 [session setActive:YES error:nil]; - 在AppDelegate.m的didFinishLaunchingWithOptions方法中**创建后台播放音视频的会话,并激活会话** ```objectivec // REMARKS: 项目配置后台可播放音视频 // SINGLE: 配置后台可播放音视频 工程文件->Capabilities -> Background modes ->Audio // CARE: 模拟器上运行时,音乐可后台运行,但是真机运行默认是不能后台播放音视频的,必须在项目中配置以上操作(后台可播放音视频),需创建会话,设置会话类别为后台播放,并激活会话. // 为确保程序运行时会执行到以下设置音视频会话(后台播放会话)代码,所以放在didFinishLaunchingWithOptions方法中执行 // 1.创建会话 AVAudioSession *session = [AVAudioSession sharedInstance]; // 2.设置类别为后台播放 AVAudioSessionCategoryPlayback: 类别为后台播放,该常量字符串在AVAudioSession.h中 [session setCategory:AVAudioSessionCategoryPlayback error:nil]; // 3.激活会话 [session setActive:YES error:nil]; return YES; 设置锁屏界面显示的内容步骤 获取锁屏中心 MPNowPlayingInfoCenter *playingInfoCenter = [MPNowPlayingInfoCenter defaultCenter]; 设置锁屏中心要展示的信息,通过设置锁屏中心nowPlayingInfo属性设置,该属性是字典 将设置的字典信息赋给nowPlayingInfo属性 playingInfoCenter.nowPlayingInfo = playingInfoDict; 让应用程序开启远程事件 [[UIApplication sharedApplication] beginReceivingRemoteControlEvents]; // REMARKS: 设置锁屏界面 /** 设置锁屏界面 */ - (void)setupLockScreenInfoWithLockImage:(UIImage *)lockImage { /* // 媒体常量 MPMediaItemPropertyAlbumTitle // 媒体音乐的标题(或名称) MPMediaItemPropertyAlbumTrackCount MPMediaItemPropertyAlbumTrackNumber MPMediaItemPropertyArtist // 作者 MPMediaItemPropertyArtwork // 封面 MPMediaItemPropertyComposer // 音乐剧作曲家的媒体项目 MPMediaItemPropertyDiscCount // 光盘在包含媒体项目的专辑的数目 MPMediaItemPropertyDiscNumber MPMediaItemPropertyGenre MPMediaItemPropertyPersistentID MPMediaItemPropertyPlaybackDuration // 媒体项目的播放持续时间(当前播放时间) MPMediaItemPropertyTitle // 显示在作者和标题上面 */ // REMARKS: 设置锁屏界面,MPNowPlayingInfoCenter锁屏中心类在MediaPlayer框架中,所以需导入MediaPlayer/MediaPlayer.h头文件 // 1.获取当前正在播放的音乐 WXMusicItem *playingMusicItem = [WXMusicTool playingMusic]; // 2.获取锁屏中心 MPNowPlayingInfoCenter *playingInfoCenter = [MPNowPlayingInfoCenter defaultCenter]; // 3.设置锁屏中心要展示的信息,通过设置锁屏中心nowPlayingInfo属性设置,该属性是字典 // 创建要可变字典,用来存放要显示在锁屏中心的信息 NSMutableDictionary *playingInfoDict = [NSMutableDictionary dictionary]; // 3.1 设置展示的音乐名称 [playingInfoDict setObject:playingMusicItem.name forKey:MPMediaItemPropertyAlbumTitle]; // 3.2 设置展示的歌手名 [playingInfoDict setObject:playingMusicItem.singer forKey:MPMediaItemPropertyArtist]; // 3.3 设置展示封面 MPMediaItemArtwork *artWork = [[MPMediaItemArtwork alloc] initWithImage:lockImage]; [playingInfoDict setObject:artWork forKey:MPMediaItemPropertyArtwork]; // 3.4 设置音乐播放的总时间 [playingInfoDict setObject:@(self.duration) forKey:MPMediaItemPropertyPlaybackDuration]; // 3.5 设置音乐当前播放的时间 [playingInfoDict setObject:@(self.currentTime) forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime]; // 3.6 将设置的字典信息赋给nowPlayingInfo属性 playingInfoCenter.nowPlayingInfo = playingInfoDict; // SINGLE: 4.让应用程序开启远程事件 [[UIApplication sharedApplication] beginReceivingRemoteControlEvents]; } 12.实现锁屏锁屏歌词展示 绘制锁屏封面和歌词 将锁屏的封面图片和歌词重新绘制,生成新图片,在显示到显示歌手图标的UIImageView上 #pragma mark - 设置锁屏界面和锁屏歌词 /** 绘制锁屏封面和歌词 */ - (void)setupLockImage { // 1.获取当前音乐的模型 WXMusicItem *currentMusicItem = [WXMusicTool playingMusic]; // 2.从当前音乐模型取出封面图片 UIImage *currentImage = [UIImage imageNamed:currentMusicItem.icon]; // 3.获取当前,上一行,下一行歌词 // 3.1 获取当前行歌词 WXLrcLineItem *currentLrcLine = self.lrcList[self.currentIndex]; // 3.2 获取上一行歌词 NSInteger previousIndex = self.currentIndex - 1; WXLrcLineItem *previousLrcLine = nil; if (previousIndex >= 0) { previousLrcLine = self.lrcList[previousIndex]; } // 3.3 获取下一行歌词 NSInteger nextIndex = self.currentIndex + 1; WXLrcLineItem *nextLrcLine = nil; if (nextIndex |
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |