股票场内基金交易,没时间盯盘?
iOS 中的音频可分为无需对内容进行控制的短音频(如音效)和需要精确控制的长音频(如音乐)。对本地音频资源的播放一般分别利用 AudioToolbox.framework 和 AVFoundation.framework 来实现,需要在文件头部添加:
1 2 |
#import <AVFoundation/AVFoundation.h> |
短音频播放(System Sound Service)
AudioToolbox.framework是一套基于C语言的框架,它以将音频注册到系统声音服务(System Sound Service)的手段实现播放。System Sound Service 是一个简单的声音播放服务,除播放音频外还可以附带振动效果,非常适合短音频的播放。它的使用存在如下一些限制:
- 音频时长不超过 30 s;
- 数据格式必须为 PCM 或 IMA4 (IMA/ADPCM);
- 音频文件必须打包为 .caf, .aif 或 .wav 文件。
使用步骤
-
加载音效文件,获得对应的音效 ID:
1234567SystemSoundID soundID;AudioServicesCreateSystemSoundID(CFURLRef inFileURL, SystemSoundID* outSystemSoundID);/*inFileURL 是 bundle 内音频文件的 url,注意转换格式;outSystemSoundID 音效 ID,一个音频文件对应一个 音效 ID。*/ -
播放音效文件:
12345// 仅播放音效AudioServicesPlaySystemSound(SystemSoundID inSystemSoundID);// 播放音效并且振动AudioServicesPlayAlertSound(SystemSoundID inSystemSoundID); -
在需要的时候销毁音效并将音效 ID 清零:
123AudioServicesDisposeSystemSoundID(inSystemSoundID);inSystemSoundID = 0;
使用技巧
实际开发中,一种情况是对短音频的调用可能非常频繁(如按钮点击),如果每次都重新加载一遍音频,对系统资源显然是巨大 的浪费;另一种情况是音效文件非常多,加载和播放操作会存在大量重复代码。此时最好的解决办法是自定义一个工具类进行封装(这里使用单例模式)。
头文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// TimAudioTool.h // #import <Foundation/Foundation.h> @interface TimAudioTool : NSObject + (instancetype) sharedAudioTool; - (void)playSound:(NSString*)fileName; /* filename 为包含文件格式的文件名 */ - (void)disposeSound:(NSString*)fileName; @end |
实现文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
// TimAudioTool.m // #import "TimAudioTool.h" #import <AVFoundation/AVFoundation.h> @interface TimAudioTool () // 用字典存储音效ID @property (strong, nonatomic) NSMutableDictionary *soundIDDictionary; @end @implementation TimAudioTool static id _audioTool; + (instancetype)allocWithZone:(struct _NSZone *)zone{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _audioTool = [super allocWithZone:zone]; }); return _audioTool; } + (id)copyWithZone:(NSZone*)zone{ return _audioTool; } + (instancetype)sharedAudioTool{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _audioTool = [self new]; }); return _audioTool; } - (NSMutableDictionary *)soundIDDictionary{ if (!_soundIDDictionary) { _soundIDDictionary = [NSMutableDictionary dictionary]; } return _soundIDDictionary; } // 播放音效 - (void)playSound:(NSString*)fileName{ if (!fileName) return; // 获取音效ID SystemSoundID soundID = [self.soundIDDictionary[fileName] unsignedIntValue]; // 如不存在则从bundle中加载 if (!soundID) { NSURL *url = [[NSBundle mainBundle] URLForResource:fileName withExtension:nil]; // 判断url是否为空 if (!url) return; AudioServicesCreateSystemSoundID((__bridge CFURLRef)url, &soundID); // 将音效ID存入字典 [self.soundIDDictionary setObject:@(soundID) forKey:fileName]; } // 播放音效 AudioServicesPlaySystemSound(soundID); } // 销毁音效 - (void)disposeSound:(NSString*)fileName{ if (!fileName) return; SystemSoundID soundID = [self.soundIDDictionary[fileName] unsignedIntValue]; AudioServicesDisposeSystemSoundID(soundID); // 将音效ID从字典中移除 [self.soundIDDictionary removeObjectForKey:fileName]; } @end |
长音频播放(AVAudioPlayer)
对于诸如音乐这种需要自定义控制的长音频,System Sound Service 显得力不从心,此时我们使用的是 AVFoundation.framework 中的 AVAudioPlayer 类。AVAudioPlayer实际上可以当作一个代码控制的对单一音频的无界面播放器。
使用步骤
- 通过 url 或NSData 为音频初始化一个 AVAudioPlayer 对象;
- 设置 AVAudioPlayer 对象属性;
- 调用 play、pause、stop 等方法进行控制。
AVAudioPlayer 的常用属性和方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
/* 可控属性 */ @property(assign, nullable) id<AVAudioPlayerDelegate> delegate /* 代理 */ @property float pan; /* 声道控制,范围从 -1.0 到 1.0。 -1.0 表示完全左声道,0.0 表示左右声道平衡,1.0 表示完全为右声道 */ @property float volume; /* 音量控制,范围从 0.0 到 1.0 */ @property BOOL enableRate; /* 是否允许改变播放速率,必须在 prepareToPlay 方法前设置 */ @property float rate; /* 播放速率控制,范围从 0.5 到 2.0,必须设置 enableRate 属性为 YES */ @property NSTimeInterval currentTime; /* 当前播放时长 */ @property NSInteger numberOfLoops; /* 循环播放次数,负数表示无限循环 */ /* 只读属性 */ @property(readonly, getter=isPlaying) BOOL playing; /* 是否正在播放 */ @property(readonly) NSUInteger numberOfChannels; /* 声道数 */ @property(readonly) NSTimeInterval duration; /* 音频总时长 */ @property(readonly, nullable) NSURL *url; /* 音频文件的 URL,如果不是以 URL 方式创建则返回 nil */ @property(readonly, nullable) NSData *data; /* 音频文件的 NSData 数据,如果不是以 NSData 方式创建则返回 nil */ @property(readonly) NSTimeInterval deviceCurrentTime; /* 输出设备播放音频的时间,注意如果播放中被暂停此时间也会继续累加 */ /* 初始化方法 */ - (nullable instancetype)initWithContentsOfURL:(NSURL *)url error:(NSError **)outError; /* 这个 URL 不能是HTTP URL,AVAudioPlayer 不支持加载网络媒体流,只能播放本地文件 */ - (nullable instancetype)initWithData:(NSData *)data error:(NSError **)outError; /* 过程控制方法 */ - (BOOL)prepareToPlay; /* 加载音频文件到缓冲区,播放前即使音频文件没有加载到缓冲区程序也会隐式调用此方法 */ - (BOOL)play; /* 播放音频 */ - (BOOL)playAtTime:(NSTimeInterval)time NS_AVAILABLE(10_7, 4_0); /* 在指定时间位置播放音频 */ - (void)pause; /* 暂停播放 */ - (void)stop; /* 停止播放 */ /* 代理方法 */ - (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag; /* 播放完成时调用 */ - (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError * __nullable)error; /* 音频解码发生错误时调用 */ |
简单应用
基于和短音频播放相似的情况,我们最好在工具类中对 AVAudioPlayer 进行一些重复代码的封装,同时提供一个返回 AVAudioPlayer 对象的方法方便自定义设置。
直接添加在上文提供的工具类中。
头文件:
1 2 3 4 5 |
- (void)playAudio:(NSString *)filename; - (void)pauseAudio:(NSString *)filename; - (void)stopAudio:(NSString *)filename; - (AVAudioPlayer *)getAudioPlayer:(NSString *)filename; |
实现文件:
-
自定义一个存放 AVAudioPlayer 对象的字典:
12@property (strong, nonatomic) NSMutableDictionary *audioDictionary;并初始化:
1234567- (NSMutableDictionary *)audioDictionary{if (!_audioDictionary) {_audioDictionary = [NSMutableDictionary dictionary];}return _audioDictionary;} -
实现声明方法:
1234567891011121314151617181920212223242526272829303132333435363738394041- (void)playAudio:(NSString* )filename{if (!filename) return;AVAudioPlayer *player = self.audioDictionary[filename];if (!player) {NSURL *url = [[NSBundle mainBundle] URLForResource:filename withExtension:nil];if (!url) return;player = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];// 初始化设置player.enableRate = YES;[player prepareToPlay];// 添加到字典中self.audioDictionary[filename] = player;}if (!player.isPlaying) {[player play];}}- (void)pauseAudio:(NSString *)filename{if (!filename) return;AVAudioPlayer *player = self.audioDictionary[filename];if (player.isPlaying) {[player pause];}}- (void)stopAudio:(NSString *)filename{if (!filename) return;AVAudioPlayer *player = self.audioDictionary[filename];[player stop];[self.audioDictionary removeObjectForKey:filename];}- (AVAudioPlayer *)getAudioPlayer:(NSString *)filename{if (!filename) return nil;return self.audioDictionary[filename];}
下面我们就利用 AVAudioPlayer 做一个简易本地音乐播放器。
-
在 storyboard 上搭建播放器界面;
-
拖线;
-
代码实现相关功能。
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174// ViewController.m//#import "ViewController.h"#import <AVFoundation/AVFoundation.h>#import "TimAudioTool.h"@interface ViewController () <AVAudioPlayerDelegate>// 音频文件名数组@property(strong, nonatomic)NSMutableArray *musics;// 播放序号@property(assign, nonatomic)int playingNumber;// 定时器@property(strong, nonatomic)CADisplayLink *link;// 判断是否正在播放@property(assign, nonatomic, getter=isPlaying)BOOL playing;// 播放按钮@property (weak, nonatomic) IBOutlet UIButton *playPauseButton;// 歌曲详情@property (weak, nonatomic) IBOutlet UILabel *audioLabel;// 播放进度@property (weak, nonatomic) IBOutlet UIProgressView *audioProgress;/*** 播放器选项*/- (IBAction)playAndPause;- (IBAction)stop;- (IBAction)next;- (IBAction)previous;- (IBAction)volumeChanged:(UISlider *)sender;@end@implementation ViewController#pragma mark - 初始化方法- (NSMutableArray *)musics{if (!_musics) {_musics = @[@"music1.mp3", @"music2.mp3", @"music3.mp3", @"music4.mp3"];}return _musics;}/*** 初始化定时器*/- (CADisplayLink *)link{if (!_link) {_link = [CADisplayLink displayLinkWithTarget:self selector:@selector(update)];}return _link;}#pragma mark - 生命周期方法- (void)viewDidLoad {[super viewDidLoad];// 设置初始曲目self.playingNumber = 0;self.audioLabel.text = [NSString stringWithFormat:@"曲目%d", self.playingNumber + 1];}#pragma mark - 监听播放器按钮点击/*** 监听播放按钮点击*/- (IBAction)playAndPause{NSString *filename = self.musics[self.playingNumber];self.audioLabel.text = [NSString stringWithFormat:@"曲目%d", self.playingNumber + 1];// 将定时器添加到 runLoop 中[self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];if (self.isPlaying) {[[TimAudioTool sharedAudioTool] pauseAudio:filename];[self.playPauseButton setImage:[UIImage imageNamed:@"play"] forState:UIControlStateNormal];} else {[[TimAudioTool sharedAudioTool] playAudio:filename];[self.playPauseButton setImage:[UIImage imageNamed:@"pause"] forState:UIControlStateNormal];// 设置代理AVAudioPlayer *player = [[TimAudioTool sharedAudioTool] getAudioPlayer:filename];player.delegate = self;}self.playing = !self.playing;}/*** 监听停止按钮点击*/- (IBAction)stop {// 废弃并清空定时器[self.link invalidate];self.link = nil;self.playing = NO;[self.playPauseButton setImage:[UIImage imageNamed:@"play"] forState:UIControlStateNormal];self.audioProgress.progress = 0;NSString *filename = self.musics[self.playingNumber];[[TimAudioTool sharedAudioTool] stopAudio:filename];}/*** 监听下一首和上一首按钮点击*/- (IBAction)next {[self stop];if (self.playingNumber < (self.musics.count - 1)) {self.playingNumber++;} else {self.playingNumber = 0;}[self playAndPause];}- (IBAction)previous {[self stop];if (self.playingNumber) {self.playingNumber--;} else {self.playingNumber = (int)self.musics.count - 1;}[self playAndPause];}/*** 监听音量滑动条*/- (IBAction)volumeChanged:(UISlider *)sender {NSString *filename = self.musics[self.playingNumber];AVAudioPlayer *player = [[TimAudioTool sharedAudioTool] getAudioPlayer:filename];player.volume = sender.value;}#pragma mark - link 回调方法- (void)update{NSString *filename = self.musics[self.playingNumber];AVAudioPlayer *player = [[TimAudioTool sharedAudioTool] getAudioPlayer:filename];CGFloat progress = player.currentTime / player.duration;[self.audioProgress setProgress:progress animated:YES];}#pragma mark- audioPlayer 代理方法- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag{// 播放完成自动跳转下一首[self next];}@end
想获得去掉 5 元限制的证券账户吗?

如果您想去掉最低交易佣金 5 元限制,使用微信扫描左边小程序二维码,访问微信小程序「优财助手」,点击底部菜单「福利」,阅读文章「通过优财开证券账户无最低交易佣金 5 元限制」,按照文章步骤操作即可获得免 5 元证券账户,股票基金交易手续费率万 2.5。
请注意,一定要按照文章描述严格操作,如错误开户是无法获得免 5 元证券账户的。