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.
899 lines
24 KiB
899 lines
24 KiB
/***************************************************************************
|
|
* Copyright (C) 2005 Paul Cifarelli *
|
|
* *
|
|
* 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. *
|
|
* *
|
|
***************************************************************************/
|
|
#include <tqthread.h>
|
|
#include <sys/param.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
#include <climits>
|
|
#include <cmath>
|
|
#include <stdarg.h>
|
|
#include <cstring>
|
|
|
|
#include <config.h>
|
|
|
|
#include <iostream>
|
|
|
|
#include "debug.h"
|
|
|
|
#include <tdelocale.h>
|
|
#include <tdemessagebox.h>
|
|
|
|
#include <tqapplication.h>
|
|
#include <tqdir.h>
|
|
#include <tqstringlist.h>
|
|
|
|
#include "helix-engine.h"
|
|
#include "helix-configdialog.h"
|
|
#include "config/helixconfig.h"
|
|
#include "helix-errors.h"
|
|
#include "helix-sp.h"
|
|
#include "hxplayercontrol.h"
|
|
#include "amarokconfig.h"
|
|
|
|
AMAROK_EXPORT_PLUGIN( HelixEngine )
|
|
|
|
#define DEBUG_PREFIX "helix-engine"
|
|
|
|
using namespace std;
|
|
|
|
extern "C"
|
|
{
|
|
#include <unistd.h>
|
|
}
|
|
|
|
#define HELIX_ENGINE_TIMER 10 // 10 ms timer
|
|
#define SCOPE_MAX_BEHIND 200 // 200 postmix buffers
|
|
|
|
|
|
#ifndef LLONG_MAX
|
|
#define LLONG_MAX 9223372036854775807LL
|
|
#endif
|
|
|
|
|
|
///returns the configuration we will use
|
|
static inline TQCString configPath() { return TQFile::encodeName( TQDir::homeDirPath() + "/.helix/config" ); }
|
|
|
|
|
|
HelixEngine::HelixEngine()
|
|
: EngineBase(), PlayerControl(),
|
|
m_state(Engine::Empty),
|
|
m_coredir(HELIX_LIBS "/common"),
|
|
m_pluginsdir(HELIX_LIBS "/plugins"),
|
|
m_codecsdir(HELIX_LIBS "/codecs"),
|
|
m_inited(false),
|
|
m_scopeplayerlast(0),
|
|
m_sfps(0.0),
|
|
m_scopedelta(0),
|
|
m_sframes(0),
|
|
m_lframes(0)
|
|
{
|
|
addPluginProperty( "HasConfigure", "true" );
|
|
addPluginProperty( "HasEqualizer", "true" );
|
|
addPluginProperty( "HasCrossfade", "true" );
|
|
addPluginProperty( "HasCDDA", "false");
|
|
|
|
memset(&m_md, 0, sizeof(m_md));
|
|
memset(hscope, 0, 2*sizeof(HelixScope));
|
|
memset(&m_scopetm, 0, sizeof(struct timeval));
|
|
memset(m_pfade, 0, 2*sizeof(FadeTrack));
|
|
}
|
|
|
|
HelixEngine::~HelixEngine()
|
|
{
|
|
m_mimes.clear();
|
|
}
|
|
|
|
int HelixEngine::print2stdout(const char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
char buf[1024];
|
|
|
|
va_start(args, fmt);
|
|
|
|
int ret = vsprintf(buf, fmt, args);
|
|
debug() << buf;
|
|
|
|
va_end(args);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
int HelixEngine::print2stderr(const char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
char buf[1024];
|
|
|
|
va_start(args, fmt);
|
|
|
|
int ret = vsprintf(buf, fmt, args);
|
|
debug() << buf;
|
|
|
|
va_end(args);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
void HelixEngine::notifyUser(unsigned long code, const char *moreinfo, const char *moreinfourl)
|
|
{
|
|
TQString *err = HelixErrors::errorText(code);
|
|
if (err)
|
|
emit statusText(i18n("Helix Core returned error: %1 %2 %3").arg(TQString(*err)).arg(TQString(moreinfo)).arg(TQString(moreinfourl)));
|
|
else
|
|
emit statusText(i18n("Helix Core returned error: <unknown>"));
|
|
}
|
|
|
|
void HelixEngine::interruptUser(unsigned long code, const char *moreinfo, const char *moreinfourl)
|
|
{
|
|
TQString *err = HelixErrors::errorText(code);
|
|
if (err)
|
|
emit infoMessage(i18n("Helix Core returned error: %1 %1 %1").arg(TQString(*err)).arg(TQString(moreinfo)).arg(TQString(moreinfourl)));
|
|
else
|
|
emit infoMessage(i18n("Helix Core returned error: <unknown>"));
|
|
|
|
// since this is a serious error, emit trackEnded so amarok knows to move on
|
|
play_finished( m_current );
|
|
}
|
|
|
|
|
|
void HelixEngine::onContacting(const char *host)
|
|
{
|
|
emit statusText( i18n("Contacting: %1").arg( TQString(host) ) );
|
|
}
|
|
|
|
void HelixEngine::onBuffering(int pcnt)
|
|
{
|
|
if (pcnt != 100) // let's not report that...
|
|
emit statusText( i18n( "Buffering %1%" ).arg( pcnt ) );
|
|
}
|
|
|
|
|
|
Amarok::PluginConfig*
|
|
HelixEngine::configure() const
|
|
{
|
|
debug() << "Starting HelixConfigDialog\n";
|
|
return new HelixConfigDialog( (HelixEngine *)this );
|
|
}
|
|
|
|
int HelixEngine::fallbackToOSS()
|
|
{
|
|
KMessageBox::information( 0, i18n("The helix library you have configured does not support ALSA, the helix-engine has fallen back to OSS") );
|
|
debug() << "Falling back to OSS\n";
|
|
return (HelixConfigDialog::setSoundSystem( (int) HelixSimplePlayer::OSS ));
|
|
}
|
|
|
|
bool
|
|
HelixEngine::init()
|
|
{
|
|
debug() << "Initializing HelixEngine\n";
|
|
struct stat s;
|
|
bool exists = false;
|
|
stop();
|
|
m_state = Engine::Empty;
|
|
|
|
m_numPlayers = 2;
|
|
m_current = 1;
|
|
|
|
m_coredir = HelixConfig::coreDirectory();
|
|
if (m_coredir.isEmpty())
|
|
m_coredir = HELIX_LIBS "/common";
|
|
|
|
m_pluginsdir = HelixConfig::pluginDirectory();
|
|
if (m_pluginsdir.isEmpty())
|
|
m_pluginsdir = HELIX_LIBS "/plugins";
|
|
|
|
m_codecsdir = HelixConfig::codecsDirectory();
|
|
if (m_codecsdir.isEmpty())
|
|
m_codecsdir = HELIX_LIBS "/codecs";
|
|
|
|
if (HelixConfig::outputplugin() == "oss")
|
|
setOutputSink( HelixSimplePlayer::OSS );
|
|
else
|
|
{
|
|
setOutputSink( HelixSimplePlayer::ALSA );
|
|
if (HelixConfig::deviceenabled())
|
|
setDevice( HelixConfig::device().utf8() );
|
|
else
|
|
setDevice("default");
|
|
}
|
|
|
|
|
|
if (!stat(m_coredir.utf8(), &s) && !stat(m_pluginsdir.utf8(), &s) && !stat(m_codecsdir.utf8(), &s))
|
|
{
|
|
long vol=0;
|
|
bool eqenabled=false;
|
|
int savedpreamp=0;
|
|
TQValueList<int> savedequalizerGains;
|
|
|
|
if (m_inited)
|
|
{
|
|
vol = PlayerControl::getVolume();
|
|
eqenabled = PlayerControl::isEQenabled();
|
|
for (unsigned int i=0; i < m_equalizerGains.size(); i++)
|
|
savedequalizerGains.append(m_equalizerGains[i]);
|
|
savedpreamp = m_preamp;
|
|
PlayerControl::tearDown();
|
|
}
|
|
|
|
PlayerControl::init(m_coredir.utf8(), m_pluginsdir.utf8(), m_codecsdir.utf8(), 2);
|
|
if (PlayerControl::initDirectSS())
|
|
{
|
|
fallbackToOSS();
|
|
|
|
PlayerControl::initDirectSS();
|
|
}
|
|
|
|
if (m_inited)
|
|
{
|
|
PlayerControl::setVolume(vol);
|
|
setEqualizerEnabled(eqenabled);
|
|
setEqualizerParameters(savedpreamp, savedequalizerGains);
|
|
}
|
|
|
|
m_inited = exists = true;
|
|
}
|
|
|
|
|
|
if (!exists || PlayerControl::getError())
|
|
{
|
|
KMessageBox::error( 0, i18n("The Helix Engine requires the RealPlayer(tm) or HelixPlayer libraries to be installed. Please make sure one is installed, and adjust the paths in \"Amarok Settings\" -> \"Engine\"") );
|
|
// we need to return true here so that the user has an oppportunity to change the directory
|
|
//return false;
|
|
return true;
|
|
}
|
|
|
|
// create a list of mime types and ext for use in canDecode()
|
|
m_mimes.resize( getMimeListLen() );
|
|
int i = 0;
|
|
const MimeList *ml = getMimeList();
|
|
MimeEntry *entry;
|
|
while (ml)
|
|
{
|
|
TQString mt = ml->mimetypes;
|
|
TQString me = ml->mimeexts;
|
|
|
|
entry = new MimeEntry;
|
|
entry->type = TQStringList::split('|', mt);
|
|
entry->ext = TQStringList::split('|', me);
|
|
m_mimes[i] = *entry;
|
|
|
|
debug() << ml->mimetypes << endl;
|
|
|
|
i++;
|
|
ml = ml->fwd;
|
|
}
|
|
|
|
debug() << "Succussful init\n";
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool
|
|
HelixEngine::load( const KURL &url, bool isStream )
|
|
{
|
|
debug() << "In load " << url.url() << endl;
|
|
|
|
if (!m_inited)
|
|
return false;
|
|
|
|
if (!canDecode(url))
|
|
{
|
|
const TQString path = url.path();
|
|
const TQString ext = path.mid( path.findRev( '.' ) + 1 ).lower();
|
|
emit statusText( i18n("No plugin found for the %1 format").arg(ext) );
|
|
return false;
|
|
}
|
|
|
|
debug() << "xfadeLength is " << m_xfadeLength << endl;
|
|
if( m_xfadeLength > 0 && m_state == Engine::Playing && !isStream &&
|
|
( 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)
|
|
{
|
|
//set m_xfadeNextTrack true here regardless to play() will work correctly; disable in there
|
|
m_xfadeNextTrack = true;
|
|
int nextPlayer = m_current ? 0 : 1;
|
|
|
|
// prepare the next player
|
|
PlayerControl::stop(nextPlayer);
|
|
resetScope(nextPlayer);
|
|
memset(&hscope[nextPlayer], 0, sizeof(HelixScope));
|
|
memset(&m_pfade[nextPlayer], 0, sizeof(FadeTrack));
|
|
|
|
if (isPlaying(m_current))
|
|
{
|
|
m_pfade[m_current].m_fadeactive = true;
|
|
m_pfade[m_current].m_startfadetime = PlayerControl::where(m_current);
|
|
setFadeout(true, m_xfadeLength, m_current);
|
|
}
|
|
Engine::Base::load( url, false ); // we don't crossfade streams ?? do we load the base here ??
|
|
PlayerControl::setURL( TQFile::encodeName( url.url() ), nextPlayer, !isStream );
|
|
m_isStream = false;
|
|
}
|
|
else
|
|
cleanup();
|
|
|
|
m_isStream = isStream;
|
|
int nextPlayer;
|
|
|
|
nextPlayer = m_current ? 0 : 1;
|
|
|
|
Engine::Base::load( url, isStream || url.protocol() == "http" );
|
|
m_state = Engine::Idle;
|
|
emit stateChanged( Engine::Idle );
|
|
m_url = url;
|
|
|
|
if (url.isLocalFile())
|
|
PlayerControl::setURL( TQFile::encodeName( url.url() ), nextPlayer, !m_isStream );
|
|
else
|
|
{
|
|
m_isStream = true;
|
|
PlayerControl::setURL( TQFile::encodeName( url.url() ), nextPlayer, !m_isStream );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
HelixEngine::play( uint offset )
|
|
{
|
|
debug() << "In play" << endl;
|
|
int nextPlayer;
|
|
|
|
if (!m_inited)
|
|
return false;
|
|
|
|
if (m_state != Engine::Playing)
|
|
{
|
|
struct timezone tz;
|
|
memset(&tz, 0, sizeof(struct timezone));
|
|
gettimeofday(&m_scopetm, &tz);
|
|
startTimer(HELIX_ENGINE_TIMER);
|
|
}
|
|
|
|
nextPlayer = m_current ? 0 : 1;
|
|
|
|
if (m_xfadeLength && m_xfadeNextTrack && !offset && isPlaying(m_current))
|
|
{
|
|
m_xfadeNextTrack = false;
|
|
PlayerControl::start(nextPlayer, true, m_xfadeLength);
|
|
}
|
|
else
|
|
PlayerControl::start(nextPlayer);
|
|
|
|
if (offset)
|
|
PlayerControl::seek( offset, nextPlayer );
|
|
|
|
if (!PlayerControl::getError())
|
|
{
|
|
if (m_state != Engine::Playing)
|
|
{
|
|
m_state = Engine::Playing;
|
|
emit stateChanged( Engine::Playing );
|
|
}
|
|
|
|
m_current = nextPlayer;
|
|
return true;
|
|
}
|
|
|
|
cleanup();
|
|
m_state = Engine::Empty;
|
|
emit stateChanged( Engine::Empty );
|
|
|
|
return false;
|
|
}
|
|
|
|
void
|
|
HelixEngine::cleanup()
|
|
{
|
|
if (!m_inited)
|
|
return;
|
|
|
|
m_url = KURL();
|
|
PlayerControl::stop(); // stop all players
|
|
resetScope(0);
|
|
resetScope(1);
|
|
killTimers();
|
|
m_isStream = false;
|
|
memset(&m_md, 0, sizeof(m_md));
|
|
memset(hscope, 0, 2*sizeof(HelixScope));
|
|
memset(m_pfade, 0, 2*sizeof(FadeTrack));
|
|
}
|
|
|
|
void
|
|
HelixEngine::stop()
|
|
{
|
|
if (!m_inited)
|
|
return;
|
|
|
|
debug() << "In stop where=" << where(m_current) << " duration=" << duration(m_current) << endl;
|
|
|
|
if( AmarokConfig::fadeout() && !m_pfade[m_current].m_fadeactive && state() == Engine::Playing )
|
|
{
|
|
debug() << "fading out...\n";
|
|
m_state = Engine::Empty;
|
|
emit stateChanged( Engine::Empty ); // tell the controller not to bother you anymore
|
|
|
|
m_pfade[m_current].m_fadeactive = true;
|
|
m_pfade[m_current].m_stopfade = true;
|
|
m_pfade[m_current].m_startfadetime = PlayerControl::where(m_current);
|
|
setFadeout(true, AmarokConfig::fadeoutLength(), m_current);
|
|
}
|
|
else
|
|
{
|
|
debug() << "Stopping immediately\n";
|
|
cleanup();
|
|
cleanUpStream(m_current);
|
|
m_state = Engine::Empty;
|
|
emit stateChanged( m_state );
|
|
}
|
|
}
|
|
|
|
|
|
void HelixEngine::play_finished(int playerIndex)
|
|
{
|
|
debug() << "Ok, finished playing the track\n";
|
|
cleanUpStream(playerIndex);
|
|
resetScope(playerIndex);
|
|
memset(&hscope[playerIndex], 0, sizeof(HelixScope));
|
|
memset(&m_pfade[playerIndex], 0, sizeof(FadeTrack));
|
|
if (playerIndex == m_current && !m_pfade[playerIndex].m_stopfade && !m_pfade[playerIndex].m_fadeactive)
|
|
{
|
|
m_state = Engine::Idle;
|
|
emit stateChanged( m_state );
|
|
emit trackEnded();
|
|
}
|
|
}
|
|
|
|
void
|
|
HelixEngine::pause()
|
|
{
|
|
if (!m_inited)
|
|
return;
|
|
|
|
// TODO: PAUSE in XFADE
|
|
debug() << "In pause\n";
|
|
if( m_state == Engine::Playing )
|
|
{
|
|
PlayerControl::pause(m_current);
|
|
m_state = Engine::Paused;
|
|
emit stateChanged( Engine::Paused );
|
|
}
|
|
}
|
|
|
|
void
|
|
HelixEngine::unpause()
|
|
{
|
|
if (!m_inited)
|
|
return;
|
|
|
|
// TODO: PAUSE in XFADE
|
|
debug() << "In unpause\n";
|
|
if ( m_state == Engine::Paused )
|
|
{
|
|
PlayerControl::resume(m_current);
|
|
m_state = Engine::Playing;
|
|
emit stateChanged( Engine::Playing );
|
|
}
|
|
}
|
|
|
|
Engine::State
|
|
HelixEngine::state() const
|
|
{
|
|
//debug() << "In state, state is " << m_state << endl;
|
|
|
|
if (!m_inited || m_url.isEmpty())
|
|
return (Engine::Empty);
|
|
|
|
return m_state;
|
|
}
|
|
|
|
uint
|
|
HelixEngine::position() const
|
|
{
|
|
if (!m_inited)
|
|
return 0;
|
|
|
|
return PlayerControl::where(m_current);
|
|
}
|
|
|
|
uint
|
|
HelixEngine::length() const
|
|
{
|
|
if (!m_inited)
|
|
return 0;
|
|
|
|
return PlayerControl::duration(m_current);
|
|
}
|
|
|
|
void
|
|
HelixEngine::seek( uint ms )
|
|
{
|
|
if (!m_inited)
|
|
return;
|
|
|
|
debug() << "In seek\n";
|
|
resetScope(0);
|
|
resetScope(1);
|
|
PlayerControl::seek(ms, m_current);
|
|
}
|
|
|
|
void
|
|
HelixEngine::setVolumeSW( uint vol )
|
|
{
|
|
if (!m_inited)
|
|
return;
|
|
|
|
debug() << "In setVolumeSW\n";
|
|
PlayerControl::setVolume(vol); // set the volume in all players!
|
|
}
|
|
|
|
|
|
bool
|
|
HelixEngine::canDecode( const KURL &url ) const
|
|
{
|
|
if (!m_inited)
|
|
return false;
|
|
|
|
debug() << "In canDecode " << url.prettyURL() << endl;
|
|
|
|
if (url.protocol() == "http" || url.protocol() == "rtsp")
|
|
return true;
|
|
|
|
const TQString path = url.path();
|
|
const TQString ext = path.mid( path.findRev( '.' ) + 1 ).lower();
|
|
|
|
if (ext != "txt")
|
|
for (int i=0; i<(int)m_mimes.size(); i++)
|
|
{
|
|
if (m_mimes[i].type.grep("audio").count() ||
|
|
m_mimes[i].type.grep("video").count() ||
|
|
m_mimes[i].type.grep("application").count())
|
|
if (m_mimes[i].ext.grep(ext).count())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void
|
|
HelixEngine::timerEvent( TQTimerEvent * )
|
|
{
|
|
PlayerControl::dispatch(); // dispatch the players
|
|
if ( m_xfadeLength <= 0 && m_state == Engine::Playing && PlayerControl::done(m_current) )
|
|
play_finished(m_current);
|
|
else if ( m_xfadeLength > 0 || AmarokConfig::fadeout() )
|
|
{
|
|
if ( m_state == Engine::Playing && isPlaying(m_current?0:1) && PlayerControl::done(m_current?0:1) )
|
|
hscope[m_current?0:1].m_lasttime = 0;
|
|
|
|
// fade on stop finished
|
|
if ( m_pfade[m_current].m_stopfade && m_pfade[m_current].m_fadeactive &&
|
|
(PlayerControl::where(m_current) > m_pfade[m_current].m_startfadetime + (unsigned)AmarokConfig::fadeoutLength() ||
|
|
PlayerControl::done(m_current)) )
|
|
{
|
|
debug() << "Stop fade end\n";
|
|
stop();
|
|
}
|
|
|
|
// crossfade finished
|
|
if ( m_pfade[m_current?0:1].m_fadeactive &&
|
|
PlayerControl::where(m_current?0:1) > m_pfade[m_current?0:1].m_startfadetime + (unsigned)m_xfadeLength)
|
|
play_finished(m_current?0:1);
|
|
}
|
|
|
|
// prune the scope(s)
|
|
prune();
|
|
|
|
struct timeval tm;
|
|
struct timezone tz;
|
|
memset(&tz, 0, sizeof(struct timezone));
|
|
gettimeofday(&tm, &tz);
|
|
m_scopedelta = (tm.tv_sec - m_scopetm.tv_sec) * 1000 + (tm.tv_usec - m_scopetm.tv_usec) / 1000; // ms
|
|
m_scopetm.tv_sec = tm.tv_sec;
|
|
m_scopetm.tv_usec = tm.tv_usec;
|
|
hscope[m_current].m_lasttime += m_scopedelta;
|
|
|
|
HelixSimplePlayer::metaData *md = getMetaData(m_current);
|
|
if (m_isStream &&
|
|
(strcmp(m_md.title, md->title) || strcmp(m_md.artist, md->artist)))
|
|
{
|
|
memcpy(&m_md, md, sizeof(m_md));
|
|
|
|
debug() << "{Title}: " << md->title << " {Artist}: " << md->artist << " {Bitrate}: " << md->bitrate << endl;
|
|
|
|
/* Real Radio One (and Rhapsody?) streams have their own format, where title is:
|
|
* clipinfo:title=<title>|artist name=<artist>|Album name=<album>|Artist:Next artist=<next artist>| \
|
|
* ordinal=<some number>|duration=<in secs>|Track:Rhapsody Track Id=<some number>
|
|
*
|
|
* for all other streams helix sends the title of the song in the artist string.
|
|
* this prevents context lookup, so we split it here (the artist and title are separated by a '-'
|
|
* we'll put the 'title' in album instead...
|
|
*/
|
|
Engine::SimpleMetaBundle bndl;
|
|
bndl.album = TQString::fromUtf8( m_md.title );
|
|
if ( bndl.album.startsWith( TQString("clipinfo:") ) )
|
|
{
|
|
bndl.album = bndl.album.remove(0, 9);
|
|
TQStringList sl = TQStringList::split('|', bndl.album);
|
|
for ( TQStringList::Iterator it = sl.begin(); it != sl.end(); ++it )
|
|
{
|
|
if ((*it).startsWith("title="))
|
|
bndl.title = (*it).section('=', 1, 1);
|
|
if ((*it).startsWith("artist name="))
|
|
bndl.artist = (*it).section('=', 1, 1);
|
|
if ((*it).startsWith("Album name="))
|
|
bndl.album = (*it).section('=', 1, 1);
|
|
if ((*it).startsWith("duration="))
|
|
bndl.length = (*it).section('=', 1, 1);
|
|
}
|
|
|
|
//debug() << "Title: " << bndl.title << endl;
|
|
//debug() << "Artist: " << bndl.artist << endl;
|
|
//debug() << "Album: " << bndl.album << endl;
|
|
//debug() << "length: " << bndl.length << endl;
|
|
}
|
|
else
|
|
{
|
|
char c,*tmp = strchr(m_md.artist, '-');
|
|
if (tmp)
|
|
{
|
|
tmp--;
|
|
c = *tmp;
|
|
*tmp = '\0';
|
|
bndl.artist = TQString::fromUtf8( m_md.artist );
|
|
*tmp = c;
|
|
tmp+=3;
|
|
bndl.title = TQString::fromUtf8( tmp );
|
|
bndl.album = TQString::fromUtf8( m_md.title );
|
|
}
|
|
else // just copy them as is...
|
|
{
|
|
bndl.title = TQString::fromUtf8( m_md.title );
|
|
bndl.artist = TQString::fromUtf8( m_md.artist );
|
|
}
|
|
}
|
|
bndl.bitrate = TQString::number( m_md.bitrate / 1000 );
|
|
emit EngineBase::metaData( bndl );
|
|
}
|
|
}
|
|
|
|
|
|
int HelixEngine::prune()
|
|
{
|
|
int err = 0;
|
|
|
|
err |= prune(0);
|
|
err |= prune(1);
|
|
|
|
return err;
|
|
}
|
|
|
|
int HelixEngine::prune(int playerIndex)
|
|
{
|
|
//
|
|
// this bit is to help us keep more accurate time than helix provides
|
|
/////////////////////////////////////////////////////////////////////
|
|
unsigned long hpos = PlayerControl::where(playerIndex);
|
|
|
|
if (hpos != hscope[playerIndex].m_lastpos
|
|
&& hpos - hscope[playerIndex].m_lastpos < hscope[playerIndex].m_lasttime - hscope[playerIndex].m_lastpos)
|
|
hscope[playerIndex].m_lasttime = hpos;
|
|
|
|
if (hpos > hscope[playerIndex].m_lasttime)
|
|
{
|
|
hscope[playerIndex].m_w = hpos;
|
|
hscope[playerIndex].m_lasttime = hpos;
|
|
}
|
|
else
|
|
hscope[playerIndex].m_w = hscope[playerIndex].m_lasttime;
|
|
|
|
hscope[playerIndex].m_lastpos = hpos;
|
|
|
|
if ( getScopeCount(playerIndex) > SCOPE_MAX_BEHIND ) // protect against naughty streams
|
|
{
|
|
resetScope(playerIndex);
|
|
return 0;
|
|
}
|
|
|
|
if (!hscope[playerIndex].m_w || !hscope[playerIndex].m_item)
|
|
return 0;
|
|
|
|
// prune, unless the player is still starting
|
|
while (hpos && hscope[playerIndex].m_item && hscope[playerIndex].m_w > hscope[playerIndex].m_item->etime)
|
|
{
|
|
//debug() << "pruning " << hpos << "," << hscope[playerIndex].m_w << "," << hscope[playerIndex].m_lasttime
|
|
// << "," << hscope[playerIndex].m_item->time << ":" << hscope[playerIndex].m_item->etime << endl;
|
|
|
|
if (hscope[playerIndex].m_item && hscope[playerIndex].m_item->allocd)
|
|
delete hscope[playerIndex].m_item;
|
|
hscope[playerIndex].m_item = getScopeBuf(playerIndex);
|
|
}
|
|
|
|
if (!hscope[playerIndex].m_item)
|
|
return 0;
|
|
|
|
if (hscope[playerIndex].m_w < hscope[playerIndex].m_item->time) // wait for the player to catchup
|
|
{
|
|
//debug() << "waiting for player to catchup " << hpos << "," << hscope[playerIndex].m_w << "," << hscope[playerIndex].m_lasttime
|
|
// << "," << hscope[playerIndex].m_item->time << ":" << hscope[playerIndex].m_item->etime << endl;
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
const Engine::Scope &HelixEngine::scope()
|
|
{
|
|
if (isPlaying(0) && isPlaying(1)) // crossfading
|
|
{
|
|
if (m_scopeplayerlast)
|
|
scope(m_current);
|
|
else
|
|
scope(m_current?0:1);
|
|
|
|
m_scopeplayerlast = !m_scopeplayerlast;
|
|
}
|
|
else
|
|
scope(m_current);
|
|
|
|
return m_scope;
|
|
}
|
|
|
|
int HelixEngine::scope(int playerIndex)
|
|
{
|
|
int i;
|
|
unsigned long t;
|
|
|
|
if (!m_inited)
|
|
return 0;
|
|
|
|
if (!hscope[playerIndex].m_item && !peekScopeTime(t, playerIndex))
|
|
hscope[playerIndex].m_item = getScopeBuf(playerIndex);
|
|
|
|
if (!prune(playerIndex))
|
|
return 0;
|
|
|
|
if (hscope[playerIndex].m_item->nchan > 2)
|
|
return 0;
|
|
|
|
int j,k=0;
|
|
short int *pint;
|
|
unsigned char b[4];
|
|
|
|
// calculate the starting offset into the buffer
|
|
int off = (hscope[playerIndex].m_item->spb * (hscope[playerIndex].m_w - hscope[playerIndex].m_item->time) /
|
|
(hscope[playerIndex].m_item->etime - hscope[playerIndex].m_item->time)) *
|
|
hscope[playerIndex].m_item->nchan * hscope[playerIndex].m_item->bps;
|
|
k = off;
|
|
while (hscope[playerIndex].m_item && hscope[playerIndex].m_scopeindex < SCOPESIZE)
|
|
{
|
|
while (k < (int) hscope[playerIndex].m_item->len)
|
|
{
|
|
for (j=0; j<hscope[playerIndex].m_item->nchan; j++)
|
|
{
|
|
switch (hscope[playerIndex].m_item->bps)
|
|
{
|
|
case 1:
|
|
b[1] = 0;
|
|
b[0] = hscope[playerIndex].m_item->buf[k];
|
|
break;
|
|
case 2:
|
|
b[1] = hscope[playerIndex].m_item->buf[k+1];
|
|
b[0] = hscope[playerIndex].m_item->buf[k];
|
|
break;
|
|
}
|
|
|
|
pint = (short *) &b[0];
|
|
|
|
if (hscope[playerIndex].m_item->nchan == 1) // duplicate mono samples
|
|
{
|
|
hscope[playerIndex].m_currentScope[hscope[playerIndex].m_scopeindex] = *pint;
|
|
hscope[playerIndex].m_scopeindex++;
|
|
hscope[playerIndex].m_currentScope[hscope[playerIndex].m_scopeindex] = *pint;
|
|
hscope[playerIndex].m_scopeindex++;
|
|
}
|
|
else
|
|
{
|
|
hscope[playerIndex].m_currentScope[hscope[playerIndex].m_scopeindex] = *pint;
|
|
hscope[playerIndex].m_scopeindex++;
|
|
}
|
|
|
|
k += hscope[playerIndex].m_item->bps;
|
|
}
|
|
|
|
if (hscope[playerIndex].m_scopeindex >= SCOPESIZE)
|
|
{
|
|
hscope[playerIndex].m_scopeindex = SCOPESIZE;
|
|
break;
|
|
}
|
|
}
|
|
// as long as we know there's another buffer...otherwise we need to wait for another
|
|
if (hscope[playerIndex].m_scopeindex < SCOPESIZE)
|
|
{
|
|
if (hscope[playerIndex].m_item && hscope[playerIndex].m_item->allocd)
|
|
delete hscope[playerIndex].m_item;
|
|
hscope[playerIndex].m_item = getScopeBuf(playerIndex);
|
|
|
|
k = 0;
|
|
|
|
if (!hscope[playerIndex].m_item)
|
|
return 0; // wait until there are some more buffers available
|
|
}
|
|
else
|
|
{
|
|
if (k >= (int) hscope[playerIndex].m_item->len)
|
|
{
|
|
if (hscope[playerIndex].m_item && hscope[playerIndex].m_item->allocd)
|
|
delete hscope[playerIndex].m_item;
|
|
hscope[playerIndex].m_item = getScopeBuf(playerIndex);
|
|
}
|
|
break; // done with the scope buffer, so hand it off
|
|
}
|
|
}
|
|
|
|
// ok, we must have a full buffer here, give it to the scope
|
|
for (i=0; i<SCOPESIZE; i++)
|
|
m_scope[i] = hscope[playerIndex].m_currentScope[i];
|
|
hscope[playerIndex].m_scopeindex = 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
void
|
|
HelixEngine::resetScope(int playerIndex)
|
|
{
|
|
if (playerIndex >=0 && playerIndex < numPlayers())
|
|
{
|
|
// make sure the scope is clear of old buffers
|
|
clearScopeQ(playerIndex);
|
|
hscope[playerIndex].m_scopeindex = 0;
|
|
if (hscope[playerIndex].m_item && hscope[playerIndex].m_item->allocd)
|
|
delete hscope[playerIndex].m_item;
|
|
hscope[playerIndex].m_w = 0;
|
|
hscope[playerIndex].m_item = 0;
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
HelixEngine::setEqualizerEnabled( bool enabled ) //SLOT
|
|
{
|
|
enableEQ(enabled);
|
|
}
|
|
|
|
|
|
// ok, this is lifted from gst... but why mess with what works?
|
|
void
|
|
HelixEngine::setEqualizerParameters( int preamp, const TQValueList<int>& bandGains ) //SLOT
|
|
{
|
|
m_preamp = ( preamp + 100 ) / 2;
|
|
|
|
m_equalizerGains.resize( bandGains.count() );
|
|
for ( uint i = 0; i < bandGains.count(); i++ )
|
|
m_equalizerGains[i] = ( *bandGains.at( i ) + 100 ) / 2;
|
|
|
|
updateEQgains();
|
|
}
|
|
|
|
|
|
namespace Debug
|
|
{
|
|
#undef helix_indent
|
|
TQCString helix_indent;
|
|
}
|
|
|
|
#include "helix-engine.moc"
|