qg777钱柜误乐

热门关键词: qg777钱柜误乐

【操作系统】iOS-QQ音乐播放器的简约完结

操作系统 1

生机勃勃. QQ音乐播放器的简易达成

每一个音乐播放器的得以完结都差十分少雷同,个人认为难题在于歌曲播放与Slider的一块,歌词的深入分析与广播的联手。这一个进度即便麻烦,然而知道起来并轻易。先来探视轻易达成结果吗。

QQ音乐播放器轻便实现

尽管功能轻易,但是依旧费用了自家不短日子来收十个中的逻辑关系,接下去我们就来分析一下音乐播放器的简约完毕。

其豆蔻年华Demo,关于歌曲播放的主要功能都落成了的。下意气风发曲、上风流倜傥曲,暂停,依据歌曲的播报进程动态滚动歌词,将前段时间正在播放的乐章放大展现,拖动进度条,歌曲跟着变动,并且应用Time Profiler进行了优化,还选用XCTest对多少个第风流罗曼蒂克的类进行了单元测量试验。

后日看来有朋友在论坛上发帖问JavaScript怎么样播放MP4歌词同步展现兴趣使然,清晨捣鼓了下.今后办好了享受给大家.原理十分轻巧,正是经过播放器的时光跟歌词里面包车型地铁年华开展比较,尽管同样就展现歌词.然后便是如何寄放歌词了.作者的思路就是把每句话push到数组里面去,然后经过数组索引来获取歌词播放器组件小编用mediaplayer,由此网页只好用IE浏览器打开上边是效果与利益图:画面有一点点搓,只好一句句呈现,还不曾精美到字扫描.近些日子还未怎么思路...然而将就下啦.下载地址

二. 主分界面包车型客车搭建

以此播放器比较轻松,独有三个分界面,创造CLPlayingViewController,使用stortyboard构造,先来看一下结构

播放器页面结构

界面共分为四有的,此中必要在意的是个中歌唱家图片限制的拉长,为了保障其在区别的显示器上都为圆形,这里先将1、3、4有的搭架子限制增多好,然后设置歌星图片间距上边第二盘部和底下第3片段歌词分别有八个相差何况居中展示,然后设置图片长度宽度比为1:1就可以,别的一些的牢笼比较轻易,这里不再赘言,增添节制照旧必要多练手艺垄断(monopoly卡塔尔国。歌唱家图片的自律如下。

明星图片Image的牢笼

背景图片的毛玻璃效果
加多毛玻璃效果的方法有众各样,要是本来就有一张模糊管理过的美景图片那就最好可是了,若无的话只可以协调丰裕,能够使用第三方框架 (D揽胜极光NRealTimeBlur卡塔尔国可能本身通过coreImage完成高斯模糊。以上二种格局成效强盛可是正如费心,我们这边只是简单的达成图片模糊,能够运用给UIImageView加多UIToolbar来落到实处

// 1.初始化toolBar
UIToolbar *toolBar = [[UIToolbar alloc] init];
// 2. 设置frame
toolBar.frame = [UIScreen mainScreen].bounds;
// 3. 设置模糊的样式
toolBar.barStyle = UIBarStyleBlack;
// 4. 添加到imageView
[self.albumView addSubview:toolBar];

而iOS8随后storyboard中现身了特意给图片增添模糊效果的控件。

Blur

作者们只需将blur增添到imageView下边然后设置blur的体制就可以,

blur的样式

亟需当心的是:blur须求增加到背景imageView下边和别的View之间,防止模糊效果影响到明星图片,播放开关等此外控件。

歌星图片打开圆角管理
此间一向改变imageView的layer进行圆角管理

- (void)viewWillLayoutSubviews
{
    [super viewWillLayoutSubviews];
    // .添加圆角
    self.iconView.layer.cornerRadius = self.iconView.bounds.size.width * 0.5;
    self.iconView.layer.masksToBounds = YES;
    // 设置边框颜色宽度
    self.iconView.layer.borderColor = CLColor(36, 36, 36, 1.0).CGColor;
    self.iconView.layer.borderWidth = 5;
}

此处须求静心的是即便大家在storyboard中为歌星图片增多约束,不过当运营到模拟器上时,荧屏大小和storyboard中显示器尺寸或然会不一样,假若在viewDidLoad中装置圆角,那时获得的演唱者图片的高低大概storyboard中的大小,所以显得在模拟器上就能使圆形计算错误,因而大家在viewWillLayoutSubviews方法中增添圆角设置。

演唱者图片的团团转动漫效果
图表转动用到宗旨动画利用CABasicAnimation改过图片z轴进行旋转,设置一定时期旋转生龙活虎圈,重复无数13回。

CABasicAnimation *rotateAnimate = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
rotateAnimate.fromValue = @(0);
rotateAnimate.toValue = @(M_PI * 2);
rotateAnimate.repeatCount = NSIntegerMax;
rotateAnimate.duration = 36;
[self.iconView.layer addAnimation:rotateAnimate forKey:nil];

末尾,改正Slider的原点的体裁,我们发将来storyboard是从未有过办法改正Slider原点的图样的,只可以更改颜色,所以必要通过代码修改Slider原点的图片

[self.progressSlider setThumbImage:[UIImage imageNamed:@"player_slider_playback_thumb"] forState:UIControlStateNormal];

现已因此真机调节和测量试验,在真机上可以往台播放音乐,何况锁屏时,彰显一些主要的歌曲消息。

三. 播放音乐

此处为了方便使用本地音乐实行播报,首先依据plist文件创制CLMusicModel模型,然后创造CLMusicTool工具类,用来赢得具有音乐以致当前正值播放的音乐设置默许播放的音乐等等。最终创造CLAVdioTool工具类用来播音音乐,以至切换上大器晚成首,下豆蔻年华首音乐。

接下去来详细深入分析这四个类的成效。
CLMusicModel模型类仅仅是歌曲模型,内含歌曲名,文件名,歌词文件名,歌星音信等。
CLMusicTool工具类提供方式用来起头化音乐列表将plist文件转变为Model,并积累到数组中,获取具备音乐数组,以致安装暗中认可播放的音乐

static NSArray *_musics;
static CLMusicModel *_playingMusic;
// 类加载的时候初始化音乐列表和播放音乐
+(void)initialize
{
    if (_musics == nil) {
         // 使用MJExtension将plist文件转化为模型
        _musics = [CLMusicModel objectArrayWithFilename:@"Musics.plist"];
    }
    if (_playingMusic == nil) {
        // 设置一开始就播放的音乐
        _playingMusic = _musics[0];
    }
}
// 获取所有音乐
+(NSArray *)Musics
{
    return _musics;
}
// 当前正在播放的音乐
+(CLMusicModel *)playingMusic
{
    return _playingMusic;
}
// 设置默认播放的音乐
+(void)setUpPlayingMusic:(CLMusicModel *)playingMusic
{
    _playingMusic = playingMusic;
}

CLAVdioTool工具类用来播放音乐,以致切换上风华正茂首,下黄金年代首音乐。这里提供多个法子,依照参数文件名找到文件路线并基于文件路线创建播放器player,成立全局字典用来储存播放器,每首歌对应二个播放器,播放音乐的时候先去字典中找到呼应的播放器进行播报,若无就创设对应的播放器。

static NSMutableDictionary *_players;
+(void)initialize
{
    _players = [NSMutableDictionary dictionary];
}
+(AVAudioPlayer *)playingMusicWithMusicFileName:(NSString *)filename
{
    AVAudioPlayer *player = nil;
    player = _players[filename];
    if (player == nil) {
        // 文件路径转化为url
        NSURL *url = [[NSBundle mainBundle]URLForResource:filename withExtension:nil];
        if (url == nil) {
            return nil;
        }
        // 创建player
        player = [[AVAudioPlayer alloc]initWithContentsOfURL:url error:nil];
        // 准备播放
        [player prepareToPlay];
        // 将播放器存储到字典中
        [_players setObject:player forKey:filename];
    }
    // 开始播放
    [player play];
    return player;
}
+(void)pauseMusicWithMusicFileName:(NSString *)filename
{
    AVAudioPlayer *player = _players[filename];
    if (player) {
        [player pause];
    }
}
+(void)stopMusicWithMusicFileName:(NSString *)filename
{
    AVAudioPlayer *player = _players[filename];
    if (player) {
        [player stop];
        [_players removeObjectForKey:filename];
        player = nil;
    }
}

那时候在CLPlayingViewController中张开音乐播放就非常轻便了,使用CLMusicTool获得当前正值播放的CLMusicModel音乐模型,对页面消息进行设置,使用CLAVdioTool依照CLMusicModel的属性音乐名,播放音乐。重要代码如下。

// 获取当前正在播放的音乐
CLMusicModel *playingMusic = [CLMusicTool playingMusic];

// 根据文件名播放音乐并且获取播放的音乐
AVAudioPlayer *currentPlayer = [CLAVdioTool playingMusicWithMusicFileName:playingMusic.filename];

基于歌曲的播报来展现对应歌词的。用UITableView来展现歌词,能够手动滚动界面查看前面或许前面的歌词。

四. Slider时间条的处理

播音时间和歌曲总时间的string管理,通过播放器可以得到已经播放时间current提姆e和歌曲总时间duration,播放器重临给大家的是秒,必要将秒转变为分钟,这里给NSString增添分类,加多stringWithTime方法将回来的NSTimeInterval转化为NSString。

+ (NSString *)stringWithTime:(NSTimeInterval)time
{
    NSInteger min = time / 60;
    NSInteger sec = (int)round(time) % 60;
    return [NSString stringWithFormat:@"%02ld:%02ld",min,sec];
}

Slider随播放时间而运动
由此抬高机械漏刻的点子,使Slider原点随着广播的日子而移动,将机械漏刻增加到主RunLoop中并改革Mode为NSRunLoopCommonModes防止在滑行时沙漏失效。

#pragma mark - 对进度条时间的处理
- (void)addProgressTimer
{
    // 定时器每1s执行一次,第一次来到这里需要先等1s,所以先刷新一下界面
    [self updateProgressInfo];
    self.progressTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(updateProgressInfo) userInfo:nil repeats:YES];
    [[NSRunLoop mainRunLoop] addTimer:self.progressTimer  forMode:NSRunLoopCommonModes];
}
- (void)removeProgressTimer
{
    [self.progressTimer invalidate];
    self.progressTimer = nil;
}
#pragma mark - 更新进度条
- (void)updateProgressInfo
{
    // 1.更新播放的时间,使用NSString分类方法处理时间。
    self.currentTimeLabel.text = [NSString stringWithTime:self.currentPlayer.currentTime];
    // 2.更新滑动条
    self.progressSlider.value = self.currentPlayer.currentTime / self.currentPlayer.duration;
}

在乎:当我们在播报音乐艺术里面增加停车计时器的时候须要先移除电磁打点计时器,然后在抬高,幸免当点击下意气风发首的时候,反应计时器未有移除,时间发出错误。

Slider滑动更新分界面和音乐播放时间
给Slider增添点击事件,监听Slider的滑动。在storyboard中给Slider增加点击事件,分别监听Slider的点击,滑动和松手。

  1. 当按Slider滑块下时移除机械漏刻。
  2. 当滑动Slider滑块时,依照滑动的数值 * 歌曲总时间测算出当下滑动点对应的播放时间,然后更新播放时间label的text。
  3. 当手指放手时,设置播放器播放时间还要增加反应计时器。
#pragma mark - slider 事件处理
- (IBAction)start {
    // 移除定时器
    [self removeProgressTimer];
}
- (IBAction)end {   
    // 1.更新播放的时间
    self.currentPlayer.currentTime = self.progressSlider.value * self.currentPlayer.duration;
    // 2.添加定时器
    [self addProgressTimer];
}
- (IBAction)progressValueChange {
    self.currentTimeLabel.text = [NSString stringWithTime:self.progressSlider.value * self.currentPlayer.duration];
}

Slider的点击直接跳转当前岁月播放
因而storyboard给Slider增添手势监听Slider的点击,当点击Slider直接跳转到点击地点上马广播。
得到点击之处,然后计算点击地点占真个Slider的百分比,依据比例总结出脚下播音时间,最终更新label时间和滑块的职责。

- (IBAction)sliderClick:(UITapGestureRecognizer *)sender {
    // 1.获取点击到的点
    CGPoint point = [sender locationInView:sender.view];
    // 2.获取点击的比例
    CGFloat ratio = point.x / self.progressSlider.bounds.size.width;
    // 3.更新播放的时间
    self.currentPlayer.currentTime = self.currentPlayer.duration * ratio;
    // 4.更新时间和滑块的位置
    [self updateProgressInfo];
}

同不经常间,当拖动进程条,歌词也会随着变动,下生龙活虎曲、上大器晚成曲依旧是能够运用的。

五. 播放暂停、上风华正茂首、下大器晚成首的点击管理

监听播放开关点击
播放按键有广播和制动踏板多少个情状,程序一起初运维就自动播放,所以率先必要在音乐后生可畏开播的时候修正播放按键的selected。

self.playWithPauseBtn.selected = currentPlayer.isPlaying;

当点击播放开关的时候首先必要改正开关的图景,然后判别音乐广播的情状,即便正在播放则暂停音乐,移除坚持计时器,况兼结束歌星图片的动漫片,假设是搁浅的则开播,加多电火花计时器,並且苏醒动漫。
停顿动漫和还原动漫通过给CALayer增多分类方法达成。分类能够直接拖到其余项目中接纳

- (void)pauseAnimate
{
    CFTimeInterval pausedTime = [self convertTime:CACurrentMediaTime() fromLayer:nil];
    self.speed = 0.0;
    self.timeOffset = pausedTime;
}
- (void)resumeAnimate
{
    CFTimeInterval pausedTime = [self timeOffset];
    self.speed = 1.0;
    self.timeOffset = 0.0;
    self.beginTime = 0.0;
    CFTimeInterval timeSincePause = [self convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
    self.beginTime = timeSincePause;
}

播放开关点击事件达成

- (IBAction)playWithPause:(UIButton *)sender {
    sender.selected = !sender.selected;
    if (self.currentPlayer.playing) {
        // 1.暂停播放器
        [self.currentPlayer pause];
        // 2.移除定时器
        [self removeProgressTimer];
        // 3.暂停旋转动画
        [self.iconView.layer pauseAnimate];
    } else {
        // 1.开始播放
        [self.currentPlayer play];
        // 2.添加定时器
        [self addProgressTimer];
        // 3.恢复动画
        [self.iconView.layer resumeAnimate];
    }
}

上风华正茂首下风度翩翩首开关点击完毕
我们得以在CLMusicTool工具类中加多获取上风华正茂首歌曲和下风华正茂首歌曲的办法,首先得到当下播报音乐的下标,然后在得到上生龙活虎首可能下风流罗曼蒂克首歌曲时需求对下标举办剖断,拿上生机勃勃首为例,借使当前歌曲的下标为0,则赶回最后豆蔻梢头首歌,造成巡回播放,借使不为0则获得上生龙活虎首就能够,不然会以致数组越界。获取下生龙活虎首判定格局大器晚成致。

// 返回上一首音乐
+ (CLMusicModel *)previousMusic
{
    NSInteger index = [_musics indexOfObject:_playingMusic];
    if (index == 0) {
        index = _musics.count -1;
    }else{
        index = index -1;
    }
    CLMusicModel *previousMusic = _musics[index];
    return previousMusic;
}
// 返回下一首音乐
+ (CLMusicModel *)nextMusic
{
    NSInteger index = [_musics indexOfObject:_playingMusic];
    if (index == _musics.count - 1) {
        index = 0;
    }else{
        index = index +1;
    }
    CLMusicModel *previousMusic = _musics[index];
    return previousMusic;
}

那时点击上意气风发首大概下大器晚成首开关时,首先结束当前播音的音乐,然后将上少年老成首或许下黄金年代首歌曲设置为暗许播放歌曲,最终伊始广播,因为停播当下音乐,开播下豆蔻梢头首音乐的代码相通,将其分红一个方法

- (IBAction)nextMusic {
    CLMusicModel *nsxtMusic = [CLMusicTool nextMusic];
    [self playMusicWithMusic:nsxtMusic];
}
- (IBAction)previousMusic {
    CLMusicModel *previousMusic = [CLMusicTool previousMusic];
    [self playMusicWithMusic:previousMusic];
}
- (void)playMusicWithMusic:(CLMusicModel *)muisc
{
    // 获取当前播放的音乐并停止
    CLMusicModel *playingMusic = [CLMusicTool playingMusic];
    [CLAVdioTool stopMusicWithMusicFileName:playingMusic.filename];
    // 设置下一首或者上一首为默认播放音乐
    [CLMusicTool setUpPlayingMusic:muisc];
    // 更新界面
    [self startPlayingMusic];
}

代码深入分析:

六. 歌词的管理

创制存放歌词的tableView
当滑动歌星图片时,会过来歌词分界面,这里往歌星图片和歌词label上边覆盖scrollView,设置scrollView的contentSize为七个显示屏的大幅度,显示歌词的tableView放在荧屏外面,构造如图。

歌词tableView布局

应用storyboard加多scrollView并自定义scrollView为CLLrcView,使用代码增添tableView,在scrollView的initWithFrame方法中开创并起先化tableView, 在layoutSubviews中对tableView实行部分安装。譬喻设置tableView的背景图片为透明,供给cell之间的线,设置tabaleView的contentInset大器晚成开始滑动到荧屏大旨。

scrollView滑动明星图片逐步消散管理
当向右滑动现身歌词时,明星图片和歌词label是渐渐消退的,大家因此scrollView的代办监听scrollView的滑动,依照scrollView.contentOffset.x的尺寸占有整个荧屏的百分比设置歌星图片和歌词label的反射率,当完全滑动一个显示器宽度时,明星图片和歌词label的发光度为0。

#pragma mark - scrollView代理方法
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    CGPoint offcetPoint = scrollView.contentOffset;
    CGFloat alpha = 1 - offcetPoint.x / self.view.frame.size.width;
    self.iconView.alpha = alpha;
    self.lrcLabel.alpha = alpha;   
}

自定义tableView的cell和cell中的label
自定义tableView的cell为CLLrcTableViewCell,对cell举办开端化,对cell的style和背景举办安装,对cell内label的frame和字体等开展设置。
自定义label为CLLrcLabel,便于我们之后对label中的歌词实行部分拍卖。

歌词的展现管理
唐诗展现处理逻辑比较冗杂,这里尽量使代码解耦,便于更清晰的精晓里面包车型客车逻辑。
率先歌词的来得在自定义的CLLrcView中的tableView中,所以给CLLrcView加多lrcName歌词文件名字属性,用来收取歌词文件的名字,然后重写setLrcName:方法依据歌词名得到歌词并对歌词实香港行政局地甩卖。先来看一下乐章文件中的歌词样式

歌词内容格式

此地需求将每一句歌词转变为歌词模型,模型中蕴涵时间和歌词,并且时间须求转接为秒。
例如 [01:32.64]宁愿相信我们前世有约 要求中间转播为time = 96 text = @"宁愿相信大家前世有约"存入到模型中。

首先需求将歌词豆蔻梢头行生龙活虎行分别转变为数组,这里创办CLLrcTool工具类用来将每朝气蓬勃行歌词分开,并将每生机勃勃行存入到数组中,当时数组中蕴藏的乐章样式为 [01:32.64]宁愿相信我们前世有约
然后创立CLLrcLine歌词模型,模型中提供格局将[01:32.64]宁愿相信我们前世有约体制的乐章分割出时间和歌词内容并转载为模型。

先来看CLLrcTool的将歌词转变为歌词数组方法

+(NSArray *)lrcToolWithLrcName:(NSString *)lrcName
{
    // 1. 获取路径
    NSString *lrcFilePath = [[NSBundle mainBundle]pathForResource:lrcName ofType:nil];
    // 2. 获取歌词
    NSString *lrcString = [NSString stringWithContentsOfFile:lrcFilePath encoding:NSUTF8StringEncoding error:nil];
    // 将歌词转化为数组,会以每个n为分隔符 将每一行转化为数组中的每个元素
    NSArray *lrcArr = [lrcString componentsSeparatedByString:@"n"];
    NSMutableArray *tempArr = [NSMutableArray array];
    // 遍历数组,将不需要的去除,并调用模型的方法,将字符串转化为模型
    for (NSString *lrcLineString in lrcArr) {
        // 过滤掉不要的字符串,如果是以这些开头 或者不是以[开头的直接退出循环
        if ([lrcLineString hasPrefix:@"[ti:"] ||
            [lrcLineString hasPrefix:@"[ar:"] ||
            [lrcLineString hasPrefix:@"[al:"] ||
            ![lrcLineString hasPrefix:@"["]) {
            continue;
        }
        // 将字符串转化为模型
        CLLrcLine *lrcLine = [CLLrcLine LrcLineString:lrcLineString];
        [tempArr addObject:lrcLine];
    }
    return tempArr;
}

模型军长字符串转变为模型的方式

// 返回模型
+ (instancetype)LrcLineString:(NSString *)lrcLineString
{
    return [[self alloc] initWithLrcLineString:lrcLineString];
}
// 将字符串分割为时间和歌词内容
- (instancetype)initWithLrcLineString:(NSString *)lrcLineString
{
    if (self = [super init]) {
        // [01:32.64]宁愿相信我们前世有约
        NSArray *lrcArray = [lrcLineString componentsSeparatedByString:@"]"];
       // 设置歌词内容
        self.text = lrcArray[1];  
       // 对时间进行处理然后设置时间
        self.time = [self timeWithString:[lrcArray[0] substringFromIndex:1]];
    }
    return self;
}
// 处理时间,将时间转化为秒
- (NSTimeInterval)timeWithString:(NSString *)timeString
{
    // 01:32.64
    NSInteger min = [[timeString componentsSeparatedByString:@":"][0] integerValue];
    NSInteger sec = [[timeString substringWithRange:NSMakeRange(3, 2)] integerValue];
    NSInteger hs = [[timeString componentsSeparatedByString:@"."][1] integerValue];
    return min * 60 + sec + hs * 0.01;
}

那时候早已拿到歌词模型的数组,刷新tableView就可以。
唯独那时的乐章是固定的,并不会依靠播放时间及时的显得超越播放的小运。

歌词的即时展现
风流倜傥经想即时的依据播放时间展现歌词,则必要获得歌曲的总时间还要应用计时器不断的取安妥前播音的时光,因为歌词的年华需求相比较标准,这里运用CADisplayLink放大计时器

#pragma mark - 歌词定时器
- (void)addLrcTimer
{
    self.lrcTiemr = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateLrcInfo)];
    [self.lrcTiemr addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}
- (void)removeLrcTimer
{
    [self.lrcTiemr invalidate];
    self.lrcTiemr = nil;
}
#pragma mark  更新歌词
- (void)updateLrcInfo
{
    self.lrcScrollView.currentTime = self.currentPlayer.currentTime;
}

为CLLrcView增加current提姆e已播放时间和duration歌曲总时间属性,重写setCurrentTime:对currentTime举行部分肯定。

  1. 收获当前歌曲歌词数组的行数。
  2. 遍历获得每风华正茂行和下后生可畏行歌词的大运。
  3. 张开剖断,当当前广播的时间超过等于第i行的时间,何况小于第i+1行的时刻则注解当前正值唱的是第i行。
  4. 将第i行移动到显示器中心,然后将第i行记录下来,更新第i行,回到tableView:tableView cellForRowAtIndexPath:艺术中剖断假使是第i行则将lable的字体放大,假如不是则改为本来的值。
  5. 因为校正第i行内容字体大小在此以前,第i-1行的剧情也被涂改正,由此在立异第i行时供给同不常候更新第i-1行。
  6. 每一次切换歌曲时,要求将近来进数清空,制止变成数组越界。

歌词的即时渲染
为达到规定的标准歌词随播放时间即时渲染转换颜色,通过重写CLLrcLabel的drawRect:方法渲染歌词的颜料,并为CLLrcLabel加多progress属性用来记录歌词的播音进程,通过广播进程的改变任何时候调用CLLrcLabel的setNeedsDisplay刷新CLLrcLabel的渲染长度。
播音进程 = (当前播送的日子 - 正在唱的乐章的上马时间)/ 当前唱的歌词供给的总时间。

主页面歌词的即时展现
将主页面歌词的label一样设置为CLLrcLabel型,为CLLrcView加多lrcLabel属性,lrcLabel是CLLrcLabel类型的,在获取当前播报放的歌词之后,通过lrcLabel属相将歌词内容回传给主页面歌词label就能够。

CLLrcView中setCurrentTime:方法

#pragma mark - 当前播放时间set方法
- (void)setCurrentTime:(NSTimeInterval)currentTime
{
    // 记录当前时间
    _currentTime = currentTime;
    // 获取歌词行数
    NSInteger count = self.lrcList.count;
    for (int i = 0; i < count; i ++) {
        // 获取i位置的歌词
        CLLrcLine *currentLrcLine = self.lrcList[i];
        // 获取下一句歌词
        NSInteger nextIndex = i + 1;
        // 先创建空的歌词模型
        CLLrcLine *nextLrcLine = nil;
        // 判断歌词是否存在
        if (nextIndex < self.lrcList.count) {
            // 说明存在
            nextLrcLine = self.lrcList[nextIndex];
        }
        // 用播放器的当前的时间和i位置歌词、i+1位置歌词的时间进行比较,如果大于等于i位置的时间并且小于等于i+1歌词的时间,说明应该显示i位置的歌词。
        // 并且如果正在显示的就是这行歌词则不用重复判断
        if (self.currentIndex != i && currentTime >= currentLrcLine.time && currentTime < nextLrcLine.time) {
            // 设置主页上的歌词
            self.lrcLabel.text = currentLrcLine.text;
            // 将当前播放的歌词移动到中间
            NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
            [self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
            // 记录上一句位置,当移动到下一句时,上一句和当前这一句都需要进行更新行
            NSIndexPath *previousPath = [NSIndexPath indexPathForRow:self.currentIndex inSection:0];
            // 记录当前播放的下标。下次来到这里,currentIndex指的就是上一句
            self.currentIndex = i;
            [self.tableView reloadRowsAtIndexPaths:@[indexPath,previousPath] withRowAnimation:UITableViewRowAnimationNone];
        }
        if (self.currentIndex == i) {
            // 获取播放速度 已经播放的时间 / 播放整句需要的时间
            CGFloat progress = (currentTime - currentLrcLine.time) / (nextLrcLine.time - currentLrcLine.time);
            // 获取当前行数的cell
            NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
            CLLrcTableViewCell *lrccell = [self.tableView cellForRowAtIndexPath:indexPath];
            // 设置歌词界面歌词的进度
            lrccell.lrcLabel.progress = progress;
            // 设置主页面歌词的进度
            self.lrcLabel.progress = progress;
        }
    }
}

CLLrcLabel的setProgress:方法

- (void)setProgress:(CGFloat)progress
{
    _progress = progress;
    [self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect {
    [super drawRect:rect];
    [[UIColor greenColor] set];
    CGRect fillRect = CGRectMake(0, 0, self.bounds.size.width * self.progress, self.bounds.size.height);
    UIRectFillUsingBlendMode(fillRect, kCGBlendModeSourceIn);
}

预备阶段,先是写了一个节奏播放的单例,用那一个单例来播放那几个demo中的音乐文件,代码如下:

七. 播放在线音乐

就算项目中播放的是本地音乐,可是使用AVFoundation播放在线音乐也特别轻巧。

// 1.创建音乐资源
NSURL *url = [NSURL URLWithString:@"url"];
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithURL:url];
// 2.创建播放器
// AVPlayer *player = [AVPlayer playerWithURL:url];
AVPlayer *player = [AVPlayer playerWithPlayerItem:playerItem];
[player play];

留意:AV奥迪(Audi卡塔尔国oPlayer只好播放本地音乐,AVPlayerItem既可以播放本地音乐也能播放在线音乐

#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
@interface ZYAudioManager : NSObject
+ (instancetype)defaultManager;

//播放音乐
- (AVAudioPlayer *)playingMusic:(NSString *)filename;
- (void)pauseMusic:(NSString *)filename;
- (void)stopMusic:(NSString *)filename;

//播放音效
- (void)playSound:(NSString *)filename;
- (void)disposeSound:(NSString *)filename;
@end



#import "ZYAudioManager.h"

@interface ZYAudioManager ()
@property (nonatomic, strong) NSMutableDictionary *musicPlayers;
@property (nonatomic, strong) NSMutableDictionary *soundIDs;
@end

static ZYAudioManager *_instance = nil;

@implementation ZYAudioManager

+ (void)initialize
{
  // 音频会话
  AVAudioSession *session = [AVAudioSession sharedInstance];

  // 设置会话类型(播放类型、播放模式,会自动停止其他音乐的播放)
  [session setCategory:AVAudioSessionCategoryPlayback error:nil];

  // 激活会话
  [session setActive:YES error:nil];
}

+ (instancetype)defaultManager
{
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    _instance = [[self alloc] init];
  });
  return _instance;
}

- (instancetype)init
{
  __block ZYAudioManager *temp = self;

  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    if ((temp = [super init]) != nil) {
      _musicPlayers = [NSMutableDictionary dictionary];
      _soundIDs = [NSMutableDictionary dictionary];
    }
  });
  self = temp;
  return self;
}

+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    _instance = [super allocWithZone:zone];
  });
  return _instance;
}

//播放音乐
- (AVAudioPlayer *)playingMusic:(NSString *)filename
{
  if (filename == nil || filename.length == 0) return nil;

  AVAudioPlayer *player = self.musicPlayers[filename];   //先查询对象是否缓存了

  if (!player) {
    NSURL *url = [[NSBundle mainBundle] URLForResource:filename withExtension:nil];

    if (!url) return nil;

    player = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];

    if (![player prepareToPlay]) return nil;

    self.musicPlayers[filename] = player;      //对象是最新创建的,那么对它进行一次缓存
  }

  if (![player isPlaying]) {         //如果没有正在播放,那么开始播放,如果正在播放,那么不需要改变什么
    [player play];
  }
  return player;
}

- (void)pauseMusic:(NSString *)filename
{
  if (filename == nil || filename.length == 0) return;

  AVAudioPlayer *player = self.musicPlayers[filename];

  if ([player isPlaying]) {
    [player pause];
  }
}
- (void)stopMusic:(NSString *)filename
{
  if (filename == nil || filename.length == 0) return;

  AVAudioPlayer *player = self.musicPlayers[filename];

  [player stop];

  [self.musicPlayers removeObjectForKey:filename];
}

//播放音效
- (void)playSound:(NSString *)filename
{
  if (!filename) return;

  //取出对应的音效ID
  SystemSoundID soundID = (int)[self.soundIDs[filename] unsignedLongValue];

  if (!soundID) {
    NSURL *url = [[NSBundle mainBundle] URLForResource:filename withExtension:nil];
    if (!url) return;

    AudioServicesCreateSystemSoundID((__bridge CFURLRef)(url), &soundID);

    self.soundIDs[filename] = @(soundID);
  }

  // 播放
  AudioServicesPlaySystemSound(soundID);
}

//摧毁音效
- (void)disposeSound:(NSString *)filename
{
  if (!filename) return;


  SystemSoundID soundID = (int)[self.soundIDs[filename] unsignedLongValue];

  if (soundID) {
    AudioServicesDisposeSystemSoundID(soundID);

    [self.soundIDs removeObjectForKey:filename];  //音效被摧毁,那么对应的对象应该从缓存中移除
  }
}
@end

八. 总结

由来,QQ音乐播放器已经主导实现,此中还应该有不菲细节还没管理到位,比如歌曲播放实现之后的拍卖,步入后台在回去的转动动漫的管理等,其它对于歌词即时体现认为讲的还不是很分明,假如有不亮堂的地点还请提出来,大家联合索求深入分析一下。

源码: github下载地址


文中假若有难堪的地点应接提议。作者是xx_cc,多头长大十分久但还未二够的玩意儿。

 正是贰个单例的设计,并从未多横祸度。笔者是用了三个字典来装播放过的歌曲了,那样即便是搁浅了,然后再开播,就平昔在缓存中加载就可以。不过意气风发旦十分大心,在 stopMusic:(NSString *卡塔尔(英语:State of Qatar)fileName  这么些法子里面,不从字典中移除掉已经告后生可畏段落播放的歌曲,那么您下再播放那首歌的时候,就能够在本来播放的快慢上前赴后继播放。在编码进程中,作者就蒙受了这些Bug,然后发现,在切换歌曲(上后生可畏曲、下风姿罗曼蒂克曲)的时候,笔者调用的是stopMusic方法,但出于自个儿未有从字典军长它移除,而导致它总是从上三回的速度开播,实际不是从头开始播放。

即便在真机上想要后台播放歌曲,除了在appDelegate以致plist里面做相应操作之外,还得将播放情势设置为:AV奥迪(Audi卡塔尔(قطر‎oSessionCategoryPlayback。特别必要在乎这里,我在模拟器上调解的时候,未有安装这种格局也是足以扩充后台播放的,不过在真机上而不是常了。后来在StackOverFlow上找到了相应的答案,供给设置播放格局。

本文由qg777发布于操作系统,转载请注明出处:【操作系统】iOS-QQ音乐播放器的简约完结

TAG标签:
Ctrl+D 将本页面保存为书签,全面了解最新资讯,方便快捷。