ios开发:一个音乐播放器的设计与实现案例

2020-01-18 16:44:35丽君

这个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