Amarok – versatile and easy to use audio player
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.
 
 
 
 
 
 

676 lines
18 KiB

/***************************************************************************
yauap-engine.h - yauap engine plugin
copyright : (C) 2006 by Sascha Sommer <saschasommer@freenet.de>
***************************************************************************/
/***************************************************************************
* *
* 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 <tqapplication.h>
#include <klocale.h>
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#define DBUS_API_SUBJECT_TO_CHANGE
#include <dbus/connection.h>
//#define MANUAL_YAUAP_START
#define YAUAP_DBUS_SERVICE "org.yauap.CommandService"
#define YAUAP_DBUS_PATH "/yauapObject"
#define YAUAP_DBUS_INTERFACE "org.yauap.CommandInterface"
#include "yauap-engine.h"
#include "debug.h"
AMAROK_EXPORT_PLUGIN( yauapEngine )
/* signal handler for DBus signals */
static DBusHandlerResult
signal_handler( DBusConnection * /*con*/, DBusMessage *msg, void *data )
{
yauapEngine* engine = (yauapEngine*)data;
const char *objectpath = dbus_message_get_path(msg);
const char *member = dbus_message_get_member(msg);
const char *interface = dbus_message_get_interface(msg);
bool handled = true;
debug() << "TQT_SIGNAL member " << member << " interface " << interface << " objpath " << objectpath << endl;
if (dbus_message_is_signal( msg, "org.yauap.CommandInterface", "MetadataSignal"))
TQApplication::postEvent(engine, new TQCustomEvent(3004));
else if(dbus_message_is_signal( msg, "org.yauap.CommandInterface", "EosSignal")) {
if (engine->m_state == Engine::Playing)
TQApplication::postEvent(engine, new TQCustomEvent(3000));
}
else if(dbus_message_is_signal( msg, "org.yauap.CommandInterface", "ErrorSignal"))
{
char* text = NULL;
DBusError error;
dbus_error_init(&error);
if(dbus_message_get_args( msg, &error, DBUS_TYPE_STRING, &text, DBUS_TYPE_INVALID)) {
TQCustomEvent* e = new TQCustomEvent(3002);
e->setData(new TQString(text));
TQApplication::postEvent(engine, e);
}
}
else
handled = false;
return (handled ? DBUS_HANDLER_RESULT_HANDLED : DBUS_HANDLER_RESULT_NOT_YET_HANDLED);
}
int
yauapProcess::commSetupDoneC()
{
int r = Amarok::Process::commSetupDoneC();
int fd = open("/dev/null", O_RDWR);
dup2(fd, 1);
dup2(fd, 2);
close(fd);
return r;
}
/* create a qt dbus connection that will receive the signals */
bool
DBusConnection::open()
{
DBusError error;
dbus_error_init( &error );
debug() << " connecting to dbus" << endl;
// close open connection
close();
/* connect to session bus */
dbus_connection = dbus_bus_get_private( DBUS_BUS_SESSION, &error ); /* dbus_bus_get somehow doesn't work here */
if( dbus_error_is_set(&error) )
{
debug() << "unable to connect to DBUS." << endl;
dbus_error_free(&error);
return false;
}
dbus_connection_set_exit_on_disconnect( dbus_connection, false );
/* create qt connection */
qt_connection = new DBusQt::Connection( this );
qt_connection->dbus_connection_setup_with_qt_main( dbus_connection );
if ( !dbus_connection_add_filter(dbus_connection, signal_handler, context, NULL) )
{
debug() << "Failed to add filter function." << endl;
return false;
}
/* Match for DBUS_INTERFACE_DBUS */
dbus_bus_add_match( dbus_connection, "type='signal',interface='org.yauap.CommandInterface'", &error);
if ( dbus_error_is_set( &error ) )
{
debug() << "Error adding match, " << error.name << " " << error.message;
dbus_error_free (&error);
return false;
}
debug() << " connected " << endl;
return true;
}
/* close qt dbus connection */
void
DBusConnection::close()
{
debug() << "close DBusConnection" << endl;
if( dbus_connection )
dbus_connection_close( dbus_connection );
if(qt_connection)
qt_connection->close();
/* DBusConnection::open () calls dbus_bus_get (), we need to unref the connection */
debug() << "calling dbus connection close" << endl;
dbus_connection = NULL;
qt_connection = NULL;
debug() << "DBusConnection closed" << endl;
}
DBusConnection::DBusConnection( yauapEngine* c )
{
qt_connection = NULL;
dbus_connection = NULL;
context = c;
}
DBusConnection::~DBusConnection()
{
close();
}
bool
DBusConnection::send(const char *method, int first_arg_type, ...)
{
dbus_uint32_t serial = 0;
bool ret = false;
TQMutexLocker lock(&m_mutex);
DBusMessage* msg = dbus_message_new_method_call(
YAUAP_DBUS_SERVICE, YAUAP_DBUS_PATH, YAUAP_DBUS_INTERFACE,
method);
if (msg) {
va_list ap;
va_start(ap, first_arg_type);
dbus_message_append_args_valist(msg, first_arg_type, ap);
va_end(ap);
ret = dbus_connection_send(dbus_connection, msg, &serial);
dbus_message_unref (msg);
}
return ret;
}
int
DBusConnection::call(const char *method, int first_arg_type, ...)
{
dbus_uint32_t ret = -1;
va_list ap;
va_start (ap, first_arg_type);
DBusMessage* msg = send_with_reply(method, first_arg_type, ap);
va_end (ap);
if (msg) {
DBusMessageIter args;
if (dbus_message_iter_init(msg, &args) &&
(DBUS_TYPE_INT32 == dbus_message_iter_get_arg_type(&args) ||
DBUS_TYPE_UINT32 == dbus_message_iter_get_arg_type(&args)))
dbus_message_iter_get_basic(&args, &ret);
dbus_message_unref (msg);
}
return ret;
}
DBusMessage*
DBusConnection::send_with_reply(const char* method, int first_arg_type, ...)
{
va_list ap;
va_start(ap, first_arg_type);
DBusMessage* msg = send_with_reply(method, first_arg_type, ap);
va_end(ap);
return msg;
}
DBusMessage*
DBusConnection::send_with_reply(const char* method, int first_arg_type, va_list ap)
{
TQMutexLocker lock(&m_mutex);
DBusMessage* msg = dbus_message_new_method_call(
YAUAP_DBUS_SERVICE, YAUAP_DBUS_PATH, YAUAP_DBUS_INTERFACE, method);
if (msg) {
DBusError error;
dbus_error_init(&error);
dbus_message_append_args_valist(msg, first_arg_type, ap);
DBusMessage* oldmsg = msg;
msg = dbus_connection_send_with_reply_and_block(dbus_connection, oldmsg, -1, &error);
DBusDispatchStatus status;
while ((status = dbus_connection_get_dispatch_status(dbus_connection)) == DBUS_DISPATCH_DATA_REMAINS)
dbus_connection_dispatch (dbus_connection);
dbus_message_unref (oldmsg);
if (!msg)
debug() << "dbus error while waiting for reply: " << error.message << endl;
}
return msg;
}
/* emit state change signal */
void
yauapEngine::change_state( Engine::State state )
{
m_state = state;
emit stateChanged(m_state);
}
/* destroy engine */
yauapEngine::~yauapEngine()
{
/* make sure we really stopped */
stop();
/* quit the player */
if ( !con->send("quit", DBUS_TYPE_INVALID) )
debug() << "quit failed " << endl;
delete con;
}
/* fetch metadata from yauap */
void
yauapEngine::update_metadata()
{
Engine::SimpleMetaBundle* bndl = new Engine::SimpleMetaBundle;
debug() << " emit metadata change " << endl;
DBusMessage* msg = con->send_with_reply("get_metadata", DBUS_TYPE_INVALID);
if (msg) {
DBusMessageIter args;
if (dbus_message_iter_init(msg, &args) && DBUS_TYPE_ARRAY ==
dbus_message_iter_get_arg_type(&args)) {
DBusMessageIter sub;
dbus_message_iter_recurse(&args, &sub);
dbus_message_iter_next(&args);
while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING) {
char* reply_ptr = 0;
dbus_message_iter_get_basic(&sub, &reply_ptr);
dbus_message_iter_next(&sub);
debug() << "reply_ptr: " << reply_ptr << endl;
#define ASSIGN(a,b) if(!strncmp(reply_ptr,b,strlen(b)) && strlen(reply_ptr + strlen(b) + 1)){ \
bndl->a = reply_ptr + strlen(b) + 1; \
continue; \
}
ASSIGN( title, "title" )
ASSIGN( artist, "artist" )
ASSIGN( album, "album" )
ASSIGN( comment, "comment" )
ASSIGN( genre, "genre" )
ASSIGN( samplerate, "samplerate" )
ASSIGN( year, "date" )
ASSIGN( tracknr, "track-number" )
ASSIGN( length, "length" )
ASSIGN( bitrate, "bitrate" )
#undef ASSIGN
}
}
dbus_message_unref(msg);
}
debug() << "title:" << bndl->title << endl;
debug() << "artist:" << bndl->artist << endl;
debug() << "album:" << bndl->album << endl;
debug() << "comment:" << bndl->comment << endl;
debug() << "genre:" << bndl->genre << endl;
debug() << "samplerate:" << bndl->samplerate << endl;
debug() << "year:" << bndl->year << endl;
debug() << "tracknr:" << bndl->tracknr << endl;
debug() << "length:" << bndl->length << endl;
debug() << "bitrate:" << bndl->bitrate << endl;
/* do not overwrite manually generated metadata from audio cds */
if(bndl->title.isEmpty() && loaded_url.protocol() == "cdda")
return;
TQCustomEvent* e = new TQCustomEvent(3003);
e->setData(bndl);
TQApplication::postEvent(this, e);
}
/* fetch current sample buffer from yauap */
const Engine::Scope &
yauapEngine::scope()
{
int len = 0;
dbus_int16_t* data = 0;
DBusMessage* msg = con->send_with_reply("get_scopedata", DBUS_TYPE_INVALID);
if (msg) {
DBusMessageIter args;
if (dbus_message_iter_init(msg, &args) && DBUS_TYPE_ARRAY ==
dbus_message_iter_get_arg_type(&args)) {
DBusMessageIter sub;
dbus_message_iter_recurse(&args, &sub);
dbus_message_iter_next(&args);
dbus_message_iter_get_fixed_array(&sub,&data,&len);
}
dbus_message_unref(msg);
}
/* 2 channel 16 bit samples */
if(len == SCOPESIZE * 2)
{
for( int i=0; i < SCOPESIZE ; i++)
m_scope[i] = data[i];
}else
debug() << "get_scopedata returned the wrong amount of data " << len << endl;
return m_scope;
}
void
yauapEngine::yauapProcessExited()
{
debug() << "yauapProcessExited!!!!!" << endl;
closeDbusConnection();
initDbusConnection();
}
void
yauapEngine::closeDbusConnection()
{
/* destroy TQt DBus connection */
delete con;
con = 0;
/* kill yauap */
#ifndef MANUAL_YAUAP_START
helper.kill();
#endif
}
bool
yauapEngine::initDbusConnection()
{
#ifndef MANUAL_YAUAP_START
/* start yauap in slave mode */
helper.clearArguments();
helper << "yauap" << "-noexit";
if( !helper.start(KProcess::NotifyOnExit, KProcess::All))
{
debug() << "could not start yauap " << endl;
emit statusText( i18n( "could not start yauap" ) );
return false;
}
#endif
/* create and open qt DBus connection so that we are able to receive signals */
con = new DBusConnection( this );
if (!con->open())
{
debug() << "could not connect to dbus" << endl;
emit statusText( i18n( "Error: could not connect to dbus" ) );
return false;
}
/* makes sure the player is stopped: retry the call a few times because it takes some
time until yauap registered its dbus service */
for( int i=0; i < 2; ++i ) {
if( con->send("stop", DBUS_TYPE_INVALID) >= 0 )
return true;
usleep(20 * 1000);
}
return false;
}
#if 0
void
yauapEngine::handleDbusError(const char* method)
{
debug() << method << " failed " << endl;
closeDbusConnection();
initDbusConnection();
}
#endif
void
yauapEngine::customEvent(TQCustomEvent *e)
{
TQString* message = static_cast<TQString*>(e->data());
switch (e->type()) {
case 3000:
m_state = Engine::Idle;
emit trackEnded();
break;
case 3002:
emit statusText(*message);
delete message;
break;
case 3003:
{
Engine::SimpleMetaBundle* bundle = static_cast<Engine::SimpleMetaBundle*>(e->data());
emit metaData(*bundle);
delete bundle;
break;
}
case 3004:
update_metadata();
break;
default:
;
}
}
/* init engine */
bool
yauapEngine::init()
{
debug() << "In init" << endl;
m_state = Engine::Idle;
connect(&helper, TQT_SIGNAL(processExited(KProcess*)), TQT_SLOT(yauapProcessExited()));
if (initDbusConnection())
return true;
emit statusText( i18n( "Error: timed out waiting for yauap" ) );
return false;
}
/* check if the given url can be decoded */
bool
yauapEngine::canDecode( const KURL &kurl ) const
{
TQCString qurl = kurl.url().utf8();
const char* url = qurl.data();
return con->call("can_decode", DBUS_TYPE_STRING, &url, DBUS_TYPE_INVALID) > 0;
}
/* load a new track FIXME: implement cross fading */
bool
yauapEngine::load( const KURL &url, bool isStream )
{
TQString qurl = url.url();
const char* curl = qurl.ascii();
m_isStream = isStream;
Engine::Base::load( url, isStream || url.protocol() == "http" );
change_state(Engine::Idle);
if (!curl || !con->call("load", DBUS_TYPE_STRING, &curl, DBUS_TYPE_INVALID))
return false;
loaded_url = url;
return true;
}
/* set volume */
void
yauapEngine::setVolumeSW( uint volume )
{
dbus_uint32_t dbus_volume = volume;
int ret;
debug() << "In setVolumeSW " << volume << endl;
ret = con->send("set_volume", DBUS_TYPE_UINT32, &dbus_volume, DBUS_TYPE_INVALID);
debug() << "=> " << ret << endl;
}
/* start playback */
bool
yauapEngine::play( uint offset )
{
dbus_uint32_t dbus_offset = offset;
debug() << "In play" << endl;
if (con->send("start", DBUS_TYPE_UINT32, &dbus_offset, DBUS_TYPE_INVALID) > 0)
{
change_state( Engine::Playing );
return true;
}
change_state( Engine::Empty );
return false;
}
/* stop playback */
void
yauapEngine::stop()
{
change_state( Engine::Empty );
if (con->send("stop", DBUS_TYPE_INVALID) <= 0)
{
debug() << "stop failed " << endl;
return;
}
change_state(Engine::Empty);
}
/* pause playback */
void
yauapEngine::pause()
{
debug() << "In pause " << endl;
if (!con->call("pause", DBUS_TYPE_INVALID))
return;
if( state() == Engine::Playing )
change_state( Engine::Paused );
else
change_state( Engine::Playing );
}
/* unpause playback */
void
yauapEngine::unpause()
{
pause();
}
/* get track length in ms */
uint
yauapEngine::length() const
{
debug() << "In length " << endl;
int length = con->call("get_length", DBUS_TYPE_INVALID);
if (length < 0) return 0;
debug() << "length is => " << length << endl;
return (uint) length;
}
/* get current position */
uint
yauapEngine::position() const
{
int position = 0;
position = con->call("get_position", DBUS_TYPE_INVALID);
if (position < 0) position = 0;
return (uint) position;
}
/* seek to offset in ms */
void
yauapEngine::seek( uint offset )
{
dbus_uint32_t dbus_offset = offset;
if (!con->send("seek", DBUS_TYPE_UINT32, &dbus_offset, DBUS_TYPE_INVALID))
debug() << "seek failed " << endl;
}
bool
yauapEngine::getAudioCDContents(const TQString &device, KURL::List &urls)
{
debug() << "Getting AudioCD contents..." << endl;
TQCString cdevice = device.latin1();
const char* cdevice_ptr = cdevice.data();
DBusMessage* msg = con->send_with_reply("get_audio_cd_contents",
DBUS_TYPE_STRING, &cdevice_ptr, DBUS_TYPE_INVALID);
if (msg) {
DBusMessageIter args;
int i = 0;
if (dbus_message_iter_init(msg, &args) && DBUS_TYPE_ARRAY ==
dbus_message_iter_get_arg_type(&args)) {
DBusMessageIter sub;
dbus_message_iter_recurse(&args, &sub);
dbus_message_iter_next(&args);
while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING) {
char* reply_ptr = 0;
dbus_message_iter_get_basic(&sub, &reply_ptr);
dbus_message_iter_next(&sub);
debug() << "reply_ptr: " << reply_ptr << endl;
Engine::SimpleMetaBundle b;
char* saveptr;
KURL url = TQString("cdda://").append( strtok_r(reply_ptr,"=",&saveptr));
urls << url;
debug() << url << endl;
b.title = TQString( i18n( "Track %1" ) ).tqarg( i+1 );
b.length = strtok_r(NULL,"=",&saveptr);
b.album = "AudioCD";
b.tracknr = i+1;
b.samplerate = "44100";
b.bitrate = "1411";
cd_tracks.push_back(b);
++i;
}
}
dbus_message_unref(msg);
}
return true;
}
bool
yauapEngine::metaDataForUrl(const KURL &url, Engine::SimpleMetaBundle &b)
{
if ( url.protocol() == "cdda" )
{
b = cd_tracks[url.host().toUInt()-1];
return true;
}
return false;
}
#include "yauap-engine.moc"