summaryrefslogtreecommitdiffstats
path: root/amarok/src/engine/xine/xine-engine.cpp
diff options
context:
space:
mode:
authortpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2010-01-09 23:52:48 +0000
committertpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2010-01-09 23:52:48 +0000
commit3ce9174229de91411a9abf5381a1f335fe0c6a98 (patch)
tree84b2736fa1b0d3fbf9c60fc04f510d2a13916b09 /amarok/src/engine/xine/xine-engine.cpp
downloadamarok-3ce9174229de91411a9abf5381a1f335fe0c6a98.tar.gz
amarok-3ce9174229de91411a9abf5381a1f335fe0c6a98.zip
Added abandoned KDE3 version of Amarok
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/amarok@1072335 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'amarok/src/engine/xine/xine-engine.cpp')
-rw-r--r--amarok/src/engine/xine/xine-engine.cpp1342
1 files changed, 1342 insertions, 0 deletions
diff --git a/amarok/src/engine/xine/xine-engine.cpp b/amarok/src/engine/xine/xine-engine.cpp
new file mode 100644
index 00000000..05412aca
--- /dev/null
+++ b/amarok/src/engine/xine/xine-engine.cpp
@@ -0,0 +1,1342 @@
+/***************************************************************************
+ * Copyright (C) 2005 Christophe Thommeret <hftom@free.fr> *
+ * (C) 2005 Ian Monroe <ian@monroe.nu> *
+ * (C) 2005,6 Mark Kretschmann <markey@web.de> *
+ * (C) 2004,5 Max Howell <max.howell@methylblue.com> *
+ * (C) 2003,4 J. Kofler <kaffeine@gmx.net> *
+ * *
+ * 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. *
+ * *
+ ***************************************************************************/
+
+#define DEBUG_PREFIX "xine-engine"
+
+#include "xine-config.h"
+#include "xinecfg.h"
+#include "xine-engine.h"
+#include "amarok.h"
+#include "amarokconfig.h"
+//these files are from libamarok
+#include "playlist.h"
+#include "enginecontroller.h"
+
+AMAROK_EXPORT_PLUGIN( XineEngine )
+
+#include <climits>
+#include <cstdlib>
+#include <cmath>
+#include "debug.h"
+
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kstandarddirs.h>
+
+#include <qapplication.h>
+#include <qdir.h>
+
+extern "C"
+{
+ #include <unistd.h>
+ #include "xine-scope.h"
+}
+
+#ifndef LLONG_MAX
+#define LLONG_MAX 9223372036854775807LL
+#endif
+
+
+//define this to use xine in a more standard way
+//#define XINE_SAFE_MODE
+
+
+///some logging static globals
+namespace Log
+{
+ static uint bufferCount = 0;
+ static uint scopeCallCount = 1; //prevent divideByZero
+ static uint noSuitableBuffer = 0;
+}
+
+///returns the configuration we will use. there is no KInstance, so using this hacked up method.
+//static inline QCString configPath() { return QFile::encodeName(KStandardDirs().localkdedir() + KStandardDirs::kde_default("data") + "amarok/xine-config"); }
+static inline QCString configPath() { return QFile::encodeName(locate( "data", "amarok/") + "xine-config" ); }
+static Fader *s_fader = 0;
+static OutFader *s_outfader = 0;
+
+
+XineEngine::XineEngine()
+ : EngineBase()
+ , m_xine( 0 )
+ , m_stream( 0 )
+ , m_audioPort( 0 )
+ , m_eventQueue( 0 )
+ , m_post( 0 )
+ , m_preamp( 1.0 )
+ , m_stopFader( false )
+ , m_fadeOutRunning ( false )
+ , m_equalizerEnabled( false )
+{
+ addPluginProperty( "HasConfigure", "true" );
+ addPluginProperty( "HasEqualizer", "true" );
+ #ifndef __NetBSD__ // NetBSD does not offer audio mixing
+ addPluginProperty( "HasCrossfade", "true" );
+ #endif
+ addPluginProperty("HasCDDA", "true"); // new property
+ debug() << "hello" << endl;
+
+}
+
+XineEngine::~XineEngine()
+{
+ // Wait until the fader thread is done
+ if( s_fader ) {
+ m_stopFader = true;
+ s_fader->resume(); // safety call if the engine is in the pause state
+ s_fader->wait();
+ }
+
+ delete s_fader;
+ delete s_outfader;
+
+ if( AmarokConfig::fadeoutOnExit() ) {
+ bool terminateFader = false;
+ fadeOut( AmarokConfig::fadeoutLength(), &terminateFader, true ); // true == exiting
+ }
+
+ if( m_xine ) xine_config_save( m_xine, configPath() );
+
+ if( m_stream ) xine_close( m_stream );
+ if( m_eventQueue ) xine_event_dispose_queue( m_eventQueue );
+ if( m_stream ) xine_dispose( m_stream );
+ if( m_audioPort ) xine_close_audio_driver( m_xine, m_audioPort );
+ if( m_post ) xine_post_dispose( m_xine, m_post );
+ if( m_xine ) xine_exit( m_xine );
+
+ debug() << "xine closed\n";
+
+ debug() << "Scope statistics:\n"
+ << " Average list size: " << Log::bufferCount / Log::scopeCallCount << endl
+ << " Buffer failure: " << double(Log::noSuitableBuffer*100) / Log::scopeCallCount << "%\n";
+}
+
+bool
+XineEngine::init()
+{
+ DEBUG_BLOCK
+
+ debug() << "'Bringing joy to small mexican gerbils, a few weeks at a time.'\n";
+
+ m_xine = xine_new();
+
+ if( !m_xine ) {
+ KMessageBox::error( 0, i18n("Amarok could not initialize xine.") );
+ return false;
+ }
+
+ #ifdef XINE_SAFE_MODE
+ xine_engine_set_param( m_xine, XINE_ENGINE_PARAM_VERBOSITY, 99 );
+ #endif
+
+ xine_config_load( m_xine, configPath() );
+ debug() << "w00t" << configPath() << endl;
+
+ xine_init( m_xine );
+
+ makeNewStream();
+
+ #ifndef XINE_SAFE_MODE
+ startTimer( 200 ); //prunes the scope
+ #endif
+
+ return true;
+}
+
+bool
+XineEngine::makeNewStream()
+{
+ m_currentAudioPlugin = XineCfg::outputPlugin();
+
+ m_audioPort = xine_open_audio_driver( m_xine, XineCfg::outputPlugin().local8Bit(), NULL );
+ if( !m_audioPort ) {
+ //TODO make engine method that is the same but parents the dialog for us
+ KMessageBox::error( 0, i18n("xine was unable to initialize any audio drivers.") );
+ return false;
+ }
+
+ m_stream = xine_stream_new( m_xine, m_audioPort, NULL );
+ if( !m_stream ) {
+ xine_close_audio_driver( m_xine, m_audioPort );
+ m_audioPort = NULL;
+ KMessageBox::error( 0, i18n("Amarok could not create a new xine stream.") );
+ return false;
+ }
+
+ if( m_eventQueue )
+ xine_event_dispose_queue( m_eventQueue );
+
+ xine_event_create_listener_thread(
+ m_eventQueue = xine_event_new_queue( m_stream ),
+ &XineEngine::XineEventListener,
+ (void*)this );
+
+ #ifndef XINE_SAFE_MODE
+ //implemented in xine-scope.h
+ m_post = scope_plugin_new( m_xine, m_audioPort );
+
+ xine_set_param( m_stream, XINE_PARAM_METRONOM_PREBUFFER, 6000 );
+ xine_set_param( m_stream, XINE_PARAM_IGNORE_VIDEO, 1 );
+ #endif
+#ifdef XINE_PARAM_EARLY_FINISHED_EVENT
+ if ( xine_check_version(1,1,1) && !(m_xfadeLength > 0) ) {
+ // enable gapless playback
+ debug() << "gapless playback enabled." << endl;
+ //xine_set_param(m_stream, XINE_PARAM_EARLY_FINISHED_EVENT, 1 );
+ }
+#endif
+ return true;
+}
+
+// Makes sure an audio port and a stream exist.
+bool
+XineEngine::ensureStream()
+{
+ if( !m_stream )
+ return makeNewStream();
+
+ return true;
+}
+
+bool
+XineEngine::load( const KURL &url, bool isStream )
+{
+ DEBUG_BLOCK
+
+ if( !ensureStream() )
+ return false;
+
+ Engine::Base::load( url, isStream );
+
+ if( s_outfader ) {
+ s_outfader->finish();
+ delete s_outfader;
+ }
+
+ if( m_xfadeLength > 0 && xine_get_status( m_stream ) == XINE_STATUS_PLAY &&
+ url.isLocalFile() &&
+ xine_get_param( m_stream, XINE_PARAM_SPEED ) != XINE_SPEED_PAUSE &&
+ ( m_xfadeNextTrack || //set by engine controller when switching tracks automatically
+ (uint) AmarokConfig::crossfadeType() == 0 || //crossfade always
+ (uint) AmarokConfig::crossfadeType() == 2 ) ) //crossfade when switching tracks manually
+ {
+ m_xfadeNextTrack = false;
+ // Stop a probably running fader
+ if( s_fader ) {
+ m_stopFader = true;
+ s_fader->finish(); // makes the fader stop abruptly
+ delete s_fader;
+ }
+ s_fader = new Fader( this, m_xfadeLength );
+ setEqualizerParameters( m_intPreamp, m_equalizerGains );
+ }
+
+ // for users who stubbonly refuse to use DMIX or buy a good soundcard
+ // why doesn't xine do this? I cannot say.
+ xine_close( m_stream );
+
+ debug() << "Before xine_open() *****" << endl;
+
+ if( xine_open( m_stream, QFile::encodeName( url.url() ) ) )
+ {
+ debug() << "After xine_open() *****" << endl;
+
+ #ifndef XINE_SAFE_MODE
+ //we must ensure the scope is pruned of old buffers
+ timerEvent( 0 );
+
+ xine_post_out_t *source = xine_get_audio_source( m_stream );
+ xine_post_in_t *target = (xine_post_in_t*)xine_post_input( m_post, const_cast<char*>("audio in") );
+ xine_post_wire( source, target );
+ #endif
+
+ playlistChanged();
+
+ return true;
+ }
+ else
+ {
+ #ifdef XINE_PARAM_GAPLESS_SWITCH
+ if ( xine_check_version(1,1,1) && !(m_xfadeLength > 0) )
+ xine_set_param( m_stream, XINE_PARAM_GAPLESS_SWITCH, 0);
+ #endif
+ }
+
+ // FAILURE to load!
+ //s_fader will delete itself
+ determineAndShowErrorMessage();
+
+ return false;
+}
+
+bool
+XineEngine::play( uint offset )
+{
+ DEBUG_BLOCK
+
+ if( !ensureStream() )
+ return false;
+
+ const bool has_audio = xine_get_stream_info( m_stream, XINE_STREAM_INFO_HAS_AUDIO );
+ const bool audio_handled = xine_get_stream_info( m_stream, XINE_STREAM_INFO_AUDIO_HANDLED );
+
+ if (has_audio && audio_handled && xine_play( m_stream, 0, offset ))
+ {
+ if( s_fader )
+ s_fader->start( QThread::LowestPriority );
+
+ emit stateChanged( Engine::Playing );
+
+ return true;
+ }
+
+ //we need to stop the track that is prepped for crossfade
+ delete s_fader;
+
+ emit stateChanged( Engine::Empty );
+
+ determineAndShowErrorMessage();
+
+ xine_close( m_stream );
+
+ return false;
+}
+
+#include "statusbar/statusbar.h"
+
+void
+XineEngine::determineAndShowErrorMessage()
+{
+ DEBUG_BLOCK
+
+ QString body;
+
+ debug() << "xine_get_error()\n";
+ switch (xine_get_error( m_stream )) {
+ case XINE_ERROR_NO_INPUT_PLUGIN:
+ body = i18n("No suitable input plugin. This often means that the url's protocol is not supported. Network failures are other possible causes.");
+ break;
+
+ case XINE_ERROR_NO_DEMUX_PLUGIN:
+ body = i18n("No suitable demux plugin. This often means that the file format is not supported.");
+ break;
+
+ case XINE_ERROR_DEMUX_FAILED:
+ body = i18n("Demuxing failed.");
+ break;
+
+ case XINE_ERROR_INPUT_FAILED:
+ body = i18n("Could not open file.");
+ break;
+
+ case XINE_ERROR_MALFORMED_MRL:
+ body = i18n("The location is malformed.");
+ break;
+
+ case XINE_ERROR_NONE:
+ // xine is thick. xine doesn't think there is an error
+ // but there may be! We check for other errors below.
+
+ default:
+ if (!xine_get_stream_info( m_stream, XINE_STREAM_INFO_AUDIO_HANDLED ))
+ {
+ // xine can read the plugin but it didn't find any codec
+ // THUS xine=daft for telling us it could handle the format in canDecode!
+ body = i18n("There is no available decoder.");
+ QString const ext = Amarok::extension( m_url.url() ).lower();
+ if (ext == "mp3" && EngineController::installDistroCodec( "xine-engine" ))
+ return;
+ }
+ else if (!xine_get_stream_info( m_stream, XINE_STREAM_INFO_HAS_AUDIO ))
+ body = i18n("There is no audio channel!");
+ break;
+ }
+
+ Amarok::StatusBar::instance()->longMessage(
+ "<b>" + i18n("Error Loading Media") + "</b><p>" + body + "<p>" + m_url.prettyURL(),
+ KDE::StatusBar::Error );
+}
+
+void
+XineEngine::stop()
+{
+ if( s_fader && s_fader->running() )
+ s_fader->resume(); // safety call if the engine is in the pause state
+
+ if ( !m_stream )
+ return;
+
+ if( AmarokConfig::fadeout() && !m_fadeOutRunning || state() == Engine::Paused )
+ {
+ s_outfader = new OutFader( this, AmarokConfig::fadeoutLength() );
+ s_outfader->start();
+ ::usleep( 100 ); //to be sure engine state won't be changed before it is checked in fadeOut()
+ m_url = KURL(); //to ensure we return Empty from state()
+
+ std::fill( m_scope.begin(), m_scope.end(), 0 );
+ }
+ else if( !m_fadeOutRunning )
+ {
+ xine_stop( m_stream );
+ xine_close( m_stream );
+ xine_set_param( m_stream, XINE_PARAM_AUDIO_CLOSE_DEVICE, 1);
+ }
+
+ emit stateChanged( Engine::Empty );
+}
+
+void
+XineEngine::pause()
+{
+ if ( !m_stream )
+ return;
+
+ if( xine_get_param( m_stream, XINE_PARAM_SPEED ) != XINE_SPEED_PAUSE )
+ {
+ if( s_fader && s_fader->running() )
+ s_fader->pause();
+
+ xine_set_param( m_stream, XINE_PARAM_SPEED, XINE_SPEED_PAUSE );
+ xine_set_param( m_stream, XINE_PARAM_AUDIO_CLOSE_DEVICE, 1);
+ emit stateChanged( Engine::Paused );
+
+ }
+}
+
+void
+XineEngine::unpause()
+{
+ if ( !m_stream )
+ return;
+
+ if( xine_get_param( m_stream, XINE_PARAM_SPEED ) == XINE_SPEED_PAUSE )
+ {
+ if( s_fader && s_fader->running() )
+ s_fader->resume();
+
+ xine_set_param( m_stream, XINE_PARAM_SPEED, XINE_SPEED_NORMAL );
+ emit stateChanged( Engine::Playing );
+ }
+}
+
+Engine::State
+XineEngine::state() const
+{
+ if ( !m_stream || m_fadeOutRunning )
+ return Engine::Empty;
+
+ switch( xine_get_status( m_stream ) )
+ {
+ case XINE_STATUS_PLAY: return xine_get_param( m_stream, XINE_PARAM_SPEED ) != XINE_SPEED_PAUSE ? Engine::Playing : Engine::Paused;
+ case XINE_STATUS_IDLE: return Engine::Empty;
+ case XINE_STATUS_STOP:
+ default: return m_url.isEmpty() ? Engine::Empty : Engine::Idle;
+ }
+}
+
+uint
+XineEngine::position() const
+{
+ if ( state() == Engine::Empty )
+ return 0;
+
+ int pos;
+ int time = 0;
+ int length;
+
+ // Workaround for problems when you seek too quickly, see BUG 99808
+ int tmp = 0, i = 0;
+ while( ++i < 4 )
+ {
+ xine_get_pos_length( m_stream, &pos, &time, &length );
+ if( time > tmp ) break;
+ usleep( 100000 );
+ }
+
+ // Here we check for new metadata periodically, because xine does not emit an event
+ // in all cases (e.g. with ogg streams). See BUG 122505
+ if ( state() != Engine::Idle && state() != Engine::Empty )
+ {
+ const Engine::SimpleMetaBundle bundle = fetchMetaData();
+ if( bundle.title != m_currentBundle.title || bundle.artist != m_currentBundle.artist ) {
+ debug() << "Metadata received." << endl;
+ m_currentBundle = bundle;
+
+ XineEngine* p = const_cast<XineEngine*>( this );
+ p->emit metaData( bundle );
+ }
+ }
+
+ return time;
+}
+
+uint
+XineEngine::length() const
+{
+ if ( !m_stream )
+ return 0;
+
+ // xine often delivers nonsense values for VBR files and such, so we only
+ // use the length for remote files
+
+ if( m_url.isLocalFile() )
+ return 0;
+
+ else {
+ int pos;
+ int time;
+ int length = 0;
+
+ xine_get_pos_length( m_stream, &pos, &time, &length );
+ if( length < 0 )
+ length=0;
+
+ return length;
+ }
+}
+
+void
+XineEngine::seek( uint ms )
+{
+ if( !ensureStream() )
+ return;
+
+ if( xine_get_param( m_stream, XINE_PARAM_SPEED ) == XINE_SPEED_PAUSE ) {
+ // FIXME this is a xine API issue really, they need to add a seek function
+ xine_play( m_stream, 0, (int)ms );
+ xine_set_param( m_stream, XINE_PARAM_SPEED, XINE_SPEED_PAUSE );
+ }
+ else
+ xine_play( m_stream, 0, (int)ms );
+}
+
+void
+XineEngine::setVolumeSW( uint vol )
+{
+ if ( !m_stream )
+ return;
+ if( !s_fader )
+ xine_set_param( m_stream, XINE_PARAM_AUDIO_AMP_LEVEL, static_cast<uint>( vol * m_preamp ) );
+}
+
+void
+XineEngine::fadeOut( uint fadeLength, bool* terminate, bool exiting )
+{
+ if( m_fadeOutRunning ) //Let us not start another fadeout...
+ return;
+
+ m_fadeOutRunning = !m_fadeOutRunning;
+ const bool isPlaying = m_stream && ( xine_get_status( m_stream ) == XINE_STATUS_PLAY );
+ const float originalVol = Engine::Base::makeVolumeLogarithmic( m_volume ) * m_preamp;
+
+ // On shutdown, limit fadeout to 3 secs max, so that we don't risk getting killed
+ const int length = exiting ? QMIN( fadeLength, 3000 ) : fadeLength;
+
+ if( length > 0 && isPlaying )
+ {
+ // fader-class doesn't work in this spot as is, so some parts need to be copied here... (ugly)
+ uint stepsCount = length < 1000 ? length / 10 : 100;
+ uint stepSizeUs = (int)( 1000.0 * (float)length / (float)stepsCount );
+
+ ::usleep( stepSizeUs );
+ QTime t;
+ t.start();
+ float mix = 0.0;
+ while ( mix < 1.0 )
+ {
+ if( *terminate ) break;
+
+ ::usleep( stepSizeUs );
+ float vol = Engine::Base::makeVolumeLogarithmic( m_volume ) * m_preamp;
+ float mix = (float)t.elapsed() / (float)length;
+ if ( mix > 1.0 )
+ {
+ break;
+ }
+ if ( m_stream )
+ {
+ float v = 4.0 * (1.0 - mix) / 3.0;
+ xine_set_param( m_stream, XINE_PARAM_AUDIO_AMP_LEVEL, (uint)( v < 1.0 ? vol * v : vol ) );
+ }
+ }
+ }
+ if( m_fadeOutRunning && m_stream )
+ xine_set_param( m_stream, XINE_PARAM_AUDIO_AMP_LEVEL, (uint) originalVol );
+ m_fadeOutRunning = !m_fadeOutRunning;
+}
+
+void
+XineEngine::setEqualizerEnabled( bool enable )
+{
+ if ( !m_stream )
+ return;
+
+ m_equalizerEnabled = enable;
+
+ if( !enable ) {
+ QValueList<int> gains;
+ for( uint x = 0; x < 10; x++ )
+ gains << -101; // sets eq gains to zero.
+
+ setEqualizerParameters( 0, gains );
+ }
+}
+
+/*
+ sets the eq params for xine engine - have to rescale eq params to fitting range (adapted from kaffeine and xfmedia)
+
+ preamp
+ pre: (-100..100)
+ post: (0.1..1.9) - this is not really a preamp but we use the xine preamp parameter for our normal volume. so we make a postamp.
+
+ gains
+ pre: (-100..100)
+ post: (1..200) - (1 = down, 100 = middle, 200 = up, 0 = off)
+ */
+void
+XineEngine::setEqualizerParameters( int preamp, const QValueList<int> &gains )
+{
+ if ( !m_stream )
+ return;
+
+ m_equalizerGains = gains;
+ m_intPreamp = preamp;
+ QValueList<int>::ConstIterator it = gains.begin();
+
+ xine_set_param( m_stream, XINE_PARAM_EQ_30HZ, int( (*it )*0.995 + 100 ) );
+ xine_set_param( m_stream, XINE_PARAM_EQ_60HZ, int( (*++it)*0.995 + 100 ) );
+ xine_set_param( m_stream, XINE_PARAM_EQ_125HZ, int( (*++it)*0.995 + 100 ) );
+ xine_set_param( m_stream, XINE_PARAM_EQ_250HZ, int( (*++it)*0.995 + 100 ) );
+ xine_set_param( m_stream, XINE_PARAM_EQ_500HZ, int( (*++it)*0.995 + 100 ) );
+ xine_set_param( m_stream, XINE_PARAM_EQ_1000HZ, int( (*++it)*0.995 + 100 ) );
+ xine_set_param( m_stream, XINE_PARAM_EQ_2000HZ, int( (*++it)*0.995 + 100 ) );
+ xine_set_param( m_stream, XINE_PARAM_EQ_4000HZ, int( (*++it)*0.995 + 100 ) );
+ xine_set_param( m_stream, XINE_PARAM_EQ_8000HZ, int( (*++it)*0.995 + 100 ) );
+ xine_set_param( m_stream, XINE_PARAM_EQ_16000HZ, int( (*++it)*0.995 + 100 ) );
+
+ m_preamp = ( preamp - 0.1 * preamp + 100 ) / 100.0;
+ setVolume( m_volume );
+}
+
+bool
+XineEngine::canDecode( const KURL &url ) const
+{
+ static QStringList list;
+ if(list.isEmpty())
+ {
+ char* exts = xine_get_file_extensions( m_xine );
+ list = QStringList::split( ' ', exts );
+ free( exts ); exts = NULL;
+ //images
+ list.remove("png");
+ list.remove("jpg");
+ list.remove("jpeg");
+ list.remove("gif");
+ list.remove("ilbm");
+ list.remove("iff");
+ //subtitles
+ list.remove("asc");
+ list.remove("txt");
+ list.remove("sub");
+ list.remove("srt");
+ list.remove("smi");
+ list.remove("ssa");
+//HACK we also check for m4a because xine plays them but
+//for some reason doesn't return the extension
+ if(!list.contains("m4a"))
+ list << "m4a";
+ }
+
+ if (url.protocol() == "cdda")
+ // play audio CDs pls
+ return true;
+
+ QString path = url.path();
+
+ // partial downloads from Konqi and other browsers
+ // tend to have a .part extension
+ if (path.endsWith( ".part" ))
+ path = path.left( path.length() - 5 );
+
+ const QString ext = path.mid( path.findRev( '.' ) + 1 ).lower();
+
+ return list.contains( ext );
+}
+
+const Engine::Scope&
+XineEngine::scope()
+{
+ if( !m_post || !m_stream || xine_get_status( m_stream ) != XINE_STATUS_PLAY )
+ return m_scope;
+
+ MyNode* const myList = scope_plugin_list( m_post );
+ metronom_t* const myMetronom = scope_plugin_metronom( m_post );
+ const int myChannels = scope_plugin_channels( m_post );
+ int scopeidx = 0;
+
+ if (myChannels > 2)
+ return m_scope;
+
+ //prune the buffer list and update m_currentVpts
+ timerEvent( 0 );
+
+ for( int n, frame = 0; frame < 512; )
+ {
+ MyNode *best_node = 0;
+
+ for( MyNode *node = myList->next; node != myList; node = node->next, Log::bufferCount++ )
+ if( node->vpts <= m_currentVpts && (!best_node || node->vpts > best_node->vpts) )
+ best_node = node;
+
+ if( !best_node || best_node->vpts_end < m_currentVpts ) {
+ Log::noSuitableBuffer++; break; }
+
+ int64_t
+ diff = m_currentVpts;
+ diff -= best_node->vpts;
+ diff *= 1<<16;
+ diff /= myMetronom->pts_per_smpls;
+
+ const int16_t*
+ data16 = best_node->mem;
+ data16 += diff;
+
+ diff += diff % myChannels; //important correction to ensure we don't overflow the buffer
+ diff /= myChannels; //use units of frames, not samples
+
+ //calculate the number of available samples in this buffer
+ n = best_node->num_frames;
+ n -= diff;
+ n += frame; //clipping for # of frames we need
+
+ if( n > 512 )
+ n = 512; //we don't want more than 512 frames
+
+ for( int a, c; frame < n; ++frame, data16 += myChannels ) {
+ for( a = c = 0; c < myChannels; ++c )
+ {
+ // we now give interleaved pcm to the scope
+ m_scope[scopeidx++] = data16[c];
+ if (myChannels == 1) // duplicate mono samples
+ m_scope[scopeidx++] = data16[c];
+ }
+ }
+
+ m_currentVpts = best_node->vpts_end;
+ m_currentVpts++; //FIXME needs to be done for some reason, or you get situations where it uses same buffer again and again
+ }
+
+ Log::scopeCallCount++;
+
+ return m_scope;
+}
+
+void
+XineEngine::timerEvent( QTimerEvent* )
+{
+ if ( !m_stream )
+ return;
+
+ //here we prune the buffer list regularly
+
+ MyNode *myList = scope_plugin_list( m_post );
+
+ if ( ! myList ) return;
+
+ //we operate on a subset of the list for thread-safety
+ MyNode * const first_node = myList->next;
+ MyNode const * const list_end = myList;
+
+ m_currentVpts = (xine_get_status( m_stream ) == XINE_STATUS_PLAY)
+ ? xine_get_current_vpts( m_stream )
+ : LLONG_MAX; //if state is not playing OR paused, empty the list
+ //: std::numeric_limits<int64_t>::max(); //TODO don't support crappy gcc 2.95
+
+ for( MyNode *prev = first_node, *node = first_node->next; node != list_end; node = node->next )
+ {
+ //we never delete first_node
+ //this maintains thread-safety
+ if( node->vpts_end < m_currentVpts ) {
+ prev->next = node->next;
+
+ free( node->mem );
+ free( node );
+
+ node = prev;
+ }
+
+ prev = node;
+ }
+}
+
+Amarok::PluginConfig*
+XineEngine::configure() const
+{
+ XineConfigDialog* xcf = new XineConfigDialog( m_xine );
+ connect(xcf, SIGNAL( settingsSaved() ), this, SLOT( configChanged() ));
+ connect(this, SIGNAL( resetConfig(xine_t*) ), xcf, SLOT( reset(xine_t*) ));
+ return xcf;
+}
+
+void
+XineEngine::customEvent( QCustomEvent *e )
+{
+ #define message static_cast<QString*>(e->data())
+
+ switch( e->type() )
+ {
+ case 3000: //XINE_EVENT_UI_PLAYBACK_FINISHED
+ emit trackEnded();
+ break;
+
+ case 3001:
+ emit infoMessage( (*message).arg( m_url.prettyURL() ) );
+ delete message;
+ break;
+
+ case 3002:
+ emit statusText( *message );
+ delete message;
+ break;
+
+ case 3003: { //meta info has changed
+ debug() << "Metadata received." << endl;
+ const Engine::SimpleMetaBundle bundle = fetchMetaData();
+ m_currentBundle = bundle;
+ emit metaData( bundle );
+ } break;
+
+ case 3004:
+ emit statusText( i18n("Redirecting to: ").arg( *message ) );
+ load( KURL( *message ), false );
+ play();
+ delete message;
+ break;
+ case 3005:
+ emit lastFmTrackChange();
+ break;
+ default:
+ ;
+ }
+
+ #undef message
+}
+//SLOT
+void XineEngine::configChanged()
+{
+ //reset xine to load new audio plugin
+ if( m_currentAudioPlugin != XineCfg::outputPlugin() )
+ {
+ stop();
+ xine_config_save( m_xine, configPath() );
+ if( m_stream ) xine_close( m_stream );
+ if( m_eventQueue ) xine_event_dispose_queue( m_eventQueue );
+ m_eventQueue = NULL;
+ if( m_stream ) xine_dispose( m_stream );
+ m_stream = NULL;
+ if( m_audioPort ) xine_close_audio_driver( m_xine, m_audioPort );
+ m_audioPort = NULL;
+ if( m_post ) xine_post_dispose( m_xine, m_post );
+ m_post = NULL;
+ if( m_xine ) xine_exit( m_xine );
+ m_xine = NULL;
+ init();
+ setEqualizerEnabled( m_equalizerEnabled );
+ if( m_equalizerEnabled )
+ setEqualizerParameters( m_intPreamp, m_equalizerGains );
+ emit resetConfig(m_xine);
+ }
+}
+
+//SLOT
+void
+XineEngine::playlistChanged()
+{
+ #ifdef XINE_PARAM_EARLY_FINISHED_EVENT
+ #ifdef XINE_PARAM_GAPLESS_SWITCH
+ if ( xine_check_version(1,1,1) && !(m_xfadeLength > 0)
+ && m_url.isLocalFile() && Playlist::instance()->isTrackAfter() )
+ {
+ xine_set_param(m_stream, XINE_PARAM_EARLY_FINISHED_EVENT, 1 );
+ debug() << "XINE_PARAM_EARLY_FINISHED_EVENT enabled" << endl;
+ }
+ else
+ {
+ //we don't want an early finish event if there is no track after the current one
+ xine_set_param(m_stream, XINE_PARAM_EARLY_FINISHED_EVENT, 0 );
+ debug() << "XINE_PARAM_EARLY_FINISHED_EVENT disabled" << endl;
+ }
+ #endif
+ #endif
+}
+
+static time_t last_error_time = 0; // hysteresis on xine errors
+static int last_error = XINE_MSG_NO_ERROR;
+
+void
+XineEngine::XineEventListener( void *p, const xine_event_t* xineEvent )
+{
+ time_t current;
+
+ if( !p ) return;
+
+ #define xe static_cast<XineEngine*>(p)
+
+ switch( xineEvent->type )
+ {
+ case XINE_EVENT_UI_SET_TITLE:
+
+ debug() << "XINE_EVENT_UI_SET_TITLE\n";
+
+ QApplication::postEvent( xe, new QCustomEvent( 3003 ) );
+
+ break;
+
+ case XINE_EVENT_UI_PLAYBACK_FINISHED:
+ debug() << "XINE_EVENT_UI_PLAYBACK_FINISHED\n";
+
+ #ifdef XINE_PARAM_GAPLESS_SWITCH
+ if ( xine_check_version(1,1,1) && xe->m_url.isLocalFile() //Remote media break with gapless
+ //don't prepare for a track that isn't coming
+ && Playlist::instance()
+ && Playlist::instance()->isTrackAfter()
+ && !AmarokConfig::crossfade() )
+ xine_set_param( xe->m_stream, XINE_PARAM_GAPLESS_SWITCH, 1);
+ #endif
+ //emit signal from GUI thread
+ QApplication::postEvent( xe, new QCustomEvent(3000) );
+ break;
+
+ case XINE_EVENT_PROGRESS: {
+ xine_progress_data_t* pd = (xine_progress_data_t*)xineEvent->data;
+
+ QString
+ msg = "%1 %2%";
+ msg = msg.arg( QString::fromUtf8( pd->description ) )
+ .arg( KGlobal::locale()->formatNumber( pd->percent, 0 ) );
+
+ QCustomEvent *e = new QCustomEvent( 3002 );
+ e->setData( new QString( msg ) );
+
+ QApplication::postEvent( xe, e );
+
+ } break;
+
+ case XINE_EVENT_MRL_REFERENCE: {
+ /// xine has read the stream and found it actually links to something else
+ /// so we need to play that instead
+
+ QString message = QString::fromUtf8( static_cast<xine_mrl_reference_data_t*>(xineEvent->data)->mrl );
+ QCustomEvent *e = new QCustomEvent( 3004 );
+ e->setData( new QString( message ) );
+
+ QApplication::postEvent( xe, e );
+
+ } break;
+
+ case XINE_EVENT_UI_MESSAGE:
+ {
+ debug() << "message received from xine\n";
+
+ xine_ui_message_data_t *data = (xine_ui_message_data_t *)xineEvent->data;
+ QString message;
+
+ switch( data->type )
+ {
+ case XINE_MSG_NO_ERROR:
+ {
+ //series of \0 separated strings, terminated with a \0\0
+ char str[2000];
+ char *p = str;
+ for( char *msg = data->messages; !(*msg == '\0' && *(msg+1) == '\0'); ++msg, ++p )
+ *p = *msg == '\0' ? '\n' : *msg;
+ *p = '\0';
+
+ debug() << str << endl;
+
+ break;
+ }
+
+ case XINE_MSG_ENCRYPTED_SOURCE:
+ break;
+
+ case XINE_MSG_UNKNOWN_HOST:
+ message = i18n("The host is unknown for the URL: <i>%1</i>"); goto param;
+ case XINE_MSG_UNKNOWN_DEVICE:
+ message = i18n("The device name you specified seems invalid."); goto param;
+ case XINE_MSG_NETWORK_UNREACHABLE:
+ message = i18n("The network appears unreachable."); goto param;
+ case XINE_MSG_AUDIO_OUT_UNAVAILABLE:
+ message = i18n("Audio output unavailable; the device is busy."); goto param;
+ case XINE_MSG_CONNECTION_REFUSED:
+ message = i18n("The connection was refused for the URL: <i>%1</i>"); goto param;
+ case XINE_MSG_FILE_NOT_FOUND:
+ message = i18n("xine could not find the URL: <i>%1</i>"); goto param;
+ case XINE_MSG_PERMISSION_ERROR:
+ message = i18n("Access was denied for the URL: <i>%1</i>"); goto param;
+ case XINE_MSG_READ_ERROR:
+ message = i18n("The source cannot be read for the URL: <i>%1</i>"); goto param;
+ case XINE_MSG_LIBRARY_LOAD_ERROR:
+ message = i18n("A problem occurred while loading a library or decoder."); goto param;
+
+ case XINE_MSG_GENERAL_WARNING:
+ message = i18n("General Warning"); goto explain;
+ case XINE_MSG_SECURITY:
+ message = i18n("Security Warning"); goto explain;
+ default:
+ message = i18n("Unknown Error"); goto explain;
+
+
+ explain:
+
+ // Don't flood the user with error messages
+ if( (last_error_time + 10) > time( &current ) &&
+ data->type == last_error )
+ {
+ last_error_time = current;
+ return;
+ }
+ last_error_time = current;
+ last_error = data->type;
+
+ if( data->explanation )
+ {
+ message.prepend( "<b>" );
+ message += "</b>:<p>";
+ message += QString::fromUtf8( (char*)data + data->explanation );
+ }
+ else break; //if no explanation then why bother!
+
+ //FALL THROUGH
+
+ param:
+
+ // Don't flood the user with error messages
+ if((last_error_time + 10) > time(&current) &&
+ data->type == last_error)
+ {
+ last_error_time = current;
+ return;
+ }
+ last_error_time = current;
+ last_error = data->type;
+
+ message.prepend( "<p>" );
+ message += "<p>";
+
+ if(data->explanation)
+ {
+ message += "xine parameters: <i>";
+ message += QString::fromUtf8( (char*)data + data->parameters );
+ message += "</i>";
+ }
+ else message += i18n("Sorry, no additional information is available.");
+
+ QApplication::postEvent( xe, new QCustomEvent(QEvent::Type(3001), new QString(message)) );
+ }
+
+ } //case
+ case XINE_EVENT_UI_CHANNELS_CHANGED: //Flameeyes used this for last.fm track changes
+ QApplication::postEvent( xe, new QCustomEvent(QEvent::Type(3005) ) );
+ break;
+ } //switch
+
+ #undef xe
+}
+
+Engine::SimpleMetaBundle
+XineEngine::fetchMetaData() const
+{
+ Engine::SimpleMetaBundle bundle;
+ bundle.title = QString::fromUtf8( xine_get_meta_info( m_stream, XINE_META_INFO_TITLE ) );
+ bundle.artist = QString::fromUtf8( xine_get_meta_info( m_stream, XINE_META_INFO_ARTIST ) );
+ bundle.album = QString::fromUtf8( xine_get_meta_info( m_stream, XINE_META_INFO_ALBUM ) );
+ bundle.comment = QString::fromUtf8( xine_get_meta_info( m_stream, XINE_META_INFO_COMMENT ) );
+ bundle.genre = QString::fromUtf8( xine_get_meta_info( m_stream, XINE_META_INFO_GENRE ) );
+ bundle.bitrate = QString::number( xine_get_stream_info( m_stream, XINE_STREAM_INFO_AUDIO_BITRATE ) / 1000 );
+ bundle.samplerate = QString::number( xine_get_stream_info( m_stream, XINE_STREAM_INFO_AUDIO_SAMPLERATE ) );
+ bundle.year = QString::fromUtf8( xine_get_meta_info( m_stream, XINE_META_INFO_YEAR ) );
+ bundle.tracknr = QString::fromUtf8( xine_get_meta_info( m_stream, XINE_META_INFO_TRACK_NUMBER ) );
+
+ return bundle;
+}
+
+bool XineEngine::metaDataForUrl(const KURL &url, Engine::SimpleMetaBundle &b)
+{
+ bool result = false;
+ xine_stream_t* tmpstream = xine_stream_new(m_xine, NULL, NULL);
+ if (xine_open(tmpstream, QFile::encodeName(url.url()))) {
+ QString audioCodec = QString::fromUtf8(xine_get_meta_info(tmpstream, XINE_META_INFO_SYSTEMLAYER));
+
+ if (audioCodec == "CDDA") {
+ QString title = QString::fromUtf8(
+ xine_get_meta_info(tmpstream, XINE_META_INFO_TITLE));
+ if ((!title.isNull()) && (!title.isEmpty())) { //no meta info
+ b.title = title;
+ b.artist =
+ QString::fromUtf8(
+ xine_get_meta_info(tmpstream, XINE_META_INFO_ARTIST));
+ b.album =
+ QString::fromUtf8(
+ xine_get_meta_info(tmpstream, XINE_META_INFO_ALBUM));
+ b.genre =
+ QString::fromUtf8(
+ xine_get_meta_info(tmpstream, XINE_META_INFO_GENRE));
+ b.year =
+ QString::fromUtf8(
+ xine_get_meta_info(tmpstream, XINE_META_INFO_YEAR));
+ b.tracknr =
+ QString::fromUtf8(
+ xine_get_meta_info(tmpstream, XINE_META_INFO_TRACK_NUMBER));
+ if( b.tracknr.isEmpty() )
+ b.tracknr = url.filename();
+ } else {
+ b.title = QString(i18n("Track %1")).arg(url.filename());
+ b.album = i18n("AudioCD");
+ }
+ }
+
+ if (audioCodec == "CDDA" || audioCodec == "WAV") {
+ result = true;
+ int samplerate = xine_get_stream_info( tmpstream, XINE_STREAM_INFO_AUDIO_SAMPLERATE );
+
+ // xine would provide a XINE_STREAM_INFO_AUDIO_BITRATE, but unfortunately not for CDDA or WAV
+ // so we calculate the bitrate by our own
+ int bitsPerSample = xine_get_stream_info( tmpstream, XINE_STREAM_INFO_AUDIO_BITS );
+ int nbrChannels = xine_get_stream_info( tmpstream, XINE_STREAM_INFO_AUDIO_CHANNELS );
+ int bitrate = (samplerate * bitsPerSample * nbrChannels) / 1000;
+
+ b.bitrate = QString::number(bitrate);
+ b.samplerate = QString::number(samplerate);
+ int pos, time, length = 0;
+ xine_get_pos_length(tmpstream, &pos, &time, &length);
+ b.length = QString::number(length / 1000);
+ }
+ xine_close(tmpstream);
+ }
+ xine_dispose(tmpstream);
+ return result;
+}
+
+bool XineEngine::getAudioCDContents(const QString &device, KURL::List &urls)
+{
+ char **xine_urls = NULL;
+ int num;
+ int i = 0;
+
+ if (!device.isNull()) {
+ debug() << "xine-engine setting CD Device to: " << device << endl;
+ xine_cfg_entry_t config;
+ if (!xine_config_lookup_entry(m_xine, "input.cdda_device", &config)) {
+ emit statusText(i18n("Failed CD device lookup in xine engine"));
+ return false;
+ }
+ config.str_value = (char *)device.latin1();
+ xine_config_update_entry(m_xine, &config);
+ }
+
+ emit statusText(i18n("Getting AudioCD contents..."));
+
+ xine_urls = xine_get_autoplay_mrls(m_xine, "CD", &num);
+
+ if (xine_urls) {
+ while (xine_urls[i]) {
+ urls << KURL(xine_urls[i]);
+ ++i;
+ }
+ }
+ else emit statusText(i18n("Could not read AudioCD"));
+
+ return true;
+}
+
+bool XineEngine::flushBuffer()
+{
+ return false;
+}
+
+bool XineEngine::lastFmProxyRequired()
+{
+ return !( xine_check_version(1,1,9) );
+}
+
+//////////////////////////////////////////////////////////////////////////////
+/// class Fader
+//////////////////////////////////////////////////////////////////////////////
+
+Fader::Fader( XineEngine *engine, uint fadeMs )
+ : QObject( engine )
+ , QThread()
+ , m_engine( engine )
+ , m_xine( engine->m_xine )
+ , m_decrease( engine->m_stream )
+ , m_increase( 0 )
+ , m_port( engine->m_audioPort )
+ , m_post( engine->m_post )
+ , m_fadeLength( fadeMs )
+ , m_paused( false )
+ , m_terminated( false )
+{
+ DEBUG_BLOCK
+
+ if( engine->makeNewStream() )
+ {
+ m_increase = engine->m_stream;
+
+ xine_set_param( m_increase, XINE_PARAM_AUDIO_AMP_LEVEL, 0 );
+ }
+ else {
+ s_fader = 0;
+ deleteLater();
+ }
+}
+
+Fader::~Fader()
+{
+ DEBUG_BLOCK
+
+ wait();
+
+ xine_close( m_decrease );
+ xine_dispose( m_decrease );
+ xine_close_audio_driver( m_xine, m_port );
+ if( m_post ) xine_post_dispose( m_xine, m_post );
+
+ if( !m_engine->m_stopFader )
+ m_engine->setVolume( m_engine->volume() );
+
+ m_engine->m_stopFader = false;
+ s_fader = 0;
+}
+
+void
+Fader::run()
+{
+ DEBUG_BLOCK
+
+ // do a volume change in 100 steps (or every 10ms)
+ uint stepsCount = m_fadeLength < 1000 ? m_fadeLength / 10 : 100;
+ uint stepSizeUs = (int)( 1000.0 * (float)m_fadeLength / (float)stepsCount );
+
+ float mix = 0.0;
+ float elapsedUs = 0.0;
+ while ( mix < 1.0 )
+ {
+ if ( m_terminated )
+ break;
+ // sleep a constant amount of time
+ QThread::usleep( stepSizeUs );
+
+ if ( m_paused )
+ continue;
+
+ elapsedUs += stepSizeUs;
+
+ // get volume (amarok main * equalizer preamp)
+ float vol = Engine::Base::makeVolumeLogarithmic( m_engine->m_volume ) * m_engine->m_preamp;
+
+ // compute the mix factor as the percentage of time spent since fade begun
+ float mix = (elapsedUs / 1000.0) / (float)m_fadeLength;
+ if ( mix > 1.0 )
+ {
+ if ( m_increase )
+ xine_set_param( m_increase, XINE_PARAM_AUDIO_AMP_LEVEL, (uint)vol );
+ break;
+ }
+
+ // change volume of streams (using dj-like cross-fade profile)
+ if ( m_decrease )
+ {
+ //xine_set_param( m_decrease, XINE_PARAM_AUDIO_AMP_LEVEL, (uint)(vol * (1.0 - mix)) ); // linear
+ float v = 4.0 * (1.0 - mix) / 3.0;
+ xine_set_param( m_decrease, XINE_PARAM_AUDIO_AMP_LEVEL, (uint)( v < 1.0 ? vol * v : vol ) );
+ }
+ if ( m_increase )
+ {
+ //xine_set_param( m_increase, XINE_PARAM_AUDIO_AMP_LEVEL, (uint)(vol * mix) ); //linear
+ float v = 4.0 * mix / 3.0;
+ xine_set_param( m_increase, XINE_PARAM_AUDIO_AMP_LEVEL, (uint)( v < 1.0 ? vol * v : vol ) );
+ }
+ }
+
+ //stop using cpu!
+ xine_stop( m_decrease );
+
+ deleteLater();
+}
+
+void
+Fader::pause()
+{
+ m_paused = true;
+}
+
+void
+Fader::resume()
+{
+ m_paused = false;
+}
+
+void
+Fader::finish()
+{
+ DEBUG_BLOCK
+ m_terminated = true;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+/// class OutFader
+//////////////////////////////////////////////////////////////////////////////
+
+OutFader::OutFader( XineEngine *engine, uint fadeLength )
+ : QObject( engine )
+ , QThread()
+ , m_engine( engine )
+ , m_terminated( false )
+ , m_fadeLength( fadeLength )
+{
+ DEBUG_BLOCK
+}
+
+OutFader::~OutFader()
+{
+ DEBUG_BLOCK
+
+ wait();
+
+ s_outfader = 0;
+}
+
+void
+OutFader::run()
+{
+ DEBUG_BLOCK
+
+ m_engine->fadeOut( m_fadeLength, &m_terminated );
+
+ xine_stop( m_engine->m_stream );
+ xine_close( m_engine->m_stream );
+ xine_set_param( m_engine->m_stream, XINE_PARAM_AUDIO_CLOSE_DEVICE, 1);
+
+ deleteLater();
+}
+
+void
+OutFader::finish()
+{
+ DEBUG_BLOCK
+ m_terminated = true;
+}
+
+#include "xine-engine.moc"