/*************************************************************************** * Copyright (C) 2005 - 2006 by * * Christian Muehlhaeuser, Last.fm Ltd * * Erik Jaelevik, Last.fm Ltd * * * * 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 #include #include #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 )