这个Demo,关于歌曲播放的主要功能都实现了的。下一曲、上一曲,暂停,根据歌曲的播放进度动态滚动歌词,将当前正在播放的歌词放大显示,拖动进度条,歌曲跟着变化,并且使用Time Profiler进行了优化,还使用XCTest对几个主要的类进行了单元测试。
已经经过真机调试,在真机上可以后台播放音乐,并且锁屏时,显示一些主要的歌曲信息。
根据歌曲的播放来显示对应歌词的。用UITableView来显示歌词,可以手动滚动界面查看后面或者前面的歌词。
并且,当拖动进度条,歌词也会随之变化,下一曲、上一曲依然是可以使用的。
代码分析:
准备阶段,先是写了一个音频播放的单例,用这个单例来播放这个demo中的音乐文件,代码如下:
#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










