MusicSuperPlayer.mm 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537
  1. //
  2. // MusicSuperPlayer.m
  3. // BuguLive
  4. //
  5. // Created by 岳克奎 on 16/12/6.
  6. // Copyright © 2016年 xfg. All rights reserved.
  7. //
  8. #import "MusicSuperPlayer.h"
  9. #import "SuperpoweredAdvancedAudioPlayer.h"
  10. #import "SuperpoweredReverb.h"
  11. #import "SuperpoweredFilter.h"
  12. #import "Superpowered3BandEQ.h"
  13. #import "SuperpoweredEcho.h"
  14. #import "SuperpoweredRoll.h"
  15. #import "SuperpoweredFlanger.h"
  16. #import "SuperpoweredSimple.h"
  17. #import "SuperpoweredIOSAudioIO.h"
  18. #import <mach/mach_time.h>
  19. #import <libkern/OSAtomic.h>
  20. #include <AudioToolbox/AudioConverter.h>
  21. @interface MusicSuperPlayer () <SuperpoweredIOSAudioIODelegate,MusicCenterManagerDelegate>
  22. @end
  23. @implementation MusicSuperPlayer
  24. {
  25. SuperpoweredAdvancedAudioPlayer *_superPlayer; //播放器
  26. SuperpoweredIOSAudioIO *_superHandles; //player处理器
  27. Superpowered3BandEQ *_superBandEQ; //音乐平衡器(主要控制音效的 高中低效果)
  28. float *_stereoBuffer; //立体缓存 感觉这应该是当前播放 缓冲区 与进度 音效息息相关
  29. SuperpoweredFX *effects[NUMFXUNITS];
  30. char * _tempBuffer;
  31. char* _outDataBuffer;
  32. int _outBufferSize;
  33. int frame;
  34. int config;
  35. CADisplayLink *displayLink;
  36. uint64_t *superpoweredAvgUnits;
  37. uint64_t *superpoweredMaxUnits;
  38. uint64_t *coreaudioAvgUnits;
  39. uint64_t *coreaudioMaxUnits;
  40. __volatile int _outBufferPushInOffset;
  41. __volatile int _outBufferCopyOutOffset;
  42. __volatile int _outBufferNowSize;//当前有多少
  43. OSSpinLock _outBufferSpinlock;
  44. }
  45. #pragma mark - life cycle------------------------------------- 控制生命周期区域 --------------------------------------
  46. /**
  47. * @brief: 单利
  48. *
  49. * @discussion:我的想法是,用单利管理,这样能够通过C++的player对应的控制器来控制。播放,暂停。如果不这样,需要频繁的
  50. */
  51. static MusicSuperPlayer *signleton = nil;
  52. + (instancetype)allocWithZone:(struct _NSZone *)zone
  53. {
  54. static dispatch_once_t onceToken;
  55. dispatch_once(&onceToken, ^{
  56. signleton = [super allocWithZone:zone];
  57. });
  58. return signleton;
  59. }
  60. + (MusicSuperPlayer *)shareManager
  61. {
  62. static dispatch_once_t onceToken;
  63. dispatch_once(&onceToken, ^{
  64. signleton = [[self alloc] init];
  65. MUSIC_CENTER_MANAGER.delegate = signleton;
  66. });
  67. return signleton;
  68. }
  69. + (id)copyWithZone:(struct _NSZone *)zone
  70. {
  71. return signleton;
  72. }
  73. + (id)mutableCopyWithZone:(struct _NSZone *)zone
  74. {
  75. return signleton;
  76. }
  77. #pragma mark -private methods ------------------------------------- 私人方法区域 ------------------------------------------
  78. #pragma mark- 生成C++播放器
  79. /**
  80. * @brief:生成C++播放器.
  81. *
  82. * @prama: samplerate 采样率 44100 48000
  83. * @prama: musicFilePathStr 音乐路径Str
  84. *
  85. * @Step: superPlayer 只需要采样率 (需要考虑如果采样率变了,再继续采用懒加载,需要调什么更变采样率)
  86. * @Step: play(false) 默认不播放状态
  87. * @Step: setBpm(124.0f) beat per minute=每分钟节拍数 没有auto-bpm检测内部,你必须设置原始bpm的跟踪与此同步。应该被称为成功后打开()
  88. * @Step: SuperpoweredFilter 过滤器。暂时理解为设置调节播放器的参数的工具
  89. * @Step:
  90. * @Step: SuperpoweredReverb 混响
  91. * @Step: _superHandles player处理器.
  92. *
  93. * @discussion:1.默认生成的C++播放器尚未加载路径。因为首次在家会走这里,如果播放器存在了,就不走这里了
  94. */
  95. -(SuperpoweredAdvancedAudioPlayer *)loadSuperPlayerwithSamplerate:(int)samplerate
  96. ofmusicFilePathStr:(NSString *)musicFilePathStr{
  97. //samplerate =44100;
  98. SuperpoweredAdvancedAudioPlayer *superPlayer = new SuperpoweredAdvancedAudioPlayer(NULL,
  99. NULL,
  100. samplerate,
  101. 0);
  102. //superPlayer->play(false); 这个应该是先默认不要播放+再调播放。目前不需要这样。播放就播放
  103. superPlayer->setBpm(124.0f);
  104. return superPlayer;
  105. }
  106. - (void)showSuperHanes{
  107. // 音效调的高中低调节
  108. _superBandEQ = new Superpowered3BandEQ(_samplerate);
  109. _superBandEQ->bands[0] = 1.0f;
  110. _superBandEQ->bands[1] = 1.0f;
  111. _superBandEQ->bands[2] = 1.0f;
  112. _superBandEQ->enable(true);
  113. //播放器处理类 负责开启关闭 但是开启关闭并不能改变播放状态,方法获取状态,需要暂停恢复方法来改变
  114. if (!_superHandles) {
  115. _superHandles =[[SuperpoweredIOSAudioIO alloc]initWithDelegate:(id<SuperpoweredIOSAudioIODelegate>)self
  116. preferredBufferSize:12
  117. preferredMinimumSamplerate:_samplerate
  118. audioSessionCategory:AVAudioSessionCategoryPlayAndRecord
  119. channels:2];
  120. }
  121. [_superHandles setdelg:self];
  122. }
  123. #pragma mark- 播放器加载歌曲
  124. /**
  125. *@brief: 播放器加载歌曲
  126. *
  127. *@Step: 需要音乐处理器 开启
  128. */
  129. - (BOOL)musicSuperPlayerPlayWithMudicPathStr:(NSString *)musicFilePathStr
  130. samplerate:(int)samplerate{
  131. //加锁
  132. _outBufferSpinlock = OS_SPINLOCK_INIT;
  133. _outBufferPushInOffset = 0;
  134. _outBufferPushInOffset = 0;
  135. //初始化 基本数据
  136. // 默认音量为6
  137. _accompanyValue = 0.5;
  138. //默认音调为0
  139. self.pitchValue = 0;
  140. //音效类型
  141. self.selectEffectTag = 0;
  142. //采用率
  143. _samplerate = samplerate;
  144. //立体缓存 初始化
  145. if (posix_memalign((void **)&_stereoBuffer, 16, 1024*4 + 128) != 0)
  146. {
  147. return false;
  148. }
  149. //输出内存 初始化
  150. _outBufferSize = 1024*4*8; //
  151. if (posix_memalign((void **)&_outDataBuffer, 16, _outBufferSize ) != 0 )
  152. {
  153. return false;
  154. }
  155. // 缓冲内存 初始化
  156. if (posix_memalign((void **)&_tempBuffer, 16, 1024*4*8 ) != 0 )
  157. {
  158. return false;
  159. }
  160. //采样率暂时默认 44100
  161. _samplerate =MUSIC_SAMPLERATE;
  162. //如果播放器存在就不再创建
  163. if (!_superPlayer) {
  164. _superPlayer = [self loadSuperPlayerwithSamplerate:_samplerate
  165. ofmusicFilePathStr:musicFilePathStr];
  166. }
  167. if (!_superHandles) {
  168. [self showSuperHanes];
  169. }
  170. /* test 测试本地数据
  171. _superPlayer->open([[[NSBundle mainBundle] pathForResource:@"track"
  172. ofType:@"mp3"] fileSystemRepresentation]);
  173. */
  174. //加载指定路径
  175. _superPlayer->open([musicFilePathStr fileSystemRepresentation]);
  176. return YES;
  177. }
  178. #pragma mark - push buffer 推送数据至缓冲区(Data)
  179. /**
  180. * @brief: 推送数据至缓冲区
  181. *
  182. * @Step:新申请的内存做初始化工作 函数void *memset(void *s, int ch, size_t n); 将s中当前位置后面的n个字节 (typedef unsigned int size_t )用 ch 替换并返回 s 。作用是在一段内存块中填充某个给定的值,它是对较大的结构体或数组进行清零操作的一种最快方法
  183. * 参数prama:1.s 指定的内存地址 2. 3.指定块的大小
  184. *
  185. *
  186. *
  187. */
  188. - (void)pushBuffer:(char *)inbuffer bufferOfSize:(int)buffersize{
  189. //枷锁
  190. OSSpinLockLock(&_outBufferSpinlock);
  191. if( _outBufferPushInOffset + buffersize > _outBufferSize )
  192. {
  193. memcpy( &_outDataBuffer[ _outBufferPushInOffset ] , inbuffer, _outBufferSize - _outBufferPushInOffset );
  194. memcpy( &_outDataBuffer[0] , &inbuffer [ _outBufferSize - _outBufferPushInOffset ] , buffersize - (_outBufferSize - _outBufferPushInOffset) );
  195. _outBufferPushInOffset = buffersize - (_outBufferSize - _outBufferPushInOffset);
  196. }
  197. else
  198. {
  199. memcpy( &_outDataBuffer[ _outBufferPushInOffset ] , inbuffer, buffersize );
  200. _outBufferPushInOffset += buffersize;
  201. }
  202. _outBufferNowSize += buffersize;
  203. if( _outBufferNowSize > _outBufferSize )
  204. {
  205. }
  206. OSSpinLockUnlock(&_outBufferSpinlock);
  207. }
  208. #pragma mark -public methods ------------------------------------------ 公有方法区域 -----------------------------------------
  209. #pragma mark- 获取当前音乐的播放状态
  210. /**
  211. * @brief: 音乐播放暂停后恢复播放
  212. *
  213. * @return: YES:代表正在播放中 NO:代表操木有音乐或播放暂停
  214. *
  215. * @use:
  216. */
  217. - (BOOL)showMusicPlayingState{
  218. if (!self->_superPlayer) {
  219. return NO;
  220. }
  221. if (self->_superPlayer->playing) {
  222. return YES;
  223. }else{
  224. return NO;
  225. }
  226. }
  227. #pragma mark - 将内存里的数据copy 推送到腾讯 (Data)
  228. /**
  229. * @brief:将内存里的数据copy 推送到混音接口 (Data)
  230. *
  231. * @prama:buffer C的字符 传入的是 混音需要 透传的 内存地址
  232. * @prama: buffersize 内存地址大小
  233. *
  234. * @discussion:如果没有足够的 buffersize 长度数据,就不拷贝 返回0 ,等着下次,
  235. *
  236. * @use: 腾讯互动SDK 混音接口 数据传输
  237. */
  238. -(int)copyOutBuffer:(char*)buffer
  239. buffersize:(int)buffersize
  240. {
  241. //枷锁
  242. OSSpinLockLock(&_outBufferSpinlock);
  243. //当前数据太小,等下一次
  244. if( _outBufferNowSize < buffersize )
  245. { //解锁
  246. OSSpinLockUnlock(&_outBufferSpinlock);
  247. return 0;
  248. }
  249. // _outBufferCopyOutOffset 腾讯的算法里也没个解释,真是醉了。理解成为 内存动态幅度偏移?
  250. if( _outBufferCopyOutOffset + buffersize > _outBufferSize )
  251. {
  252. memcpy( buffer , &_outDataBuffer[ _outBufferCopyOutOffset ], _outBufferSize - _outBufferCopyOutOffset );
  253. memcpy( &buffer[ _outBufferSize - _outBufferCopyOutOffset ] , &_outDataBuffer[ 0 ], buffersize - (_outBufferSize - _outBufferCopyOutOffset) );
  254. _outBufferCopyOutOffset = buffersize - (_outBufferSize - _outBufferCopyOutOffset);
  255. }
  256. else
  257. {
  258. memcpy( buffer , &_outDataBuffer[ _outBufferCopyOutOffset ], buffersize );
  259. _outBufferCopyOutOffset += buffersize;
  260. }
  261. _outBufferNowSize -= buffersize;
  262. //解锁
  263. OSSpinLockUnlock(&_outBufferSpinlock);
  264. //返回的是 int 作为混音memcpy 块的 大小,感觉腾讯给的算法不对劲呢
  265. return buffersize;
  266. }
  267. #pragma mark-system delegate ------------------------------------------ 实现系统代理区域----------------------------------------
  268. #pragma mark-SuperpoweredIOSAudioIODelegate 处理器的代理方法(player delegate)
  269. /**
  270. * @brief:音乐处理器的代理方法
  271. *
  272. * @prama: buffers 变化的过程中返回的数据
  273. * @prama: inputChannels
  274. * @prama: outputChannels
  275. * @prama: numberOfSamples
  276. * @prama: samplerate
  277. * @prama: hostTime
  278. *
  279. *
  280. * @Step:stereoBuffer准备好了,我们把完成音频到所请求的缓冲区。
  281. */
  282. - (BOOL)audioProcessingCallback:(float **)buffers
  283. inputChannels:(unsigned int)inputChannels
  284. outputChannels:(unsigned int)outputChannels
  285. numberOfSamples:(unsigned int)numberOfSamples
  286. samplerate:(unsigned int)samplerate
  287. hostTime:(UInt64)hostTime{
  288. if( !_superPlayer->playing )
  289. {
  290. return false;
  291. }
  292. if(samplerate!=self->_samplerate){
  293. _samplerate =samplerate;
  294. _superPlayer->setSamplerate(samplerate);
  295. _superBandEQ->setSamplerate(samplerate);
  296. }
  297. //设置 silence
  298. //_accompanyValue 第三个参数0-1.0f 因为代码总是不断走这里 是不是可以 控制音量 通过单利player管理器把playerVC记录的sldier数据传过来。非常爽。
  299. //numberOfSamples 腾讯混音 音帧 默认48000 但是 采样率这么高,直播卡啊,而且44100 是人耳朵识别极限。那就就统一 44100
  300. bool silence = !_superPlayer->process(_stereoBuffer,
  301. false,
  302. numberOfSamples,
  303. _accompanyValue,
  304. 0.0f,
  305. -1.0);
  306. //_superBandEQ 这个类总感觉是在控制 将要播放活正字啊播放 那段内存数据,比如改变音效效果LMH(高中低),真不知道设置音效类型居然改变的是声音的高中低。真是醉了。
  307. //_stereoBuffer 感觉这应该是当前播放 缓冲区 与进度 音效息息相关 突然感觉 _superBandEQ 处理器 当前进行的功能 跟内存是什么有关系。到底什么关系呢。
  308. //numberOfSamples 不知道用统一的采用率。会是什么情况、
  309. _superBandEQ->process(_stereoBuffer, _stereoBuffer, numberOfSamples);
  310. //buffers 注意
  311. if (!silence){ SuperpoweredDeInterleave(_stereoBuffer,
  312. buffers[0],
  313. buffers[1],
  314. numberOfSamples);
  315. }
  316. //_tempBuffer 临时内存 初始化
  317. //memset初始化每一块内存 初始化地址 _tempBuffer。 块的大小 1024*8*4--->后期闲暇时候,可以考虑块的更变,能不能解决某些问题。比如观众看了,是不传下去的数据太小咯
  318. memset(_tempBuffer, 0, 1024*8*4);
  319. SuperpoweredFloatToShortInt(_stereoBuffer, (short int*)_tempBuffer, numberOfSamples); //播放非必须
  320. //把当前播放的数据 嫩到推送到临时存储区,之前改变过bufferOfSize,但是 电流声很大。
  321. [self pushBuffer:_tempBuffer
  322. bufferOfSize: numberOfSamples *4];
  323. MUSIC_CENTER_MANAGER.recordMusicTotalDuration = _superPlayer->durationSeconds;
  324. MUSIC_CENTER_MANAGER.recordMusicCurrentDuration = _superPlayer->positionSeconds;
  325. MUSIC_CENTER_MANAGER.musicPresent = _superPlayer->positionPercent;
  326. return !silence;
  327. }
  328. #pragma mark - App进程打断后回到暂定方法 (player delegate Method)
  329. /**
  330. * @brief:音乐打断后回到暂定方法
  331. *
  332. * @use: :音乐进程打断后
  333. *
  334. * @discusssion:要告诉调度中心,并且自身要暂停
  335. */
  336. - (void)interruptionStarted {
  337. if(_superPlayer){
  338. _superPlayer->pause(0,1);
  339. MUSIC_CENTER_MANAGER.musicPlayingState = NO;
  340. }
  341. }
  342. #pragma mark - 音乐打断结束后
  343. /**
  344. * @brief:音乐打断结束后回到暂定方法
  345. *
  346. * @discusssion: _superPlayer->onMediaserverInterrupt(); If the player plays Apple Lossless audio files, then we need this. Otherwise unnecessary.如果玩家扮演苹果无损音频文件,然后我们需要这个。否则没有必要的。
  347. * @use:打断结束
  348. */
  349. - (void)interruptionEnded {
  350. //
  351. }
  352. #pragma mark - 记录允许拒绝(player delegate Method)
  353. /**
  354. * @brief:音乐记录允许拒绝
  355. *
  356. * @use: :??
  357. */
  358. - (void)recordPermissionRefused {
  359. }
  360. /**
  361. * @brief:?
  362. *
  363. * @use: :??
  364. */
  365. - (void)mapChannels:(multiOutputChannelMap *)outputMap
  366. inputMap:(multiInputChannelMap *)inputMap
  367. externalAudioDeviceName:(NSString *)externalAudioDeviceName
  368. outputsAndInputs:(NSString *)outputsAndInputs {
  369. }
  370. #pragma mark- get+Set -------------------------------------------------- get+Se方法区域 ----------------------------------------
  371. //#pragma mark - 音乐路径变更
  372. ///**
  373. // * @brief: 音乐路径变更
  374. // *
  375. // * @prama:musicFilePathStr
  376. // *
  377. // *@use: 比如切换歌曲
  378. // */
  379. //- (void)setMusicFilePathStr:(NSString *)musicFilePathStr{
  380. // if(_musicFilePathStr!=musicFilePathStr){
  381. // _musicFilePathStr = musicFilePathStr;
  382. // }
  383. //}
  384. #pragma mark - 采样率变更
  385. /**
  386. * @brief: 采样率变更
  387. *
  388. * @prama:samplerate
  389. *
  390. *@use: 采样率变更,但是目前固定44100
  391. */
  392. - (void)setSamplerate:(int)samplerate{
  393. if (_samplerate!= samplerate) {
  394. _samplerate = samplerate;
  395. }
  396. }
  397. #pragma mark - 音调Value的调节
  398. /**
  399. * @brief: 音调Value的调节
  400. *
  401. * @prama:pitchValue 必须int -12 ~ 12
  402. *
  403. *@use:音效调节音调Value,数据代理传到音效界面,再代理传到播放器 通过单利赋值。内部在让C++播放器改变下就ok
  404. */
  405. - (void)setPitchValue:(NSInteger)pitchValue{
  406. if (_pitchValue>=-12&&_pitchValue<=12) {
  407. _pitchValue = pitchValue;
  408. if (self->_superPlayer != NULL) {
  409. self->_superPlayer->setPitchShift((int)pitchValue);
  410. }
  411. }
  412. }
  413. #pragma mark - 音调Value的调节
  414. /**
  415. * @brief: 音效效果 Value的调节
  416. *
  417. * @prama: selectEffectTag
  418. *
  419. *@discussion:总感觉设置木有意思。还不如让用户自己调节数据。 库里的音效效果比较怪里怪气的,应该处理音效效果滴。但是设计却要处理 高中低音,真是醉了
  420. *
  421. *@use:音效的效果类型调节
  422. */
  423. - (void)setSelectEffectTag:(NSInteger)selectEffectTag{
  424. _selectEffectTag = selectEffectTag;
  425. if(_superBandEQ){
  426. //原声
  427. if(_selectEffectTag == 0){
  428. self->_superBandEQ->bands[0] = 1.0f;
  429. self->_superBandEQ->bands[0] = 1.0f;
  430. self->_superBandEQ->bands[0] = 1.0f;
  431. }
  432. //悠扬
  433. if(_selectEffectTag == 1){
  434. self->_superBandEQ->bands[0] = 1.0f;
  435. self->_superBandEQ->bands[0] = 1.5f;
  436. self->_superBandEQ->bands[0] = 1.5f;
  437. }
  438. //圆润
  439. if(_selectEffectTag == 2){
  440. self->_superBandEQ->bands[0] = 1.5f;
  441. self->_superBandEQ->bands[0] = 1.5f;
  442. self->_superBandEQ->bands[0] = 1.0f;
  443. }
  444. //流行
  445. if(_selectEffectTag == 3){
  446. self->_superBandEQ->bands[0] = 1.1f;
  447. self->_superBandEQ->bands[0] = 1.3f;
  448. self->_superBandEQ->bands[0] = 1.0f;
  449. }
  450. //轻松
  451. if(_selectEffectTag == 4){
  452. self->_superBandEQ->bands[0] = 1.0f;
  453. self->_superBandEQ->bands[0] = 0.8f;
  454. self->_superBandEQ->bands[0] = 0.3f;
  455. }
  456. //空灵
  457. if(_selectEffectTag == 5){
  458. self->_superBandEQ->bands[0] = 1.0f;
  459. self->_superBandEQ->bands[0] = 1.5f;
  460. self->_superBandEQ->bands[0] = 1.5f;
  461. }
  462. }
  463. }
  464. #pragma mark - C++销毁
  465. /**
  466. * @brief: C++销毁
  467. *
  468. * @discussion: 到底销毁哪些。我也不是很清楚。毕竟对C++不了解。貌似没有主动销毁。到底销毁 这对象变量不?
  469. *
  470. @use:当这个单利销毁。那基本是杀死app时候。
  471. */
  472. - (void)dealloc{
  473. if( _outDataBuffer )
  474. free( _outDataBuffer );
  475. if( _stereoBuffer )
  476. free( _stereoBuffer );
  477. if( _tempBuffer)
  478. free( _tempBuffer );
  479. }
  480. #pragma mark -音乐管理中心代理方法
  481. //音乐加载
  482. - (BOOL)showMusicPlayOfMusicPathStr:(NSString *)musicPathStr
  483. ofSamplerateNum:(int)samplerateNum{
  484. return [self musicSuperPlayerPlayWithMudicPathStr:musicPathStr
  485. samplerate:samplerateNum];
  486. }
  487. #pragma -音乐 播放 暂停
  488. - (BOOL)musicStartOrStopPlayOfPlayingState:(BOOL)isPlayingSate{
  489. if (!_superPlayer) {
  490. // [FanweMessage alert:ASLocalizedString(@"当前音乐播放器不存在")];
  491. return NO;
  492. }
  493. //需要 播放
  494. if(isPlayingSate){
  495. //上面2个不管 状态如何都要走 下面切换 要根据播放器状态 因为第一次加载 获取的状态也是NO
  496. if (!_superPlayer->playing) {
  497. _superPlayer->togglePlayback();
  498. [_superHandles start];
  499. }
  500. }else{
  501. //暂停
  502. if(_superPlayer->playing){
  503. _superPlayer->togglePlayback();
  504. [_superHandles stop];
  505. }
  506. }
  507. return _superPlayer->playing;
  508. }
  509. @end