轻仿QQ音乐之音频歌词播放、锁屏歌词-b

时间:2022-08-29 19:26:33

先上效果图

轻仿QQ音乐之音频歌词播放、锁屏歌词-b

歌词播放界面

轻仿QQ音乐之音频歌词播放、锁屏歌词-b

音乐播放界面

轻仿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存储文件

轻仿QQ音乐之音频歌词播放、锁屏歌词-b

音乐模型所对应的 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的更多相关文章

  1. 基于jQuery仿QQ音乐播放器网页版代码

    基于jQuery仿QQ音乐播放器网页版代码是一款黑色样式风格的网页QQ音乐播放器样式代码.效果图如下: 在线预览   源码下载 实现的代码. html代码: <div class="m ...

  2. iOS开发手记-仿QQ音乐播放器动态歌词的实现

    最近朋友想做个音乐App,让我帮忙参考下.其中歌词动态滚动的效果,正好我之前也没做过,顺便学习一下,先来个预览效果. 实现思路 歌词常见的就是lrc歌词了,我们这里也是通过解析lrc歌词文件来获取其播 ...

  3. Android自定义View,高仿QQ音乐歌词滚动控件!

    最近在以QQ音乐为样板做一个手机音乐播放器,源码下篇博文放出.今天我想聊的是这个QQ音乐播放器中歌词显示控件的问题,和小伙伴们一起来探讨怎么实现这个歌词滚动的效果.OK,废话不多说,先来看看效果图: ...

  4. 一个开源音乐播放器,低仿QQ音乐!

    有暇,弄了个音乐播放器,页面效果整体上参考了QQ音乐,相关API使用了易源数据提供的相关接口(https://www.showapi.com/api/lookPoint/213),在此表示感谢.先来看 ...

  5. QQ音乐vkey获取&comma;更新播放url

    QQ音乐接口播放经常换, 最开始 url: `http://ws.stream.qqmusic.qq.com/${musicData.songid}.m4a?fromtag=46` 然后 url:`h ...

  6. iOS音频的后台播放 锁屏

    初始化AudioSession和基本配置 音频播放器采用的AVPlayer ,在程序启动的时候需要配置AudioSession,AudioSession负责应用音频的设置,比如支不支持后台,打断等等, ...

  7. wpf 仿QQ音乐歌词卡拉OK

    最近用WPF做了个音乐播放器,读取歌词.歌词同步都已经实现了.卡拉OK逐字变色 也实现了,但是逐字变色时不能根据歌手唱的快慢来逐字显示.请问各位大神,这个如何解决,有何思路?(附上我做的界面) 感谢各 ...

  8. wpf仿qq边缘自动停靠,支持多屏

    wpf完全模仿qq边缘自动隐藏功能,采用鼠标钩子获取鼠标当前状态,在通过当前鼠标的位置和点击状态来计算是否需要隐藏. 以下是实现的具体方法: 一.鼠标钩子实时获取当前鼠标的位置和点击状态 /// &l ...

  9. iOS 音乐播放器之锁屏效果&plus;歌词解析

    概述 功能描述:锁屏歌曲信息.控制台远程控制音乐播放:暂停/播放.上一首/下一首.快进/快退.列表菜单弹框和拖拽控制台的进度条调节进度(结合了QQ音乐和网易云音乐在锁屏状态下的效果).歌词解析并随音乐 ...

随机推荐

  1. CSS3中&colon;nth-child和&colon;nth-of-type的区别深入理解。 关于&colon;nth-child和&colon;nth-of-type的区别之前一直没太注意,经深入理解才发现里面其实暗藏玄机

    关于:nth-child和:nth-of-type的区别之前一直没太注意.最近打算深入了解一些CSS3,才发现里面其实暗藏玄机. :nth-child可以选择父元素下的字元素,:nth-of-type ...

  2. dotnetGen 系列终于开源了

    https://github.com/2881099/dotnetGen .Net 3.0 + SqlServer 生成器 https://github.com/2881099/dotnetGen_s ...

  3. ASP&period;NET十分有用的页面间传值方法

    一.目前在ASP.NET中页面传值共有这么几种方式: 1.表单提交,   <form action= "target.aspx" method = "post&qu ...

  4. parquet code demo

    http://www.programcreek.com/java-api-examples/index.php?source_dir=hiped2-master/src/main/java/hip/c ...

  5. FZU2179(数位dp)

    传送门:Chriswho 题意:求区间[1,n]内能整除自己本身各位数字的数的个数. 分析:这题跟CF 55D Beautiful numbers一样的,一个数能被它的所有非零数位整除,则能被它们的最 ...

  6. Scrapy 通过登录的方式爬取豆瓣影评数据

    Scrapy 通过登录的方式爬取豆瓣影评数据 爬虫 Scrapy 豆瓣 Fly 由于需要爬取影评数据在来做分析,就选择了豆瓣影评来抓取数据,工具使用的是Scrapy工具来实现.scrapy工具使用起来 ...

  7. jenkins gradle 编译遇到tomcat异常

    使用gradle在jenkins 上构建的时候遇到 进行构建的时候报"Could not load Logmanager org.apache.juli.ClassLoaderLogMana ...

  8. Java8新特性之四:接口默认方法和静态方法

    在JDK1.8以前,接口(interface)没有提供任何具体的实现,在<JAVA编程思想>中是这样描述的:"interface这个关键字产生了一个完全抽象的类,它根本就没有提供 ...

  9. 如何给PDF文件制作书签

    书本阅读的时候我们有时候会制作一些漂亮的书签,那么电子文档也是有书签的,要怎么制作小伙伴们都知道吗?应该会有许多的小伙伴还不知道,今天就为大家分享一下电子文件如何添加书签的.就以PDF这个现在常用的电 ...

  10. 23 &comma; CSS 构造列表与导航

    1. 列表图片 2. 背景列表 3. 翻转列表 4. 水平导航 1. 内边距与外边距 Ul { Margin: 0; Padding: 0; } 2. 使用图片作为列表图标 Ul { Margin: ...