/*************************************************************************** * Copyright (C) 2005 Christophe Thommeret * * (C) 2005 Ian Monroe * * (C) 2005,6 Mark Kretschmann * * (C) 2004,5 Max Howell * * (C) 2003,4 J. Kofler * * * * 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 #include #include #include "debug.h" #include #include #include #include #include extern "C" { #include #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 //define this to enable libxine debug spew //#define XINE_DEBUG_ENGINE 1 ///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 TDEInstance, so using this hacked up method. //static inline TQCString configPath() { return TQFile::encodeName(TDEStandardDirs().localtdedir() + TDEStandardDirs::kde_default("data") + "amarok/xine-config"); } static inline TQCString configPath() { return TQFile::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; } #ifdef XINE_DEBUG_ENGINE xine_set_param(m_stream, XINE_PARAM_VERBOSITY, XINE_VERBOSITY_DEBUG); #endif // XINE_DEBUG_ENGINE 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 stubbornly 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, TQFile::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("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( TQThread::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 TQString 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."); TQString 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( "" + i18n("Error Loading Media") + "

" + body + "

" + 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( 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; } bool seekable = (xine_get_stream_info( m_stream, XINE_STREAM_INFO_SEEKABLE ) != 0); if (!seekable) { return; } bool use_xine_ui_seek_method = false; TQString audioCodec = TQString::fromUtf8(xine_get_meta_info(m_stream, XINE_META_INFO_SYSTEMLAYER)); if (audioCodec == "FLAC") { // Work around Xine bug with FLAC files // See Bug 1204 use_xine_ui_seek_method = true; } 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 if (use_xine_ui_seek_method) { int pos, time, length = 0; xine_get_pos_length( m_stream, &pos, &time, &length ); xine_play( m_stream, ((ms*65535.0)/length), 0 ); } else { xine_play( m_stream, 0, (int)ms ); } xine_set_param( m_stream, XINE_PARAM_SPEED, XINE_SPEED_PAUSE ); } else { if (use_xine_ui_seek_method) { int pos, time, length = 0; xine_get_pos_length( m_stream, &pos, &time, &length ); xine_play( m_stream, ((ms*65535.0)/length), 0 ); } 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( 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 ? TQMIN( 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 ); TQTime 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 ) { TQValueList 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 TQValueList &gains ) { if ( !m_stream ) return; m_equalizerGains = gains; m_intPreamp = preamp; TQValueList::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 TQStringList list; if(list.isEmpty()) { char* exts = xine_get_file_extensions( m_xine ); list = TQStringList::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; TQString 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 TQString 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( TQTimerEvent* ) { 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::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, TQT_SIGNAL( settingsSaved() ), this, TQT_SLOT( configChanged() )); connect(this, TQT_SIGNAL( resetConfig(xine_t*) ), xcf, TQT_SLOT( reset(xine_t*) )); return xcf; } void XineEngine::customEvent( TQCustomEvent *e ) { #define message static_cast(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(p) switch( xineEvent->type ) { case XINE_EVENT_UI_SET_TITLE: debug() << "XINE_EVENT_UI_SET_TITLE\n"; TQApplication::postEvent( xe, new TQCustomEvent( 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 TQApplication::postEvent( xe, new TQCustomEvent(3000) ); break; case XINE_EVENT_PROGRESS: { xine_progress_data_t* pd = (xine_progress_data_t*)xineEvent->data; TQString msg = "%1 %2%"; msg = msg.arg( TQString::fromUtf8( pd->description ) ) .arg( TDEGlobal::locale()->formatNumber( pd->percent, 0 ) ); TQCustomEvent *e = new TQCustomEvent( 3002 ); e->setData( new TQString( msg ) ); TQApplication::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 TQString message = TQString::fromUtf8( static_cast(xineEvent->data)->mrl ); TQCustomEvent *e = new TQCustomEvent( 3004 ); e->setData( new TQString( message ) ); TQApplication::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; TQString 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: %1"); goto param; case XINE_MSG_UNKNOWN_DEVICE: message = i18n("The device name you specified seems invalid.
%1"); goto param; case XINE_MSG_NETWORK_UNREACHABLE: message = i18n("The network appears unreachable.
%1"); goto param; case XINE_MSG_AUDIO_OUT_UNAVAILABLE: message = i18n("Audio output unavailable; the device is busy.
%1"); goto param; case XINE_MSG_CONNECTION_REFUSED: message = i18n("The connection was refused for the URL: %1"); goto param; case XINE_MSG_FILE_NOT_FOUND: message = i18n("xine could not find the URL: %1"); goto param; case XINE_MSG_PERMISSION_ERROR: message = i18n("Access was denied for the URL: %1"); goto param; case XINE_MSG_READ_ERROR: message = i18n("The source cannot be read for the URL: %1"); goto param; case XINE_MSG_LIBRARY_LOAD_ERROR: message = i18n("A problem occurred while loading a library or decoder.
%1"); 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( ¤t ) && data->type == last_error ) { last_error_time = current; return; } last_error_time = current; last_error = data->type; if( data->explanation ) { message.prepend( "" ); message += ":

"; message += TQString::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(¤t) && data->type == last_error) { last_error_time = current; return; } last_error_time = current; last_error = data->type; message.prepend( "

" ); message += "

"; if(data->explanation) { message += "xine parameters: "; message += TQString::fromUtf8( (char*)data + data->parameters ); message += ""; } else message += i18n("Sorry, no additional information is available."); TQApplication::postEvent( xe, new TQCustomEvent(TQEvent::Type(3001), new TQString(message)) ); } } //case case XINE_EVENT_UI_CHANNELS_CHANGED: //Flameeyes used this for last.fm track changes TQApplication::postEvent( xe, new TQCustomEvent(TQEvent::Type(3005) ) ); break; } //switch #undef xe } Engine::SimpleMetaBundle XineEngine::fetchMetaData() const { Engine::SimpleMetaBundle bundle; bundle.title = TQString::fromUtf8( xine_get_meta_info( m_stream, XINE_META_INFO_TITLE ) ); bundle.artist = TQString::fromUtf8( xine_get_meta_info( m_stream, XINE_META_INFO_ARTIST ) ); bundle.album = TQString::fromUtf8( xine_get_meta_info( m_stream, XINE_META_INFO_ALBUM ) ); bundle.comment = TQString::fromUtf8( xine_get_meta_info( m_stream, XINE_META_INFO_COMMENT ) ); bundle.genre = TQString::fromUtf8( xine_get_meta_info( m_stream, XINE_META_INFO_GENRE ) ); bundle.bitrate = TQString::number( xine_get_stream_info( m_stream, XINE_STREAM_INFO_AUDIO_BITRATE ) / 1000 ); bundle.samplerate = TQString::number( xine_get_stream_info( m_stream, XINE_STREAM_INFO_AUDIO_SAMPLERATE ) ); bundle.year = TQString::fromUtf8( xine_get_meta_info( m_stream, XINE_META_INFO_YEAR ) ); bundle.tracknr = TQString::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, TQFile::encodeName(url.url()))) { TQString audioCodec = TQString::fromUtf8(xine_get_meta_info(tmpstream, XINE_META_INFO_SYSTEMLAYER)); if (audioCodec == "CDDA") { TQString title = TQString::fromUtf8( xine_get_meta_info(tmpstream, XINE_META_INFO_TITLE)); if ((!title.isNull()) && (!title.isEmpty())) { //no meta info b.title = title; b.artist = TQString::fromUtf8( xine_get_meta_info(tmpstream, XINE_META_INFO_ARTIST)); b.album = TQString::fromUtf8( xine_get_meta_info(tmpstream, XINE_META_INFO_ALBUM)); b.genre = TQString::fromUtf8( xine_get_meta_info(tmpstream, XINE_META_INFO_GENRE)); b.year = TQString::fromUtf8( xine_get_meta_info(tmpstream, XINE_META_INFO_YEAR)); b.tracknr = TQString::fromUtf8( xine_get_meta_info(tmpstream, XINE_META_INFO_TRACK_NUMBER)); if( b.tracknr.isEmpty() ) b.tracknr = url.filename(); } else { b.title = TQString(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 = TQString::number(bitrate); b.samplerate = TQString::number(samplerate); int pos, time, length = 0; xine_get_pos_length(tmpstream, &pos, &time, &length); b.length = TQString::number(length / 1000); } xine_close(tmpstream); } xine_dispose(tmpstream); return result; } bool XineEngine::getAudioCDContents(const TQString &device, KURL::List &urls) { #if XINE_MAJOR_VERSION > 1 || ( XINE_MAJOR_VERSION == 1 && XINE_MINOR_VERSION >= 2 ) const char * const* xine_urls = NULL; #else char **xine_urls = NULL; #endif 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 ) : TQObject( engine ) , TQThread() , 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 TQThread::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 ) : TQObject( engine ) , TQThread() , 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"