framework2
1226 строк · 36.3 Кб
1//
2// ofAVEngineSoundPlayer.cpp
3// soundPlayerExample
4//
5// Created by Theo Watson on 3/24/21.
6// Modified by Dan Rosser 9/5/22
7
8#include "ofAVEngineSoundPlayer.h"
9
10#ifdef OF_SOUND_PLAYER_AV_ENGINE
11
12#include "ofUtils.h"
13#include "ofMath.h"
14#include "ofLog.h"
15
16//REFS: https://github.com/ooper-shlab/AVAEMixerSample-Swift/blob/master/AVAEMixerSample/AudioEngine.m
17// https://developer.apple.com/documentation/avfaudio/avaudioengine
18// https://developer.apple.com/forums/thread/14138
19// https://developer.apple.com/forums/thread/48442
20// https://github.com/garynewby/XYAudioView/blob/master/XYAudioView/BasicAVAudioEngine.m
21// https://github.com/twilio/video-quickstart-ios/blob/master/AudioDeviceExample/AudioDevices/ExampleAVAudioEngineDevice.m
22
23static BOOL audioSessionSetup = NO;
24static AVAudioEngine * _engine = nullptr;
25
26static NSString *kShouldEnginePauseNotification = @"kShouldEnginePauseNotification";
27
28@interface AVEnginePlayer : NSObject
29
30@property(nonatomic, retain) NSTimer * timer;
31
32- (BOOL)loadWithFile:(NSString*)file;
33- (BOOL)loadWithPath:(NSString*)path;
34- (BOOL)loadWithURL:(NSURL*)url;
35- (BOOL)loadWithSoundFile:(AVAudioFile*)aSoundFile;
36
37- (void)unloadSound;
38
39- (void)play;
40- (void)play: (float)startTime;
41- (void)pause;
42- (void)stop;
43
44- (BOOL)isLoaded;
45- (BOOL)isPlaying;
46
47- (void)volume:(float)value;
48- (float)volume;
49
50- (void)pan:(float)value;
51- (float)pan;
52
53- (void)speed:(float)value;
54- (float)speed;
55
56- (void)loop:(BOOL)value;
57- (BOOL)loop;
58
59- (void)multiPlay:(BOOL)value;
60- (BOOL)multiPlay;
61
62- (void)position:(float)value;
63- (float)position;
64
65- (void)positionMs:(int)value;
66- (int)positionMs;
67
68- (float)positionSeconds;
69- (float)soundDurationSeconds;
70
71
72- (void)sessionInterupted;
73
74- (AVAudioFile *)getSoundFile;
75
76- (AVAudioEngine *)engine;
77
78- (void)beginInterruption; /* something has caused your audio session to be interrupted */
79
80- (void)endInterruption; /* endInterruptionWithFlags: will be called instead if implemented. */
81
82/* notification for input become available or unavailable */
83- (void)inputIsAvailableChanged:(BOOL)isInputAvailable;
84
85@end
86
87
88@interface AVEnginePlayer ()
89
90//@property(nonatomic, strong) AVAudioEngine *engine;
91@property(nonatomic, strong) AVAudioMixerNode *mainMixer;
92@property(nonatomic, strong) AVAudioUnitVarispeed *variSpeed;
93@property(nonatomic, strong) AVAudioPlayerNode *soundPlayer;
94@property(nonatomic, strong) AVAudioFile *soundFile;
95@property(nonatomic, assign) bool mShouldLoop;
96@property(nonatomic, assign) BOOL bInterruptedWhileRunning;
97@property(nonatomic, assign) bool bIsPlaying;
98@property(nonatomic, assign) int mGaurdCount;
99@property(nonatomic, assign) int mRestorePlayCount;
100@property(nonatomic, assign) bool mMultiPlay;
101@property(nonatomic, assign) bool isSessionInterrupted;
102@property(nonatomic, assign) bool isConfigChangePending;
103@property(nonatomic, assign) float mRequestedPositonSeconds;
104@property(nonatomic, assign) AVAudioFramePosition startedSampleOffset;
105
106@property(nonatomic, assign) bool mPlayingAtInterruption;
107@property(nonatomic, assign) float mPositonSecondsAtInterruption;
108
109@property(nonatomic, assign) BOOL resetAudioEngine;
110
111@end
112
113@implementation AVEnginePlayer
114
115@synthesize timer;
116
117- (AVAudioEngine *) engine {
118
119if( _engine == nullptr ){
120@autoreleasepool {
121_engine = [[AVAudioEngine alloc] init];
122}
123self.resetAudioEngine = NO;
124}
125
126return _engine;
127}
128
129
130- (void) engineReconnect {
131NSLog(@"engineReconnect");
132
133if( [self engine] != nil && [[self engine] isRunning] ){
134NSLog(@"engineReconnect isRunning");
135} else {
136NSLog(@"engineReconnect is NOT Running");
137}
138if([self engine]) {
139BOOL found = NO;
140for(AVAudioPlayerNode* node in [self engine].attachedNodes) {
141if(node == self.soundPlayer) {
142break;
143}
144}
145if(found) {
146NSLog(@"engineReconnect found Node AVAudioPlayerNode - Disconnecting");
147[[self engine] detachNode:self.soundPlayer];
148}
149found = NO;
150for(AVAudioUnitVarispeed* node in [self engine].attachedNodes) {
151if(node == self.variSpeed) {
152break;
153}
154}
155if(found) {
156NSLog(@"engineReconnect found Node variSpeed- Disconnecting");
157[[self engine] detachNode:self.variSpeed];
158}
159}
160}
161
162- (void) engineReset {
163if( [self engine] != nil && [[self engine] isRunning] ){
164NSLog(@"engineReset isRunning");
165} else {
166NSLog(@"engineReset is NOT Running");
167}
168if([self engine] && [[self engine] isRunning]) {
169[_engine stop];
170self.resetAudioEngine = NO;
171}
172@autoreleasepool {
173if(_engine != nil) {
174_engine = nil;
175}
176}
177[self engine];
178}
179
180
181- (void)sessionInterupted {
182self.isSessionInterrupted = YES;
183}
184
185
186/* the interruption is over */
187- (void)endInterruptionWithFlags:(NSUInteger)flags API_AVAILABLE(ios(4.0), watchos(2.0), tvos(9.0)) { /* Currently the only flag is AVAudioSessionInterruptionFlags_ShouldResume. */
188NSLog(@"AVEnginePlayer::endInterruptionWithFlags");
189if(flags == AVAudioSessionInterruptionTypeBegan) {
190[self beginInterruption];
191} else if(flags == AVAudioSessionInterruptionTypeEnded) {
192[self endInterruption];
193}
194}
195
196/* notification for input become available or unavailable */
197- (void)inputIsAvailableChanged:(BOOL)isInputAvailable {
198NSLog(@"AVEnginePlayer::inputIsAvailableChanged");
199}
200
201// setupSharedSession is to prevent other iOS Classes closing the audio feed, such as AVAssetReader, when reading from disk
202// It is set once on first launch of a AVAudioPlayer and remains as a set property from then on
203- (void) setupSharedSession {
204#ifndef TARGET_OSX
205if(audioSessionSetup) {
206return;
207}
208NSString * playbackCategory = AVAudioSessionCategoryPlayback;
209
210AVAudioSession * audioSession = [AVAudioSession sharedInstance];
211NSError * err = nil;
212
213
214if(![audioSession setCategory:playbackCategory
215withOptions:AVAudioSessionCategoryOptionInterruptSpokenAudioAndMixWithOthers
216error:&err]) {
217
218NSLog(@"Unable to setCategory: withOptions error %@, %@", err, [err userInfo]);
219err = nil;
220
221}
222
223if(![[AVAudioSession sharedInstance] setActive: YES withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error: &err]) {
224NSLog(@"Unable to setActive: error %@, %@", err, [err userInfo]);
225err = nil;
226}
227
228double hwSampleRate = 44100.0;
229BOOL success = [[AVAudioSession sharedInstance] setPreferredSampleRate:hwSampleRate error:&err];
230if (!success) NSLog(@"Error setting preferred sample rate! %@\n", [err localizedDescription]);
231
232
233audioSessionSetup = YES;
234#endif
235}
236
237- (instancetype)init
238{
239self = [super init];
240if (self) {
241[self setupSharedSession];
242NSError * error = nil;
243
244_mainMixer = [[self engine] mainMixerNode];
245_mainMixer.outputVolume = 0.98;
246
247_mShouldLoop = false;
248_mGaurdCount = 0;
249_mMultiPlay = false;
250_bIsPlaying = false;
251_isSessionInterrupted = NO;
252_mRequestedPositonSeconds = 0.0f;
253_startedSampleOffset = 0;
254_bInterruptedWhileRunning = NO;
255_resetAudioEngine = NO;
256_mPlayingAtInterruption = NO;
257_mPositonSecondsAtInterruption = 0;
258_isConfigChangePending = NO;
259
260
261#ifndef TARGET_OSX
262
263//from: https://github.com/robovm/apple-ios-samples/blob/master/UsingAVAudioEngineforPlaybackMixingandRecording/AVAEMixerSample/AudioEngine.m
264
265[[NSNotificationCenter defaultCenter] addObserver:self
266selector:@selector(handleInterruption:)
267name:AVAudioSessionInterruptionNotification
268object:[AVAudioSession sharedInstance]];
269
270[[NSNotificationCenter defaultCenter] addObserver:self
271selector:@selector(handleRouteChange:)
272name:AVAudioSessionRouteChangeNotification
273object:[AVAudioSession sharedInstance]];
274
275[[NSNotificationCenter defaultCenter] addObserver:self
276selector:@selector(handleMediaServicesReset:)
277name:AVAudioSessionMediaServicesWereLostNotification
278object:[AVAudioSession sharedInstance]];
279
280[[NSNotificationCenter defaultCenter] addObserver:self
281selector:@selector(handleMediaServicesReset:)
282name:AVAudioSessionMediaServicesWereResetNotification
283object:[AVAudioSession sharedInstance]];
284
285[[NSNotificationCenter defaultCenter] addObserver:self
286selector:@selector(handleInterruption:)
287name:AVAudioSessionSilenceSecondaryAudioHintNotification
288object:[AVAudioSession sharedInstance]];
289
290
291
292if (@available(iOS 14.5, *)) {
293if(![[AVAudioSession sharedInstance] setPrefersNoInterruptionsFromSystemAlerts:YES error:&error]) {
294NSLog(@"Unable to setPrefersNoInterruptionsFromSystemAlerts: error %@, %@", error, [error userInfo]);
295error = nil;
296}
297} else {
298// Fallback on earlier versions
299}
300
301#endif
302
303__typeof(self) __weak weak_self = self;
304
305[[NSNotificationCenter defaultCenter] addObserverForName:kShouldEnginePauseNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
306
307/* pausing stops the audio engine and the audio hardware, but does not deallocate the resources allocated by prepare().
308When your app does not need to play audio, you should pause or stop the engine (as applicable), to minimize power consumption.
309*/
310bool isPlaying = [weak_self isPlaying] || weak_self.bIsPlaying == YES;
311if (!weak_self.isSessionInterrupted && !weak_self.isConfigChangePending) {
312
313
314NSLog(@"Pausing Engine");
315[[weak_self engine] pause];
316[[weak_self engine] reset];
317
318}
319}];
320
321// sign up for notifications from the engine if there's a hardware config change
322[[NSNotificationCenter defaultCenter] addObserverForName:AVAudioEngineConfigurationChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
323
324weak_self.resetAudioEngine = YES;
325
326NSLog(@"Received a AVAudioEngineConfigurationChangeNotification %@ notification! NOTE: %@", AVAudioEngineConfigurationChangeNotification, note.description);
327
328bool isPlaying = [weak_self isPlaying] || weak_self.bIsPlaying == YES;
329float posSecs = [weak_self positionSeconds];
330
331weak_self.mPlayingAtInterruption = isPlaying;
332weak_self.mPositonSecondsAtInterruption = posSecs;
333weak_self.bInterruptedWhileRunning = YES;
334
335[weak_self engineReconnect];
336
337weak_self.isConfigChangePending = YES;
338
339if (!weak_self.isSessionInterrupted) {
340NSLog(@"Received a %@ notification!", AVAudioEngineConfigurationChangeNotification);
341NSLog(@"Re-wiring connections");
342[weak_self makeEngineConnections];
343} else {
344NSLog(@"Session is interrupted, deferring changes");
345}
346
347
348dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.033f), dispatch_get_main_queue(), ^{
349[weak_self handleInterruption:note];
350});
351
352}];
353
354
355
356}
357
358return self;
359}
360
361- (void) handleMediaServicesReset:(NSNotification *)notification {
362
363NSUInteger interruptionType = [notification.userInfo[AVAudioSessionInterruptionTypeKey] unsignedIntegerValue];
364
365NSLog(@"Media services have been reset!");
366NSLog(@"Re-wiring connections and starting once again");
367
368// Re-configure the audio session per QA1749
369audioSessionSetup = NO;
370[self setupSharedSession];
371
372[self engineReset];
373
374if(_mainMixer != nil) {
375_mainMixer = nil;
376}
377[self engine];
378
379
380[self attachNodes];
381[self makeEngineConnections];
382
383
384[self startEngine];
385
386}
387
388- (void) handleRouteChange:(NSNotification *)notification {
389
390NSUInteger interruptionType = [notification.userInfo[AVAudioSessionInterruptionTypeKey] unsignedIntegerValue];
391
392UInt8 reasonValue = [[notification.userInfo valueForKey:AVAudioSessionRouteChangeReasonKey] intValue];
393
394#ifndef TARGET_OSX
395AVAudioSessionRouteDescription *routeDescription = [notification.userInfo valueForKey:AVAudioSessionRouteChangePreviousRouteKey];
396#endif
397
398NSLog(@"Route change:");
399switch (reasonValue) {
400case AVAudioSessionRouteChangeReasonNewDeviceAvailable:
401NSLog(@" NewDeviceAvailable");
402break;
403case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:
404NSLog(@" OldDeviceUnavailable");
405break;
406case AVAudioSessionRouteChangeReasonCategoryChange:
407NSLog(@" CategoryChange");
408#ifndef TARGET_OSX
409NSLog(@" New Category: %@", [[AVAudioSession sharedInstance] category]);
410#endif
411break;
412case AVAudioSessionRouteChangeReasonOverride:
413NSLog(@" Override");
414break;
415case AVAudioSessionRouteChangeReasonWakeFromSleep:
416NSLog(@" WakeFromSleep");
417break;
418case AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory:
419NSLog(@" NoSuitableReasonForCategory");
420break;
421default:
422NSLog(@" ReasonUnknown");
423}
424
425#ifndef TARGET_OSX
426NSLog(@"Previous route:\n");
427NSLog(@"%@", routeDescription);
428#endif
429}
430
431- (void) handleInterruption:(NSNotification *)notification {
432
433NSUInteger interruptionType = [notification.userInfo[AVAudioSessionInterruptionTypeKey] unsignedIntegerValue];
434
435
436NSLog(@"AVEnginePlayer::handleInterruption: notification:%@ %@ interruptionType: %lu", notification.name, notification.description, (unsigned long)interruptionType);
437
438
439if(interruptionType == AVAudioSessionInterruptionTypeBegan) {
440[self beginInterruption];
441} else if(interruptionType == AVAudioSessionInterruptionTypeEnded) {
442[self endInterruption];
443
444[self startEngine];
445}
446
447}
448
449- (void)beginInterruption {
450
451NSLog(@"AVEnginePlayer::beginInterruption");
452
453_isSessionInterrupted = YES;
454
455if([self isPlaying] || _bIsPlaying == YES) {
456self.bInterruptedWhileRunning = YES;
457}
458
459if([self isValid]) {
460[self.soundPlayer stop];
461}
462
463// if([self.delegate respondsToSelector:@selector(soundStreamBeginInterruption:)]) {
464// [self.delegate soundStreamBeginInterruption:self];
465// }
466}
467
468
469- (void) attachNodes {
470if( self.variSpeed == nullptr ){
471// Speed manipulator
472@autoreleasepool {
473self.variSpeed = [[AVAudioUnitVarispeed alloc] init];
474}
475self.variSpeed.rate = 1.0;
476
477}
478
479if( self.soundPlayer == nil ){
480// Sound player
481@autoreleasepool {
482self.soundPlayer = [[AVAudioPlayerNode alloc] init];
483}
484}
485
486if(self.soundPlayer != nil && self.variSpeed != nil) {
487[[self engine] attachNode:self.soundPlayer];
488[[self engine] attachNode:self.variSpeed];
489}
490}
491
492- (void) makeEngineConnections {
493_mainMixer = [[self engine] mainMixerNode];
494
495AVAudioFormat * stereoFormat;
496@autoreleasepool {
497stereoFormat = [[AVAudioFormat alloc] initStandardFormatWithSampleRate:44100 channels:2];
498}
499if(self.soundPlayer != nil) {
500[[self engine] connect:self.soundPlayer to:self.variSpeed format:stereoFormat];
501}
502if(self.variSpeed != nil) {
503[[self engine] connect:self.variSpeed to:self.mainMixer format:stereoFormat];
504}
505}
506
507- (void)endInterruption {
508NSLog(@"AVEnginePlayer::endInterruption");
509
510NSError *error;
511#ifndef TARGET_OSX
512bool success = [[AVAudioSession sharedInstance] setActive:YES error:&error];
513if (!success)
514NSLog(@"AVAudioSession set active failed with error: %@", [error localizedDescription]);
515else {
516#endif
517_isSessionInterrupted = NO;
518if (_isConfigChangePending) {
519// there is a pending config changed notification
520NSLog(@"Responding to earlier engine config changed notification. Re-wiring connections");
521[self startEngine];
522[self makeEngineConnections];
523
524_isConfigChangePending = NO;
525}
526#ifndef TARGET_OSX
527}
528#endif
529// [self engineReconnect];
530
531
532
533
534if(self.bInterruptedWhileRunning || _bIsPlaying == YES) {
535self.bInterruptedWhileRunning = NO;
536bool isPlaying = [self isPlaying] || _bIsPlaying == YES;
537float posSecs = [self positionSeconds] > 0 ? [self positionSeconds] : _mPositonSecondsAtInterruption;
538
539// std::cout << " isPlaying is " << isPlaying << " pos secs is " << posSecs << std::endl;
540
541
542if( isPlaying && posSecs >= 0 && posSecs < ([self soundDurationSeconds] + 0.017f) && self.mRestorePlayCount == 0){
543self.mRestorePlayCount++;
544
545__typeof(self) __weak weak_self = self;
546
547dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.017f), dispatch_get_main_queue(), ^{
548weak_self.mRestorePlayCount--;
549if(weak_self.bIsPlaying == NO) return;
550[weak_self play:posSecs];
551});
552}
553}
554
555// if([self.delegate respondsToSelector:@selector(soundStreamEndInterruption:)]) {
556// [self.delegate soundStreamEndInterruption:self];
557// }
558}
559
560//----------------------------------------------------------- load / unload.
561- (BOOL)loadWithFile:(NSString*)file {
562NSArray * fileSplit = [file componentsSeparatedByString:@"."];
563NSURL * fileURL = [[NSBundle mainBundle] URLForResource:[fileSplit objectAtIndex:0]
564withExtension:[fileSplit objectAtIndex:1]];
565return [self loadWithURL:fileURL];
566}
567
568- (BOOL)loadWithPath:(NSString*)path {
569NSURL * fileURL = [NSURL fileURLWithPath:path];
570return [self loadWithURL:fileURL];
571}
572
573- (BOOL)loadWithURL:(NSURL*)url {
574[self stop];
575
576NSError *error;
577@autoreleasepool {
578self.soundFile = [[AVAudioFile alloc] initForReading:url error:&error];
579}
580if (error) {
581NSLog(@"Error: %@", [error localizedDescription]);
582self.soundFile = nil;
583return NO;
584}else{
585NSLog(@"Sound file %@ loaded!", url);
586}
587
588return [self loadWithSoundFile:self.soundFile];
589}
590
591- (void)startEngine{
592
593NSError * error = nil;
594BOOL engineRunning = NO;
595BOOL problem = NO;
596if( ![[self engine] isRunning] ){
597[[self engine] startAndReturnError:&error];
598if (error) {
599NSLog(@"Error starting engine: %@", [error localizedDescription]);
600// if(self.resetAudioEngine) {
601// [self engineReset];
602// }
603problem = YES;
604
605} else {
606NSLog(@"Engine start successful");
607if(self.resetAudioEngine) {
608// [self engineReset];
609if(self.resetAudioEngine == NO)
610problem = YES;
611
612}
613engineRunning = YES;
614// [self engineReconnect];
615}
616}else{
617// NSLog(@"Engine already running");
618engineRunning = YES;
619}
620
621if( self.variSpeed == nullptr ){
622// Speed manipulator
623@autoreleasepool {
624self.variSpeed = [[AVAudioUnitVarispeed alloc] init];
625}
626self.variSpeed.rate = 1.0;
627problem = YES;
628}
629
630if( self.soundPlayer == nil ){
631// Sound player
632@autoreleasepool {
633self.soundPlayer = [[AVAudioPlayerNode alloc] init];
634}
635problem = YES;
636}
637
638if(_mainMixer != [[self engine] mainMixerNode]) {
639_mainMixer = [[self engine] mainMixerNode];
640problem = YES;
641}
642
643if(problem == YES) {
644//[[self engine] reset];
645//NSLog(@"Engine start successful - re-attaching nodes");
646[[self engine] attachNode:self.variSpeed];
647[[self engine] attachNode:self.soundPlayer];
648
649[[self engine] connect:self.variSpeed to:self.mainMixer format:[_mainMixer outputFormatForBus:0]];
650[[self engine] connect:self.soundPlayer to:self.variSpeed format:[_mainMixer outputFormatForBus:0]];
651}
652}
653
654- (BOOL)loadWithSoundFile:(AVAudioFile *)aSoundFile {
655[self stop];
656
657self.soundFile = aSoundFile;
658
659[self startEngine];
660
661self.mGaurdCount=0;
662self.mRequestedPositonSeconds = 0;
663self.startedSampleOffset = 0;
664self.mRestorePlayCount =0;
665
666return YES;
667}
668
669- (void)dealloc {
670[self unloadSound];
671}
672
673- (BOOL)isValid {
674if(self.soundPlayer != nil && self.soundPlayer.engine != nil) {
675return YES;
676}
677return NO;
678}
679
680- (void)unloadSound {
681[self stop];
682if([self isValid]) {
683[[self engine] detachNode:self.soundPlayer];
684}
685self.soundPlayer = nil;
686self.soundFile = nil;
687self.variSpeed = nil;
688}
689
690- (void)play{
691[self stop];
692self.mRequestedPositonSeconds = 0.0;
693[self play:self.mRequestedPositonSeconds];
694}
695
696- (void)playloop{
697self.mRequestedPositonSeconds = 0.0;
698[self play:self.mRequestedPositonSeconds];
699}
700
701//----------------------------------------------------------- play / pause / stop.
702- (void)play:(float)startTime{
703if([self isPlaying]) {
704[self pause];
705}
706if(self.soundPlayer == nil) {
707NSLog(@"play - soundPlayer is null");
708return;
709}
710if(_isSessionInterrupted || _isConfigChangePending){
711__typeof(self) __weak weak_self = self;
712dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.3f), dispatch_get_main_queue(), ^{
713[weak_self play:startTime];
714});
715return;
716}
717
718if( self.engine == nil || ![[self engine] isRunning] ){
719NSLog(@"play - engine is null or not running - starting");
720[self startEngine];
721}
722
723if( self.engine == nil || ![[self engine] isRunning] ){
724NSLog(@"play - engine is NULL or not running");
725return;
726}
727BOOL found = NO;
728
729for(AVAudioPlayerNode* node in [self engine].attachedNodes) {
730if(node == self.soundPlayer) {
731found = YES;
732break;
733}
734}
735
736if(self.soundPlayer.engine == nil || found == NO) {
737
738if(found == NO) {
739NSLog(@"play - engine is valid - however NO attachedNode for soundPlayer found - reseting");
740} else {
741NSLog(@"play - engine is valid - however soundPlayer.engine is NULL - reseting");
742}
743_mainMixer = [[self engine] mainMixerNode];
744
745[[self engine] attachNode:self.soundPlayer];
746[[self engine] connect:self.soundPlayer to:self.variSpeed format:[_mainMixer outputFormatForBus:0]];
747
748[[self engine] attachNode:self.variSpeed];
749[[self engine] connect:self.variSpeed to:self.mainMixer format:[_mainMixer outputFormatForBus:0]];
750}
751
752//we do this as we can't cancel completion handlers.
753//and queded completion handlers can interupt a playing track if we have retriggered it
754//so we basically do this to execute only the last completion handler ( the one for the current playing track ), and ignore the earlier ones.
755self.mGaurdCount++;
756_mPositonSecondsAtInterruption = 0;
757_isSessionInterrupted = NO;
758_mPlayingAtInterruption = NO;
759self.mRestorePlayCount = 0;
760
761self.startedSampleOffset = self.soundFile.processingFormat.sampleRate * startTime;
762AVAudioFramePosition numFramesToPlay = self.soundFile.length - self.startedSampleOffset;
763const float epsilon = 0.0000001f;
764if( startTime <= epsilon){
765self.startedSampleOffset = 0;
766numFramesToPlay = self.soundFile.length;
767}
768
769// std::cout << " startedSampleOffset is " << self.startedSampleOffset << " numFrames is " << numFramesToPlay << " self.mGaurdCount is " << self.mGaurdCount << std::endl;
770
771self.mRestorePlayCount = 0;
772
773
774
775[self.soundPlayer play];
776_bIsPlaying = YES;
777
778__typeof(self) __weak weak_self = self;
779
780[self.soundPlayer scheduleSegment:self.soundFile startingFrame:self.startedSampleOffset frameCount:numFramesToPlay atTime:0 completionHandler:^{
781
782dispatch_async(dispatch_get_main_queue(), ^{
783weak_self.mGaurdCount--;
784
785if(weak_self.bIsPlaying == NO) return;
786
787//std::cout << " need gaurd is " << self.mGaurdCount << std::endl;
788
789if( weak_self.mGaurdCount == 0 ){
790float time = [weak_self positionSeconds];
791
792float duration = [weak_self soundDurationSeconds];
793
794//have to do all this because the completion handler fires before the player is actually finished - which isn't very helpful
795float remainingTime = duration-time;
796if( remainingTime < 0 ){
797remainingTime = 0;
798}
799
800//all the other time stuff accounts for the speed / rate, except the remaining time delay
801remainingTime /= ofClamp(self.variSpeed.rate, 0.01, 100.0);
802
803if( weak_self.mShouldLoop ){
804[NSObject cancelPreviousPerformRequestsWithTarget:weak_self selector:@selector(playloop) object: weak_self.soundPlayer];
805[weak_self performSelector:@selector(playloop) withObject:self.soundPlayer afterDelay:remainingTime];
806}else{
807[NSObject cancelPreviousPerformRequestsWithTarget:weak_self selector:@selector(stop) object: weak_self.soundPlayer];
808[weak_self performSelector:@selector(stop) withObject:weak_self.soundPlayer afterDelay:remainingTime];
809}
810
811// NSLog(@"play - scheduleSegment mShouldLoop:%i - mGaurdCount:%i", _mShouldLoop, _mGaurdCount);
812} else {
813// NSLog(@"play - scheduleSegment - mGaurdCount:%i", _mGaurdCount);
814}
815
816if( weak_self.mGaurdCount < 0 ){
817weak_self.mGaurdCount=0;
818}
819
820});
821
822}];
823}
824
825- (AVAudioFile *)getSoundFile{
826return self.soundFile;
827}
828
829- (void)pause {
830_bIsPlaying = NO;
831if([self isValid]) {
832[self.soundPlayer pause];
833}
834[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(playloop) object: self.soundPlayer];
835[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(stop) object: self.soundPlayer];
836}
837
838- (void)stop {
839
840__typeof(self) __weak weak_self = self;
841
842if(_isSessionInterrupted || _isConfigChangePending){
843dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.3f), dispatch_get_main_queue(), ^{
844[weak_self stop];
845});
846return;
847}
848
849if([self isValid]) {
850[self.soundPlayer stop];
851}
852_bIsPlaying = NO;
853
854[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(playloop) object: self.soundPlayer];
855[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(stop) object: self.soundPlayer];
856
857self.startedSampleOffset = 0;
858}
859
860//----------------------------------------------------------- states.
861- (BOOL)isLoaded {
862return (self.soundPlayer != nil);
863}
864
865- (BOOL)isPlaying {
866if(![self isValid]) return NO;
867return self.soundPlayer.isPlaying;
868}
869
870//----------------------------------------------------------- properties.
871- (void)volume:(float)value {
872if(![self isValid]) return;
873self.soundPlayer.volume = value;
874}
875
876- (float)volume {
877if(![self isValid]) return 0;
878return self.soundPlayer.volume;
879}
880
881- (void)pan:(float)value {
882if(![self isValid]) return;
883self.soundPlayer.pan = value;
884}
885
886- (float)pan {
887if(![self isValid]) return 0;
888return self.soundPlayer.pan;
889}
890
891- (void)speed:(float)value {
892if([self isValid]) {
893self.variSpeed.rate = value;
894}
895}
896
897- (float)speed {
898if(![self isValid]) return 0;
899return self.variSpeed.rate;
900}
901
902- (void)loop:(BOOL)bLoop {
903self.mShouldLoop = bLoop;
904}
905
906- (BOOL)loop {
907return self.mShouldLoop;
908}
909
910- (void)multiPlay:(BOOL)value {
911self.mMultiPlay = value;
912}
913
914- (BOOL)multiPlay {
915return self.mMultiPlay;
916}
917
918- (void)position:(float)value {
919if( [self isLoaded] ){
920self.mRequestedPositonSeconds = ofClamp(value, 0, 1) * [self soundDurationSeconds];
921
922if( [self isPlaying] ){
923[self play];
924}
925}
926}
927
928- (float)position {
929if(self.soundPlayer == nil) {
930return 0;
931}
932
933if( [self isPlaying] ){
934float time = [self positionSeconds];
935float duration = [self soundDurationSeconds];
936
937if( duration > 0 ){
938float pct = ofClamp(time/duration, 0, 1);
939//NSLog(@"time is %f out of %f pct is %f", time, duration, pct );
940return pct;
941}
942}
943return 0;
944}
945
946- (void)positionMs:(int)value {
947if( [self isLoaded] ){
948float oldDuration = [self positionSeconds];
949self.mRequestedPositonSeconds = ofClamp(((float)value)/1000.0, 0, [self soundDurationSeconds]);
950
951NSLog(@"positionMS: from: %f toNewPos: %f", oldDuration, self.mRequestedPositonSeconds );
952
953if( [self isPlaying] ){
954[self play];
955}
956}
957}
958
959- (int)positionMs {
960float timeSeconds = [self positionSeconds];
961return timeSeconds/1000.0;
962}
963
964- (float)positionSeconds{
965if( [self isPlaying] && self.variSpeed != nil && self.engine != nil){
966AVAudioTime * playerTime = [self.soundPlayer playerTimeForNodeTime:self.soundPlayer.lastRenderTime];
967float time = 0;
968if(playerTime != nil)
969time =((self.startedSampleOffset + playerTime.sampleTime) / playerTime.sampleRate);
970return time;
971}
972return 0.0;
973}
974
975- (float)soundDurationSeconds{
976if( [self isLoaded] && self.variSpeed != nil && self.engine != nil){
977float duration = 0.0;
978if(self.soundFile.processingFormat != nil)
979duration = self.soundFile.length / self.soundFile.processingFormat.sampleRate;
980return duration;
981}
982return 0.0;
983}
984
985@end
986
987
988
989using namespace std;
990
991ofAVEngineSoundPlayer::ofAVEngineSoundPlayer() {
992soundPlayer = NULL;
993}
994
995ofAVEngineSoundPlayer::~ofAVEngineSoundPlayer() {
996unload();
997}
998
999bool ofAVEngineSoundPlayer::load(const std::filesystem::path& fileName, bool stream) {
1000if(soundPlayer != NULL) {
1001unload();
1002}
1003
1004string filePath = ofToDataPath(fileName);
1005@autoreleasepool {
1006soundPlayer = [[AVEnginePlayer alloc] init];
1007}
1008BOOL bOk = [(AVEnginePlayer *)soundPlayer loadWithPath:[NSString stringWithUTF8String:filePath.c_str()]];
1009
1010return bOk;
1011}
1012
1013void ofAVEngineSoundPlayer::unload() {
1014if(soundPlayer != NULL) {
1015
1016[(AVEnginePlayer *)soundPlayer unloadSound];
1017@autoreleasepool {
1018soundPlayer = nil;
1019}
1020}
1021if( bAddedUpdate ){
1022ofRemoveListener(ofEvents().update, this, &ofAVEngineSoundPlayer::updateFunction);
1023bAddedUpdate = false;
1024}
1025cleanupMultiplayers();
1026}
1027
1028void ofAVEngineSoundPlayer::play() {
1029if(soundPlayer == NULL) {
1030return;
1031}
1032
1033auto mainPlayer = (AVEnginePlayer *)soundPlayer;
1034if( [mainPlayer multiPlay] && [mainPlayer isPlaying] ){
1035
1036AVEnginePlayer * extraPlayer;
1037@autoreleasepool {
1038extraPlayer = [[AVEnginePlayer alloc] init];
1039}
1040BOOL bOk = [extraPlayer loadWithSoundFile:[mainPlayer getSoundFile]];
1041if( bOk ){
1042[extraPlayer speed:[mainPlayer speed]];
1043[extraPlayer pan:[mainPlayer pan]];
1044[extraPlayer volume:[mainPlayer volume]];
1045[extraPlayer play];
1046
1047mMultiplayerSoundPlayers.push_back(extraPlayer);
1048
1049if( !bAddedUpdate ){
1050ofAddListener(ofEvents().update, this, &ofAVEngineSoundPlayer::updateFunction);
1051bAddedUpdate = true;
1052}
1053}
1054
1055}else{
1056[(AVEnginePlayer *)soundPlayer play];
1057}
1058}
1059
1060void ofAVEngineSoundPlayer::cleanupMultiplayers(){
1061for( auto mMultiPlayer : mMultiplayerSoundPlayers ){
1062if( mMultiPlayer != NULL ){
1063[(AVEnginePlayer *)mMultiPlayer stop];
1064[(AVEnginePlayer *)mMultiPlayer unloadSound];
1065@autoreleasepool {
1066mMultiPlayer = nil;
1067}
1068}
1069}
1070mMultiplayerSoundPlayers.clear();
1071}
1072
1073bool ofAVEngineSoundPlayer::removeMultiPlayer(void * aPlayer){
1074return( aPlayer == NULL );
1075}
1076
1077//better do do this in a thread?
1078//feels safer to use ofEvents().update so we don't need to lock.
1079void ofAVEngineSoundPlayer::updateFunction( ofEventArgs & args ){
1080
1081vector <ObjectType> playerPlayingList;
1082
1083for( auto mMultiPlayerPtr : mMultiplayerSoundPlayers ){
1084if( mMultiPlayerPtr != NULL ){
1085if( [(AVEnginePlayer *)mMultiPlayerPtr isLoaded] && [(AVEnginePlayer *)mMultiPlayerPtr isPlaying] ){
1086playerPlayingList.push_back(mMultiPlayerPtr);
1087}else{
1088[(AVEnginePlayer *)mMultiPlayerPtr unloadSound];
1089@autoreleasepool {
1090mMultiPlayerPtr = nil;
1091}
1092}
1093}
1094}
1095
1096mMultiplayerSoundPlayers = playerPlayingList;
1097}
1098
1099void ofAVEngineSoundPlayer::stop() {
1100if(soundPlayer == NULL) {
1101return;
1102}
1103[(AVEnginePlayer *)soundPlayer stop];
1104cleanupMultiplayers();
1105}
1106
1107void ofAVEngineSoundPlayer::setVolume(float value) {
1108if(soundPlayer == NULL) {
1109return;
1110}
1111[(AVEnginePlayer *)soundPlayer volume:value];
1112}
1113
1114void ofAVEngineSoundPlayer::setPan(float value) {
1115if(soundPlayer == NULL) {
1116return;
1117}
1118[(AVEnginePlayer *)soundPlayer pan:value];
1119}
1120
1121void ofAVEngineSoundPlayer::setSpeed(float value) {
1122if(soundPlayer == NULL) {
1123return;
1124}
1125[(AVEnginePlayer *)soundPlayer speed:value];
1126}
1127
1128void ofAVEngineSoundPlayer::setPaused(bool bPause) {
1129if(soundPlayer == NULL) {
1130return;
1131}
1132if(bPause) {
1133[(AVEnginePlayer *)soundPlayer pause];
1134} else {
1135[(AVEnginePlayer *)soundPlayer play];
1136}
1137}
1138
1139void ofAVEngineSoundPlayer::setLoop(bool bLoop) {
1140if(soundPlayer == NULL) {
1141return;
1142}
1143[(AVEnginePlayer *)soundPlayer loop:bLoop];
1144}
1145
1146void ofAVEngineSoundPlayer::setMultiPlay(bool bMultiPlay) {
1147if(soundPlayer == NULL) {
1148return;
1149}
1150[(AVEnginePlayer *)soundPlayer multiPlay:bMultiPlay];
1151}
1152
1153void ofAVEngineSoundPlayer::setPosition(float position) {
1154if(soundPlayer == NULL) {
1155return;
1156}
1157[(AVEnginePlayer *)soundPlayer position:position];
1158}
1159
1160void ofAVEngineSoundPlayer::setPositionMS(int positionMS) {
1161if(soundPlayer == NULL) {
1162return;
1163}
1164[(AVEnginePlayer *)soundPlayer positionMs:positionMS];
1165}
1166
1167float ofAVEngineSoundPlayer::getPosition() const{
1168if(soundPlayer == NULL) {
1169return 0;
1170}
1171return [(AVEnginePlayer *)soundPlayer position];
1172}
1173
1174int ofAVEngineSoundPlayer::getPositionMS() const {
1175if(soundPlayer == NULL) {
1176return 0;
1177}
1178return [(AVEnginePlayer *)soundPlayer positionMs];
1179}
1180
1181bool ofAVEngineSoundPlayer::isPlaying() const{
1182if(soundPlayer == NULL) {
1183return false;
1184}
1185
1186bool bMainPlaying = [(AVEnginePlayer *)soundPlayer isPlaying];
1187if( !bMainPlaying && mMultiplayerSoundPlayers.size() ){
1188return true;
1189}
1190
1191return bMainPlaying;
1192}
1193
1194float ofAVEngineSoundPlayer::getSpeed() const{
1195if(soundPlayer == NULL) {
1196return 0;
1197}
1198return [(AVEnginePlayer *)soundPlayer speed];
1199}
1200
1201float ofAVEngineSoundPlayer::getPan() const{
1202if(soundPlayer == NULL) {
1203return 0;
1204}
1205return [(AVEnginePlayer *)soundPlayer pan];
1206}
1207
1208bool ofAVEngineSoundPlayer::isLoaded() const{
1209if(soundPlayer == NULL) {
1210return false;
1211}
1212return [(AVEnginePlayer *)soundPlayer isLoaded];
1213}
1214
1215float ofAVEngineSoundPlayer::getVolume() const{
1216if(soundPlayer == NULL) {
1217return false;
1218}
1219return [(AVEnginePlayer *)soundPlayer volume];
1220}
1221
1222void * ofAVEngineSoundPlayer::getAVEnginePlayer() {
1223return (__bridge void *)soundPlayer;
1224}
1225
1226#endif
1227