Sign in Join
Wiki copytemporary

macOS Audio Player

(macOS only)

Supported Audio File and Data Formats

AVAudioPlayer

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.

Using an audio player you can:

Example Code

The example code below provides a demo audio player application which implements the basic requirements for such an application.

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.

pascal
//
// Note: Lazarus IDE 2.2.0 and Free Pascal Compiler v3.2.2 used
//

unit unit1;

{$mode objfpc}{$H+}
{$modeswitch objectivec1}
{$linkframework AVFoundation}

interface

uses
  Forms,     // for Form
  SysUtils,  // for FloatToStr
  StdCtrls,  // for labels, buttons
  CocoaAll,  // for Cocoa types
  ComCtrls,  // for trackbar
  ExtCtrls;  // for timer

type

  { TForm1 }
  AVAudioPlayerPtr = ^AVAudioPlayer;
  AVAudioPlayerDelegateProtocolPtr = ^AVAudioPlayerDelegateProtocol;

  AVAudioPlayerDelegateProtocol = ObjCProtocol;

  AVAudioPlayer = objcclass external(NSObject)
  private
    _internal: id;
  public
    {Initializes and returns an audio player for playing a designated sound file.
     url: a URL identifying the sound file to play. The audio data must be in a
     format supported by Core Audio.
     outError:  If an error occurs, on return the NSError object describes the
     error. Set to NULL to ignore errors.}
     function initWithContentsOfURL_error (url: NSURL; outError: NSErrorPtr): id;
                            message 'initWithContentsOfURL:error:';
    {Plays a sound asynchronously. Calling this method implicitly calls the
     prepareToPlay() method if the audio player is not already prepared to play.}
    function play: Boolean; message 'play';

    {A value of 0.0 indicates silence; a value of 1.0 (the default) indicates
     full volume for the audio player instance. Use this property to control an
     audio player’s volume relative to other audio output.}
    function volume: Single; message 'volume';
    procedure setVolume(newValue: Single); message 'setVolume:';

    {A value of 0, which is the default, means to play the sound once.
     Set a positive integer value to specify the number of times to return
     to the start and play again. For example, specifying a value of 1
     results in a total of two plays of the sound. Set any negative
     integer value to loop the sound indefinitely until you call the
     stop() method.}
    function  numberOfLoops: Integer; message 'numberOfLoops';
    procedure setNumberOfLoops(newValue: Integer); message 'setNumberOfLoops:';

    {A Boolean value that indicates whether the audio player is playing (true)
     or not (false).}
    function isPlaying: Boolean; message 'isPlaying';

    {Calling this method preloads buffers and acquires the audio hardware
     needed for playback, which minimises the lag between calling the play()
     method and the start of sound output.}
    function prepareToPlay: Boolean; message 'prepareToPlay';

    {Pauses playback; sound remains ready to resume playback from where
    it left off. It does not release the audio hardware that was acquired
    on calling play() or prepareToPlay().}
    procedure pause; message 'pause';

    {The stop method does not reset the value of the currentTime property to 0.
     If you call stop during playback and then call play(), playback resumes
     at the point where it left off. Callikng this method undoes the setup
     performed on calling the play() or prepareToPlay() methods.}
    procedure stop; message 'stop';

    {The total duration, in seconds, of the sound associated with the audio
    player.}
    function duration: Double; message 'duration';

    {If the sound is playing, currentTime is the offset of the current playback
    position, measured in seconds from the start of the sound. If the sound is
    not playing, currentTime is the offset of where playing starts upon calling
    the play() method, measured in seconds from the start of the sound.
    By setting this property you can seek to a specific point in a sound file or
    implement audio fast-forward and rewind functions.}
    function currentTime: Double; message 'currentTime';
    procedure setCurrentTime(newValue: Double); message 'setCurrentTime:';

    {To enable adjustable playback rate for an audio player, set this property
     to true after you initialize the player and before you call the
     prepareToPlay() instance method for the player.}
    function enableRate: Boolean; message 'enableRate';
    procedure setEnableRate(newValue:Boolean); message 'setEnableRate:';

    {This property’s default value of 1.0 provides normal playback rate. The
     available range is from 0.5 for half-speed playback through 2.0 for
     double-speed playback.
     To set an audio player’s playback rate, you must first enable rate
     adjustment as described in the enableRate property description.}
    function rate: Single; message 'rate';
    procedure setRate(newValue: Single); message 'setRate:';

    {The UID of the current audio player.}
    function currentDevice: NSString; message 'currentDevice';

    {Number of audio channels in the sound associated with the audio player.}
    function numberOfChannels: Integer; message 'numberOfChannels';

    {The default value for the meteringEnabled property is off (Boolean false).
     Before using metering for an audio player, you need to enable it by
     setting this property to true.}
    function isMeteringEnabled: Boolean; message 'isMeteringEnabled';
    procedure setMeteringEnabled(newValue:Boolean); message 'setMeteringEnabled:';

    {To obtain current audio power values, you must call this method before
     calling averagePowerForChannel or peakPowerForChannel}
    procedure updateMeters; message 'updateMeters';

    {Returns the average power for a given channel, in decibels, for the sound
    being played.
    Channel numbers are zero-indexed. A monaural signal, or the left channel
    of a stereo signal, has channel number 0.
    A return value of 0 dB indicates full scale, or maximum power; a return
    value of –160 dB indicates minimum power (that is, near silence). If the
    signal provided to the audio player exceeds ±full scale, then the return
    value may exceed 0 (that is, it may enter the positive range).}
    function avgPwrForChannel(newValue:LongWord): Double; message 'averagePowerForChannel:';

    {Returns the peak power for a given channel, in decibels, for the sound
    being played.
    Channel numbers are zero-indexed. A monaural signal, or the left channel
    of a stereo signal, has channel number 0.
    A return value of 0 dB indicates full scale, or maximum power; a return
    value of –160 dB indicates minimum power (that is, near silence). If the
    signal provided to the audio player exceeds ±full scale, then the return
    value may exceed 0 (that is, it may enter the positive range).}
    function peakPwrForChannel(newValue:LongWord): Double; message 'peakPowerForChannel:';

    procedure setDelegate(newValue: AVAudioPlayerDelegateProtocol); message 'setDelegate:';
    function delegate: AVAudioPlayerDelegateProtocol; message 'delegate';
  end;

  AVAudioPlayerDelegateProtocol = objcprotocol external name 'AVAudioPlayerDelegate' (NSObjectProtocol)
   optional
   procedure audioPlayerDidFinishPlaying_successfully (player: AVAudioPlayer; flag: ObjCBOOL); message 'audioPlayerDidFinishPlaying:successfully:';
   procedure audioPlayerDecodeErrorDidOccur_error (player: AVAudioPlayer; error: NSError); message 'audioPlayerDecodeErrorDidOccur:error:';
  end;

  { TMyDelegate }
  TMyDelegate = objcclass(NSObject, AVAudioPlayerDelegateProtocol)
  public
    procedure audioPlayerDidFinishPlaying_successfully(player: AVAudioPlayer; flag: ObjCBOOL); message 'audioPlayerDidFinishPlaying:successfully:';
    //procedure audioPlayerDecodeErrorDidOccur_error(player: AVAudioPlayer; error: NSError); message 'audioPlayerDecodeErrorDidOccur:error:';
  end;

  TForm1 = class(TForm)
    DurationLabel: TLabel;
    Timer1: TTimer;
    TitleLabel: TLabel;
    ResumeButton: TButton;
    StopButton: TButton;
    PauseButton: TButton;
    PlayButton: TButton;
    TrackBar1: TTrackBar;
    procedure PauseButtonClick(Sender: TObject);
    procedure PlayButtonClick(Sender: TObject);
    procedure ResumeButtonClick(Sender: TObject);
    procedure StopButtonClick(Sender: TObject);
    procedure Timer1Update(Sender: TObject);

  private

  public

  end;

var
  Form1: TForm1;
  MyAudioPlayer : AVAudioPlayer = Nil;
  fileDuration: NSTimeInterval = 0;
  myDelegate: TMyDelegate = Nil;

implementation

{$R *.lfm}

procedure TMyDelegate.audioPlayerDidFinishPlaying_successfully(player: AVAudioPlayer; flag: ObjCBool);
begin
  if(flag) then
    begin
      // Set button states
      Form1.PlayButton.Enabled := True;
      Form1.PauseButton.Enabled := False;
      Form1.ResumeButton.Enabled := False;
      Form1.StopButton.Enabled := False;
    end;
end;

// Play audio procedure
procedure PlayAudio(audioFileName : NSString; numberOfLoops: Integer);
var
  path: NSString;
  url : NSURL;
  err : NSError;
begin
  // Path to your application bundle's resource directory
  // with the filename appended
  path := NSBundle.mainBundle.resourcePath.stringByAppendingPathComponent(audioFileName);
  url  := NSURL.fileURLWithPath(path);

  // Setup audio player with file
  MyAudioPlayer := AVAudioPlayer.alloc.initWithContentsOfURL_error(url, @err);

  // Setup labels
  Form1.TitleLabel.Caption := 'Audio filename: Minuet in G.mp3';
  Form1.DurationLabel.Caption := 'Duration: ' + FloatToStr(Trunc(MyAudioPlayer.duration)) + ' seconds';

  // Setup delegate
  myDelegate := TMyDelegate.alloc.init;
  MyAudioPlayer.setDelegate(myDelegate);

  // Setup and play
  if Assigned(MyAudioPlayer) then
    begin
      Form1.TrackBar1.Max := Trunc(MyAudioPlayer.duration);
      MyAudioPlayer.setNumberOfLoops(numberOfLoops);
      Form1.Timer1.Enabled := True;
      MyAudioPlayer.play
    end
  else
    // Use the Applications > Utilities > Console application to find error messages
    NSLog(NSStr('Error in procedure PlayAudio(): %@'), err);
end;

procedure TForm1.PlayButtonClick(Sender: TObject);
begin
  // Destroy the existing audio player if any before
  // creating another in PlayAudio procedure
  if Assigned(MyAudioPlayer) then
    begin
      MyAudioPlayer.release;
      MyAudioPlayer := Nil;
    end;

  // Set button states
  PlayButton.Enabled := False;
  ResumeButton.Enabled :=  False;
  PauseButton.Enabled := True;
  StopButton.Enabled := True;

  // Play audio file located in your application bundle Resources directory
  // This particular demo file can be found on your Mac (up to Mojave) at:
  // /Applications/iPhoto.app/Contents/Resources/Music/Minuet in G.mp3
  // and should be copied to your application bundle Resources directory.
  PlayAudio(NSStr('Minuet in G.mp3'), 0);
end;

// Pause audio
procedure TForm1.PauseButtonClick(Sender: TObject);
begin
  if(MyAudioPlayer.isPlaying) then
    begin
      MyAudioPlayer.Pause;

      // Stop timer updating track bar
      Timer1.Enabled := False;

      // Set button states
      PlayButton.Enabled := False;
      PauseButton.Enabled := False;
      ResumeButton.Enabled :=  True;
      StopButton.Enabled := False;
    end;
end;

// Resume audio
procedure TForm1.ResumeButtonClick(Sender: TObject);
begin
  // Note: This will restart the audio where it left off after a pause
  //       unless audio has been reset to zero
  // Do not create yet another audio player by calling PlayAudio() again :-)
  if(MyAudioPlayer.currentTime <> 0) then
    begin
      MyAudioPlayer.play;

      // Re-start timer updating track bar
      Timer1.Enabled := True;

      // Set button states
      PlayButton.Enabled := False;
      ResumeButton.Enabled :=  False;
      PauseButton.Enabled := True;
      StopButton.Enabled := True;
    end;
end;

// Stop audio
procedure TForm1.StopButtonClick(Sender: TObject);
begin
  // if playing then stop, otherwise ignore
  if(MyAudioPlayer.IsPlaying) then
    begin
      MyAudioPlayer.stop;               // audio can still be resumed, so...
      MyAudioPlayer.setCurrentTime(0);  // reset audio to start

      // Set button states
      PlayButton.Enabled := True;
      PauseButton.Enabled := False;
      ResumeButton.Enabled := False;
      StopButton.Enabled := False;

      // Stop timer, reset trackbar position and duration
      Timer1.Enabled := False;
      TrackBar1.Position := 0;
      DurationLabel.Caption := 'Duration: ' + FloatToStr(Trunc(MyAudioPlayer.duration)) + ' seconds';
    end;
end;

procedure TForm1.Timer1Update(Sender: TObject);
begin
  // Update trackbar pos and duration
  TrackBar1.Position := Trunc(MyAudioPlayer.currentTime);
  DurationLabel.Caption := 'Duration: ' + FloatToStr(Trunc(MyAudioPlayer.Duration - MyAudioPlayer.currentTime)) + ' seconds';
end;

finalization

// Cleanup
If (Assigned (MyAudioPlayer)) then
  begin
    MyAudioPlayer.Release;
    MyDelegate.Release;
    MyAudioPlayer := Nil;
  end;

end.

The full project source code for this application is available from SourceForge.

See also

External links


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.

Revision 1, last changed by WikiArchive .

Talk about this page

Nobody has opened a bench for this page yet. Sign in to open one.