You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1384 lines
41 KiB
1384 lines
41 KiB
/*************************************************************************** |
|
* 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 <tdelocale.h> |
|
#include <tdemessagebox.h> |
|
#include <kstandarddirs.h> |
|
|
|
#include <tqapplication.h> |
|
#include <tqdir.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 |
|
|
|
//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<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( 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( |
|
"<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; |
|
} |
|
|
|
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<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 ? 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<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 TQValueList<int> &gains ) |
|
{ |
|
if ( !m_stream ) |
|
return; |
|
|
|
m_equalizerGains = gains; |
|
m_intPreamp = preamp; |
|
TQValueList<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 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<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, 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<TQString*>(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"; |
|
|
|
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<xine_mrl_reference_data_ext_t*>(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: <i>%1</i>"); goto param; |
|
case XINE_MSG_UNKNOWN_DEVICE: |
|
message = i18n("The device name you specified seems invalid.<br>%1"); goto param; |
|
case XINE_MSG_NETWORK_UNREACHABLE: |
|
message = i18n("The network appears unreachable.<br>%1"); goto param; |
|
case XINE_MSG_AUDIO_OUT_UNAVAILABLE: |
|
message = i18n("Audio output unavailable; the device is busy.<br>%1"); 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.<br>%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( "<b>" ); |
|
message += "</b>:<p>"; |
|
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( "<p>" ); |
|
message += "<p>"; |
|
|
|
if(data->explanation) |
|
{ |
|
message += "xine parameters: <i>"; |
|
message += TQString::fromUtf8( (char*)data + data->parameters ); |
|
message += "</i>"; |
|
} |
|
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 = XineEngine::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"
|
|
|