From 6ea04c0f7bc308427e07e4af90f3d54fab721014 Mon Sep 17 00:00:00 2001 From: Syed Haris Ali Date: Tue, 30 Jun 2015 02:11:07 -0700 Subject: [PATCH 1/9] rewrote EZAudioPlayer to use new EZOutput, added notifications, added NSCopying protocol to EZAudioFile, updated play file examples for iOS and Mac --- EZAudio/EZAudioFile.h | 2 +- EZAudio/EZAudioFile.m | 9 + EZAudio/EZAudioPlayer.h | 297 ++++++---- EZAudio/EZAudioPlayer.m | 522 +++++++++++------- .../PlayFileViewController.h | 6 +- .../PlayFileViewController.m | 91 ++- .../MainStoryboard.storyboard | 4 +- .../PlayFileViewController.h | 55 +- .../PlayFileViewController.m | 190 +++---- 9 files changed, 680 insertions(+), 496 deletions(-) diff --git a/EZAudio/EZAudioFile.h b/EZAudio/EZAudioFile.h index 3ae5f3a2..f5533619 100644 --- a/EZAudio/EZAudioFile.h +++ b/EZAudio/EZAudioFile.h @@ -80,7 +80,7 @@ typedef void (^EZAudioWaveformDataCompletionBlock)(float **waveformData, int len /** The EZAudioFile provides a lightweight and intuitive way to asynchronously interact with audio files. These interactions included reading audio data, seeking within an audio file, getting information about the file, and pulling the waveform data for visualizing the contents of the audio file. The EZAudioFileDelegate provides event callbacks for when reads, seeks, and various updates happen within the audio file to allow the caller to interact with the action in meaningful ways. Common use cases here could be to read the audio file's data as AudioBufferList structures for output (see EZOutput) and visualizing the audio file's data as a float array using an audio plot (see EZAudioPlot). */ -@interface EZAudioFile : NSObject +@interface EZAudioFile : NSObject //------------------------------------------------------------------------------ #pragma mark - Properties diff --git a/EZAudio/EZAudioFile.m b/EZAudio/EZAudioFile.m index 048196f0..a76618b6 100644 --- a/EZAudio/EZAudioFile.m +++ b/EZAudio/EZAudioFile.m @@ -160,6 +160,15 @@ + (instancetype)audioFileWithURL:(NSURL *)url clientFormat:clientFormat]; } +//------------------------------------------------------------------------------ +#pragma mark - NSCopying +//------------------------------------------------------------------------------ + +- (id)copyWithZone:(NSZone *)zone +{ + return [EZAudioFile audioFileWithURL:self.url]; +} + //------------------------------------------------------------------------------ #pragma mark - Class Methods //------------------------------------------------------------------------------ diff --git a/EZAudio/EZAudioPlayer.h b/EZAudio/EZAudioPlayer.h index 49d831de..66117c88 100644 --- a/EZAudio/EZAudioPlayer.h +++ b/EZAudio/EZAudioPlayer.h @@ -28,13 +28,23 @@ #import "EZAudioFile.h" #import "EZOutput.h" -#if TARGET_OS_IPHONE - #import -#elif TARGET_OS_MAC -#endif - @class EZAudioPlayer; +//------------------------------------------------------------------------------ +#pragma mark - Notifications +//------------------------------------------------------------------------------ + +FOUNDATION_EXPORT NSString * const EZAudioPlayerDidChangeAudioFileNotification; +FOUNDATION_EXPORT NSString * const EZAudioPlayerDidChangeOutputDeviceNotification; +FOUNDATION_EXPORT NSString * const EZAudioPlayerDidChangePanNotification; +FOUNDATION_EXPORT NSString * const EZAudioPlayerDidChangePlayStateNotification; +FOUNDATION_EXPORT NSString * const EZAudioPlayerDidChangeVolumeNotification; +FOUNDATION_EXPORT NSString * const EZAudioPlayerDidReachEndOfFileNotification; +FOUNDATION_EXPORT NSString * const EZAudioPlayerDidSeekNotification; + +//------------------------------------------------------------------------------ +#pragma mark - EZAudioPlayerDelegate +//------------------------------------------------------------------------------ /** The EZAudioPlayerDelegate provides event callbacks for the EZAudioPlayer. These type of events are triggered by changes in the EZAudioPlayer's state and allow someone implementing the EZAudioPlayer to more easily update their user interface. Events are triggered anytime the EZAudioPlayer resumes/pauses playback, reaches the end of the file, reads audio data and converts it to float data visualizations (using the EZAudioFile), and updates its cursor position within the audio file during playback (use this for the play position on a slider on the user interface). @@ -43,26 +53,8 @@ @protocol EZAudioPlayerDelegate @optional -/** - Triggered by the EZAudioPlayer when the playback has been resumed or started. - @param audioPlayer The instance of the EZAudioPlayer that triggered the event - @param audioFile The instance of the EZAudioFile that the event was triggered from - */ --(void)audioPlayer:(EZAudioPlayer*)audioPlayer didResumePlaybackOnAudioFile:(EZAudioFile*)audioFile; -/** - Triggered by the EZAudioPlayer when the playback has been paused. - @param audioPlayer The instance of the EZAudioPlayer that triggered the event - @param audioFile The instance of the EZAudioFile that the event was triggered from - */ --(void)audioPlayer:(EZAudioPlayer*)audioPlayer didPausePlaybackOnAudioFile:(EZAudioFile*)audioFile; - -/** - Triggered by the EZAudioPlayer when the output has reached the end of the EZAudioFile it's playing. If the EZAudioPlayer has its `shouldLoop` property set to true this will trigger, but playback will continue to loop once its hit the end of the audio file. - @param audioPlayer The instance of the EZAudioPlayer that triggered the event - @param audioFile The instance of the EZAudioFile that the event was triggered from - */ --(void)audioPlayer:(EZAudioPlayer*)audioPlayer reachedEndOfAudioFile:(EZAudioFile*)audioFile; +//------------------------------------------------------------------------------ /** Triggered by the EZAudioPlayer's internal EZAudioFile's EZAudioFileDelegate callback and notifies the delegate of the read audio data as a float array instead of a buffer list. Common use case of this would be to visualize the float data using an audio plot or audio data dependent OpenGL sketch. @@ -72,11 +64,13 @@ @param numberOfChannels The number of channels. 2 for stereo, 1 for mono. @param audioFile The instance of the EZAudioFile that the event was triggered from */ --(void) audioPlayer:(EZAudioPlayer*)audioPlayer - readAudio:(float**)buffer +- (void) audioPlayer:(EZAudioPlayer *)audioPlayer + playedAudio:(float **)buffer withBufferSize:(UInt32)bufferSize withNumberOfChannels:(UInt32)numberOfChannels - inAudioFile:(EZAudioFile*)audioFile;; + inAudioFile:(EZAudioFile *)audioFile;; + +//------------------------------------------------------------------------------ /** Triggered by EZAudioPlayer's internal EZAudioFile's EZAudioFileDelegate callback and notifies the delegate of the current playback position. The framePosition provides the current frame position and can be calculated against the EZAudioPlayer's total frames using the `totalFrames` function from the EZAudioPlayer. @@ -84,18 +78,27 @@ @param framePosition The new frame index as a 64-bit signed integer @param audioFile The instance of the EZAudioFile that the event was triggered from */ --(void)audioPlayer:(EZAudioPlayer*)audioPlayer - updatedPosition:(SInt64)framePosition - inAudioFile:(EZAudioFile*)audioFile; +- (void)audioPlayer:(EZAudioPlayer *)audioPlayer + updatedPosition:(SInt64)framePosition + inAudioFile:(EZAudioFile *)audioFile; @end +//------------------------------------------------------------------------------ +#pragma mark - EZAudioPlayer +//------------------------------------------------------------------------------ + /** The EZAudioPlayer acts as the master delegate (the EZAudioFileDelegate) over whatever EZAudioFile it is using for playback. Classes that want to get the EZAudioFileDelegate callbacks should implement the EZAudioPlayer's EZAudioPlayerDelegate on the EZAudioPlayer instance. */ -@interface EZAudioPlayer : NSObject +@interface EZAudioPlayer : NSObject +//------------------------------------------------------------------------------ #pragma mark - Properties +//------------------------------------------------------------------------------ + ///----------------------------------------------------------- /// @name Properties ///----------------------------------------------------------- @@ -103,89 +106,134 @@ /** The EZAudioPlayerDelegate that will handle the audio player callbacks */ -@property (nonatomic,assign) id audioPlayerDelegate; +@property (nonatomic, weak) id delegate; + +//------------------------------------------------------------------------------ /** A BOOL indicating whether the player should loop the file */ -@property (nonatomic,assign) BOOL shouldLoop; +@property (nonatomic, assign) BOOL shouldLoop; +//------------------------------------------------------------------------------ #pragma mark - Initializers +//------------------------------------------------------------------------------ + ///----------------------------------------------------------- /// @name Initializers ///----------------------------------------------------------- +/** + Initializes the EZAudioPlayer with an EZAudioPlayerDelegate. + @param delegate The receiver that will act as the EZAudioPlayerDelegate. Set to nil if it should have no delegate or use the initWithAudioFile: function instead. + @return The newly created instance of the EZAudioPlayer + */ +- (instancetype)initWithDelegate:(id)delegate; + /** Initializes the EZAudioPlayer with an EZAudioFile instance. This does not use the EZAudioFile by reference, but instead creates a separate EZAudioFile instance with the same file at the given file path provided by the internal NSURL to use for internal seeking so it doesn't cause any locking between the caller's instance of the EZAudioFile. @param audioFile The instance of the EZAudioFile to use for initializing the EZAudioPlayer @return The newly created instance of the EZAudioPlayer */ --(EZAudioPlayer*)initWithEZAudioFile:(EZAudioFile*)audioFile; +- (instancetype)initWithAudioFile:(EZAudioFile *)audioFile; + +//------------------------------------------------------------------------------ /** Initializes the EZAudioPlayer with an EZAudioFile instance and provides a way to assign the EZAudioPlayerDelegate on instantiation. This does not use the EZAudioFile by reference, but instead creates a separate EZAudioFile instance with the same file at the given file path provided by the internal NSURL to use for internal seeking so it doesn't cause any locking between the caller's instance of the EZAudioFile. @param audioFile The instance of the EZAudioFile to use for initializing the EZAudioPlayer - @param audioPlayerDelegate The receiver that will act as the EZAudioPlayerDelegate. Set to nil if it should have no delegate or use the initWithEZAudioFile: function instead. + @param delegate The receiver that will act as the EZAudioPlayerDelegate. Set to nil if it should have no delegate or use the initWithAudioFile: function instead. @return The newly created instance of the EZAudioPlayer */ --(EZAudioPlayer*)initWithEZAudioFile:(EZAudioFile*)audioFile - withDelegate:(id)audioPlayerDelegate; +- (instancetype)initWithAudioFile:(EZAudioFile *)audioFile + delegate:(id)delegate; + +//------------------------------------------------------------------------------ /** Initializes the EZAudioPlayer with an NSURL instance representing the file path of the audio file. @param url The NSURL instance representing the file path of the audio file. @return The newly created instance of the EZAudioPlayer */ --(EZAudioPlayer*)initWithURL:(NSURL*)url; +- (instancetype)initWithURL:(NSURL*)url; + +//------------------------------------------------------------------------------ /** Initializes the EZAudioPlayer with an NSURL instance representing the file path of the audio file and a caller to assign as the EZAudioPlayerDelegate on instantiation. @param url The NSURL instance representing the file path of the audio file. - @param audioPlayerDelegate The receiver that will act as the EZAudioPlayerDelegate. Set to nil if it should have no delegate or use the initWithEZAudioFile: function instead. + @param delegate The receiver that will act as the EZAudioPlayerDelegate. Set to nil if it should have no delegate or use the initWithAudioFile: function instead. @return The newly created instance of the EZAudioPlayer */ --(EZAudioPlayer*)initWithURL:(NSURL*)url - withDelegate:(id)audioPlayerDelegate; - +- (instancetype)initWithURL:(NSURL*)url + delegate:(id)delegate; +//------------------------------------------------------------------------------ #pragma mark - Class Initializers +//------------------------------------------------------------------------------ + ///----------------------------------------------------------- /// @name Class Initializers ///----------------------------------------------------------- /** - Class initializer that initializes the EZAudioPlayer with an EZAudioFile instance. This does not use the EZAudioFile by reference, but instead creates a separate EZAudioFile instance with the same file at the given file path provided by the internal NSURL to use for internal seeking so it doesn't cause any locking between the caller's instance of the EZAudioFile. + Class initializer that creates a default EZAudioPlayer. + @return The newly created instance of the EZAudioPlayer + */ ++ (instancetype)audioPlayer; + +//------------------------------------------------------------------------------ + +/** + Class initializer that creates a default EZAudioPlayer with an EZAudioPlayerDelegate.. + @return The newly created instance of the EZAudioPlayer + */ ++ (instancetype)audioPlayerWithDelegate:(id)delegate; + +//------------------------------------------------------------------------------ + +/** + Class initializer that creates the EZAudioPlayer with an EZAudioFile instance. This does not use the EZAudioFile by reference, but instead creates a separate EZAudioFile instance with the same file at the given file path provided by the internal NSURL to use for internal seeking so it doesn't cause any locking between the caller's instance of the EZAudioFile. @param audioFile The instance of the EZAudioFile to use for initializing the EZAudioPlayer @return The newly created instance of the EZAudioPlayer */ -+(EZAudioPlayer*)audioPlayerWithEZAudioFile:(EZAudioFile*)audioFile; ++ (instancetype)audioPlayerWithAudioFile:(EZAudioFile *)audioFile; + +//------------------------------------------------------------------------------ /** - Class initializer that initializes the EZAudioPlayer with an EZAudioFile instance and provides a way to assign the EZAudioPlayerDelegate on instantiation. This does not use the EZAudioFile by reference, but instead creates a separate EZAudioFile instance with the same file at the given file path provided by the internal NSURL to use for internal seeking so it doesn't cause any locking between the caller's instance of the EZAudioFile. + Class initializer that creates the EZAudioPlayer with an EZAudioFile instance and provides a way to assign the EZAudioPlayerDelegate on instantiation. This does not use the EZAudioFile by reference, but instead creates a separate EZAudioFile instance with the same file at the given file path provided by the internal NSURL to use for internal seeking so it doesn't cause any locking between the caller's instance of the EZAudioFile. @param audioFile The instance of the EZAudioFile to use for initializing the EZAudioPlayer - @param audioPlayerDelegate The receiver that will act as the EZAudioPlayerDelegate. Set to nil if it should have no delegate or use the audioPlayerWithEZAudioFile: function instead. + @param delegate The receiver that will act as the EZAudioPlayerDelegate. Set to nil if it should have no delegate or use the audioPlayerWithAudioFile: function instead. @return The newly created instance of the EZAudioPlayer */ -+(EZAudioPlayer*)audioPlayerWithEZAudioFile:(EZAudioFile*)audioFile - withDelegate:(id)audioPlayerDelegate; ++ (instancetype)audioPlayerWithAudioFile:(EZAudioFile *)audioFile + delegate:(id)delegate; + +//------------------------------------------------------------------------------ /** - Class initializer that initializes the EZAudioPlayer with an NSURL instance representing the file path of the audio file. + Class initializer that creates the EZAudioPlayer with an NSURL instance representing the file path of the audio file. @param url The NSURL instance representing the file path of the audio file. @return The newly created instance of the EZAudioPlayer */ -+(EZAudioPlayer*)audioPlayerWithURL:(NSURL*)url; ++ (instancetype)audioPlayerWithURL:(NSURL*)url; + +//------------------------------------------------------------------------------ /** - Class initializer that initializes the EZAudioPlayer with an NSURL instance representing the file path of the audio file and a caller to assign as the EZAudioPlayerDelegate on instantiation. + Class initializer that creates the EZAudioPlayer with an NSURL instance representing the file path of the audio file and a caller to assign as the EZAudioPlayerDelegate on instantiation. @param url The NSURL instance representing the file path of the audio file. - @param audioPlayerDelegate The receiver that will act as the EZAudioPlayerDelegate. Set to nil if it should have no delegate or use the audioPlayerWithURL: function instead. + @param delegate The receiver that will act as the EZAudioPlayerDelegate. Set to nil if it should have no delegate or use the audioPlayerWithURL: function instead. @return The newly created instance of the EZAudioPlayer */ -+(EZAudioPlayer*)audioPlayerWithURL:(NSURL*)url - withDelegate:(id)audioPlayerDelegate; ++ (instancetype)audioPlayerWithURL:(NSURL*)url + delegate:(id)delegate; +//------------------------------------------------------------------------------ #pragma mark - Singleton +//------------------------------------------------------------------------------ + ///----------------------------------------------------------- /// @name Shared Instance ///----------------------------------------------------------- @@ -194,108 +242,157 @@ The shared instance (singleton) of the audio player. Most applications will only have one instance of the EZAudioPlayer that can be reused with multiple different audio files. * @return The shared instance of the EZAudioPlayer. */ -+(EZAudioPlayer*)sharedAudioPlayer; ++ (instancetype)sharedAudioPlayer; + +//------------------------------------------------------------------------------ +#pragma mark - Properties +//------------------------------------------------------------------------------ -#pragma mark - Getters ///----------------------------------------------------------- -/// @name Getting The Audio Player's Properties +/// @name Properties ///----------------------------------------------------------- /** - Provides the EZAudioFile instance that is being used as the datasource for playback. - @return The EZAudioFile instance that is currently being used for playback. + Provides the EZAudioFile instance that is being used as the datasource for playback. When set it creates a copy of the EZAudioFile provided for internal use. This does not use the EZAudioFile by reference, but instead creates a separate EZAudioFile instance with the same file at the given file path provided by the internal NSURL to use for internal seeking so it doesn't cause any locking between the caller's instance of the EZAudioFile. + */ +@property (nonatomic, readwrite, copy) EZAudioFile *audioFile; + +//------------------------------------------------------------------------------ + +/** + Provides the current offset in the audio file as an NSTimeInterval (i.e. in seconds). When setting this it will determine the correct frame offset and perform a `seekToFrame` to the new time offset. + @warning Make sure the new current time offset is less than the `duration` or you will receive an invalid seek assertion. */ --(EZAudioFile*)audioFile; +@property (nonatomic, readwrite) NSTimeInterval currentTime; + +//------------------------------------------------------------------------------ + + +@property (readwrite) EZAudioDevice *device; + +//------------------------------------------------------------------------------ /** - Provides the current time (a.k.a. the seek position) in seconds within the audio file that's being used for playback. This can be helpful when displaying the audio player's current time over duration. - @return A float representing the current time within the audio file used for playback. + Provides the duration of the audio file in seconds. */ --(float)currentTime; +@property (readonly) NSTimeInterval duration; + +//------------------------------------------------------------------------------ + +/** + Provides the current time as an NSString with the time format MM:SS. + */ +@property (readonly) NSString *formattedCurrentTime; + +//------------------------------------------------------------------------------ + +/** + Provides the duration as an NSString with the time format MM:SS. + */ +@property (readonly) NSString *formattedDuration; + +//------------------------------------------------------------------------------ + +/** + <#Description#> + */ +@property (nonatomic, strong, readwrite) EZOutput *output; + +//------------------------------------------------------------------------------ + +/** + Provides the total duration of the audio file in seconds. + @deprecated This property is deprecated starting in version 0.4.0. + @note Please use `duration` property instead. + @return The total duration of the audio file as a Float32. + */ +@property (readonly) NSTimeInterval totalDuration __attribute__((deprecated));; + +//------------------------------------------------------------------------------ /** Provides a flag indicating whether the EZAudioPlayer has reached the end of the audio file used for playback. @return A BOOL indicating whether or not the EZAudioPlayer has reached the end of the file it is using for playback. */ --(BOOL)endOfFile; +@property (readonly) BOOL isEndOfFile; + +//------------------------------------------------------------------------------ /** Provides the frame index (a.k.a the seek positon) within the audio file being used for playback. This can be helpful when seeking through the audio file. @return An SInt64 representing the current frame index within the audio file used for playback. */ --(SInt64)frameIndex; +@property (readonly) SInt64 frameIndex; + +//------------------------------------------------------------------------------ /** Provides a flag indicating whether the EZAudioPlayer is currently playing back any audio. @return A BOOL indicating whether or not the EZAudioPlayer is performing playback, */ --(BOOL)isPlaying; +@property (readonly) BOOL isPlaying; -/** - Provides the EZOutput instance that is being used to provide playback to the system output. - @return The EZOutput instance that is currently being used for output playback. - */ --(EZOutput*)output; +//------------------------------------------------------------------------------ /** - Provides the total duration of the current audio file being used for playback (in seconds). - @return A float representing the total duration of the current audio file being used for playback in seconds. + <#Description#> */ --(float)totalDuration; +@property (nonatomic, assign) float pan; + +//------------------------------------------------------------------------------ /** Provides the total amount of frames in the current audio file being used for playback. @return A SInt64 representing the total amount of frames in the current audio file being used for playback. */ --(SInt64)totalFrames; +@property (readonly) SInt64 totalFrames; + +//------------------------------------------------------------------------------ /** Provides the file path that's currently being used by the player for playback. @return The NSURL representing the file path of the audio file being used for playback. */ --(NSURL*)url; +@property (nonatomic, copy, readonly) NSURL *url; -#pragma mark - Setters -///----------------------------------------------------------- -/// @name Setting The File/Output -///----------------------------------------------------------- +//------------------------------------------------------------------------------ -/** - Sets the EZAudioFile to use for playback. This does not use the EZAudioFile by reference, but instead creates a separate EZAudioFile instance with the same file at the given file path provided by the internal NSURL to use for internal seeking so it doesn't cause any locking between the caller's instance of the EZAudioFile. - @param audioFile The new EZAudioFile instance that should be used for playback - */ --(void)setAudioFile:(EZAudioFile*)audioFile; +@property (nonatomic, assign) float volume; -/** - Sets the EZOutput to route playback. By default this uses the [EZOutput sharedOutput] singleton. - @param output The new EZOutput instance that should be used for playback - */ --(void)setOutput:(EZOutput*)output; +//------------------------------------------------------------------------------ +#pragma mark - Actions +//------------------------------------------------------------------------------ -#pragma mark - Methods ///----------------------------------------------------------- -/// @name Play/Pause/Seeking the Player +/// @name Controlling Playback ///----------------------------------------------------------- /** - Starts or resumes playback. + Starts playback. */ --(void)play; +- (void)play; + +//------------------------------------------------------------------------------ /** - Pauses playback. + Loads an EZAudioFile and immediately starts playing it. + @param audioFile An EZAudioFile to use for immediate playback. */ --(void)pause; +- (void)playAudioFile:(EZAudioFile *)audioFile; + +//------------------------------------------------------------------------------ /** - Stops playback. + Pauses playback. */ --(void)stop; +- (void)pause; + +//------------------------------------------------------------------------------ /** Seeks playback to a specified frame within the internal EZAudioFile. This will notify the EZAudioFileDelegate (if specified) with the audioPlayer:updatedPosition:inAudioFile: function. @param frame The new frame position to seek to as a SInt64. */ --(void)seekToFrame:(SInt64)frame; +- (void)seekToFrame:(SInt64)frame; -@end +@end \ No newline at end of file diff --git a/EZAudio/EZAudioPlayer.m b/EZAudio/EZAudioPlayer.m index 69e60cc1..5e1ecfa8 100644 --- a/EZAudio/EZAudioPlayer.m +++ b/EZAudio/EZAudioPlayer.m @@ -26,259 +26,333 @@ #import "EZAudioPlayer.h" #import "EZAudioUtilities.h" -#if TARGET_OS_IPHONE -#elif TARGET_OS_MAC -#endif -@interface EZAudioPlayer () +//------------------------------------------------------------------------------ +#pragma mark - Notifications +//------------------------------------------------------------------------------ + +NSString * const EZAudioPlayerDidChangeAudioFileNotification = @"EZAudioPlayerDidChangeAudioFileNotification"; +NSString * const EZAudioPlayerDidChangeOutputDeviceNotification = @"EZAudioPlayerDidChangeOutputDeviceNotification"; +NSString * const EZAudioPlayerDidChangePanNotification = @"EZAudioPlayerDidChangePanNotification"; +NSString * const EZAudioPlayerDidChangePlayStateNotification = @"EZAudioPlayerDidChangePlayStateNotification"; +NSString * const EZAudioPlayerDidChangeVolumeNotification = @"EZAudioPlayerDidChangeVolumeNotification"; +NSString * const EZAudioPlayerDidReachEndOfFileNotification = @"EZAudioPlayerDidReachEndOfFileNotification"; +NSString * const EZAudioPlayerDidSeekNotification = @"EZAudioPlayerDidSeekNotification"; + +//------------------------------------------------------------------------------ +#pragma mark - EZAudioPlayer (Implementation) +//------------------------------------------------------------------------------ + +@implementation EZAudioPlayer + +//------------------------------------------------------------------------------ +#pragma mark - Class Methods +//------------------------------------------------------------------------------ + ++ (instancetype)audioPlayer { - BOOL _eof; + return [[self alloc] init]; } -@property (nonatomic,strong,setter=setAudioFile:) EZAudioFile *audioFile; -@property (nonatomic,strong,setter=setOutput:) EZOutput *output; -@end -@implementation EZAudioPlayer -@synthesize audioFile = _audioFile; -@synthesize audioPlayerDelegate = _audioPlayerDelegate; -@synthesize output = _output; -@synthesize shouldLoop = _shouldLoop; +//------------------------------------------------------------------------------ -#pragma mark - Initializers --(id)init { - self = [super init]; - if(self){ - [self _configureAudioPlayer]; - } - return self; ++ (instancetype)audioPlayerWithDelegate:(id)delegate +{ + return [[self alloc] initWithDelegate:delegate]; } --(EZAudioPlayer*)initWithEZAudioFile:(EZAudioFile *)audioFile { - return [self initWithEZAudioFile:audioFile withDelegate:nil]; +//------------------------------------------------------------------------------ + ++ (instancetype)audioPlayerWithAudioFile:(EZAudioFile *)audioFile +{ + return [[self alloc] initWithAudioFile:audioFile]; +} + +//------------------------------------------------------------------------------ + ++ (instancetype)audioPlayerWithAudioFile:(EZAudioFile *)audioFile + delegate:(id)delegate +{ + return [[self alloc] initWithAudioFile:audioFile + delegate:delegate]; +} + +//------------------------------------------------------------------------------ + ++ (instancetype)audioPlayerWithURL:(NSURL *)url +{ + return [[self alloc] initWithURL:url]; } --(EZAudioPlayer *)initWithEZAudioFile:(EZAudioFile *)audioFile - withDelegate:(id)audioPlayerDelegate { - self = [super init]; - if(self){ - // This should make a separate reference to the audio file - [self _configureAudioPlayer]; - self.audioFile = audioFile; - self.audioPlayerDelegate = audioPlayerDelegate; - } - return self; +//------------------------------------------------------------------------------ + ++ (instancetype)audioPlayerWithURL:(NSURL *)url + delegate:(id)delegate +{ + return [[self alloc] initWithURL:url delegate:delegate]; } --(EZAudioPlayer *)initWithURL:(NSURL *)url { - return [self initWithURL:url withDelegate:nil]; +//------------------------------------------------------------------------------ +#pragma mark - Initialization +//------------------------------------------------------------------------------ + +- (instancetype)init +{ + self = [super init]; + if (self) + { + [self setup]; + } + return self; } --(EZAudioPlayer *)initWithURL:(NSURL *)url - withDelegate:(id)audioPlayerDelegate { - self = [super init]; - if(self){ - [self _configureAudioPlayer]; - self.audioFile = [[EZAudioFile alloc] initWithURL:url]; - self.audioFile.delegate = self; - self.audioPlayerDelegate = audioPlayerDelegate; - } - return self; +//------------------------------------------------------------------------------ + +- (instancetype)initWithDelegate:(id)delegate +{ + self = [self init]; + if (self) + { + self.delegate = delegate; + } + return self; } -#pragma mark - Class Initializers -+(EZAudioPlayer *)audioPlayerWithEZAudioFile:(EZAudioFile *)audioFile { - return [[EZAudioPlayer alloc] initWithEZAudioFile:audioFile]; +//------------------------------------------------------------------------------ + +- (instancetype)initWithAudioFile:(EZAudioFile *)audioFile +{ + return [self initWithAudioFile:audioFile delegate:nil]; } -+(EZAudioPlayer *)audioPlayerWithEZAudioFile:(EZAudioFile *)audioFile - withDelegate:(id)audioPlayerDelegate { - return [[EZAudioPlayer alloc] initWithEZAudioFile:audioFile - withDelegate:audioPlayerDelegate]; +//------------------------------------------------------------------------------ + +- (instancetype)initWithAudioFile:(EZAudioFile *)audioFile + delegate:(id)delegate +{ + self = [self initWithDelegate:delegate]; + if (self) + { + self.audioFile = audioFile; + } + return self; } -+(EZAudioPlayer *)audioPlayerWithURL:(NSURL *)url { - return [[EZAudioPlayer alloc] initWithURL:url]; +//------------------------------------------------------------------------------ + +- (instancetype)initWithURL:(NSURL *)url +{ + return [self initWithURL:url delegate:nil]; } -+(EZAudioPlayer *)audioPlayerWithURL:(NSURL *)url - withDelegate:(id)audioPlayerDelegate { - return [[EZAudioPlayer alloc] initWithURL:url - withDelegate:audioPlayerDelegate]; +//------------------------------------------------------------------------------ + +- (instancetype)initWithURL:(NSURL *)url + delegate:(id)delegate +{ + self = [self initWithDelegate:delegate]; + if (self) + { + self.audioFile = [EZAudioFile audioFileWithURL:url delegate:self]; + } + return self; } +//------------------------------------------------------------------------------ #pragma mark - Singleton -+(EZAudioPlayer *)sharedAudioPlayer { - static EZAudioPlayer *_sharedAudioPlayer = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - _sharedAudioPlayer = [[EZAudioPlayer alloc] init]; - }); - return _sharedAudioPlayer; -} - -#pragma mark - Private Configuration --(void)_configureAudioPlayer { - - // Defaults - self.output = [EZOutput sharedOutput]; - -#if TARGET_OS_IPHONE - // Configure the AVSession - AVAudioSession *audioSession = [AVAudioSession sharedInstance]; - NSError *err = NULL; - [audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:&err]; - if (err){ - NSLog(@"There was an error creating the audio session"); - } - [audioSession overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:NULL]; - if (err){ - NSLog(@"There was an error sending the audio to the speakers"); - } -#elif TARGET_OS_MAC -#endif - +//------------------------------------------------------------------------------ + ++ (instancetype)sharedAudioPlayer +{ + static EZAudioPlayer *player; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^ + { + player = [[self alloc] init]; + }); + return player; +} + +//------------------------------------------------------------------------------ +#pragma mark - Setup +//------------------------------------------------------------------------------ + +- (void)setup +{ + self.output = [EZOutput output]; } +//------------------------------------------------------------------------------ #pragma mark - Getters --(EZAudioFile*)audioFile { - return _audioFile; +//------------------------------------------------------------------------------ + +- (NSTimeInterval)currentTime +{ + return [self.audioFile currentTime]; } --(float)currentTime { - NSAssert(_audioFile,@"No audio file to perform the seek on, check that EZAudioFile is not nil"); - return [EZAudioUtilities MAP:self.audioFile.frameIndex - leftMin:0 - leftMax:self.audioFile.totalFrames - rightMin:0 - rightMax:self.audioFile.duration]; +//------------------------------------------------------------------------------ + +- (EZAudioDevice *)device +{ + return [self.output device]; } --(BOOL)endOfFile { - return _eof; +//------------------------------------------------------------------------------ + +- (NSTimeInterval)duration +{ + return [self.audioFile duration]; } --(SInt64)frameIndex { - NSAssert(_audioFile,@"No audio file to perform the seek on, check that EZAudioFile is not nil"); - return _audioFile.frameIndex; +//------------------------------------------------------------------------------ + +- (NSString *)formattedCurrentTime +{ + return [self.audioFile formattedCurrentTime]; } --(BOOL)isPlaying { - return self.output.isPlaying; +//------------------------------------------------------------------------------ + +- (NSString *)formattedDuration +{ + return [self.audioFile formattedDuration]; } --(EZOutput*)output { - NSAssert(_output,@"No output was found, this should by default be the EZOutput shared instance"); - return _output; +//------------------------------------------------------------------------------ + +- (SInt64)frameIndex +{ + return [self.audioFile frameIndex]; } --(float)totalDuration { - NSAssert(_audioFile,@"No audio file to perform the seek on, check that EZAudioFile is not nil"); - return _audioFile.duration; +//------------------------------------------------------------------------------ + +- (BOOL)isPlaying +{ + return [self.output isPlaying]; } --(SInt64)totalFrames { - NSAssert(_audioFile,@"No audio file to perform the seek on, check that EZAudioFile is not nil"); - return _audioFile.totalFrames; +//------------------------------------------------------------------------------ + +- (float)pan +{ + return [self.output pan]; } --(NSURL *)url { - NSAssert(_audioFile,@"No audio file to perform the seek on, check that EZAudioFile is not nil"); - return _audioFile.url; +//------------------------------------------------------------------------------ + +- (float)volume +{ + return [self.output volume]; } +//------------------------------------------------------------------------------ #pragma mark - Setters --(void)setAudioFile:(EZAudioFile *)audioFile { - if (_audioFile){ - _audioFile.delegate = nil; - } - _eof = NO; - _audioFile = [EZAudioFile audioFileWithURL:audioFile.url]; +//------------------------------------------------------------------------------ + +- (void)setAudioFile:(EZAudioFile *)audioFile +{ + _audioFile = [audioFile copy]; _audioFile.delegate = self; - NSAssert(_output,@"No output was found, this should by default be the EZOutput shared instance"); - [_output setInputFormat:self.audioFile.clientFormat]; + AudioStreamBasicDescription inputFormat = _audioFile.clientFormat; + [self.output setInputFormat:inputFormat]; + [[NSNotificationCenter defaultCenter] postNotificationName:EZAudioPlayerDidChangeAudioFileNotification + object:self]; } --(void)setOutput:(EZOutput*)output { - _output = output; - _output.dataSource = self; +//------------------------------------------------------------------------------ + +- (void)setCurrentTime:(NSTimeInterval)currentTime +{ + [self.audioFile setCurrentTime:currentTime]; + [[NSNotificationCenter defaultCenter] postNotificationName:EZAudioPlayerDidSeekNotification + object:self]; } -#pragma mark - Methods --(void)play { - NSAssert(_audioFile,@"No audio file to perform the seek on, check that EZAudioFile is not nil"); - if (_audioFile){ - [_output startPlayback]; - if (self.frameIndex != self.totalFrames){ - _eof = NO; - } - if (self.audioPlayerDelegate){ - if ([self.audioPlayerDelegate respondsToSelector:@selector(audioPlayer:didResumePlaybackOnAudioFile:)]){ - // Notify the delegate we're starting playback - [self.audioPlayerDelegate audioPlayer:self didResumePlaybackOnAudioFile:_audioFile]; - } - } - } -} - --(void)pause { - NSAssert(self.audioFile,@"No audio file to perform the seek on, check that EZAudioFile is not nil"); - if (_audioFile){ - [_output stopPlayback]; - if (self.audioPlayerDelegate){ - if ([self.audioPlayerDelegate respondsToSelector:@selector(audioPlayer:didPausePlaybackOnAudioFile:)]){ - // Notify the delegate we're pausing playback - [self.audioPlayerDelegate audioPlayer:self didPausePlaybackOnAudioFile:_audioFile]; - } - } - } +//------------------------------------------------------------------------------ + +- (void)setDevice:(EZAudioDevice *)device +{ + [self.output setDevice:device]; +} + +//------------------------------------------------------------------------------ + +- (void)setOutput:(EZOutput *)output +{ + _output = output; + _output.dataSource = self; + _output.delegate = self; } --(void)seekToFrame:(SInt64)frame { - NSAssert(_audioFile,@"No audio file to perform the seek on, check that EZAudioFile is not nil"); - if (_audioFile){ - [_audioFile seekToFrame:frame]; - } - if (self.frameIndex != self.totalFrames){ - _eof = NO; - } +//------------------------------------------------------------------------------ + +- (void)setPan:(float)pan +{ + [self.output setPan:pan]; + [[NSNotificationCenter defaultCenter] postNotificationName:EZAudioPlayerDidChangePanNotification + object:self]; } --(void)stop { - NSAssert(_audioFile,@"No audio file to perform the seek on, check that EZAudioFile is not nil"); - if (_audioFile){ - [_output stopPlayback]; - [_audioFile seekToFrame:0]; - _eof = NO; - } +//------------------------------------------------------------------------------ + +- (void)setVolume:(float)volume +{ + [self.output setVolume:volume]; + [[NSNotificationCenter defaultCenter] postNotificationName:EZAudioPlayerDidChangeVolumeNotification + object:self]; } -#pragma mark - EZAudioFileDelegate --(void)audioFile:(EZAudioFile *)audioFile - readAudio:(float **)buffer - withBufferSize:(UInt32)bufferSize -withNumberOfChannels:(UInt32)numberOfChannels { - if (self.audioPlayerDelegate){ - if ([self.audioPlayerDelegate respondsToSelector:@selector(audioPlayer:readAudio:withBufferSize:withNumberOfChannels:inAudioFile:)]){ - [self.audioPlayerDelegate audioPlayer:self - readAudio:buffer - withBufferSize:bufferSize - withNumberOfChannels:numberOfChannels - inAudioFile:audioFile]; - } - } +//------------------------------------------------------------------------------ +#pragma mark - Actions +//------------------------------------------------------------------------------ + +- (void)play +{ + [self.output startPlayback]; } --(void)audioFile:(EZAudioFile *)audioFile updatedPosition:(SInt64)framePosition { - if (self.audioPlayerDelegate){ - if ([self.audioPlayerDelegate respondsToSelector:@selector(audioPlayer:updatedPosition:inAudioFile:)]){ - [self.audioPlayerDelegate audioPlayer:self - updatedPosition:framePosition - inAudioFile:audioFile]; - } - } +//------------------------------------------------------------------------------ + +- (void)playAudioFile:(EZAudioFile *)audioFile +{ + // + // stop playing anything that might currently be playing + // + [self pause]; + + // + // set new stream + // + self.audioFile = audioFile; + + // + // begin playback + // + [self play]; +} + +//------------------------------------------------------------------------------ + +- (void)pause +{ + [self.output stopPlayback]; +} + +//------------------------------------------------------------------------------ + +- (void)seekToFrame:(SInt64)frame +{ + [self.audioFile seekToFrame:frame]; + [[NSNotificationCenter defaultCenter] postNotificationName:EZAudioPlayerDidSeekNotification + object:self]; } +//------------------------------------------------------------------------------ #pragma mark - EZOutputDataSource --(OSStatus) output:(EZOutput *)output +//------------------------------------------------------------------------------ + +- (OSStatus) output:(EZOutput *)output shouldFillAudioBufferList:(AudioBufferList *)audioBufferList withNumberOfFrames:(UInt32)frames timestamp:(const AudioTimeStamp *)timestamp @@ -286,16 +360,74 @@ -(OSStatus) output:(EZOutput *)output if (self.audioFile) { UInt32 bufferSize; + BOOL eof; [self.audioFile readFrames:frames audioBufferList:audioBufferList bufferSize:&bufferSize - eof:&_eof]; - if (_eof && self.shouldLoop) + eof:&eof]; + if (eof && self.shouldLoop) { [self seekToFrame:0]; } + else if (eof) + { + [self pause]; + [[NSNotificationCenter defaultCenter] postNotificationName:EZAudioPlayerDidReachEndOfFileNotification + object:self]; + } } return noErr; } -@end +//------------------------------------------------------------------------------ +#pragma mark - EZAudioFileDelegate +//------------------------------------------------------------------------------ + +- (void)audioFile:(EZAudioFile *)audioFile updatedPosition:(SInt64)framePosition +{ + if ([self.delegate respondsToSelector:@selector(audioPlayer:updatedPosition:inAudioFile:)]) + { + [self.delegate audioPlayer:self + updatedPosition:framePosition + inAudioFile:audioFile]; + } +} + +//------------------------------------------------------------------------------ +#pragma mark - EZOutputDelegate +//------------------------------------------------------------------------------ + +- (void)output:(EZOutput *)output changedDevice:(EZAudioDevice *)device +{ + [[NSNotificationCenter defaultCenter] postNotificationName:EZAudioPlayerDidChangeOutputDeviceNotification + object:self]; +} + +//------------------------------------------------------------------------------ + +- (void)output:(EZOutput *)output changedPlayingState:(BOOL)isPlaying +{ + [[NSNotificationCenter defaultCenter] postNotificationName:EZAudioPlayerDidChangePlayStateNotification + object:self]; +} + +//------------------------------------------------------------------------------ + +- (void) output:(EZOutput *)output + playedAudio:(float **)buffer + withBufferSize:(UInt32)bufferSize + withNumberOfChannels:(UInt32)numberOfChannels +{ + if ([self.delegate respondsToSelector:@selector(audioPlayer:playedAudio:withBufferSize:withNumberOfChannels:inAudioFile:)]) + { + [self.delegate audioPlayer:self + playedAudio:buffer + withBufferSize:bufferSize + withNumberOfChannels:numberOfChannels + inAudioFile:self.audioFile]; + } +} + +//------------------------------------------------------------------------------ + +@end \ No newline at end of file diff --git a/EZAudioExamples/OSX/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.h b/EZAudioExamples/OSX/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.h index d87a87c7..b4df9c56 100644 --- a/EZAudioExamples/OSX/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.h +++ b/EZAudioExamples/OSX/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.h @@ -37,9 +37,7 @@ Using the EZOutputDataSource to provide output data to the EZOutput component. */ @interface PlayFileViewController : NSViewController + EZAudioPlayerDelegate> #pragma mark - Components /** @@ -50,7 +48,7 @@ /** The EZOutput component used to output the audio file's audio data. */ -@property (nonatomic, strong) EZOutput *output; +@property (nonatomic, strong) EZAudioPlayer *player; /** The CoreGraphics based audio plot diff --git a/EZAudioExamples/OSX/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.m b/EZAudioExamples/OSX/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.m index 2ade878d..edaca62f 100644 --- a/EZAudioExamples/OSX/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.m +++ b/EZAudioExamples/OSX/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.m @@ -55,8 +55,8 @@ - (void)awakeFromNib // // Create EZOutput to play audio data // - self.output = [EZOutput outputWithDataSource:self]; - self.output.delegate = self; + self.player = [EZAudioPlayer audioPlayerWithDelegate:self]; + self.player.shouldLoop = YES; // // Reload the menu for the output device selector popup button @@ -66,8 +66,8 @@ - (void)awakeFromNib // // Configure UI components // - self.volumeSlider.floatValue = [self.output volume]; - self.volumeLabel.floatValue = [self.output volume]; + self.volumeSlider.floatValue = [self.player volume]; + self.volumeLabel.floatValue = [self.player volume]; self.rollingHistoryLengthSlider.intValue = [self.audioPlot rollingHistoryLength]; self.rollingHistoryLengthLabel.intValue = [self.audioPlot rollingHistoryLength]; @@ -84,7 +84,7 @@ - (void)awakeFromNib - (void)changedOutput:(NSMenuItem *)item { EZAudioDevice *device = [item representedObject]; - [self.output setDevice:device]; + [self.player setDevice:device]; } //------------------------------------------------------------------------------ @@ -110,7 +110,7 @@ - (void)changePlotType:(id)sender - (void)changeVolume:(id)sender { float value = [(NSSlider *)sender floatValue]; - [self.output setVolume:value]; + [self.player setVolume:value]; self.volumeLabel.floatValue = value; } @@ -142,7 +142,7 @@ - (void)openFile:(id)sender -(void)play:(id)sender { - if (![self.output isPlaying]) + if (![self.player isPlaying]) { if (self.eof) { @@ -152,11 +152,11 @@ -(void)play:(id)sender { self.audioPlot.plotType = EZPlotTypeRolling; } - [self.output startPlayback]; + [self.player play]; } else { - [self.output stopPlayback]; + [self.player pause]; } } @@ -165,7 +165,7 @@ -(void)play:(id)sender -(void)seekToFrame:(id)sender { double value = [(NSSlider*)sender doubleValue]; - [self.audioFile seekToFrame:(SInt64)value]; + [self.player seekToFrame:(SInt64)value]; self.positionLabel.doubleValue = value; } @@ -208,7 +208,7 @@ -(void)openFileWithFilePathURL:(NSURL*)filePathURL // // Stop playback // - [self.output stopPlayback]; + [self.player pause]; // // Clear the audio plot @@ -218,7 +218,7 @@ -(void)openFileWithFilePathURL:(NSURL*)filePathURL // // Load the audio file and customize the UI // - self.audioFile = [EZAudioFile audioFileWithURL:filePathURL delegate:self]; + self.audioFile = [EZAudioFile audioFileWithURL:filePathURL]; self.eof = NO; self.filePathLabel.stringValue = filePathURL.lastPathComponent; self.positionSlider.minValue = 0.0f; @@ -226,11 +226,6 @@ -(void)openFileWithFilePathURL:(NSURL*)filePathURL self.playButton.state = NSOffState; self.plotSegmentControl.selectedSegment = 1; - // - // Set the client format from the EZAudioFile on the output - // - [self.output setInputFormat:self.audioFile.clientFormat]; - // // Change back to a buffer plot, but mirror and fill the waveform // @@ -250,6 +245,11 @@ -(void)openFileWithFilePathURL:(NSURL*)filePathURL [weakSelf.audioPlot updateBuffer:waveformData[0] withBufferSize:length]; }]; + + // + // Play the audio file + // + [self.player setAudioFile:self.audioFile]; } //------------------------------------------------------------------------------ @@ -274,7 +274,7 @@ - (void)reloadOutputDevicePopUpButtonMenu // if you are connected to an external display by default the external // display's microphone might be used instead of the mac's built in // mic. - if ([device isEqual:[self.output device]]) + if ([device isEqual:[self.player device]]) { defaultOutputDeviceItem = item; } @@ -289,58 +289,35 @@ - (void)reloadOutputDevicePopUpButtonMenu } //------------------------------------------------------------------------------ -#pragma mark - EZAudioFileDelegate +#pragma mark - EZAudioPlayerDelegate //------------------------------------------------------------------------------ --(void)audioFile:(EZAudioFile *)audioFile updatedPosition:(SInt64)framePosition +- (void) audioPlayer:(EZAudioPlayer *)audioPlayer + playedAudio:(float **)buffer + withBufferSize:(UInt32)bufferSize + withNumberOfChannels:(UInt32)numberOfChannels + inAudioFile:(EZAudioFile *)audioFile { __weak typeof (self) weakSelf = self; dispatch_async(dispatch_get_main_queue(), ^{ - if (![weakSelf.positionSlider.cell isHighlighted]) - { - weakSelf.positionSlider.floatValue = (float)framePosition; - weakSelf.positionLabel.floatValue = (float)framePosition; - } + [weakSelf.audioPlot updateBuffer:buffer[0] + withBufferSize:bufferSize]; }); } -//------------------------------------------------------------------------------ -#pragma mark - EZOutputDataSource //------------------------------------------------------------------------------ --(OSStatus) output:(EZOutput *)output - shouldFillAudioBufferList:(AudioBufferList *)audioBufferList - withNumberOfFrames:(UInt32)frames - timestamp:(const AudioTimeStamp *)timestamp -{ - if (self.audioFile) - { - UInt32 bufferSize; - [self.audioFile readFrames:frames - audioBufferList:audioBufferList - bufferSize:&bufferSize - eof:&_eof]; - if (_eof) - { - [self seekToFrame:0]; - } - } - return noErr; -} - -//------------------------------------------------------------------------------ -#pragma mark - EZOutputDelegate -//------------------------------------------------------------------------------ - -- (void) output:(EZOutput *)output - playedAudio:(float **)buffer - withBufferSize:(UInt32)bufferSize - withNumberOfChannels:(UInt32)numberOfChannels +- (void)audioPlayer:(EZAudioPlayer *)audioPlayer + updatedPosition:(SInt64)framePosition + inAudioFile:(EZAudioFile *)audioFile { __weak typeof (self) weakSelf = self; dispatch_async(dispatch_get_main_queue(), ^{ - [weakSelf.audioPlot updateBuffer:buffer[0] - withBufferSize:bufferSize]; + if (!weakSelf.positionSlider.highlighted) + { + weakSelf.positionSlider.floatValue = (float)framePosition; + weakSelf.positionLabel.integerValue = framePosition; + } }); } diff --git a/EZAudioExamples/iOS/EZAudioPlayFileExample/EZAudioPlayFileExample/MainStoryboard.storyboard b/EZAudioExamples/iOS/EZAudioPlayFileExample/EZAudioPlayFileExample/MainStoryboard.storyboard index 26ddd8a4..d8386d4c 100644 --- a/EZAudioExamples/iOS/EZAudioPlayFileExample/EZAudioPlayFileExample/MainStoryboard.storyboard +++ b/EZAudioExamples/iOS/EZAudioPlayFileExample/EZAudioPlayFileExample/MainStoryboard.storyboard @@ -149,8 +149,8 @@ - - + + diff --git a/EZAudioExamples/iOS/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.h b/EZAudioExamples/iOS/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.h index 247ee929..3d1f8b8e 100644 --- a/EZAudioExamples/iOS/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.h +++ b/EZAudioExamples/iOS/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.h @@ -16,79 +16,104 @@ */ #define kAudioFileDefault [[NSBundle mainBundle] pathForResource:@"simple-drum-beat" ofType:@"wav"] +//------------------------------------------------------------------------------ +#pragma mark - PlayFileViewController +//------------------------------------------------------------------------------ + /** - Using the EZOutputDataSource to provide output data to the EZOutput component. + The PlayFileViewController will demonstrate how to play an audio file and plot + the audio data in real-time. */ -@interface PlayFileViewController : UIViewController +@interface PlayFileViewController : UIViewController +//------------------------------------------------------------------------------ #pragma mark - Components +//------------------------------------------------------------------------------ + /** - The EZAudioFile representing of the currently selected audio file + An EZAudioFile that will be used to load the audio file at the file path specified */ @property (nonatomic, strong) EZAudioFile *audioFile; +//------------------------------------------------------------------------------ + /** - The EZOutput representing the output currently being used to play the audio file. + An EZAudioPlayer that will be used for playback */ -@property (nonatomic, strong) EZOutput *output; +@property (nonatomic, strong) EZAudioPlayer *player; + +//------------------------------------------------------------------------------ /** The CoreGraphics based audio plot */ @property (nonatomic, weak) IBOutlet EZAudioPlot *audioPlot; -#pragma mark - UI Extras +//------------------------------------------------------------------------------ + /** A label to display the current file path with the waveform shown */ @property (nonatomic, weak) IBOutlet UILabel *filePathLabel; +//------------------------------------------------------------------------------ + /** A slider to indicate the current frame position in the audio file */ -@property (nonatomic, weak) IBOutlet UISlider *framePositionSlider; +@property (nonatomic, weak) IBOutlet UISlider *positionSlider; + +//------------------------------------------------------------------------------ /** - A slider to indicate the current rolling history length of the audio plot. + A slider to indicate the rolling history length of the audio plot. */ @property (nonatomic, weak) IBOutlet UISlider *rollingHistorySlider; +//------------------------------------------------------------------------------ + /** A slider to indicate the volume on the audio player */ @property (nonatomic, weak) IBOutlet UISlider *volumeSlider; -/** - A BOOL indicating whether or not we've reached the end of the file - */ -@property (nonatomic,assign) BOOL eof; - +//------------------------------------------------------------------------------ #pragma mark - Actions +//------------------------------------------------------------------------------ + /** Switches the plot drawing type between a buffer plot (visualizes the current stream of audio data from the update function) or a rolling plot (visualizes the audio data over time, this is the classic waveform look) */ - (IBAction)changePlotType:(id)sender; +//------------------------------------------------------------------------------ + /** Changes the length of the rolling history of the audio plot. */ - (IBAction)changeRollingHistoryLength:(id)sender; +//------------------------------------------------------------------------------ + /** Changes the volume of the audio player. */ - (IBAction)changeVolume:(id)sender; +//------------------------------------------------------------------------------ + /** Begins playback if a file is loaded. Pauses if the file is already playing. */ - (IBAction)play:(id)sender; +//------------------------------------------------------------------------------ + /** Seeks to a specific frame in the audio file. */ - (IBAction)seekToFrame:(id)sender; +//------------------------------------------------------------------------------ + @end diff --git a/EZAudioExamples/iOS/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.m b/EZAudioExamples/iOS/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.m index 4f38b3ee..48bba441 100644 --- a/EZAudioExamples/iOS/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.m +++ b/EZAudioExamples/iOS/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.m @@ -20,53 +20,31 @@ - (UIStatusBarStyle)preferredStatusBarStyle - (void)viewDidLoad { [super viewDidLoad]; - - // - // Setup the AVAudioSession. EZMicrophone will not work properly on iOS - // if you don't do this! - // - AVAudioSession *session = [AVAudioSession sharedInstance]; - NSError *error; - [session setCategory:AVAudioSessionCategoryPlayback error:&error]; - if (error) - { - NSLog(@"Error setting up audio session category: %@", error.localizedDescription); - } - [session setActive:YES error:&error]; - if (error) - { - NSLog(@"Error setting up audio session active: %@", error.localizedDescription); - } // - // Customize the plot's look + // Customizing the audio plot's look // - // Background color self.audioPlot.backgroundColor = [UIColor colorWithRed: 0.816 green: 0.349 blue: 0.255 alpha: 1]; - // Waveform color self.audioPlot.color = [UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0]; - // Plot type self.audioPlot.plotType = EZPlotTypeBuffer; - // Fill self.audioPlot.shouldFill = YES; - // Mirror self.audioPlot.shouldMirror = YES; - - // - // Create an EZOutput instance - // - self.output = [EZOutput outputWithDataSource:self]; - self.output.delegate = self; // - // Customize UI controls + // Create the audio player // - self.volumeSlider.value = [self.output volume]; - self.rollingHistorySlider.value = [self.audioPlot rollingHistoryLength]; + self.player = [EZAudioPlayer audioPlayer]; + self.player.delegate = self; + self.player.shouldLoop = YES; // - // Try opening the sample file + // Customize UI components // + self.rollingHistorySlider.value = (float)[self.audioPlot rollingHistoryLength]; + + /* + Try opening the sample file + */ [self openFileWithFilePathURL:[NSURL fileURLWithPath:kAudioFileDefault]]; } @@ -103,53 +81,56 @@ - (void)changeRollingHistoryLength:(id)sender - (void)changeVolume:(id)sender { float value = [(UISlider *)sender value]; - [self.output setVolume:value]; + [self.player setVolume:value]; } //------------------------------------------------------------------------------ - (void)openFileWithFilePathURL:(NSURL *)filePathURL { - // Stop playback - [self.output stopPlayback]; + // + // Create the EZAudioPlayer + // + self.audioFile = [EZAudioFile audioFileWithURL:filePathURL]; - self.audioFile = [EZAudioFile audioFileWithURL:filePathURL delegate:self]; - self.eof = NO; + // + // Update the UI + // self.filePathLabel.text = filePathURL.lastPathComponent; - self.framePositionSlider.maximumValue = (float)self.audioFile.totalFrames; - - // Set the input format from the EZAudioFile on the output - [self.output setInputFormat:[self.audioFile clientFormat]]; + self.positionSlider.maximumValue = (float)self.audioFile.totalFrames; + self.volumeSlider.value = [self.player volume]; + // // Plot the whole waveform + // self.audioPlot.plotType = EZPlotTypeBuffer; self.audioPlot.shouldFill = YES; self.audioPlot.shouldMirror = YES; - __weak typeof (self) weakSelf = self; [self.audioFile getWaveformDataWithCompletionBlock:^(float **waveformData, int length) - { - [weakSelf.audioPlot updateBuffer:waveformData[0] - withBufferSize:length]; - }]; + { + [weakSelf.audioPlot updateBuffer:waveformData[0] + withBufferSize:length]; + }]; + + // + // Play the audio file + // + [self.player setAudioFile:self.audioFile]; } //------------------------------------------------------------------------------ - (void)play:(id)sender { - if (![self.output isPlaying]) + if ([self.player isPlaying]) { - if (self.eof) - { - [self.audioFile seekToFrame:0]; - } - [self.output startPlayback]; + [self.player pause]; } else { - [self.output stopPlayback]; + [self.player play]; } } @@ -157,100 +138,65 @@ - (void)play:(id)sender - (void)seekToFrame:(id)sender { - [self.audioFile seekToFrame:(SInt64)[(UISlider *)sender value]]; + [self.player seekToFrame:(SInt64)[(UISlider *)sender value]]; } //------------------------------------------------------------------------------ -#pragma mark - Utility -//------------------------------------------------------------------------------ - -/* - Give the visualization of the current buffer (this is almost exactly the openFrameworks audio input eample) - */ -- (void)drawBufferPlot -{ - self.audioPlot.plotType = EZPlotTypeBuffer; - self.audioPlot.shouldMirror = NO; - self.audioPlot.shouldFill = NO; -} - +#pragma mark - EZAudioPlayerDelegate //------------------------------------------------------------------------------ -/* - Give the classic mirrored, rolling waveform look - */ -- (void)drawRollingPlot +- (void) audioPlayer:(EZAudioPlayer *)audioPlayer + playedAudio:(float **)buffer + withBufferSize:(UInt32)bufferSize + withNumberOfChannels:(UInt32)numberOfChannels + inAudioFile:(EZAudioFile *)audioFile { - self.audioPlot.plotType = EZPlotTypeRolling; - self.audioPlot.shouldFill = YES; - self.audioPlot.shouldMirror = YES; + __weak typeof (self) weakSelf = self; + dispatch_async(dispatch_get_main_queue(), ^{ + [weakSelf.audioPlot updateBuffer:buffer[0] + withBufferSize:bufferSize]; + }); } -//------------------------------------------------------------------------------ -#pragma mark - EZAudioFileDelegate //------------------------------------------------------------------------------ -- (void)audioFile:(EZAudioFile *)audioFile - updatedPosition:(SInt64)framePosition +- (void)audioPlayer:(EZAudioPlayer *)audioPlayer + updatedPosition:(SInt64)framePosition + inAudioFile:(EZAudioFile *)audioFile { - __weak typeof (self) weakSelf = self; + __weak typeof (self) weakSelf = self; dispatch_async(dispatch_get_main_queue(), ^{ - if (!weakSelf.framePositionSlider.touchInside) + if (!weakSelf.positionSlider.touchInside) { - weakSelf.framePositionSlider.value = (float)framePosition; + weakSelf.positionSlider.value = (float)framePosition; } }); } //------------------------------------------------------------------------------ -#pragma mark - EZOutputDataSource +#pragma mark - Utility //------------------------------------------------------------------------------ -- (OSStatus) output:(EZOutput *)output - shouldFillAudioBufferList:(AudioBufferList *)audioBufferList - withNumberOfFrames:(UInt32)frames - timestamp:(const AudioTimeStamp *)timestamp +/* + Give the visualization of the current buffer (this is almost exactly the openFrameworks audio input eample) + */ +- (void)drawBufferPlot { - if (self.audioFile) - { - UInt32 bufferSize; - BOOL eof; - [self.audioFile readFrames:frames - audioBufferList:audioBufferList - bufferSize:&bufferSize - eof:&eof]; - if (eof) - { - [self.audioFile seekToFrame:0]; - } - } - return noErr; + self.audioPlot.plotType = EZPlotTypeBuffer; + self.audioPlot.shouldMirror = NO; + self.audioPlot.shouldFill = NO; } -//------------------------------------------------------------------------------ -#pragma mark - EZOutputDelegate //------------------------------------------------------------------------------ -- (void) output:(EZOutput *)output - playedAudio:(float **)buffer - withBufferSize:(UInt32)bufferSize - withNumberOfChannels:(UInt32)numberOfChannels +/* + Give the classic mirrored, rolling waveform look + */ +- (void)drawRollingPlot { - __weak typeof (self) weakSelf = self; - dispatch_async(dispatch_get_main_queue(), ^{ - if ([self.output isPlaying]) - { - if (weakSelf.audioPlot.plotType == EZPlotTypeBuffer && - weakSelf.audioPlot.shouldFill == YES && - weakSelf.audioPlot.shouldMirror == YES) - { - weakSelf.audioPlot.shouldFill = NO; - weakSelf.audioPlot.shouldMirror = NO; - } - [weakSelf.audioPlot updateBuffer:buffer[0] - withBufferSize:bufferSize]; - } - }); + self.audioPlot.plotType = EZPlotTypeRolling; + self.audioPlot.shouldFill = YES; + self.audioPlot.shouldMirror = YES; } //------------------------------------------------------------------------------ From f5f40304d058c3468a5e6635b425d413302a5e46 Mon Sep 17 00:00:00 2001 From: Syed Haris Ali Date: Tue, 30 Jun 2015 02:13:31 -0700 Subject: [PATCH 2/9] code clean up --- EZAudio/EZAudioPlayer.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/EZAudio/EZAudioPlayer.h b/EZAudio/EZAudioPlayer.h index 66117c88..c90a7adb 100644 --- a/EZAudio/EZAudioPlayer.h +++ b/EZAudio/EZAudioPlayer.h @@ -64,8 +64,8 @@ FOUNDATION_EXPORT NSString * const EZAudioPlayerDidSeekNotification; @param numberOfChannels The number of channels. 2 for stereo, 1 for mono. @param audioFile The instance of the EZAudioFile that the event was triggered from */ -- (void) audioPlayer:(EZAudioPlayer *)audioPlayer - playedAudio:(float **)buffer +- (void) audioPlayer:(EZAudioPlayer *)audioPlayer + playedAudio:(float **)buffer withBufferSize:(UInt32)bufferSize withNumberOfChannels:(UInt32)numberOfChannels inAudioFile:(EZAudioFile *)audioFile;; From 199f63d7e29745766db19fbdb8af3393f79a34b7 Mon Sep 17 00:00:00 2001 From: Syed Haris Ali Date: Tue, 30 Jun 2015 02:28:15 -0700 Subject: [PATCH 3/9] more code cleanup --- EZAudio/EZAudioPlayer.h | 49 ++++++++++++++++++++--------------------- EZAudio/EZAudioPlayer.m | 15 +++++++++++++ 2 files changed, 39 insertions(+), 25 deletions(-) diff --git a/EZAudio/EZAudioPlayer.h b/EZAudio/EZAudioPlayer.h index c90a7adb..fa217634 100644 --- a/EZAudio/EZAudioPlayer.h +++ b/EZAudio/EZAudioPlayer.h @@ -123,13 +123,6 @@ FOUNDATION_EXPORT NSString * const EZAudioPlayerDidSeekNotification; /// @name Initializers ///----------------------------------------------------------- -/** - Initializes the EZAudioPlayer with an EZAudioPlayerDelegate. - @param delegate The receiver that will act as the EZAudioPlayerDelegate. Set to nil if it should have no delegate or use the initWithAudioFile: function instead. - @return The newly created instance of the EZAudioPlayer - */ -- (instancetype)initWithDelegate:(id)delegate; - /** Initializes the EZAudioPlayer with an EZAudioFile instance. This does not use the EZAudioFile by reference, but instead creates a separate EZAudioFile instance with the same file at the given file path provided by the internal NSURL to use for internal seeking so it doesn't cause any locking between the caller's instance of the EZAudioFile. @param audioFile The instance of the EZAudioFile to use for initializing the EZAudioPlayer @@ -150,6 +143,15 @@ FOUNDATION_EXPORT NSString * const EZAudioPlayerDidSeekNotification; //------------------------------------------------------------------------------ +/** + Initializes the EZAudioPlayer with an EZAudioPlayerDelegate. + @param delegate The receiver that will act as the EZAudioPlayerDelegate. Set to nil if it should have no delegate or use the initWithAudioFile: function instead. + @return The newly created instance of the EZAudioPlayer + */ +- (instancetype)initWithDelegate:(id)delegate; + +//------------------------------------------------------------------------------ + /** Initializes the EZAudioPlayer with an NSURL instance representing the file path of the audio file. @param url The NSURL instance representing the file path of the audio file. @@ -184,14 +186,6 @@ FOUNDATION_EXPORT NSString * const EZAudioPlayerDidSeekNotification; //------------------------------------------------------------------------------ -/** - Class initializer that creates a default EZAudioPlayer with an EZAudioPlayerDelegate.. - @return The newly created instance of the EZAudioPlayer - */ -+ (instancetype)audioPlayerWithDelegate:(id)delegate; - -//------------------------------------------------------------------------------ - /** Class initializer that creates the EZAudioPlayer with an EZAudioFile instance. This does not use the EZAudioFile by reference, but instead creates a separate EZAudioFile instance with the same file at the given file path provided by the internal NSURL to use for internal seeking so it doesn't cause any locking between the caller's instance of the EZAudioFile. @param audioFile The instance of the EZAudioFile to use for initializing the EZAudioPlayer @@ -212,6 +206,14 @@ FOUNDATION_EXPORT NSString * const EZAudioPlayerDidSeekNotification; //------------------------------------------------------------------------------ +/** + Class initializer that creates a default EZAudioPlayer with an EZAudioPlayerDelegate.. + @return The newly created instance of the EZAudioPlayer + */ ++ (instancetype)audioPlayerWithDelegate:(id)delegate; + +//------------------------------------------------------------------------------ + /** Class initializer that creates the EZAudioPlayer with an NSURL instance representing the file path of the audio file. @param url The NSURL instance representing the file path of the audio file. @@ -267,7 +269,9 @@ FOUNDATION_EXPORT NSString * const EZAudioPlayerDidSeekNotification; //------------------------------------------------------------------------------ - +/** + <#Description#> + */ @property (readwrite) EZAudioDevice *device; //------------------------------------------------------------------------------ @@ -306,15 +310,7 @@ FOUNDATION_EXPORT NSString * const EZAudioPlayerDidSeekNotification; @note Please use `duration` property instead. @return The total duration of the audio file as a Float32. */ -@property (readonly) NSTimeInterval totalDuration __attribute__((deprecated));; - -//------------------------------------------------------------------------------ - -/** - Provides a flag indicating whether the EZAudioPlayer has reached the end of the audio file used for playback. - @return A BOOL indicating whether or not the EZAudioPlayer has reached the end of the file it is using for playback. - */ -@property (readonly) BOOL isEndOfFile; +@property (readonly) NSTimeInterval totalDuration __attribute__((deprecated)); //------------------------------------------------------------------------------ @@ -357,6 +353,9 @@ FOUNDATION_EXPORT NSString * const EZAudioPlayerDidSeekNotification; //------------------------------------------------------------------------------ +/** + <#Description#> + */ @property (nonatomic, assign) float volume; //------------------------------------------------------------------------------ diff --git a/EZAudio/EZAudioPlayer.m b/EZAudio/EZAudioPlayer.m index 5e1ecfa8..f5f128af 100644 --- a/EZAudio/EZAudioPlayer.m +++ b/EZAudio/EZAudioPlayer.m @@ -241,6 +241,20 @@ - (float)pan //------------------------------------------------------------------------------ +- (NSTimeInterval)totalDuration +{ + return [self duration]; +} + +//------------------------------------------------------------------------------ + +- (SInt64)totalFrames +{ + return [self.audioFile totalFrames]; +} + +//------------------------------------------------------------------------------ + - (float)volume { return [self.output volume]; @@ -372,6 +386,7 @@ - (OSStatus) output:(EZOutput *)output else if (eof) { [self pause]; + [self seekToFrame:0]; [[NSNotificationCenter defaultCenter] postNotificationName:EZAudioPlayerDidReachEndOfFileNotification object:self]; } From a832f27a5a930e960be6b8e63c498db8de4d135f Mon Sep 17 00:00:00 2001 From: Syed Haris Ali Date: Tue, 30 Jun 2015 12:36:24 -0700 Subject: [PATCH 4/9] added nice description method to EZAudioFile and updated play file examples to use notifications --- EZAudio/EZAudioFile.m | 23 +++++++++ .../PlayFileViewController.m | 46 +++++++++++++++++ .../PlayFileViewController.m | 49 +++++++++++++++++++ 3 files changed, 118 insertions(+) diff --git a/EZAudio/EZAudioFile.m b/EZAudio/EZAudioFile.m index a76618b6..b6cb0df2 100644 --- a/EZAudio/EZAudioFile.m +++ b/EZAudio/EZAudioFile.m @@ -676,6 +676,29 @@ - (void)setCurrentTime:(NSTimeInterval)currentTime [self seekToFrame:frame]; } +//------------------------------------------------------------------------------ +#pragma mark - Description +//------------------------------------------------------------------------------ + +- (NSString *)description +{ + return [NSString stringWithFormat:@"%@ {\n" + " url: %@,\n" + " duration: %f,\n" + " totalFrames: %lld,\n" + " metadata: %@,\n" + " fileFormat: { %@ },\n" + " clientFormat: { %@ } \n" + "}", + [super description], + [self url], + [self duration], + [self totalFrames], + [self metadata], + [EZAudioUtilities stringForAudioStreamBasicDescription:[self fileFormat]], + [EZAudioUtilities stringForAudioStreamBasicDescription:[self clientFormat]]]; +} + //------------------------------------------------------------------------------ @end diff --git a/EZAudioExamples/OSX/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.m b/EZAudioExamples/OSX/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.m index edaca62f..22a3eff3 100644 --- a/EZAudioExamples/OSX/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.m +++ b/EZAudioExamples/OSX/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.m @@ -71,12 +71,58 @@ - (void)awakeFromNib self.rollingHistoryLengthSlider.intValue = [self.audioPlot rollingHistoryLength]; self.rollingHistoryLengthLabel.intValue = [self.audioPlot rollingHistoryLength]; + [self setupNotifications]; + // // Try opening the sample file // [self openFileWithFilePathURL:[NSURL fileURLWithPath:kAudioFileDefault]]; } +//------------------------------------------------------------------------------ +#pragma mark - Notifications +//------------------------------------------------------------------------------ + +- (void)setupNotifications +{ + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(audioPlayerDidChangeAudioFile:) + name:EZAudioPlayerDidChangeAudioFileNotification + object:self.player]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(audioPlayerDidChangeOutputDevice:) + name:EZAudioPlayerDidChangeOutputDeviceNotification + object:self.player]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(audioPlayerDidChangePlayState:) + name:EZAudioPlayerDidChangePlayStateNotification + object:self.player]; +} + +//------------------------------------------------------------------------------ + +- (void)audioPlayerDidChangeAudioFile:(NSNotification *)notification +{ + EZAudioPlayer *player = [notification object]; + NSLog(@"Player changed audio file: %@", [player audioFile]); +} + +//------------------------------------------------------------------------------ + +- (void)audioPlayerDidChangeOutputDevice:(NSNotification *)notification +{ + EZAudioPlayer *player = [notification object]; + NSLog(@"Player changed output device: %@", [player device]); +} + +//------------------------------------------------------------------------------ + +- (void)audioPlayerDidChangePlayState:(NSNotification *)notification +{ + EZAudioPlayer *player = [notification object]; + NSLog(@"Player change play state, isPlaying: %i", [player isPlaying]); +} + //------------------------------------------------------------------------------ #pragma mark - Actions //------------------------------------------------------------------------------ diff --git a/EZAudioExamples/iOS/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.m b/EZAudioExamples/iOS/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.m index 48bba441..aa43454a 100644 --- a/EZAudioExamples/iOS/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.m +++ b/EZAudioExamples/iOS/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.m @@ -42,12 +42,61 @@ - (void)viewDidLoad // self.rollingHistorySlider.value = (float)[self.audioPlot rollingHistoryLength]; + // + // Listen for EZAudioPlayer notifications + // + [self setupNotifications]; + /* Try opening the sample file */ [self openFileWithFilePathURL:[NSURL fileURLWithPath:kAudioFileDefault]]; } +//------------------------------------------------------------------------------ +#pragma mark - Notifications +//------------------------------------------------------------------------------ + +- (void)setupNotifications +{ + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(audioPlayerDidChangeAudioFile:) + name:EZAudioPlayerDidChangeAudioFileNotification + object:self.player]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(audioPlayerDidChangeOutputDevice:) + name:EZAudioPlayerDidChangeOutputDeviceNotification + object:self.player]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(audioPlayerDidChangePlayState:) + name:EZAudioPlayerDidChangePlayStateNotification + object:self.player]; +} + +//------------------------------------------------------------------------------ + +- (void)audioPlayerDidChangeAudioFile:(NSNotification *)notification +{ + EZAudioPlayer *player = [notification object]; + NSLog(@"Player changed audio file: %@", [player audioFile]); +} + +//------------------------------------------------------------------------------ + +- (void)audioPlayerDidChangeOutputDevice:(NSNotification *)notification +{ + EZAudioPlayer *player = [notification object]; + NSLog(@"Player changed output device: %@", [player device]); +} + +//------------------------------------------------------------------------------ + +- (void)audioPlayerDidChangePlayState:(NSNotification *)notification +{ + EZAudioPlayer *player = [notification object]; + NSLog(@"Player change play state, isPlaying: %i", [player isPlaying]); +} + //------------------------------------------------------------------------------ #pragma mark - Actions //------------------------------------------------------------------------------ From 115dc2b8a0c54f4051b59d9ddbcc313de54bc8f5 Mon Sep 17 00:00:00 2001 From: Syed Haris Ali Date: Tue, 30 Jun 2015 13:03:34 -0700 Subject: [PATCH 5/9] updated play file example for notifications and added loop checkbox for OSX, fixed audio session not being set on iOS FFT example --- .../PlayFileViewController.h | 10 +++++++ .../PlayFileViewController.m | 26 +++++++++++++++++++ .../PlayFileViewController.xib | 14 ++++++++++ .../EZAudioFFTExample/FFTViewController.m | 19 +++++++++++++- .../PlayFileViewController.m | 3 +-- 5 files changed, 69 insertions(+), 3 deletions(-) diff --git a/EZAudioExamples/OSX/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.h b/EZAudioExamples/OSX/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.h index b4df9c56..19479311 100644 --- a/EZAudioExamples/OSX/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.h +++ b/EZAudioExamples/OSX/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.h @@ -61,6 +61,11 @@ */ @property (nonatomic, weak) IBOutlet NSTextField *filePathLabel; +/** + A checkbox button to that allows you to specify if the audio player should loop. + */ +@property (nonatomic, weak) IBOutlet NSButton *loopCheckboxButton; + /** A label to display the audio file's current position. */ @@ -112,6 +117,11 @@ */ - (IBAction)changeRollingHistoryLength:(id)sender; +/** + Switches the loop state on the audio player regarding whether the current playing audio file should loop back to the beginning when it finishes. + */ +- (IBAction)changeShouldLoop:(id)sender; + /** Changes the volume of the audio coming out of the EZOutput. */ diff --git a/EZAudioExamples/OSX/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.m b/EZAudioExamples/OSX/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.m index 22a3eff3..f9b1ed31 100644 --- a/EZAudioExamples/OSX/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.m +++ b/EZAudioExamples/OSX/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.m @@ -70,7 +70,11 @@ - (void)awakeFromNib self.volumeLabel.floatValue = [self.player volume]; self.rollingHistoryLengthSlider.intValue = [self.audioPlot rollingHistoryLength]; self.rollingHistoryLengthLabel.intValue = [self.audioPlot rollingHistoryLength]; + self.loopCheckboxButton.state = [self.player shouldLoop]; + // + // Listen for state changes to the EZAudioPlayer + // [self setupNotifications]; // @@ -97,6 +101,12 @@ - (void)setupNotifications selector:@selector(audioPlayerDidChangePlayState:) name:EZAudioPlayerDidChangePlayStateNotification object:self.player]; + + // This notification will only trigger if the player's shouldLoop property is set to NO + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(audioPlayerDidReachEndOfFile:) + name:EZAudioPlayerDidReachEndOfFileNotification + object:self.player]; } //------------------------------------------------------------------------------ @@ -123,6 +133,14 @@ - (void)audioPlayerDidChangePlayState:(NSNotification *)notification NSLog(@"Player change play state, isPlaying: %i", [player isPlaying]); } +//------------------------------------------------------------------------------ + +- (void)audioPlayerDidReachEndOfFile:(NSNotification *)notification +{ + NSLog(@"Player did reach end of file!"); + [self.playButton setState:NSOffState]; +} + //------------------------------------------------------------------------------ #pragma mark - Actions //------------------------------------------------------------------------------ @@ -153,6 +171,14 @@ - (void)changePlotType:(id)sender //------------------------------------------------------------------------------ +- (void)changeShouldLoop:(id)sender +{ + NSInteger state = [(NSButton *)sender state]; + [self.player setShouldLoop:state]; +} + +//------------------------------------------------------------------------------ + - (void)changeVolume:(id)sender { float value = [(NSSlider *)sender floatValue]; diff --git a/EZAudioExamples/OSX/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.xib b/EZAudioExamples/OSX/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.xib index f9435050..1a9facb6 100644 --- a/EZAudioExamples/OSX/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.xib +++ b/EZAudioExamples/OSX/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.xib @@ -8,7 +8,9 @@ + + @@ -192,6 +194,16 @@ + @@ -199,6 +211,7 @@ + @@ -209,6 +222,7 @@ + diff --git a/EZAudioExamples/iOS/EZAudioFFTExample/EZAudioFFTExample/FFTViewController.m b/EZAudioExamples/iOS/EZAudioFFTExample/EZAudioFFTExample/FFTViewController.m index 2261db26..de628ba7 100644 --- a/EZAudioExamples/iOS/EZAudioFFTExample/EZAudioFFTExample/FFTViewController.m +++ b/EZAudioExamples/iOS/EZAudioFFTExample/EZAudioFFTExample/FFTViewController.m @@ -49,6 +49,23 @@ - (void)viewDidLoad { [super viewDidLoad]; + // + // Setup the AVAudioSession. EZMicrophone will not work properly on iOS + // if you don't do this! + // + AVAudioSession *session = [AVAudioSession sharedInstance]; + NSError *error; + [session setCategory:AVAudioSessionCategoryPlayAndRecord error:&error]; + if (error) + { + NSLog(@"Error setting up audio session category: %@", error.localizedDescription); + } + [session setActive:YES error:&error]; + if (error) + { + NSLog(@"Error setting up audio session active: %@", error.localizedDescription); + } + /* Customizing the audio plot's look */ @@ -70,7 +87,7 @@ - (void)viewDidLoad Start the microphone */ self.microphone = [EZMicrophone microphoneWithDelegate:self - startsImmediately:YES]; + startsImmediately:YES]; } diff --git a/EZAudioExamples/iOS/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.m b/EZAudioExamples/iOS/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.m index aa43454a..e6187e18 100644 --- a/EZAudioExamples/iOS/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.m +++ b/EZAudioExamples/iOS/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.m @@ -33,8 +33,7 @@ - (void)viewDidLoad // // Create the audio player // - self.player = [EZAudioPlayer audioPlayer]; - self.player.delegate = self; + self.player = [EZAudioPlayer audioPlayerWithDelegate:self]; self.player.shouldLoop = YES; // From f2084dab2a9d755536a5c290199d1f5702dc26b5 Mon Sep 17 00:00:00 2001 From: Syed Haris Ali Date: Tue, 30 Jun 2015 13:44:40 -0700 Subject: [PATCH 6/9] added clean up to remove observers when done --- .../PlayFileViewController.m | 9 +++++++++ .../PlayFileViewController.m | 15 +++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/EZAudioExamples/OSX/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.m b/EZAudioExamples/OSX/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.m index f9b1ed31..435f769a 100644 --- a/EZAudioExamples/OSX/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.m +++ b/EZAudioExamples/OSX/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.m @@ -32,6 +32,15 @@ @interface PlayFileViewController () @implementation PlayFileViewController +//------------------------------------------------------------------------------ +#pragma mark - Dealloc +//------------------------------------------------------------------------------ + +- (void)dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + //------------------------------------------------------------------------------ #pragma mark - Customize the Audio Plot //------------------------------------------------------------------------------ diff --git a/EZAudioExamples/iOS/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.m b/EZAudioExamples/iOS/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.m index e6187e18..ff8413fd 100644 --- a/EZAudioExamples/iOS/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.m +++ b/EZAudioExamples/iOS/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.m @@ -10,13 +10,28 @@ @implementation PlayFileViewController +//------------------------------------------------------------------------------ +#pragma mark - Dealloc +//------------------------------------------------------------------------------ + +- (void)dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +//------------------------------------------------------------------------------ #pragma mark - Status Bar Style +//------------------------------------------------------------------------------ + - (UIStatusBarStyle)preferredStatusBarStyle { return UIStatusBarStyleLightContent; } +//------------------------------------------------------------------------------ #pragma mark - Customize the Audio Plot +//------------------------------------------------------------------------------ + - (void)viewDidLoad { [super viewDidLoad]; From 4d952e2fa53448907b10197f4a66464e266cc265 Mon Sep 17 00:00:00 2001 From: Syed Haris Ali Date: Tue, 30 Jun 2015 14:09:12 -0700 Subject: [PATCH 7/9] added more documentation and proper AVAudioSession for play file iOS example --- EZAudio/EZAudioPlayer.h | 49 +++++++++++++------ EZAudio/EZAudioPlayer.m | 7 --- .../PlayFileViewController.m | 24 +++++++++ 3 files changed, 58 insertions(+), 22 deletions(-) diff --git a/EZAudio/EZAudioPlayer.h b/EZAudio/EZAudioPlayer.h index fa217634..1b8dc12e 100644 --- a/EZAudio/EZAudioPlayer.h +++ b/EZAudio/EZAudioPlayer.h @@ -34,12 +34,39 @@ #pragma mark - Notifications //------------------------------------------------------------------------------ +/** + Notification that occurs whenever the EZAudioPlayer changes its `audioFile` property. Check the new value using the EZAudioPlayer's `audioFile` property. + */ FOUNDATION_EXPORT NSString * const EZAudioPlayerDidChangeAudioFileNotification; + +/** + Notification that occurs whenever the EZAudioPlayer changes its `device` property. Check the new value using the EZAudioPlayer's `device` property. + */ FOUNDATION_EXPORT NSString * const EZAudioPlayerDidChangeOutputDeviceNotification; + +/** + Notification that occurs whenever the EZAudioPlayer changes its `output` component's `pan` property. Check the new value using the EZAudioPlayer's `pan` property. + */ FOUNDATION_EXPORT NSString * const EZAudioPlayerDidChangePanNotification; + +/** + Notification that occurs whenever the EZAudioPlayer changes its `output` component's play state. Check the new value using the EZAudioPlayer's `isPlaying` property. + */ FOUNDATION_EXPORT NSString * const EZAudioPlayerDidChangePlayStateNotification; + +/** + Notification that occurs whenever the EZAudioPlayer changes its `output` component's `volume` property. Check the new value using the EZAudioPlayer's `volume` property. + */ FOUNDATION_EXPORT NSString * const EZAudioPlayerDidChangeVolumeNotification; + +/** + Notification that occurs whenever the EZAudioPlayer has reached the end of a file and its `shouldLoop` property has been set to NO. + */ FOUNDATION_EXPORT NSString * const EZAudioPlayerDidReachEndOfFileNotification; + +/** + Notification that occurs whenever the EZAudioPlayer performs a seek via the `seekToFrame` method or `setCurrentTime:` property setter. Check the new `currentTime` or `frameIndex` value using the EZAudioPlayer's `currentTime` or `frameIndex` property, respectively. + */ FOUNDATION_EXPORT NSString * const EZAudioPlayerDidSeekNotification; //------------------------------------------------------------------------------ @@ -47,7 +74,7 @@ FOUNDATION_EXPORT NSString * const EZAudioPlayerDidSeekNotification; //------------------------------------------------------------------------------ /** - The EZAudioPlayerDelegate provides event callbacks for the EZAudioPlayer. These type of events are triggered by changes in the EZAudioPlayer's state and allow someone implementing the EZAudioPlayer to more easily update their user interface. Events are triggered anytime the EZAudioPlayer resumes/pauses playback, reaches the end of the file, reads audio data and converts it to float data visualizations (using the EZAudioFile), and updates its cursor position within the audio file during playback (use this for the play position on a slider on the user interface). + The EZAudioPlayerDelegate provides event callbacks for the EZAudioPlayer. Since 0.5.0 the EZAudioPlayerDelegate provides a smaller set of delegate methods in favor of notifications to allow multiple receivers of the EZAudioPlayer event callbacks since only one player is typically used in an application. Specifically, these methods are provided for high frequency callbacks that wrap the EZAudioPlayer's internal EZAudioFile and EZOutput instances. @warning These callbacks don't necessarily occur on the main thread so make sure you wrap any UI code in a GCD block like: dispatch_async(dispatch_get_main_queue(), ^{ // Update UI }); */ @protocol EZAudioPlayerDelegate @@ -89,7 +116,7 @@ FOUNDATION_EXPORT NSString * const EZAudioPlayerDidSeekNotification; //------------------------------------------------------------------------------ /** - The EZAudioPlayer acts as the master delegate (the EZAudioFileDelegate) over whatever EZAudioFile it is using for playback. Classes that want to get the EZAudioFileDelegate callbacks should implement the EZAudioPlayer's EZAudioPlayerDelegate on the EZAudioPlayer instance. + The EZAudioPlayer provides an interface that combines the EZAudioFile and EZOutput to play local audio files. This class acts as the master delegate (the EZAudioFileDelegate) over whatever EZAudioFile instance, the `audioFile` property, it is using for playback as well as the EZOutputDelegate and EZOutputDataSource over whatever EZOutput instance is set as the `output`. Classes that want to get the EZAudioFileDelegate callbacks should implement the EZAudioPlayer's EZAudioPlayerDelegate on the EZAudioPlayer instance. Since 0.5.0 the EZAudioPlayer offers notifications over the usual delegate methods to allow multiple receivers to get the EZAudioPlayer's state changes since one player will typically be used in one application. The EZAudioPlayerDelegate, the `delegate`, provides callbacks for high frequency methods that simply wrap the EZAudioFileDelegate and EZOutputDelegate callbacks for providing the audio buffer played as well as the position updating (you will typically have one scrub bar in an application). */ @interface EZAudioPlayer : NSObject + The EZAudioDevice instance that is being used by the `output`. Similarly, setting this just sets the `device` property of the `output`. */ @property (readwrite) EZAudioDevice *device; @@ -298,7 +325,7 @@ FOUNDATION_EXPORT NSString * const EZAudioPlayerDidSeekNotification; //------------------------------------------------------------------------------ /** - <#Description#> + Provides the EZOutput that is being used to handle the actual playback of the audio data. This property is also settable, but note that the EZAudioPlayer will become the output's EZOutputDataSource and EZOutputDelegate. To listen for the EZOutput's delegate methods your view should implement the EZAudioPlayerDelegate and set itself as the EZAudioPlayer's `delegate`. */ @property (nonatomic, strong, readwrite) EZOutput *output; @@ -331,20 +358,12 @@ FOUNDATION_EXPORT NSString * const EZAudioPlayerDidSeekNotification; //------------------------------------------------------------------------------ /** - <#Description#> + Provides the current pan from the audio player's internal `output` component. Setting the pan adjusts the direction of the audio signal from left (0) to right (1). Default is 0.5 (middle). */ @property (nonatomic, assign) float pan; //------------------------------------------------------------------------------ -/** - Provides the total amount of frames in the current audio file being used for playback. - @return A SInt64 representing the total amount of frames in the current audio file being used for playback. - */ -@property (readonly) SInt64 totalFrames; - -//------------------------------------------------------------------------------ - /** Provides the file path that's currently being used by the player for playback. @return The NSURL representing the file path of the audio file being used for playback. @@ -354,7 +373,7 @@ FOUNDATION_EXPORT NSString * const EZAudioPlayerDidSeekNotification; //------------------------------------------------------------------------------ /** - <#Description#> + Provides the current volume from the audio player's internal `output` component. Setting the volume adjusts the gain of the output between 0 and 1. Default is 1. */ @property (nonatomic, assign) float volume; diff --git a/EZAudio/EZAudioPlayer.m b/EZAudio/EZAudioPlayer.m index f5f128af..554ab802 100644 --- a/EZAudio/EZAudioPlayer.m +++ b/EZAudio/EZAudioPlayer.m @@ -241,13 +241,6 @@ - (float)pan //------------------------------------------------------------------------------ -- (NSTimeInterval)totalDuration -{ - return [self duration]; -} - -//------------------------------------------------------------------------------ - - (SInt64)totalFrames { return [self.audioFile totalFrames]; diff --git a/EZAudioExamples/iOS/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.m b/EZAudioExamples/iOS/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.m index ff8413fd..42760808 100644 --- a/EZAudioExamples/iOS/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.m +++ b/EZAudioExamples/iOS/EZAudioPlayFileExample/EZAudioPlayFileExample/PlayFileViewController.m @@ -36,6 +36,23 @@ - (void)viewDidLoad { [super viewDidLoad]; + // + // Setup the AVAudioSession. EZMicrophone will not work properly on iOS + // if you don't do this! + // + AVAudioSession *session = [AVAudioSession sharedInstance]; + NSError *error; + [session setCategory:AVAudioSessionCategoryPlayAndRecord error:&error]; + if (error) + { + NSLog(@"Error setting up audio session category: %@", error.localizedDescription); + } + [session setActive:YES error:&error]; + if (error) + { + NSLog(@"Error setting up audio session active: %@", error.localizedDescription); + } + // // Customizing the audio plot's look // @@ -51,6 +68,13 @@ - (void)viewDidLoad self.player = [EZAudioPlayer audioPlayerWithDelegate:self]; self.player.shouldLoop = YES; + // Override the output to the speaker + [session overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:&error]; + if (error) + { + NSLog(@"Error overriding output to the speaker: %@", error.localizedDescription); + } + // // Customize UI components // From 081c7047f5f1751aba04495e728d5ea353ceb83f Mon Sep 17 00:00:00 2001 From: Syed Haris Ali Date: Tue, 30 Jun 2015 14:10:43 -0700 Subject: [PATCH 8/9] cleaned up AVAudioSession stuff --- .../EZAudioRecordExample/AppDelegate.m | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/EZAudioExamples/iOS/EZAudioRecordExample/EZAudioRecordExample/AppDelegate.m b/EZAudioExamples/iOS/EZAudioRecordExample/EZAudioRecordExample/AppDelegate.m index a70716a0..4ba0bb45 100644 --- a/EZAudioExamples/iOS/EZAudioRecordExample/EZAudioRecordExample/AppDelegate.m +++ b/EZAudioExamples/iOS/EZAudioRecordExample/EZAudioRecordExample/AppDelegate.m @@ -14,21 +14,7 @@ @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - // Override point for customization after application launch. - - // Remember to configure your audio session - AVAudioSession *audioSession = [AVAudioSession sharedInstance]; - NSError *err = NULL; - [audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:&err]; - if( err ){ - NSLog(@"There was an error creating the audio session"); - } - [audioSession overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:NULL]; - if( err ){ - NSLog(@"There was an error sending the audio to the speakers"); - } - return YES; } From 33bdcee78ab776b216f09be8b467f9ca85b185ec Mon Sep 17 00:00:00 2001 From: Syed Haris Ali Date: Tue, 30 Jun 2015 14:13:37 -0700 Subject: [PATCH 9/9] accidentally deleted totalFrames property, added back --- EZAudio/EZAudioPlayer.h | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/EZAudio/EZAudioPlayer.h b/EZAudio/EZAudioPlayer.h index 1b8dc12e..737ccfdc 100644 --- a/EZAudio/EZAudioPlayer.h +++ b/EZAudio/EZAudioPlayer.h @@ -331,16 +331,6 @@ FOUNDATION_EXPORT NSString * const EZAudioPlayerDidSeekNotification; //------------------------------------------------------------------------------ -/** - Provides the total duration of the audio file in seconds. - @deprecated This property is deprecated starting in version 0.4.0. - @note Please use `duration` property instead. - @return The total duration of the audio file as a Float32. - */ -@property (readonly) NSTimeInterval totalDuration __attribute__((deprecated)); - -//------------------------------------------------------------------------------ - /** Provides the frame index (a.k.a the seek positon) within the audio file being used for playback. This can be helpful when seeking through the audio file. @return An SInt64 representing the current frame index within the audio file used for playback. @@ -364,6 +354,14 @@ FOUNDATION_EXPORT NSString * const EZAudioPlayerDidSeekNotification; //------------------------------------------------------------------------------ +/** + Provides the total amount of frames in the current audio file being used for playback. + @return A SInt64 representing the total amount of frames in the current audio file being used for playback. + */ +@property (readonly) SInt64 totalFrames; + +//------------------------------------------------------------------------------ + /** Provides the file path that's currently being used by the player for playback. @return The NSURL representing the file path of the audio file being used for playback.