void Audio_Stream::streamHasBytesAvailable(UInt8 *data, UInt32 numBytes)
{
    AS_TRACE("%s: %u bytes\n", __FUNCTION__, (unsigned int)numBytes);
    
    if (!m_httpStreamRunning) {
        AS_TRACE("%s: stray callback detected!\n", __PRETTY_FUNCTION__);
        return;
    }
    
    if (m_fileOutput) {
        m_fileOutput->write(data, numBytes);
    }
	
    if (m_audioStreamParserRunning) {
        OSStatus result = AudioFileStreamParseBytes(m_audioFileStream, numBytes, data, 0);
        
        if (result != 0) {
            AS_TRACE("%s: AudioFileStreamParseBytes error %d\n", __PRETTY_FUNCTION__, (int)result);
            closeAndSignalError(AS_ERR_STREAM_PARSE);
        } else if (m_initializationError == kAudioConverterErr_FormatNotSupported) {
            AS_TRACE("Audio stream initialization failed due to unsupported format\n");
            closeAndSignalError(AS_ERR_UNSUPPORTED_FORMAT);
        } else if (m_initializationError != noErr) {
            AS_TRACE("Audio stream initialization failed due to unknown error\n");
            closeAndSignalError(AS_ERR_OPEN);
        }
    }
}
void Audio_Stream::streamIsReadyRead()
{
    if (m_audioStreamParserRunning) {
        AS_TRACE("%s: parser already running!\n", __PRETTY_FUNCTION__);
        return;
    }
    
    /* Check if the stream's MIME type begins with audio/ */
    std::string contentType = m_httpStream->contentType();
    
    const char *audioContentType = "audio/";
    size_t audioContentTypeLength = strlen(audioContentType);
    
    if (contentType.compare(0, audioContentTypeLength, audioContentType) != 0) {
        closeAndSignalError(AS_ERR_OPEN);
        return;
    }
    
    /* OK, it should be an audio stream, let's try to open it */
    OSStatus result = AudioFileStreamOpen(this,
                                          propertyValueCallback,
                                          streamDataCallback,
                                          audioStreamTypeFromContentType(contentType),
                                          &m_audioFileStream);
    
    if (result == 0) {
        AS_TRACE("%s: audio file stream opened.\n", __PRETTY_FUNCTION__);
        m_audioStreamParserRunning = true;
    } else {
        closeAndSignalError(AS_ERR_OPEN);
    }
}
void Audio_Stream::open()
{
    if (m_httpStreamRunning) {
        AS_TRACE("%s: already running: return\n", __PRETTY_FUNCTION__);
        return;
    }
    
    if (m_needNewQueue && m_audioQueue) {
        m_needNewQueue = false;
        
        closeAudioQueue();
    }
    
    m_contentLength = 0;
    m_seekTime = 0;
    m_processedPacketsSizeTotal = 0;
    m_processedPacketsCount = 0;
    m_bitrateBufferIndex = 0;
    
    if (m_httpStream->open()) {
        AS_TRACE("%s: HTTP stream opened, buffering...\n", __PRETTY_FUNCTION__);
        m_httpStreamRunning = true;
        setState(BUFFERING);
    } else {
        AS_TRACE("%s: failed to open the HTTP stream\n", __PRETTY_FUNCTION__);
        closeAndSignalError(AS_ERR_OPEN);
    }
}
void Audio_Stream::streamIsReadyRead()
{
    if (m_audioStreamParserRunning) {
        AS_TRACE("%s: parser already running!\n", __PRETTY_FUNCTION__);
        return;
    }
    
    CFStringRef audioContentType = CFSTR("audio/");
    const CFIndex audioContentTypeLength = CFStringGetLength(audioContentType);
    bool matchesAudioContentType = false;
    
    CFStringRef contentType = m_httpStream->contentType();
    
    if (m_contentType) {
        CFRelease(m_contentType), m_contentType = 0;
    }
    if (contentType) {
        m_contentType = CFStringCreateCopy(kCFAllocatorDefault, contentType);
    
        /* Check if the stream's MIME type begins with audio/ */
        matchesAudioContentType = (kCFCompareEqualTo ==
                                    CFStringCompareWithOptions(contentType, CFSTR("audio/"),
                                                               CFRangeMake(0, audioContentTypeLength),
                                                               0));
    }
    
    if (m_strictContentTypeChecking && !matchesAudioContentType) {
        closeAndSignalError(AS_ERR_OPEN);
        return;
    }
    
    m_audioDataByteCount = 0;
    
    /* OK, it should be an audio stream, let's try to open it */
    OSStatus result = AudioFileStreamOpen(this,
                                          propertyValueCallback,
                                          streamDataCallback,
                                          audioStreamTypeFromContentType(contentType),
                                          &m_audioFileStream);
    
    if (result == 0) {
        AS_TRACE("%s: audio file stream opened.\n", __PRETTY_FUNCTION__);
        m_audioStreamParserRunning = true;
    } else {
        closeAndSignalError(AS_ERR_OPEN);
    }
}
void Audio_Stream::streamErrorOccurred()
{
    AS_TRACE("%s\n", __PRETTY_FUNCTION__);
    
    if (!m_httpStreamRunning) {
        AS_TRACE("%s: stray callback detected!\n", __PRETTY_FUNCTION__);
        return;
    }
    
    closeAndSignalError(AS_ERR_NETWORK);
}
void Audio_Stream::streamHasBytesAvailable(UInt8 *data, CFIndex numBytes)
{
    AS_TRACE("%s: %lu bytes\n", __FUNCTION__, numBytes);
    
    if (!m_httpStreamRunning) {
        AS_TRACE("%s: stray callback detected!\n", __PRETTY_FUNCTION__);
        return;
    }
	
    if (m_audioStreamParserRunning) {
        OSStatus result = AudioFileStreamParseBytes(m_audioFileStream, numBytes, data, 0);
        if (result != 0) {
            AS_TRACE("%s: AudioFileStreamParseBytes error %d\n", __PRETTY_FUNCTION__, (int)result);
            closeAndSignalError(AS_ERR_STREAM_PARSE);
        }
    }
}
void Audio_Stream::streamEndEncountered()
{
    AS_TRACE("%s\n", __PRETTY_FUNCTION__);
    
    if (!m_httpStreamRunning) {
        AS_TRACE("%s: stray callback detected!\n", __PRETTY_FUNCTION__);
        return;
    }
    
    setState(END_OF_FILE);
    
    m_httpStream->close();
    m_httpStreamRunning = false;
    
    /*
     * When the audio playback is fine, the queue will signal
     * back that the playback has ended. However, if there was
     * a problem with the playback (a corrupted audio file for instance),
     * the queue will not signal back.
     */
    if (!m_audioQueue->initialized()) {
        closeAndSignalError(AS_ERR_STREAM_PARSE);
    }
}
void Audio_Stream::audioQueueBuffersEmpty()
{
    AS_TRACE("%s: enter\n", __PRETTY_FUNCTION__);
    
    Stream_Configuration *config = Stream_Configuration::configuration();
    
    if (m_httpStreamRunning && FAILED != state()) {
        /* Still feeding the audio queue with data,
           don't stop yet */
        setState(BUFFERING);
        
        if (m_firstBufferingTime == 0) {
            // Never buffered, just increase the counter
            m_firstBufferingTime = CFAbsoluteTimeGetCurrent();
            m_bounceCount++;
            
            AS_TRACE("stream buffered, increasing bounce count %zu, interval %i\n", m_bounceCount, config->bounceInterval);
        } else {
            // Buffered before, calculate the difference
            CFAbsoluteTime cur = CFAbsoluteTimeGetCurrent();
            
            int diff = cur - m_firstBufferingTime;
            
            if (diff >= config->bounceInterval) {
                // More than bounceInterval seconds passed from the last
                // buffering. So not a continuous bouncing. Reset the
                // counters.
                m_bounceCount = 0;
                m_firstBufferingTime = 0;
                
                AS_TRACE("%i seconds passed from last buffering, resetting counters, interval %i\n", diff, config->bounceInterval);
            } else {
                m_bounceCount++;
                
                AS_TRACE("%i seconds passed from last buffering, increasing bounce count to %zu, interval %i\n", diff, m_bounceCount, config->bounceInterval);
            }
        }
        
        // Check if we have reached the bounce state
        if (m_bounceCount >= config->maxBounceCount) {
            closeAndSignalError(AS_ERR_BOUNCING);
        }
        
        return;
    }
    
    AS_TRACE("%s: closing the audio queue\n", __PRETTY_FUNCTION__);
    
    if (m_audioStreamParserRunning) {
        if (AudioFileStreamClose(m_audioFileStream) != 0) {
            AS_TRACE("%s: AudioFileStreamClose failed\n", __PRETTY_FUNCTION__);
        }
        m_audioStreamParserRunning = false;
    }
    
    // Keep the audio queue running until it has finished playing
    audioQueue()->stop(false);
    m_needNewQueue = true;
    
    AS_TRACE("%s: leave\n", __PRETTY_FUNCTION__);
}
void Audio_Stream::open()
{
    if (m_httpStreamRunning) {
        AS_TRACE("%s: already running: return\n", __PRETTY_FUNCTION__);
        return;
    }
    
    if (m_needNewQueue && m_audioQueue) {
        m_needNewQueue = false;
        
        closeAudioQueue();
    }
    
    m_contentLength = 0;
    m_seekTime = 0;
    m_bounceCount = 0;
    m_firstBufferingTime = 0;
    m_processedPacketsCount = 0;
    m_bitrateBufferIndex = 0;
    m_initializationError = noErr;
    
    if (m_watchdogTimer) {
        CFRunLoopTimerInvalidate(m_watchdogTimer);
        CFRelease(m_watchdogTimer), m_watchdogTimer = 0;
    }
    
    Stream_Configuration *config = Stream_Configuration::configuration();
    
    if (m_contentType) {
        CFRelease(m_contentType), m_contentType = NULL;
    }
    
    if (m_httpStream->open()) {
        AS_TRACE("%s: HTTP stream opened, buffering...\n", __PRETTY_FUNCTION__);
        m_httpStreamRunning = true;
        setState(BUFFERING);
        
        if (config->startupWatchdogPeriod > 0) {
            /*
             * Start the WD if we have one requested. In this way we can track
             * that the stream doesn't stuck forever on the buffering state
             * (for instance some network error condition)
             */
            
            CFRunLoopTimerContext ctx = {0, this, NULL, NULL, NULL};
            
            m_watchdogTimer = CFRunLoopTimerCreate(NULL,
                                                   CFAbsoluteTimeGetCurrent() + config->startupWatchdogPeriod,
                                                   0,
                                                   0,
                                                   0,
                                                   watchdogTimerCallback,
                                                   &ctx);
            
            AS_TRACE("Starting the startup watchdog, period %i seconds\n", config->startupWatchdogPeriod);
            
            CFRunLoopAddTimer(CFRunLoopGetCurrent(), m_watchdogTimer, kCFRunLoopCommonModes);
        }
    } else {
        AS_TRACE("%s: failed to open the HTTP stream\n", __PRETTY_FUNCTION__);
        closeAndSignalError(AS_ERR_OPEN);
    }
}