最终效果图:
模型
// // Sentence.h // 36_声词同步 // // Created by beyond on 14-9-12. // Copyright (c) 2014年 com.beyond. All rights reserved. // 模型,句子 #import <Foundation/Foundation.h> @interface Sentence : NSObject // 文字 @property (nonatomic, copy) NSString *text; // 在音频文件中,朗诵开始的时间 @property (nonatomic, assign) double startTime; @end
// // BeyondController.m // 36_声词同步 // // Created by beyond on 14-9-12. // Copyright (c) 2014年 com.beyond. All rights reserved. // #import "BeyondController.h" #import "Sentence.h" #import "SongTool.h" @interface BeyondController () // 句子对象数组 @property (nonatomic, strong) NSArray *sentenceArr; // 开始播放就开启时钟,监听 音乐的播放进度 @property (nonatomic, strong) CADisplayLink *link; // 音乐播放器 可以获得当前播放的时间 【currentTime】 @property (nonatomic, strong) AVAudioPlayer *sentencePlayer; @end @implementation BeyondController // 懒加载,需要时才创建 时钟 - (CADisplayLink *)link { if (!_link) { // 绑定时钟方法 self.link = [CADisplayLink displayLinkWithTarget:self selector:@selector(update)]; } return _link; } // 懒加载,需要时才创建 句子对象数组,从Plist中的字典数组,转成对象数组 - (NSArray *)sentenceArr { if (!_sentenceArr) { self.sentenceArr = [Sentence objectArrayWithFilename:@"redStory.plist"]; } return _sentenceArr; } - (void)viewDidLoad { [super viewDidLoad]; // 工具类播放音乐,并用成员变量记住 创建的播放器对象【可拿到播放的currentTime】 self.sentencePlayer = [SongTool playMusic:@"一东.mp3"]; // 播放背景音乐 [SongTool playMusic:@"Background.caf"]; // 同时,开启时钟 [self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; self.tableView.contentInset = UIEdgeInsetsMake(20, 0, 0, 0); } #pragma mark - 时钟方法 // 重点~~~~时钟绑定的方法 - (void)update { // 获取当前播放的位置(第多少秒,比如第10秒) double currentTime = self.sentencePlayer.currentTime; // 遍历,找出对应的一句 int count = self.sentenceArr.count; for (int i = 0; i<count; i++) { // 1.当前词句 Sentence *s = self.sentenceArr[i]; // 2.获得下一条句子对象 int next = i + 1; Sentence *nextS = nil; // 需防止越界 if (next < count) { nextS = self.sentenceArr[next]; } // 3.关键判断,如果当前播放的时间,大于i对应的时间,并且小于i+1对应的时间 if (currentTime >= s.startTime && currentTime < nextS.startTime) { // 选中并高亮对应行的文字 NSIndexPath *path = [NSIndexPath indexPathForRow:i inSection:0]; [self.tableView selectRowAtIndexPath:path animated:YES scrollPosition:UITableViewScrollPositionTop]; break; } } } #pragma mark - tableView数据源方法 // 共多少行,即多少句 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.sentenceArr.count; } // 每一行的独一无二的文字 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { // 1.创建cell static NSString *cellID = @"Sentence"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID]; if (cell == nil) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellID]; } // 2.设置cell 独一无二的数据 Sentence *s = self.sentenceArr[indexPath.row]; cell.textLabel.text = s.text; return cell; } @end
音乐播放工具类
// // SongTool.h // 36_声词同步 // // Created by beyond on 14-9-12. // Copyright (c) 2014年 com.beyond. All rights reserved. // #import <Foundation/Foundation.h> // 音乐工具类,必须导入AVFoundation的主头文件 #import <AVFoundation/AVFoundation.h> @interface SongTool : NSObject // 类方法, 播放音乐, 参数:音乐文件名 如@"a.mp3",同时为了能够给播放器AVAudioPlayer对象设置代理,在创建好播放器对象后,将其返回给调用者 + (AVAudioPlayer *)playMusic:(NSString *)filename; // 类方法, 暂停音乐, 参数:音乐文件名 如@"a.mp3" + (void)pauseMusic:(NSString *)filename; // 类方法, 停止音乐, 参数:音乐文件名 如@"a.mp3",记得从字典中移除 + (void)stopMusic:(NSString *)filename; // 返回当前正在播放的音乐播放器,方便外界控制其快进,后退或其他方法 + (AVAudioPlayer *)currentPlayingAudioPlayer; @end
// // SongTool.m // 36_声词同步 // // Created by beyond on 14-9-12. // Copyright (c) 2014年 com.beyond. All rights reserved. // #import "SongTool.h" @implementation SongTool // 字典,存放所有的音乐播放器,键是:音乐名,值是对应的音乐播放器对象audioPlayer // 一首歌对应一个音乐播放器 static NSMutableDictionary *_audioPlayerDict; #pragma mark - Life Cycle + (void)initialize { // 字典,键是:音乐名,值是对应的音乐播放器对象 _audioPlayerDict = [NSMutableDictionary dictionary]; // 设置后台播放 [self sutupForBackgroundPlay]; } // 设置后台播放 + (void)sutupForBackgroundPlay { // 后台播放三步曲之第三步,设置 音频会话类型 AVAudioSession *session = [AVAudioSession sharedInstance]; // 类型是:播放和录音 [session setCategory:AVAudioSessionCategoryPlayAndRecord error:nil]; // 而且要激活 音频会话 [session setActive:YES error:nil]; } #pragma mark - 供外界调用 // 类方法, 播放音乐, 参数:音乐文件名 如@"a.mp3" // 同时为了能够给播放器AVAudioPlayer对象设置代理,在创建好播放器对象后,将其返回给调用者 + (AVAudioPlayer *)playMusic:(NSString *)filename { // 健壮性判断 if (!filename) return nil; // 1.先从字典中,根据音乐文件名,取出对应的audioPlayer AVAudioPlayer *audioPlayer = _audioPlayerDict[filename]; if (!audioPlayer) { // 如果没有,才需创建对应的音乐播放器,并且存入字典 // 1.1加载音乐文件 NSURL *url = [[NSBundle mainBundle] URLForResource:filename withExtension:nil]; // 健壮性判断 if (!url) return nil; // 1.2根据音乐的URL,创建对应的audioPlayer audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil]; // 1.3开始缓冲 [audioPlayer prepareToPlay]; // 如果要实现变速播放,必须同时设置下面两个参数 // audioPlayer.enableRate = YES; // audioPlayer.rate = 10.0; // 1.4最后,放入字典 _audioPlayerDict[filename] = audioPlayer; } // 2.如果是暂停或停止时,才需要开始播放 if (!audioPlayer.isPlaying) { [audioPlayer play]; } // 3.返回创建好的播放器,方便调用者设置代理,监听播放器的进度currentTime return audioPlayer; } // 类方法, 暂停音乐, 参数:音乐文件名 如@"a.mp3" + (void)pauseMusic:(NSString *)filename { // 健壮性判断 if (!filename) return; // 1.先从字典中,根据key【文件名】,取出audioPlayer【肯定 有 值】 AVAudioPlayer *audioPlayer = _audioPlayerDict[filename]; // 2.如果是正在播放,才需要暂停 if (audioPlayer.isPlaying) { [audioPlayer pause]; } } // 类方法, 停止音乐, 参数:音乐文件名 如@"a.mp3",记得从字典中移除 + (void)stopMusic:(NSString *)filename { // 健壮性判断 if (!filename) return; // 1.先从字典中,根据key【文件名】,取出audioPlayer【肯定 有 值】 AVAudioPlayer *audioPlayer = _audioPlayerDict[filename]; // 2.如果是正在播放,才需要停止 if (audioPlayer.isPlaying) { // 2.1停止 [audioPlayer stop]; // 2.2最后,记得从字典中移除 [_audioPlayerDict removeObjectForKey:filename]; } } // 返回当前正在播放的音乐播放器,方便外界控制其快进,后退或其他方法 + (AVAudioPlayer *)currentPlayingAudioPlayer { // 遍历字典的键,再根据键取出值,如果它是正在播放,则返回该播放器 for (NSString *filename in _audioPlayerDict) { AVAudioPlayer *audioPlayer = _audioPlayerDict[filename]; if (audioPlayer.isPlaying) { return audioPlayer; } } return nil; } @end