轻仿QQ音乐之音频歌词播放、锁屏歌词-b
先上效果图
歌词播放界面
音乐播放界面
锁屏歌词界面
一. 项目概述
前面内容实在是太基础。。只想看知识点的同学可以直接跳到第三部分的干货
项目播放的mp3文件及lrc文件均来自QQ音乐
本文主要主要讲解锁屏歌词的实现,音频、歌词的播放网上资源略多,因此不做重点讲解,项目也是采取最简单的MVC+storyboard方式
项目GitHub地址: https://github.com/PengfeiWang666/WPFMusicPlayer
音乐模型-->WPFMusic
/** 图片 */
@property (nonatomic,copy) NSString *image;
/** 歌词 */
@property (nonatomic,copy) NSString *lrc;
/** 歌曲 */
@property (nonatomic,copy) NSString *mp3;
/** 歌曲名 */
@property (nonatomic,copy) NSString *name;
/** 歌手 */
@property (nonatomic,copy) NSString *singer;
/** 专辑 */
@property (nonatomic,copy) NSString *album;
/** 类型 */
@property (nonatomic,assign) WPFMusicType type;
对应plist存储文件
音乐模型所对应的 plist 存储文件
歌词模型-->WPFLyric
/** 歌词开始时间 */
@property (nonatomic,assign) NSTimeInterval time;
/** 歌词内容 */
@property (nonatomic,copy) NSString *content;
歌词展示界面-->WPFLyricView
@property (nonatomic,weak) id <WPFLyricViewDelegate> delegate;
/** 歌词模型数组 */
@property (nonatomic,strong) NSArray *lyrics;
/** 每行歌词行高 */
@property (nonatomic,assign) NSInteger rowHeight;
/** 当前正在播放的歌词索引 */
@property (nonatomic,assign) NSInteger currentLyricIndex;
/** 歌曲播放进度 */
@property (nonatomic,assign) CGFloat lyricProgress;
/** 竖直滚动的view,即歌词View */
@property (nonatomic,weak) UIScrollView *vScrollerView;
#warning 以下为私有属性/* 水平滚动的大view,包含音乐播放界面及歌词界面 */
@property (nonatomic,weak) UIScrollView *hScrollerView;
/** 定位播放的View */
@property (nonatomic,weak) WPFSliderView *sliderView;
当前正在播放的歌词label-->WPFColorLabel
/** 歌词播放进度 */
@property (nonatomic,assign) CGFloat progress;
/** 歌词颜色 */
@property (nonatomic,strong) UIColor *currentColor;
播放管理对象-->WPFPlayManager
/** 单例分享 */
+ (instancetype)sharedPlayManager;
/**
* 播放音乐的方法 *
* @param fileName 音乐文件的名称
* @param complete 播放完毕后block回调
*/
- (void)playMusicWithFileName:(NSString *)fileName didComplete:(void(^)())complete;/** 音乐暂停 */- (void)pause;
歌词解析器-->WPFLyricParser (主要就是根据 .lrc 文件解析歌词的方法)
+ (NSArray *)parserLyricWithFileName:(NSString *)fileName { // 根据文件名称获取文件地址
NSString *path = [[NSBundle mainBundle] pathForResource:fileName ofType:nil]; // 根据文件地址获取转化后的总体的字符串
NSString *lyricStr = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL]; // 将歌词总体字符串按行拆分开,每句都作为一个数组元素存放到数组中
NSArray *lineStrs = [lyricStr componentsSeparatedByString:@"\n"]; // 设置歌词时间正则表达式格式
NSString *pattern = @"\\[[0-9]{2}:[0-9]{2}.[0-9]{2}\\]"; NSRegularExpression *reg = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:NULL]; // 创建可变数组存放歌词模型
NSMutableArray *lyrics = [NSMutableArray array]; // 遍历歌词字符串数组
for (NSString *lineStr in lineStrs) {
NSArray *results = [reg matchesInString:lineStr options:0 range:NSMakeRange(0, lineStr.length)]; // 歌词内容
NSTextCheckingResult *lastResult = [results lastObject]; NSString *content = [lineStr substringFromIndex:lastResult.range.location + lastResult.range.length]; // 每一个结果的range
for (NSTextCheckingResult *result in results) { NSString *time = [lineStr substringWithRange:result.range]; #warning 对于类似 NSDateFormatter 的重大开小对象,最好使用单例管理
NSDateFormatter *formatter = [NSDateFormatter sharedDateFormatter];
formatter.dateFormat = @"[mm:ss.SS]"; NSDate *timeDate = [formatter dateFromString:time]; NSDate *initDate = [formatter dateFromString:@"[00:00.00]"]; // 创建模型
WPFLyric *lyric = [[WPFLyric alloc] init];
lyric.content = content; // 歌词的开始时间
lyric.time = [timeDate timeIntervalSinceDate:initDate]; // 将歌词对象添加到模型数组汇总
[lyrics addObject:lyric];
}
} // 按照时间正序排序
NSSortDescriptor *sortDes = [NSSortDescriptor sortDescriptorWithKey:@"time" ascending:YES];
[lyrics sortUsingDescriptors:@[sortDes]];
return lyrics;
}
二. 主要知识点讲解
音频播放AppDelegate中操作
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // 注册后台播放
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryPlayback error:NULL]; // 开启远程事件 -->自动切歌
[application beginReceivingRemoteControlEvents];
return YES;
}
音频播放加载文件播放方式
NSURL *url = [[NSBundle mainBundle] URLForResource:fileName withExtension:nil];AVAudioPlayer *player = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:NULL];
在 ViewController 中点击事件
#warning 播放/暂停按钮点击事件
- (IBAction)play {
WPFPlayManager *playManager = [WPFPlayManager sharedPlayManager];
if (self.playBtn.selected == NO) {
[self startUpdateProgress];
WPFMusic *music = self.musics[self.currentMusicIndex];
[playManager playMusicWithFileName:music.mp3 didComplete:^{
[self next];
}];
self.playBtn.selected = YES;
}else{
self.playBtn.selected = NO;
[playManager pause];
[self stopUpdateProgress];
}
}
#warning 下一曲按钮点击事件
- (IBAction)next { // 循环播放
if (self.currentMusicIndex == self.musics.count -1) {
self.currentMusicIndex = 0;
}else{
self.currentMusicIndex ++;
}
[self changeMusic];
}
#warning changeMusic 方法// 重置音乐对象,各种基础赋值
- (void)changeMusic { // 防止切歌时歌词数组越界
self.currentLyricIndex = 0; // 切歌时销毁当前的定时器
[self stopUpdateProgress];
WPFPlayManager *pm = [WPFPlayManager sharedPlayManager];
WPFMusic *music = self.musics[self.currentMusicIndex]; // 歌词
// 解析歌词
self.lyrics = [WPFLyricParser parserLyricWithFileName:music.lrc]; // 给竖直歌词赋值
self.lyricView.lyrics = self.lyrics; // 专辑
self.albumLabel.text = music.album; // 歌手
self.singerLabel.text = [NSString stringWithFormat:@"— %@ —", music.singer]; // 图片
UIImage *image = [UIImage imageNamed:music.image];
self.vCenterImageView.image = image;
self.bgImageView.image = image;
self.hCennterImageView.image = image;
self.playBtn.selected = NO;
self.navigationItem.title = music.name;
[self play];
self.durationLabel.text = [WPFTimeTool stringWithTime:pm.duration];
}
三. 锁屏歌词详细讲解
更新锁屏界面的方法最好在一句歌词唱完之后的方法中调用(还是结合代码添加注释吧,干讲... 臣妾做不到啊)
- (void)updateLockScreen {
#warning 锁屏界面的一切信息都要通过这个原生的类来创建:MPNowPlayingInfoCenter
// 获取音乐播放信息中心
MPNowPlayingInfoCenter *nowPlayingInfoCenter = [MPNowPlayingInfoCenter defaultCenter]; // 创建可变字典存放信息
NSMutableDictionary *info = [NSMutableDictionary dictionary]; // 获取当前正在播放的音乐对象
WPFMusic *music = self.musics[self.currentMusicIndex];
WPFPlayManager *playManager = [WPFPlayManager sharedPlayManager]; // 专辑名称
info[MPMediaItemPropertyAlbumTitle] = music.album; // 歌手
info[MPMediaItemPropertyArtist] = music.singer; // 专辑图片
info[MPMediaItemPropertyArtwork] = [[MPMediaItemArtwork alloc] initWithImage:[self lyricImage]]; // 当前播放进度
info[MPNowPlayingInfoPropertyElapsedPlaybackTime] = @(playManager.currentTime); // 音乐总时间
info[MPMediaItemPropertyPlaybackDuration] = @(playManager.duration); // 音乐名称
info[MPMediaItemPropertyTitle] = music.name;
nowPlayingInfoCenter.nowPlayingInfo = info;
}
更新锁屏歌词的原理就是获取专辑图片后,将前后三句歌词渲染到图片上,使用富媒体将当前正在播放的歌词和前后的歌词区分开大小和颜色
- (UIImage *)lyricImage {
WPFMusic *music = self.musics[self.currentMusicIndex];
WPFLyric *lyric = self.lyrics[self.currentLyricIndex];
WPFLyric *lastLyric = [[WPFLyric alloc] init];
WPFLyric *nextLyric = [[WPFLyric alloc] init];
if (self.currentLyricIndex > 0) {
lastLyric = self.lyrics[self.currentLyricIndex - 1];
if (!lastLyric.content.length && self.currentLyricIndex > 1) {
lastLyric = self.lyrics[self.currentLyricIndex - 2]; } }
if (self.lyrics.count > self.currentLyricIndex + 1) {
nextLyric = self.lyrics[self.currentLyricIndex + 1]; // 筛选空的时间间隔歌词
if (!nextLyric.content.length && self.lyrics.count > self.currentLyricIndex + 2) {
nextLyric = self.lyrics[self.currentLyricIndex + 2];
}
}
UIImage *bgImage = [UIImage imageNamed:music.image]; // 创建ImageView
UIImageView *imgView = [[UIImageView alloc] initWithImage:bgImage];
imgView.bounds = CGRectMake(0, 0, 640, 640);
imgView.contentMode = UIViewContentModeScaleAspectFill; // 添加遮罩
UIView *cover = [[UIView alloc] initWithFrame:imgView.bounds];
cover.backgroundColor = [UIColor colorWithWhite:0 alpha:0.3];
[imgView addSubview:cover]; // 添加歌词
UILabel *lyricLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 480, 620, 150)];
lyricLabel.textAlignment = NSTextAlignmentCenter;
lyricLabel.numberOfLines = 3;
NSString *lyricString = [NSString stringWithFormat:@"%@ \n%@ \n %@", lastLyric.content, lyric.content, nextLyric.content];
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:lyricString attributes:@{
NSFontAttributeName : [UIFont systemFontOfSize:29],
NSForegroundColorAttributeName : [UIColor lightGrayColor] }];
[attributedString addAttributes:@{
NSFontAttributeName : [UIFont systemFontOfSize:34],
NSForegroundColorAttributeName : [UIColor whiteColor] } range:[lyricString rangeOfString:lyric.content]];
lyricLabel.attributedText = attributedString;
[imgView addSubview:lyricLabel]; // 开始画图
UIGraphicsBeginImageContext(imgView.frame.size);
CGContextRef context = UIGraphicsGetCurrentContext();
[imgView.layer renderInContext:context]; // 获取图片
UIImage *img = UIGraphicsGetImageFromCurrentImageContext(); // 结束上下文
UIGraphicsEndImageContext();
return img;
}
当然不是所有的时候都要去更新锁屏多媒体信息的,可以采用下面的方法进行监听优化:只在锁屏而且屏幕亮着的时候才会去设置,啥都不说了,都在代码里了
#warning 声明的全局变量及通知名称
static uint64_t isScreenBright;static uint64_t isLocked;
#define kSetLockScreenLrcNoti @"kSetLockScreenLrcNoti"
#warning 在 viewDidLoad 方法中监听 // 监听锁屏状态 static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), NULL, updateEnabled, CFSTR("com.apple.iokit.hid.displayStatus"), NULL, CFNotificationSuspensionBehaviorDeliverImmediately); CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), NULL, lockState, CFSTR("com.apple.springboard.lockstate"), NULL, CFNotificationSuspensionBehaviorDeliverImmediately);
});
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateLockScreen) name:kSetLockScreenLrcNoti object:nil];
上面两个监听对应的方法:
// 监听在锁定状态下,屏幕是黑暗状态还是明亮状态
static void updateEnabled(CFNotificationCenterRef center, void* observer, CFStringRef name, const void* object, CFDictionaryRef userInfo) { // uint64_t state;
int token;
notify_register_check("com.apple.iokit.hid.displayStatus", &token);
notify_get_state(token, &isScreenBright);
notify_cancel(token);
[ViewController checkoutIfSetLrc]; // NSLog(@"锁屏状态:%llu",isScreenBright);}// 监听屏幕是否被锁定static void lockState(CFNotificationCenterRef center, void* observer, CFStringRef name, const void* object, CFDictionaryRef userInfo) { uint64_t state; int token; notify_register_check("com.apple.springboard.lockstate", &token); notify_get_state(token, &state); notify_cancel(token);
isLocked = state;
[ViewController checkoutIfSetLrc]; // NSLog(@"lockState状态:%llu",state);}#warning 这个方法不太好,有好想法的可在评论区讨论+ (void)checkoutIfSetLrc { // 如果当前屏幕被锁定 && 屏幕处于 active 状态,就发送通知调用对象方法
if (isLocked && isScreenBright) {
[[NSNotificationCenter defaultCenter] postNotificationName:kSetLockScreenLrcNoti object:nil];
}
}
最后再附一下GitHub地址:https://github.com/PengfeiWang666/WPFMusicPlayer
雾化:
- (UIImage *)blur:(CGFloat)radius
{
CIContext *context = [CIContext contextWithOptions:nil];
CIImage *inputImage = [[CIImage alloc] initWithImage:self];
// create blur filter
CIFilter *filter = [CIFilter filterWithName:@"CIGaussianBlur"];
[filter setValue:inputImage forKey:kCIInputImageKey];
[filter setValue:[NSNumber numberWithFloat:radius] forKey:@"inputRadius"];
// blur image
CIImage *result = [filter valueForKey:kCIOutputImageKey];
float insert = 60;
CGRect extent = CGRectInset(filter.outputImage.extent, insert, insert);
CGImageRef cgImage = [context createCGImage:result fromRect:extent];
UIImage *image = [UIImage imageWithCGImage:cgImage];
CGImageRelease(cgImage);
return image;
}
- (UIImage *)blur
{
return [self blur:9.0f];
}
-(void)asyncApplyBlur:(UIImageAsyncBlurBlock)block
{
__weak typeof(self) wself = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIImage *blueImage = [wself blur
];
dispatch_async(dispatch_get_main_queue(), ^{
block(blueImage);
});
// block([wself blur]);
});
}
轻仿QQ音乐之音频歌词播放、锁屏歌词-b的更多相关文章
- 基于jQuery仿QQ音乐播放器网页版代码
基于jQuery仿QQ音乐播放器网页版代码是一款黑色样式风格的网页QQ音乐播放器样式代码.效果图如下: 在线预览 源码下载 实现的代码. html代码: <div class="m ...
- iOS开发手记-仿QQ音乐播放器动态歌词的实现
最近朋友想做个音乐App,让我帮忙参考下.其中歌词动态滚动的效果,正好我之前也没做过,顺便学习一下,先来个预览效果. 实现思路 歌词常见的就是lrc歌词了,我们这里也是通过解析lrc歌词文件来获取其播 ...
- Android自定义View,高仿QQ音乐歌词滚动控件!
最近在以QQ音乐为样板做一个手机音乐播放器,源码下篇博文放出.今天我想聊的是这个QQ音乐播放器中歌词显示控件的问题,和小伙伴们一起来探讨怎么实现这个歌词滚动的效果.OK,废话不多说,先来看看效果图: ...
- 一个开源音乐播放器,低仿QQ音乐!
有暇,弄了个音乐播放器,页面效果整体上参考了QQ音乐,相关API使用了易源数据提供的相关接口(https://www.showapi.com/api/lookPoint/213),在此表示感谢.先来看 ...
- QQ音乐vkey获取,更新播放url
QQ音乐接口播放经常换, 最开始 url: `http://ws.stream.qqmusic.qq.com/${musicData.songid}.m4a?fromtag=46` 然后 url:`h ...
- iOS音频的后台播放 锁屏
初始化AudioSession和基本配置 音频播放器采用的AVPlayer ,在程序启动的时候需要配置AudioSession,AudioSession负责应用音频的设置,比如支不支持后台,打断等等, ...
- wpf 仿QQ音乐歌词卡拉OK
最近用WPF做了个音乐播放器,读取歌词.歌词同步都已经实现了.卡拉OK逐字变色 也实现了,但是逐字变色时不能根据歌手唱的快慢来逐字显示.请问各位大神,这个如何解决,有何思路?(附上我做的界面) 感谢各 ...
- 基于React的仿QQ音乐(移动端)
前言 由于这段时间工作上也是挺忙的,就没有时间去写这个项目,中间一直都是写写停停,进度也是非常慢的.正好前几天都还比较空,就赶紧抓着空闲时间去写这个项目,最后紧赶慢赶地完成了.本项目采用了React的 ...
- wpf仿qq边缘自动停靠,支持多屏
wpf完全模仿qq边缘自动隐藏功能,采用鼠标钩子获取鼠标当前状态,在通过当前鼠标的位置和点击状态来计算是否需要隐藏. 以下是实现的具体方法: 一.鼠标钩子实时获取当前鼠标的位置和点击状态 /// &l ...
随机推荐
- Bootstrap之导航栏(2015年-05年-20日)
<nav class="navbar navbar-default" style="border-color: transparent;">< ...
- Unity3D鼠标点击物体产生事件
如果需要处理鼠标点击物体的情况, 可以当数据接触物体时,鼠标手势改变,然后点击后和NPC产生对话等: using UnityEngine; using System.Collections; publ ...
- ASP.NET中在不同的子域中共享Session
天遇到了这个问题,于是研究了一下.要解决这个问题,首先就要明白一些Session的机理.Session在服务器是以散列表形式存在的,我们都知道Session是会话级的,每个用户访问都会生成一个Sess ...
- form提交数据中文乱码问题总结
一:form在前台以post方式提交数据: 浏览器将数据(假设为“中国”)发送给服务器的时候,将数据变成0101的二进制数据(假设为98 99)时必然要查码表,浏览器以哪个码表打开网页,浏览器就以哪个 ...
- sql常识-BETWEEN 操作符
BETWEEN 操作符 操作符 BETWEEN ... AND 会选取介于两个值之间的数据范围.这些值可以是数值.文本或者日期. SQL BETWEEN 语法 SELECT column_name(s ...
- asp.net php asp jsp 301重定向的代码
介绍一下针对各类程序系统实施301重定向的代码: 1.Linux主机重定向 Godaddy的Liunx主机,Godaddy本身已经支持Apache,所以直接创建一个.htaccess文件就可以了,一般 ...
- JavaScript之表格修改
讲到表格,我们不免都了解它的属性及用途. colspan跨列(纵向的)和rowspan跨行(横向的). 表格中<tr></tr>标签标示行标签:<td></t ...
- OC9_文件操作
// // main.m // OC9_文件操作 // // Created by zhangxueming on 15/6/19. // Copyright (c) 2015年 zhangxuemi ...
- MVC 生成PDf表格并插入图片
最近做的项目中有一个功能,将最终的个人信息生成PDF表格,并插入图片.对于没接触过的程序员来说回一片茫然,网上有多种生成PDf的方法,我给大家介绍一下我认为比较简单,好操作的一种. iTextShar ...
- 双网卡route配置
目前仅适用于windows: 192.168.*.*网段适用于上外网的 10网段适用于内网 route add 10.0.0.0 mask 255.0.0.0 10.34.6.1route add 1 ...