macOS Audio Player: what changed
Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.
Title: macOS Audio Player
| Old | New | Line | |
|---|---|---|---|
| 1 | + | *(macOS only)* | |
| 2 | + | ||
| 3 | + | ## Supported Audio File and Data Formats | |
| 4 | + | ||
| 5 | + | ## AVAudioPlayer | |
| 6 | + | ||
| 7 | + | The AVAudioPlayer class lets you play sound in any audio format available in macOS from a file or memory. The properties of this class are used for managing information about a sound such as the playback point within the sound’s timeline and for accessing playback options such as volume and looping. | |
| 8 | + | ||
| 9 | + | Using an audio player you can: | |
| 10 | + | ||
| 11 | + | - Play sounds of any duration (unlike [[macOS Play Alert Sound|System Sound Services]] that has a 30 second limit); | |
| 12 | + | - Play sounds from files or memory buffers; | |
| 13 | + | - Play sounds when your application is in the background; | |
| 14 | + | - Loop sounds (including indefinitely); | |
| 15 | + | - Play multiple sounds simultaneously, one sound per audio player, with precise synchronisation; | |
| 16 | + | - Control relative playback level, stereo positioning, and playback rate for each sound you are playing; | |
| 17 | + | - Seek to a particular point in a sound file, which supports such application features as fast forward and rewind; and | |
| 18 | + | - Obtain data you can use for playback-level metering. | |
| 19 | + | ||
| 20 | + | ## Example Code | |
| 21 | + | ||
| 22 | + | The example code below provides a demo audio player application which implements the basic requirements for such an application. | |
| 23 | + | ||
| 24 | + | > **Note:** Not every method in the AVAudioPlayer class has been implemented in the code below. Most methods have been implemented: play audio from a file, pause playback, resume playback, stop playback, set/get volume, set/get number of loops, set/get current time (ie forward, rewind) and set/get rate (ie speed) of playback; etc. Check the Apple documentation for the rest. The implemented methods are described in detail in the code below. | |
| 25 | + | ||
| 26 | + | ``` pascal | |
| 27 | + | // | |
| 28 | + | // Note: Lazarus IDE 2.2.0 and Free Pascal Compiler v3.2.2 used | |
| 29 | + | // | |
| 30 | + | ||
| 31 | + | unit unit1; | |
| 32 | + | ||
| 33 | + | {$mode objfpc}{$H+} | |
| 34 | + | {$modeswitch objectivec1} | |
| 35 | + | {$linkframework AVFoundation} | |
| 36 | + | ||
| 37 | + | interface | |
| 38 | + | ||
| 39 | + | uses | |
| 40 | + | Forms, // for Form | |
| 41 | + | SysUtils, // for FloatToStr | |
| 42 | + | StdCtrls, // for labels, buttons | |
| 43 | + | CocoaAll, // for Cocoa types | |
| 44 | + | ComCtrls, // for trackbar | |
| 45 | + | ExtCtrls; // for timer | |
| 46 | + | ||
| 47 | + | type | |
| 48 | + | ||
| 49 | + | { TForm1 } | |
| 50 | + | AVAudioPlayerPtr = ^AVAudioPlayer; | |
| 51 | + | AVAudioPlayerDelegateProtocolPtr = ^AVAudioPlayerDelegateProtocol; | |
| 52 | + | ||
| 53 | + | AVAudioPlayerDelegateProtocol = ObjCProtocol; | |
| 54 | + | ||
| 55 | + | AVAudioPlayer = objcclass external(NSObject) | |
| 56 | + | private | |
| 57 | + | _internal: id; | |
| 58 | + | public | |
| 59 | + | {Initializes and returns an audio player for playing a designated sound file. | |
| 60 | + | url: a URL identifying the sound file to play. The audio data must be in a | |
| 61 | + | format supported by Core Audio. | |
| 62 | + | outError: If an error occurs, on return the NSError object describes the | |
| 63 | + | error. Set to NULL to ignore errors.} | |
| 64 | + | function initWithContentsOfURL_error (url: NSURL; outError: NSErrorPtr): id; | |
| 65 | + | message 'initWithContentsOfURL:error:'; | |
| 66 | + | {Plays a sound asynchronously. Calling this method implicitly calls the | |
| 67 | + | prepareToPlay() method if the audio player is not already prepared to play.} | |
| 68 | + | function play: Boolean; message 'play'; | |
| 69 | + | ||
| 70 | + | {A value of 0.0 indicates silence; a value of 1.0 (the default) indicates | |
| 71 | + | full volume for the audio player instance. Use this property to control an | |
| 72 | + | audio player’s volume relative to other audio output.} | |
| 73 | + | function volume: Single; message 'volume'; | |
| 74 | + | procedure setVolume(newValue: Single); message 'setVolume:'; | |
| 75 | + | ||
| 76 | + | {A value of 0, which is the default, means to play the sound once. | |
| 77 | + | Set a positive integer value to specify the number of times to return | |
| 78 | + | to the start and play again. For example, specifying a value of 1 | |
| 79 | + | results in a total of two plays of the sound. Set any negative | |
| 80 | + | integer value to loop the sound indefinitely until you call the | |
| 81 | + | stop() method.} | |
| 82 | + | function numberOfLoops: Integer; message 'numberOfLoops'; | |
| 83 | + | procedure setNumberOfLoops(newValue: Integer); message 'setNumberOfLoops:'; | |
| 84 | + | ||
| 85 | + | {A Boolean value that indicates whether the audio player is playing (true) | |
| 86 | + | or not (false).} | |
| 87 | + | function isPlaying: Boolean; message 'isPlaying'; | |
| 88 | + | ||
| 89 | + | {Calling this method preloads buffers and acquires the audio hardware | |
| 90 | + | needed for playback, which minimises the lag between calling the play() | |
| 91 | + | method and the start of sound output.} | |
| 92 | + | function prepareToPlay: Boolean; message 'prepareToPlay'; | |
| 93 | + | ||
| 94 | + | {Pauses playback; sound remains ready to resume playback from where | |
| 95 | + | it left off. It does not release the audio hardware that was acquired | |
| 96 | + | on calling play() or prepareToPlay().} | |
| 97 | + | procedure pause; message 'pause'; | |
| 98 | + | ||
| 99 | + | {The stop method does not reset the value of the currentTime property to 0. | |
| 100 | + | If you call stop during playback and then call play(), playback resumes | |
| 101 | + | at the point where it left off. Callikng this method undoes the setup | |
| 102 | + | performed on calling the play() or prepareToPlay() methods.} | |
| 103 | + | procedure stop; message 'stop'; | |
| 104 | + | ||
| 105 | + | {The total duration, in seconds, of the sound associated with the audio | |
| 106 | + | player.} | |
| 107 | + | function duration: Double; message 'duration'; | |
| 108 | + | ||
| 109 | + | {If the sound is playing, currentTime is the offset of the current playback | |
| 110 | + | position, measured in seconds from the start of the sound. If the sound is | |
| 111 | + | not playing, currentTime is the offset of where playing starts upon calling | |
| 112 | + | the play() method, measured in seconds from the start of the sound. | |
| 113 | + | By setting this property you can seek to a specific point in a sound file or | |
| 114 | + | implement audio fast-forward and rewind functions.} | |
| 115 | + | function currentTime: Double; message 'currentTime'; | |
| 116 | + | procedure setCurrentTime(newValue: Double); message 'setCurrentTime:'; | |
| 117 | + | ||
| 118 | + | {To enable adjustable playback rate for an audio player, set this property | |
| 119 | + | to true after you initialize the player and before you call the | |
| 120 | + | prepareToPlay() instance method for the player.} | |
| 121 | + | function enableRate: Boolean; message 'enableRate'; | |
| 122 | + | procedure setEnableRate(newValue:Boolean); message 'setEnableRate:'; | |
| 123 | + | ||
| 124 | + | {This property’s default value of 1.0 provides normal playback rate. The | |
| 125 | + | available range is from 0.5 for half-speed playback through 2.0 for | |
| 126 | + | double-speed playback. | |
| 127 | + | To set an audio player’s playback rate, you must first enable rate | |
| 128 | + | adjustment as described in the enableRate property description.} | |
| 129 | + | function rate: Single; message 'rate'; | |
| 130 | + | procedure setRate(newValue: Single); message 'setRate:'; | |
| 131 | + | ||
| 132 | + | {The UID of the current audio player.} | |
| 133 | + | function currentDevice: NSString; message 'currentDevice'; | |
| 134 | + | ||
| 135 | + | {Number of audio channels in the sound associated with the audio player.} | |
| 136 | + | function numberOfChannels: Integer; message 'numberOfChannels'; | |
| 137 | + | ||
| 138 | + | {The default value for the meteringEnabled property is off (Boolean false). | |
| 139 | + | Before using metering for an audio player, you need to enable it by | |
| 140 | + | setting this property to true.} | |
| 141 | + | function isMeteringEnabled: Boolean; message 'isMeteringEnabled'; | |
| 142 | + | procedure setMeteringEnabled(newValue:Boolean); message 'setMeteringEnabled:'; | |
| 143 | + | ||
| 144 | + | {To obtain current audio power values, you must call this method before | |
| 145 | + | calling averagePowerForChannel or peakPowerForChannel} | |
| 146 | + | procedure updateMeters; message 'updateMeters'; | |
| 147 | + | ||
| 148 | + | {Returns the average power for a given channel, in decibels, for the sound | |
| 149 | + | being played. | |
| 150 | + | Channel numbers are zero-indexed. A monaural signal, or the left channel | |
| 151 | + | of a stereo signal, has channel number 0. | |
| 152 | + | A return value of 0 dB indicates full scale, or maximum power; a return | |
| 153 | + | value of –160 dB indicates minimum power (that is, near silence). If the | |
| 154 | + | signal provided to the audio player exceeds ±full scale, then the return | |
| 155 | + | value may exceed 0 (that is, it may enter the positive range).} | |
| 156 | + | function avgPwrForChannel(newValue:LongWord): Double; message 'averagePowerForChannel:'; | |
| 157 | + | ||
| 158 | + | {Returns the peak power for a given channel, in decibels, for the sound | |
| 159 | + | being played. | |
| 160 | + | Channel numbers are zero-indexed. A monaural signal, or the left channel | |
| 161 | + | of a stereo signal, has channel number 0. | |
| 162 | + | A return value of 0 dB indicates full scale, or maximum power; a return | |
| 163 | + | value of –160 dB indicates minimum power (that is, near silence). If the | |
| 164 | + | signal provided to the audio player exceeds ±full scale, then the return | |
| 165 | + | value may exceed 0 (that is, it may enter the positive range).} | |
| 166 | + | function peakPwrForChannel(newValue:LongWord): Double; message 'peakPowerForChannel:'; | |
| 167 | + | ||
| 168 | + | procedure setDelegate(newValue: AVAudioPlayerDelegateProtocol); message 'setDelegate:'; | |
| 169 | + | function delegate: AVAudioPlayerDelegateProtocol; message 'delegate'; | |
| 170 | + | end; | |
| 171 | + | ||
| 172 | + | AVAudioPlayerDelegateProtocol = objcprotocol external name 'AVAudioPlayerDelegate' (NSObjectProtocol) | |
| 173 | + | optional | |
| 174 | + | procedure audioPlayerDidFinishPlaying_successfully (player: AVAudioPlayer; flag: ObjCBOOL); message 'audioPlayerDidFinishPlaying:successfully:'; | |
| 175 | + | procedure audioPlayerDecodeErrorDidOccur_error (player: AVAudioPlayer; error: NSError); message 'audioPlayerDecodeErrorDidOccur:error:'; | |
| 176 | + | end; | |
| 177 | + | ||
| 178 | + | { TMyDelegate } | |
| 179 | + | TMyDelegate = objcclass(NSObject, AVAudioPlayerDelegateProtocol) | |
| 180 | + | public | |
| 181 | + | procedure audioPlayerDidFinishPlaying_successfully(player: AVAudioPlayer; flag: ObjCBOOL); message 'audioPlayerDidFinishPlaying:successfully:'; | |
| 182 | + | //procedure audioPlayerDecodeErrorDidOccur_error(player: AVAudioPlayer; error: NSError); message 'audioPlayerDecodeErrorDidOccur:error:'; | |
| 183 | + | end; | |
| 184 | + | ||
| 185 | + | TForm1 = class(TForm) | |
| 186 | + | DurationLabel: TLabel; | |
| 187 | + | Timer1: TTimer; | |
| 188 | + | TitleLabel: TLabel; | |
| 189 | + | ResumeButton: TButton; | |
| 190 | + | StopButton: TButton; | |
| 191 | + | PauseButton: TButton; | |
| 192 | + | PlayButton: TButton; | |
| 193 | + | TrackBar1: TTrackBar; | |
| 194 | + | procedure PauseButtonClick(Sender: TObject); | |
| 195 | + | procedure PlayButtonClick(Sender: TObject); | |
| 196 | + | procedure ResumeButtonClick(Sender: TObject); | |
| 197 | + | procedure StopButtonClick(Sender: TObject); | |
| 198 | + | procedure Timer1Update(Sender: TObject); | |
| 199 | + | ||
| 200 | + | private | |
| 201 | + | ||
| 202 | + | public | |
| 203 | + | ||
| 204 | + | end; | |
| 205 | + | ||
| 206 | + | var | |
| 207 | + | Form1: TForm1; | |
| 208 | + | MyAudioPlayer : AVAudioPlayer = Nil; | |
| 209 | + | fileDuration: NSTimeInterval = 0; | |
| 210 | + | myDelegate: TMyDelegate = Nil; | |
| 211 | + | ||
| 212 | + | implementation | |
| 213 | + | ||
| 214 | + | {$R *.lfm} | |
| 215 | + | ||
| 216 | + | procedure TMyDelegate.audioPlayerDidFinishPlaying_successfully(player: AVAudioPlayer; flag: ObjCBool); | |
| 217 | + | begin | |
| 218 | + | if(flag) then | |
| 219 | + | begin | |
| 220 | + | // Set button states | |
| 221 | + | Form1.PlayButton.Enabled := True; | |
| 222 | + | Form1.PauseButton.Enabled := False; | |
| 223 | + | Form1.ResumeButton.Enabled := False; | |
| 224 | + | Form1.StopButton.Enabled := False; | |
| 225 | + | end; | |
| 226 | + | end; | |
| 227 | + | ||
| 228 | + | // Play audio procedure | |
| 229 | + | procedure PlayAudio(audioFileName : NSString; numberOfLoops: Integer); | |
| 230 | + | var | |
| 231 | + | path: NSString; | |
| 232 | + | url : NSURL; | |
| 233 | + | err : NSError; | |
| 234 | + | begin | |
| 235 | + | // Path to your application bundle's resource directory | |
| 236 | + | // with the filename appended | |
| 237 | + | path := NSBundle.mainBundle.resourcePath.stringByAppendingPathComponent(audioFileName); | |
| 238 | + | url := NSURL.fileURLWithPath(path); | |
| 239 | + | ||
| 240 | + | // Setup audio player with file | |
| 241 | + | MyAudioPlayer := AVAudioPlayer.alloc.initWithContentsOfURL_error(url, @err); | |
| 242 | + | ||
| 243 | + | // Setup labels | |
| 244 | + | Form1.TitleLabel.Caption := 'Audio filename: Minuet in G.mp3'; | |
| 245 | + | Form1.DurationLabel.Caption := 'Duration: ' + FloatToStr(Trunc(MyAudioPlayer.duration)) + ' seconds'; | |
| 246 | + | ||
| 247 | + | // Setup delegate | |
| 248 | + | myDelegate := TMyDelegate.alloc.init; | |
| 249 | + | MyAudioPlayer.setDelegate(myDelegate); | |
| 250 | + | ||
| 251 | + | // Setup and play | |
| 252 | + | if Assigned(MyAudioPlayer) then | |
| 253 | + | begin | |
| 254 | + | Form1.TrackBar1.Max := Trunc(MyAudioPlayer.duration); | |
| 255 | + | MyAudioPlayer.setNumberOfLoops(numberOfLoops); | |
| 256 | + | Form1.Timer1.Enabled := True; | |
| 257 | + | MyAudioPlayer.play | |
| 258 | + | end | |
| 259 | + | else | |
| 260 | + | // Use the Applications > Utilities > Console application to find error messages | |
| 261 | + | NSLog(NSStr('Error in procedure PlayAudio(): %@'), err); | |
| 262 | + | end; | |
| 263 | + | ||
| 264 | + | procedure TForm1.PlayButtonClick(Sender: TObject); | |
| 265 | + | begin | |
| 266 | + | // Destroy the existing audio player if any before | |
| 267 | + | // creating another in PlayAudio procedure | |
| 268 | + | if Assigned(MyAudioPlayer) then | |
| 269 | + | begin | |
| 270 | + | MyAudioPlayer.release; | |
| 271 | + | MyAudioPlayer := Nil; | |
| 272 | + | end; | |
| 273 | + | ||
| 274 | + | // Set button states | |
| 275 | + | PlayButton.Enabled := False; | |
| 276 | + | ResumeButton.Enabled := False; | |
| 277 | + | PauseButton.Enabled := True; | |
| 278 | + | StopButton.Enabled := True; | |
| 279 | + | ||
| 280 | + | // Play audio file located in your application bundle Resources directory | |
| 281 | + | // This particular demo file can be found on your Mac (up to Mojave) at: | |
| 282 | + | // /Applications/iPhoto.app/Contents/Resources/Music/Minuet in G.mp3 | |
| 283 | + | // and should be copied to your application bundle Resources directory. | |
| 284 | + | PlayAudio(NSStr('Minuet in G.mp3'), 0); | |
| 285 | + | end; | |
| 286 | + | ||
| 287 | + | // Pause audio | |
| 288 | + | procedure TForm1.PauseButtonClick(Sender: TObject); | |
| 289 | + | begin | |
| 290 | + | if(MyAudioPlayer.isPlaying) then | |
| 291 | + | begin | |
| 292 | + | MyAudioPlayer.Pause; | |
| 293 | + | ||
| 294 | + | // Stop timer updating track bar | |
| 295 | + | Timer1.Enabled := False; | |
| 296 | + | ||
| 297 | + | // Set button states | |
| 298 | + | PlayButton.Enabled := False; | |
| 299 | + | PauseButton.Enabled := False; | |
| 300 | + | ResumeButton.Enabled := True; | |
| 301 | + | StopButton.Enabled := False; | |
| 302 | + | end; | |
| 303 | + | end; | |
| 304 | + | ||
| 305 | + | // Resume audio | |
| 306 | + | procedure TForm1.ResumeButtonClick(Sender: TObject); | |
| 307 | + | begin | |
| 308 | + | // Note: This will restart the audio where it left off after a pause | |
| 309 | + | // unless audio has been reset to zero | |
| 310 | + | // Do not create yet another audio player by calling PlayAudio() again :-) | |
| 311 | + | if(MyAudioPlayer.currentTime <> 0) then | |
| 312 | + | begin | |
| 313 | + | MyAudioPlayer.play; | |
| 314 | + | ||
| 315 | + | // Re-start timer updating track bar | |
| 316 | + | Timer1.Enabled := True; | |
| 317 | + | ||
| 318 | + | // Set button states | |
| 319 | + | PlayButton.Enabled := False; | |
| 320 | + | ResumeButton.Enabled := False; | |
| 321 | + | PauseButton.Enabled := True; | |
| 322 | + | StopButton.Enabled := True; | |
| 323 | + | end; | |
| 324 | + | end; | |
| 325 | + | ||
| 326 | + | // Stop audio | |
| 327 | + | procedure TForm1.StopButtonClick(Sender: TObject); | |
| 328 | + | begin | |
| 329 | + | // if playing then stop, otherwise ignore | |
| 330 | + | if(MyAudioPlayer.IsPlaying) then | |
| 331 | + | begin | |
| 332 | + | MyAudioPlayer.stop; // audio can still be resumed, so... | |
| 333 | + | MyAudioPlayer.setCurrentTime(0); // reset audio to start | |
| 334 | + | ||
| 335 | + | // Set button states | |
| 336 | + | PlayButton.Enabled := True; | |
| 337 | + | PauseButton.Enabled := False; | |
| 338 | + | ResumeButton.Enabled := False; | |
| 339 | + | StopButton.Enabled := False; | |
| 340 | + | ||
| 341 | + | // Stop timer, reset trackbar position and duration | |
| 342 | + | Timer1.Enabled := False; | |
| 343 | + | TrackBar1.Position := 0; | |
| 344 | + | DurationLabel.Caption := 'Duration: ' + FloatToStr(Trunc(MyAudioPlayer.duration)) + ' seconds'; | |
| 345 | + | end; | |
| 346 | + | end; | |
| 347 | + | ||
| 348 | + | procedure TForm1.Timer1Update(Sender: TObject); | |
| 349 | + | begin | |
| 350 | + | // Update trackbar pos and duration | |
| 351 | + | TrackBar1.Position := Trunc(MyAudioPlayer.currentTime); | |
| 352 | + | DurationLabel.Caption := 'Duration: ' + FloatToStr(Trunc(MyAudioPlayer.Duration - MyAudioPlayer.currentTime)) + ' seconds'; | |
| 353 | + | end; | |
| 354 | + | ||
| 355 | + | finalization | |
| 356 | + | ||
| 357 | + | // Cleanup | |
| 358 | + | If (Assigned (MyAudioPlayer)) then | |
| 359 | + | begin | |
| 360 | + | MyAudioPlayer.Release; | |
| 361 | + | MyDelegate.Release; | |
| 362 | + | MyAudioPlayer := Nil; | |
| 363 | + | end; | |
| 364 | + | ||
| 365 | + | end. | |
| 366 | + | ``` | |
| 367 | + | ||
| 368 | + | The full project source code for this application is available from [SourceForge](https://sourceforge.net/p/lazarus-wiki-projects/code/ci/master/tree/laz_avaudioplayer/). | |
| 369 | + | ||
| 370 | + | ## See also | |
| 371 | + | ||
| 372 | + | - [[macOS Play Alert Sound|System Sound Services]] | |
| 373 | + | - [[macOS Audio Recorder]] | |
| 374 | + | - [[macOS Video Player]] - also plays audio (especially streaming audio). | |
| 375 | + | - [[macOS MIDI Player]] | |
| 376 | + | - [[macOS NSSound]] | |
| 377 | + | - [[macOS Sound Utilities]] | |
| 378 | + | - [[Multimedia Programming]] | |
| 379 | + | ||
| 380 | + | ## External links | |
| 381 | + | ||
| 382 | + | - [Apple: Working with Audio](https://developer.apple.com/audio/) | |
| 383 | + | - [Apple: AVFoundation Framework](https://developer.apple.com/documentation/avfoundation) | |
| 384 | + | - [Apple: AVAudioPlayer](https://developer.apple.com/documentation/avfoundation/avaudioplayer) | |
| 385 | + | ||
| 386 | + | --- | |
| 387 | + | *From the Free Pascal wiki, page "macOS Audio Player", as it stood on 2022-01-22 after a change by Trev, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).* | |
| 388 | + | Category: Audio | |
| 389 | + | Category: Code | |
| 390 | + | Category: Example programs | |
| 391 | + | Category: Multimedia | |
| 392 | + | Category: macOS |
