All pastes #76891 Raw Edit

Stuff

public text v1 · immutable
·published 2006-07-02 07:08 UTC
rendered paste body
/***************************************************************************
 *   Copyright (C) 2005 - 2006 by                                          *
 *      Christian Muehlhaeuser, Last.fm Ltd <chris@last.fm>                *
 *      Erik Jaelevik, Last.fm Ltd <erik@last.fm>                          *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Steet, Fifth Floor, Boston, MA  02111-1307, USA.          *
 ***************************************************************************/

#include <QDebug>
#include <QtGui>

#include <containerutils.h>
#include "httpinput.h"
#include "Loqqer.h"


HTTPInput::HTTPInput()
    : m_http( 0 )
    , m_streaming( false )
    , m_skipToSync( false )
    , m_skippedData( false )
    , m_refillBuffer( true )
    , m_bufferSize( 24576 )
{
    gLogger.Init( savePath( "httpinput.log" ), false );
    gLogger.SetLevel( 4 );

    LOGL( 3, "Initialising HTTP Input" );

    m_iWebService = webService();

    connect( &m_http, SIGNAL( readyRead( QHttpResponseHeader ) ), this, SLOT( dataAvailable( QHttpResponseHeader ) ) );
    connect( &m_http, SIGNAL( responseHeaderReceived( QHttpResponseHeader ) ), this, SLOT( responseHeaderReceived( QHttpResponseHeader ) ) );
    connect( &m_http, SIGNAL( stateChanged( int ) ), this, SLOT( stateChanged( int ) ) );

    connect( m_iWebService, SIGNAL( stationChanged( QString, QString ) ), this, SLOT( stationChanged() ) );
    connect( m_iWebService, SIGNAL( streamingUrl( QUrl ) ), this, SLOT( setStreamingUrl( QUrl ) ) );
    connect( m_iWebService, SIGNAL( metaDataResultRadio( MetaData ) ), this, SLOT( setMetaData( MetaData ) ) );
}


void
HTTPInput::startStreaming()
{
    // Called from Container::start.
    // TODO: this shouldn't be called directly, a relevant load should
    // be done first depending on what's highlighted in the tree.

    LOGL( 3, "Starting streaming from: " << m_url.toString() );

    m_mutex.lock();
    m_buffer.clear();
    m_mutex.unlock();
    emit clearBuffers();

    m_http.abort();
    m_http.setHost( m_url.host(), m_url.port() > 0 ? m_url.port() : 80 );
    m_http.get( m_url.path() + "?" + QString( m_url.encodedQuery() ) );

    m_skipToSync = true;
    emit preparedStreaming();
}


void
HTTPInput::stopStreaming()
{
    // Called from Container::stop

    LOGL( 3, "Stopping streaming" );

    setMetaData( MetaData() );

    m_http.abort();
    m_mutex.lock();
    m_buffer.clear();
    m_mutex.unlock();

    emit stoppedStreaming();
    emit listenerCommand( MetaData(), PCMD_STOP );
    emit clearBuffers();
}


void
HTTPInput::load( const QString& url )
{
    // Called by search or sidebar

    LOGL( 3, "Loading: " << url );

    m_skipToSync = true;
    m_iWebService->changeStation( url );
}


void
HTTPInput::stationChanged()
{
    if ( !isStreaming() )
        startStreaming();
}


void
HTTPInput::requestData()
{
    // When we completely run out of buffer, we should rather
    // refill the initial buffer of 32k (m_refillBuffer)
    if ( !m_refillBuffer && m_buffer.size() == 0 &&
          m_http.state() != QHttp::Unconnected && m_http.state() != QHttp::Closing )
    {
        LOGL( 3, "Buffer empty, rebuffering " << m_bufferSize );
        m_refillBuffer = true;
        emit bufferEmpty();
    }

    // Did we initially fill the buffer up to m_bufferSize?
    // If so, we can start emitting signals.
    if ( m_refillBuffer )
    {
        if ( m_buffer.size() >= m_bufferSize )
        {
            LOGL( 3, "Buffer full, starting playback" );
            m_refillBuffer = false;
            emit bufferFull();
        }
        else
        {
            if ( m_buffer.size() > 0 )
                emit bufferEmpty( m_buffer.size() );

            return;
        }
    }

    m_mutex.lock();
    if ( m_skipToSync && m_buffer.contains( "SYNC" ) )
    {
        int i = m_buffer.indexOf( "SYNC" );
        if ( ( m_buffer.size() - i ) > m_bufferSize )
        {
            LOGL( 3, "SYNC found, throwing away " << i << " bytes, skipping to next song" );
            m_buffer.remove( 0, i );
            m_skipToSync = false;
            m_skippedData = true;
        }
    }

    QByteArray buffer;
    buffer.append( m_buffer.left( 1024 ) );
    m_buffer.remove( 0, 1024 );
    m_mutex.unlock();

    // Check if the buffer contains a SYNC signal and if
    // we need to request new metadata via the WebService.
    if ( buffer.contains( "SYNC" ) )
    {
        if ( m_skippedData )
        {
            buffer.remove( 0, buffer.indexOf( "SYNC" ) );
            emit clearBuffers();
        }
        m_skipToSync = false;
        m_skippedData = false;

        buffer.remove( buffer.indexOf( "SYNC" ), 4 );

        LOGL( 3, "SYNC found, requesting metadata from web service" );
        m_iWebService->requestMetaDataRadio();
    }

    emit newData( buffer );
}


void
HTTPInput::dataAvailable( const QHttpResponseHeader &resp )
{
    char inBuf[8192];
    m_mutex.lock();

    memset( inBuf, 0, sizeof( inBuf ) );
    int len = m_http.read( inBuf, sizeof( inBuf ) );

    if ( strstr( inBuf, "HTTP/1.0 " ) != NULL )
    {
        if ( strstr( inBuf, "HTTP/1.0 401" ) != NULL ) responseHeaderReceived( QHttpResponseHeader( 401 ) );
        if ( strstr( inBuf, "HTTP/1.0 503" ) != NULL ) responseHeaderReceived( QHttpResponseHeader( 503 ) );
        if ( strstr( inBuf, "HTTP/1.0 666" ) != NULL ) responseHeaderReceived( QHttpResponseHeader( 666 ) );
        if ( strstr( inBuf, "HTTP/1.0 667" ) != NULL ) responseHeaderReceived( QHttpResponseHeader( 667 ) );
    }

    for ( int i = 0; i < len; i++ )
        m_buffer.append( inBuf[ i ] );

    m_mutex.unlock();
}


void
HTTPInput::responseHeaderReceived( const QHttpResponseHeader &resp )
{
    switch( resp.statusCode() )
    {
        case 401:
        {
            emit error( resp.statusCode(), tr( "Your session has timed out. Please try to login again." ) );
            break;
        }

        case 503:
        {
            emit error( resp.statusCode(), tr( "The radio server is too busy. Please try again in a few minutes." ) );
            break;
        }

        case 666:
        {
            emit error( resp.statusCode(), tr( "Radio server is down for maintenance. Please try again in a few minutes." ) );
            break;
        }

        case 667:
        {
            emit error( resp.statusCode(), tr( "There is not enough content to play this station." ) );
            break;
        }
    }
}


void
HTTPInput::stateChanged( int state )
{
    switch( state )
    {
        case QHttp::Reading:
        {
            if ( !m_streaming )
            {
                m_streaming = true;
                emit startedStreaming();
            }

            break;
        }

        case QHttp::Unconnected:
        {
            if ( m_streaming )
            {
                m_streaming = false;
                stopStreaming(); // will emit stoppedStreaming
            }

            break;
        }
    }
}


Q_EXPORT_PLUGIN2( input, HTTPInput )