gusucode.com > audiovideo工具箱matlab源码程序 > audiovideo/@audioplayer/audioplayer.m

    classdef (CaseInsensitiveProperties=true, TruncatedProperties=true) ...
        audioplayer < hgsetget
    %audioplayer Audio player object.
    %   
    %   audioplayer(Y, Fs) creates an audioplayer object for signal Y, using
    %   sample rate Fs.  A handle to the object is returned.
    %
    %   audioplayer(Y, Fs, NBITS) creates an audioplayer object and uses NBITS
    %   bits per sample for floating point signal Y.  Valid values for NBITS
    %   are 8, 16, and 24.  The default number of bits per sample for floating
    %   point signals is 16.
    %
    %   audioplayer(Y, Fs, NBITS, ID) creates an audioplayer object using
    %   audio device identifier ID for output.  If ID equals -1 the default
    %   output device will be used.
    %
    %   audioplayer(R) creates an audioplayer object from AUDIORECORDER object R.
    %
    %   audioplayer(R, ID) creates an audioplayer object from AUDIORECORDER
    %   object R using audio device identifier ID for output.
    %
    % audioplayer Methods:
    %   get          - Query properties of audioplayer object.
    %   isplaying    - Query whether playback is in progress.
    %   pause        - Pause playback.
    %   play         - Play audio from beginning to end.
    %   playblocking - Play, and do not return control until playback
    %                  completes.
    %   resume       - Restart playback from paused position.
    %   set          - set properties of audioplayer object.
    %   stop         - stop playback.
    %
    % audioplayer Properties:
    %   BitsPerSample    - Number of bits per sample. (Read-only)
    %   CurrentSample    - Current sample that the audio output device 
    %                      is playing. If the device is not playing, 
    %                      CurrentSample is the next sample to play with 
    %                      play or resume. (Read-only)
    %   DeviceID         - Identifier for audio device. (Read-only)
    %   NumberOfChannels - Number of audio channels. (Read-only)
    %   Running          - Status of the audio player: 'on' or 'off'. 
    %                      (Read-only)
    %   SampleRate       - Sampling frequency in Hz.
    %   TotalSamples     - Total length of the audio data in samples. 
    %                      (Read-only)
    %   Tag              - String that labels the object.
    %   Type             - Name of the class: 'audioplayer'. (Read-only)
    %   UserData         - Any type of additional data to store with 
    %                      the object.
    %   StartFcn         - Function to execute one time when playback starts.
    %   StopFcn          - Function to execute one time when playback stops.
    %   TimerFcn         - Function to execute repeatedly during playback. 
    %                      To specify time intervals for the repetitions, 
    %                      use the TimerPeriod property.
    %   TimerPeriod      - Time in seconds between TimerFcn callbacks.   
    %
    % Example:  
    %
    %       % Load snippet of Handel's Hallelujah Chorus and play back
    %       % only the first three seconds.
    %       load handel;
    %       p = audioplayer(y, Fs);
    %       play(p, [1 (get(p, 'SampleRate') * 3)]);
    %
    % See also AUDIORECORDER, AUDIODEVINFO, AUDIOPLAYER/GET,
    %          AUDIOPLAYER/SET.
    
    % Author(s): SM NH DTL
    % Copyright 1984-2015 The MathWorks, Inc.
    %   
    
    % --------------------------------------------------------------------
    % General properties
    % --------------------------------------------------------------------
    properties(GetAccess='public', SetAccess='public')
        SampleRate              % Sampling frequency in Hz.
    end
    
    properties(GetAccess='public', SetAccess='private')
        BitsPerSample           % Number of bits per audio Sample
        NumberOfChannels        % Number of channels of the device
        DeviceID                % ID of the Device to be used for playback
    end
    
    % --------------------------------------------------------------------
    % Playback properties
    % --------------------------------------------------------------------
    properties(GetAccess='public', SetAccess='private', Dependent)
        CurrentSample           % Current sample number being played
        TotalSamples            % Total number of samples being played
        Running                 % Is the player in running state
    end
    
    % --------------------------------------------------------------------
    % Callback Properties
    % --------------------------------------------------------------------
    properties(GetAccess='public', SetAccess='public')
        StartFcn                % Handle to a user-specified callback function executed once when playback starts.
        StopFcn                 % Handle to a user-specified callback function executed once when playback stops.
        TimerFcn                % Handle to a user-specified callback function executed repeatedly (at TimerPeriod intervals) during playback.
        TimerPeriod = 0.05      % Time, in seconds, between TimerFcn callbacks.
        Tag = ''                % User-specified object label string.
        UserData                % Some user defined data.
    end
    
    properties(GetAccess='public', SetAccess='private', Transient)
        Type = 'audioplayer'    % For backward compatibility
    end
    
    % --------------------------------------------------------------------
    % Persistent internal properties
    % --------------------------------------------------------------------
    properties(GetAccess='private', SetAccess='private')
        AudioDataType           % Type of the audio signal specified in Matlab type. e.g. 'double', 'single' 'int16', 'int8' etc.
        AudioData               % Audio data to playback.
        HostApiID               % API ID for a particular driver model
    end
    
    % --------------------------------------------------------------------
    % Non persistent, internal properties
    % --------------------------------------------------------------------
    properties(GetAccess='private', SetAccess='private', Transient)
        Channel                 % Sink for the audioplayer
        ChannelListener         % Listener for Channel events
        StartIndex              % Starting point of the Audio Sample being played
        EndIndex                % Ending point of the Audio Sample being played
        SamplesPlayed           % Number of samples sent to the Device
        Timer                   % Timer object created by the user
        TimerListener           % listener for Timer's ExecuteFcn event
    end
    
    % --------------------------------------------------------------------
    % Non persistent, internal, dependent
    % --------------------------------------------------------------------
    properties(GetAccess='private', SetAccess='private', Dependent)
        Options                 % Options Structure to pass to obj.Channel
    end
    
    % --------------------------------------------------------------------
    % Constants
    % --------------------------------------------------------------------
    properties(Constant, GetAccess='private')
        MinimumSampleRate  = 80
        MaximumSampleRate  = 1e6
        MinimumTimerPeriod = .001
        MaximumNumChannels = 2
        DesiredLatency     = 0.025 % Set the Latency (in seconds) we want the audio device to run at.
        DefaultDeviceID    = -1
    end
    
    % --------------------------------------------------------------------
    % Lifetime
    % --------------------------------------------------------------------
    methods(Access='public')
        % External Method Declaration
        play(obj, varargin)
        
        function obj = audioplayer(varargin)
            narginchk(1,4);
            obj.DeviceID = obj.DefaultDeviceID;
            
            fromaudiorecorder = isa(varargin{1}, 'audiorecorder');
            
            % If the Argument 1 is an audiorecorder object.
            if fromaudiorecorder
                % Audioplayer constructor with recorder taken at most 2
                % arguments.
                if(nargin > 2)
                    error(message('MATLAB:audiovideo:audioplayer:numericinputs'));
                end
                
                recorder = varargin{1};
                
                obj.BitsPerSample = get(recorder, 'BitsPerSample');
                % In case recorder is empty, use a try-catch.
                try
                    switch obj.BitsPerSample
                        case 8
                            signal = getaudiodata(recorder, 'uint8');
                        case 16
                            signal = getaudiodata(recorder, 'int16');
                        case 24
                            signal = getaudiodata(recorder, 'double');
                        otherwise
                            error(message('MATLAB:audiovideo:audioplayer:invalidbitpersample'));
                    end
                    
                    obj.SampleRate = get(recorder, 'SampleRate');
                    
                catch exception
                    throw(exception);
                end
                
                if(nargin == 2)
                    % Second argument should be DeviceID
                    % TODO: Yet to decide the DeviceID shift logic
                    if isnumeric(varargin{2})
                        obj.DeviceID = varargin{2};
                    else
                        error(message('MATLAB:audiovideo:audioplayer:invaliddeviceID'));
                    end
                end
                
            else % Signal doesn't come from audiorecorder.
                if(nargin == 1)
                    error(message('MATLAB:audiovideo:audioplayer:mustbeaudiorecorder'));
                end
                
                try
                    audioplayer.checkNumericArgument(varargin{:});
                    signal = varargin{1};
                    obj.SampleRate = varargin{2};
                    if(nargin >= 3)
                        obj.BitsPerSample = varargin{3};
                    else
                        obj.BitsPerSample = audioplayer.getBitsPerSampleForThisSignal(signal);
                    end
                    
                    if(nargin == 4)
                        obj.DeviceID = varargin{4};
                    end
                catch exception
                    throw(exception);
                end
            end
            obj.AudioData = signal;
            obj.initialize();
        end
        
        function delete(obj)
            if isempty(obj.Channel)
                % obj may be partially initialized during loadbobj,
                % or during an error in the constructor
                return; 
            end
            
            stop(obj);
            
            obj.uninitialize();
        end
        
    end

    methods(Static, Hidden)
        %------------------------------------------------------------------
        % Persistence. Forward Declaration.
        %------------------------------------------------------------------
        obj = loadobj(B)
    end
    
    methods(Access='public', Hidden)
        function clearAudioData(obj)
            if obj.isplaying
                return; % disallowed during playback
            end
            
            % Remove all audio data
            obj.AudioData = [1 1];
        end
    end
    
    methods(Access='private')
        function initialize(obj)
            if obj.hasNoAudioHardware()
                return;
            end
            
            % Initialize other un-documented properties.
            
            % Grab the default device.
            obj.HostApiID = multimedia.internal.audio.device.HostApi.Default;
            
            obj.AudioDataType = class(obj.AudioData);
            
            % Initialize other documented/Hidden properties
            obj.NumberOfChannels = size(obj.AudioData,2);
            obj.StartIndex = 1;
            
            % The number of samples sent to the audio device must be an
            % exact multiple of the buffer size. See g1130462 for details.
            endIndex = obj.TotalSamples;
            bufferSize = audiovideo.internal.audio.Converter.secondsToSamples(...
                                        obj.DesiredLatency, obj.SampleRate);
            extraSamples = rem(obj.TotalSamples, bufferSize);
            actEndIndex = endIndex + bufferSize - extraSamples;
            obj.EndIndex = actEndIndex;
            
            % Grab the directory where Converter plugin and device plugin
            % is present. toolboxdir() prefixes correctly if deployed.
            pluginDir = toolboxdir(fullfile('shared','multimedia','bin',...
                computer('arch')));
            
            % Create Channel object and give it
            try
                obj.Channel = asyncio.Channel( ...
                    fullfile(pluginDir, 'audiodeviceplugin'),...
                    fullfile(pluginDir, 'audiomlconverter'),...
                    obj.Options, [0, Inf]);
            catch exception
                throw(obj.createDeviceException(exception));
            end
            
            
            obj.ChannelListener = event.listener(obj.Channel,'Custom', ...
               @(src,event)(obj.onCustomEvent(event)));
            
            % Never timout when waiting on the output stream
            obj.Channel.OutputStream.Timeout = Inf; 

        end
        
        function uninitialize(obj)
            obj.uninitializeTimer(); %make sure timer object is cleaned up   
            
            obj.Channel.close();

            delete(obj.ChannelListener);
            obj.ChannelListener = [];
        end
    end
    
    %----------------------------------------------------------------------
    % Custom Getters/Setters
    %----------------------------------------------------------------------
    methods
        function set.BitsPerSample(obj, value)
            % Check for valid BitsPerSample
            if ~isscalar(value)
                error(message('MATLAB:audiovideo:audioplayer:nonscalarBitsPerSample'));
            end
        
            if value ~= 8 && value ~= 16 && value ~= 24
                error(message('MATLAB:audiovideo:audioplayer:bitsupport'));
            end
            
            obj.BitsPerSample = value;
        end
        
        function set.DeviceID(obj, value)
            if ~(isscalar(value) && ~isinf(value))
                error(message('MATLAB:audiovideo:audioplayer:nonscalarDeviceID'));
            end
            
            % Get the list of output devices
            deviceInfos = multimedia.internal.audio.device.DeviceInfo.getDevicesForDefaultHostApi;
            outputInfos = deviceInfos([deviceInfos.NumberOfOutputs] > 0);
            
            if ~(value==obj.DefaultDeviceID || ismember(value, [outputInfos.ID]))
                error(message('MATLAB:audiovideo:audioplayer:invaliddeviceID'));
            end
            
            obj.DeviceID = value;
        end
        
        function set.SampleRate(obj, value)
            % check for valid sample rate
            if value < obj.MinimumSampleRate || value > obj.MaximumSampleRate
                error(message('MATLAB:audiovideo:audioplayer:invalidSampleRate', obj.MinimumSampleRate, obj.MaximumSampleRate));
            end
            
            if ~(isscalar(value) && ~isinf(value))
                error(message('MATLAB:audiovideo:audioplayer:nonscalarSampleRate'));
            end
            
            sampleRateChanged = obj.SampleRate ~= value;
            obj.SampleRate = value;
            
            if obj.isplaying() && sampleRateChanged
                % Player is already playing. Stop it and restart it so
                % that new rate is passed down the channel to the device.
                pause(obj);
                
                % Get the Current Sample Before calling stop
                startPosition = obj.CurrentSample; %#ok<MCSUP>
              
                stop(obj);
                
                stopPosition = obj.EndIndex;
                if stopPosition > obj.TotalSamples
                    stopPosition = obj.TotalSamples;
                end
                play(obj, [startPosition stopPosition]); %#ok<MCSUP>
            end
        end
        
        function set.NumberOfChannels(obj, value)
            if ~(value > 0 && value <= obj.MaximumNumChannels)
                error(message('MATLAB:audiovideo:audioplayer:invalidnumberofchannels'));
            end
            obj.NumberOfChannels = value;
        end
        
        function set.AudioData(obj, value)
            if ~isnumeric(value)
                error(message('MATLAB:audiovideo:audioplayer:invalidsignal'));
            end
            
            if isempty(value)
                error(message('MATLAB:audiovideo:audioplayer:nonemptysignal'));
            end
            
            % transpose row vectors
            [rows, cols] = size(value);
            if cols > rows
                value = value';
            end
            
            % convert int8 to uint8 to preserve 
            % backward compatibility.
            if(isa(value, 'int8'))
                obj.AudioData = uint8(int32(value) + 128);
            else
                obj.AudioData = value;
            end
        end
        
        function value = get.CurrentSample(obj)
            totalSamplesQueued = obj.EndIndex - obj.StartIndex + 1;
            
            if isempty(obj.Channel)
                % channel in the process of being initialized
                % (in initializeChannel function)
                dataSent = totalSamplesQueued; 
            else
                dataSent = totalSamplesQueued - obj.Channel.OutputStream.DataToSend;
            end
            
            if(dataSent >= totalSamplesQueued)
                % We always point to the next sample to be played. When we
                % are done playing, the next sample to be played is the 1st
                % sample.
                value = 1;
            else
                value = obj.StartIndex + dataSent;
            end
        end
        
        function value = get.TotalSamples(obj)
            value = size(obj.AudioData, 1);
        end
        
        function value = get.Running(obj)
            if obj.isplaying()
                value = 'on';
            else
                value = 'off';
            end
        end
                
        function set.StartFcn(obj, value)
            obj.validateFcn(value);
            obj.StartFcn = value;
        end
        
        function set.StopFcn(obj, value)
            obj.validateFcn(value);
            obj.StopFcn = value;
        end
        
        function set.TimerFcn(obj, value)
            obj.validateFcn(value);
            obj.TimerFcn = value;
        end
        
        function set.TimerPeriod(obj, value)
            validateattributes(value, {'numeric'}, {'positive', 'scalar'});
            if(value < obj.MinimumTimerPeriod)
                error(message('MATLAB:audiovideo:audioplayer:invalidtimerperiod'));
            end
            
            obj.TimerPeriod = value;
            
            if isTimerValid(obj)
                obj.Timer.Period = value;  %#ok<MCSUP>
            end
        end
        
        function set.Tag(obj, value)
            if ~(ischar(value))
                error(message('MATLAB:audiovideo:audioplayer:TagMustBeString'));
            end
            obj.Tag = value;
        end
        
        
        function value = get.Options(obj)
            import multimedia.internal.audio.device.DeviceInfo;
            import audiovideo.internal.audio.*;

            value.HostApiID = int32(obj.HostApiID);
            
            if obj.DeviceID == obj.DefaultDeviceID
                value.DeviceID = int32(DeviceInfo.getDefaultOutputDeviceID(obj.HostApiID));
            else
                value.DeviceID = int32(obj.DeviceID);
            end
           
            % OutputChannels is a vector of channel indices to playback 
            value.OutputChannels = int32(1:obj.NumberOfChannels);
            value.SampleRate = uint32(obj.SampleRate);
            value.BufferSize = uint32(Converter.secondsToSamples(obj.DesiredLatency, obj.SampleRate));
            value.QueueDuration = uint32(computeQueueDuration(value.BufferSize));
            value.AudioDataType = obj.AudioDataType;
            value.BitsPerSample = uint32(obj.BitsPerSample);
            value.SamplesUntilDone = uint64(obj.EndIndex - obj.CurrentSample + 1);
        end
    end
    
    %----------------------------------------------------------------------
    % Function Callbacks/Helper Functions
    %----------------------------------------------------------------------
    methods(Access='private')
        
        function executeTimerCallback(obj)  
            internal.Callback.execute(obj.TimerFcn, obj);
        end
        
        function onCustomEvent(obj, event)
            % Process any custom events from the Channel
            switch event.Type
                case 'StartEvent'
                    obj.startTimer();
                case 'DoneEvent'
                    stop(obj); % stop if we are done
            end
        end
        
    end
    
    %----------------------------------------------------------------------
    % Timer related functionalities
    %----------------------------------------------------------------------
    methods(Access='private')
        function initializeTimer(obj)
            if isempty(obj.TimerFcn)
                % Initialize the timer only if there only if there is a valid
                % TimerFcn.
                return;
            end
            
            obj.Timer = internal.IntervalTimer(obj.TimerPeriod);
            obj.TimerListener = event.listener(obj.Timer, 'Executing', ...
                @(~,~)(obj.executeTimerCallback));
        end
        
        function uninitializeTimer(obj)
            if ~isTimerValid(obj)
                return;
            end
            delete(obj.TimerListener);
            obj.TimerListener = [];
        end
        
        function startTimer(obj)
            if ~isTimerValid(obj)
                return;
            end
            
            start(obj.Timer);
        end
        
        function stopTimer(obj)
            if ~isTimerValid(obj)
                return;
            end
            
            stop(obj.Timer);
        end
   
        function valid = isTimerValid(obj)
            valid = ~isempty(obj.Timer) && isvalid(obj.Timer);
        end
    end
    
    %----------------------------------------------------------------------
    % Helper Functions
    %----------------------------------------------------------------------
    methods(Access='private', Static)

        function checkNumericArgument(varargin)
            sz = size(varargin, 2);
            for i = 1:sz
                if(~isnumeric(varargin{i}))
                    error(message('MATLAB:audiovideo:audioplayer:numericinputs'));
                end
            end
        end
        
        function bits = getBitsPerSampleForThisSignal(thesignal)
            switch class(thesignal)
                case {'double', 'single', 'int16'}
                    bits = 16;
                case {'int8', 'uint8'}
                    bits = 8;
                otherwise
                    error(message('MATLAB:audiovideo:audioplayer:unsupportedtype'));
            end
        end
        
        function validateFcn(fcn)
            if ~internal.Callback.validate(fcn)
                error(message('MATLAB:audiovideo:audioplayer:invalidfunctionhandle'));    
            end
        end
        
        function noAudio = hasNoAudioHardware()
            persistent hasNoAudio;
            if ~isempty(hasNoAudio)
                noAudio = hasNoAudio;
            else
                % are there any audio outputs?
                deviceInfos = multimedia.internal.audio.device.DeviceInfo.getDevicesForDefaultHostApi;
                outputInfos = deviceInfos([deviceInfos.NumberOfOutputs] > 0);

                % Enumerating devices is expensive,
                % Cache the value here to make subsequent calls faster.
                hasNoAudio = isempty(outputInfos);
                
                noAudio = hasNoAudio;
            end
            
            if (noAudio)
                % Channel is not initialized, warn here instead of erring
                % to support running on systems with no audio outputs
                warning(message('MATLAB:audiovideo:audioplayer:noAudioOutputDevice'));
                return;
            end
        end
        
        function exceptionObj = createDeviceException(exception)
            msg = strrep(exception.message, 'PortAudio', 'Device');
            exceptionObj = MException('MATLAB:audiovideo:audioplayer:DeviceError', msg);
        end
    end
    
    methods(Access='private')
        function settableProps = getSettableProperties(obj)
            % Returns a list of publicly settable properties.
            % TODO: Reduce to fields(set(obj)) when g449420 is done.
            settableProps = {};
            props = fieldnames(obj);
            for ii=1:length(props)
                p = findprop(obj, props{ii});
                if strcmpi(p.SetAccess,'public')
                    settableProps{end+1} = props{ii}; %#ok<AGROW>
                end
            end
        end
    end
    
    
    %----------------------------------------------------------------------
    % audioplayer Functions
    %----------------------------------------------------------------------
    methods(Access='public')
        
        function c = horzcat(varargin)
            %HORZCAT Horizontal concatenation of audioplayer objects.
            
            if (nargin == 1)
                c = varargin{1};
            else
                error(message('MATLAB:audiovideo:audioplayer:noconcatenation'));
            end
        end
        
        function c = vertcat(varargin)
            %VERTCAT Vertical concatenation of audioplayer objects.
            
            if (nargin == 1)
                c = varargin{1};
            else
                error(message('MATLAB:audiovideo:audioplayer:noconcatenation'));
            end
        end
        
        function status = isplaying(obj)
            %ISPLAYING Indicates if playback is in progress.
            %
            %    STATUS = ISPLAYING(OBJ) returns true or false, indicating
            %    whether playback is or is not in progress.
            %
            %    See also AUDIOPLAYER, AUDIODEVINFO, AUDIOPLAYER/GET,
            %             AUDIOPLAYER/SET.
            
            status = ~isempty(obj.Channel) && obj.Channel.isOpen();
        end
        
        function stop(obj)
            %STOP Stops playback in progress.
            %
            %    STOP(OBJ) stops the current playback.
            %
            %    See also AUDIOPLAYER, AUDIODEVINFO, AUDIOPLAYER/GET,
            %             AUDIOPLAYER/SET, AUDIOPLAYER/PLAY, AUDIOPLAYER/PLAYBLOCKING,
            %             AUDIOPLAYER/PAUSE, AUDIOPLAYER/RESUME
            if obj.hasNoAudioHardware()
                return;
            end
            
            obj.Channel.OutputStream.flush();
            obj.pause();
        end
        
        function pause(obj)
            %PAUSE Pauses playback in progress.
            %
            %    PAUSE(OBJ) pauses the current playback.  Use RESUME
            %    or PLAY to resume playback.
            %
            %    See also AUDIOPLAYER, AUDIODEVINFO, AUDIOPLAYER/GET,
            %             AUDIOPLAYER/SET, AUDIOPLAYER/RESUME,
            %             AUDIOPLAYER/PLAY.
            if obj.hasNoAudioHardware()
                return;
            end
            
            if ~obj.isplaying()
                return;
            end
            
            obj.Channel.close();
            
            % Stop and uninitialize the timer if needed
            obj.stopTimer();
            obj.uninitializeTimer();

            
            internal.Callback.execute(obj.StopFcn, obj);
            
         end
        
        function resume(obj)
            %RESUME Resumes paused playback.
            %
            %    RESUME(OBJ) continues playback from paused location.
            %
            %    See also AUDIOPLAYER, AUDIODEVINFO, AUDIOPLAYER/GET,
            %             AUDIOPLAYER/SET, AUDIOPLAYER/PAUSE.
            if obj.hasNoAudioHardware()
                return;
            end
            
            if obj.isplaying()
                return;
            end
            
            % If there is no data to send resume
            % from the beginning
            if (obj.Channel.OutputStream.DataToSend == 0)
                obj.play();
                return;
            end
            
            % initialize the Timer if needed
            % (timer will be started in onCustomEvent)
            obj.initializeTimer();
            
            
            % Execute StartFcn
            internal.Callback.execute(obj.StartFcn, obj);
            
            try
                obj.Channel.open(obj.Options);
            catch exception
                throw(obj.createDeviceException(exception));
            end
            
        end
        
        
        function playblocking(obj, varargin)
            %PLAYBLOCKING Synchronous playback of audio samples in audioplayer object.
            %
            %    PLAYBLOCKING(OBJ) plays from beginning; does not return until
            %                      playback completes.
            %
            %    PLAYBLOCKING(OBJ, START) plays from START sample; does not return until
            %                      playback completes.
            %
            %    PLAYBLOCKING(OBJ, [START STOP]) plays from START sample until STOP sample;
            %                      does not return until playback completes.
            %
            %    Use the PLAY method for asynchronous playback.
            %
            %    See also AUDIOPLAYER, AUDIODEVINFO, AUDIOPLAYER/GET,
            %             AUDIOPLAYER/SET, AUDIOPLAYER/PLAY.
            
            if obj.hasNoAudioHardware()
               return; 
            end
            
            obj.play(varargin{:});
            obj.Channel.OutputStream.drain();
            
            % Wait till the last buffer has played till the end.
            while isplaying(obj)
                pause(0.01);
            end
            
            stop(obj);
            
        end
        
    end
end