/*************************************************************************** noatunplayer.cpp - description ------------------- begin : Sun Jan 19 2003 copyright : (C) 2003 by Oliver N�le email : yammi-developer@lists.sourceforge.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. * * * ***************************************************************************/ #include "options.h" #include "noatunplayer.h" #include #include #include #include using namespace std; #include #include #include #include "songentryint.h" NoatunPlayer::NoatunPlayer(YammiModel* model) : MediaPlayer( model ) { // register ourselve client = new DCOPClient(); client->attach(); QCString realAppId = client->registerAs("yammi"); currentPlayer=1; fade=100; ensurePlayerIsRunning(false); } bool NoatunPlayer::finishInitialization() { ensurePlayerIsRunning(true); // find out which one of the players is playing and take that one as current player if(getStatus()==STOPPED) { currentPlayer=0; } // this will be the inactive player currentPlayer=(currentPlayer+1) % 2; sendDcopCommandInt("setVolume(int)", 0); clearActivePlayerPlaylist(); // this will be the active player currentPlayer=(currentPlayer+1) % 2; sendDcopCommandInt("setVolume(int)", 100); connect( &fadeTimer, SIGNAL(timeout()), SLOT(onFade()) ); return true; } NoatunPlayer::~NoatunPlayer() {} //******************************************************** /** * Clear playlist of active player. */ void NoatunPlayer::clearActivePlayerPlaylist() { sendDcopCommand(QString("clear()")); return; } /** * check whether two instances of Noatun are running, if not: tries to start them * returns true on success */ bool NoatunPlayer::ensurePlayerIsRunning(bool block) { int count=0; QString replyStr; for(int tries=0; count<2 && tries<10; tries++) { count=0; QProcess proc; proc.addArgument("dcop"); if ( !proc.start() ) { kdError() << "could not start dcop process\n"; return false; } while(proc.isRunning()) {} if(!proc.normalExit()) { kdError() << "normalExit is false for dcop command (trying to continue...)\n"; } while(replyStr=proc.readLineStdout()) { if(replyStr.startsWith("noatun")) { // kdDebug() << count << ". noatun process found: " << replyStr << "\n"; QString idStr=replyStr.mid(7); if(idStr=="") { kdError() << "it looks like you have the following option checked in noatun:\n"; kdError() << "\"allow only one instance\"\n"; kdError() << "=> disable it first, restart noatun and then restart yammi!\n"; kdFatal() << "(I think I feel like crashing now...)\n!!!!!!!\n"; return false; } int id=atoi(idStr); playerId[count]=id; count++; } } for(int i=count; i<2; i++) { // if not enough players running: start the missing ones! kdDebug() << "trying to start another instance of noatun...\n"; // does not work well: /* KProcess proc; proc << "noatun"; proc.start(KProcess::Block); */ system("noatun"); } /* if(count < 2) { // wait for noatun to startup QWaitCondition wait; wait.wait(1000); }*/ } if(count < 2) { kdError() << "could not start the two required noatun instances!\n"; kdError() << "have you \"allow only one instance\" option checked? disable it first!\n"; return false; } return true; } int NoatunPlayer::getCurrentPlayerId() { return playerId[currentPlayer]; } int NoatunPlayer::getOtherPlayerId() { return playerId[(currentPlayer+1) % 2]; } // fade: 0 = beginning of fading, 100 = fading complete void NoatunPlayer::onFade() { // kdDebug() << getCurrentPlayerId() << "onFade()" << endl; int currentTime=getCurrentTime(); int totalTime=getTotalTime(); // kdDebug() << "currentTime: " << currentTime << "totalTime: " << totalTime << endl; if(totalTime == -1) { // this is a workaround for noatun sometimes not starting a song sendDcopCommand("play()"); } if(currentTime <= 0) { fade++; if(fade>6) { if(totalTime <= 0) { kdDebug() << "current song apparently not playable (totalTime <= 0), skipping..." << endl; kdDebug() << "file: " << playlist->at(0)->song()->location() << endl; model->skipUnplayableSongs(true); if(playlist->count() >= 2) { QString location=model->checkAvailability(playlist->at(1)->song()); if(location!="" && location!="never") { playlistAdd(location, true); playlist->removeFirst(); } playlistChanged(); fade = 0; } } } } else { fade = currentTime * 100 / model->config()->fadeTime; } if(fade<100) { sendDcopCommandInt("setVolume(int)", 100-(fade*(100-model->config()->fadeOutEnd)/100), fadeOut); sendDcopCommandInt("setVolume(int)", model->config()->fadeInStart+(fade*(100-model->config()->fadeInStart)/100), fadeIn); fadeTimer.start( 200, TRUE ); } else { fade=100; // set volume to 100 of fadeIn player, remove song of fadeOut player sendDcopCommandInt("setVolume(int)", 100, fadeIn); sendDcopCommandInt("setVolume(int)", 0, fadeOut); sendDcopCommand(QString("clear()"), fadeOut); } } /** * Checks, whether a song change should take place. */ void NoatunPlayer::check() { // 1. check, whether status has changed PlayerStatus newStatus=getStatus(); if(newStatus!=lastStatus) { lastStatus=newStatus; if(newStatus==STOPPED) { // heuristic: if we were at end of last song and only one song left in playlist // => we should remove it if(playlist->count()==1 && timeLeft<2000) { sendDcopCommand(QString("clear()")); playlist->removeFirst(); playlistChanged(); } if(playlist->count()>1 && model->config()->fadeTime==0) { // no crossfading configured // heuristic: if we were at end of last song, we should start next one if(timeLeft<2000) { // kdDebug() << "heuristic, starting song change...\n"; startSongChange(); } } } statusChanged(); } timeLeft=getTotalTime()-getCurrentTime(); // 2. check, whether we should initiate a song change (start crossfading) if(model->config()->fadeTime>0) { if(getStatus()==PLAYING && timeLeftconfig()->fadeTime && timeLeft>0) { if(fade<100) { // don't start fading if we are still fading last song? TODO: really? kdDebug() << "start crossfading, but we are still crossfading (" << fade << ")\n"; sendDcopCommand(QString("clear()"), getOtherPlayerId()); } startSongChange(); } } else { if(getStatus()==PLAYING && timeLeft<1000 && timeLeft>0) { // kdDebug() << "should prepare for next songchange without crossfading...\n"; } } } /** * Starts a song change, if possible (if >=2 songs in playlist) */ void NoatunPlayer::startSongChange(bool withoutCrossfading) { kdDebug() << getCurrentPlayerId() << "startSongChange()" << endl; model->skipUnplayableSongs(true); if(playlist->count()<2) { // we can't make a song change if there is no next song return; } PlayerStatus status=getStatus(); if(model->config()->fadeTime==0 || withoutCrossfading || status!=PLAYING) { // no crossfading clearActivePlayerPlaylist(); QString location=model->checkAvailability(playlist->at(1)->song()); if(location!="" && location!="never") { playlistAdd(location, status==PLAYING || model->config()->fadeTime==0); playlist->removeFirst(); } playlistChanged(); statusChanged(); } else { // crossfading fade=0; fadeOut=getCurrentPlayerId(); currentPlayer=(currentPlayer+1) % 2; fadeIn=getCurrentPlayerId(); // clearActivePlayerPlaylist(); QString location=model->checkAvailability(playlist->at(1)->song()); if(location!="" && location!="never") { playlistAdd(location, true); // problem with noatun communication: // sometimes player does not start to play playlist->removeFirst(); } else { kdDebug() << "no valid location for next song" << endl; } playlistChanged(); fadeTimer.start( 200, TRUE ); // change volume every 200ms } } /** * Adds a file to the current noatun player. */ void NoatunPlayer::playlistAdd(QString filename, bool autoStart) { kdDebug() << getCurrentPlayerId() << "playlistAdd()" << endl; int id=getCurrentPlayerId(); QString str=QString("noatun-%1").arg(id); QByteArray data; QDataStream arg(data, IO_WriteOnly); KURL url; url.setPath(filename); QString escapedPath = url.url(); arg << escapedPath; arg << autoStart; if (!client->send(str.local8Bit(), "Noatun", "addFile(QString, bool)", data)) { kdDebug() << "could not add file to noatun playlist, dcop send() failed\n"; } } /// start playing bool NoatunPlayer::play() { sendDcopCommand("play()"); return true; } /// pause bool NoatunPlayer::pause() { if(getStatus()==PAUSED) { return true; } sendDcopCommand("playpause()"); if(fade<100) { // if we are currently crossfading, we should pause both players sendDcopCommand("clear()", getOtherPlayerId()); } return true; } /// toggle between play and pause bool NoatunPlayer::playPause() { sendDcopCommand("playpause()"); if(fade<100) { // if we are currently crossfading, we should playPause both players sendDcopCommand("clear()", getOtherPlayerId()); } return true; } /// skip forward in playlist bool NoatunPlayer::skipForward(bool withoutCrossfading) { kdDebug() << getCurrentPlayerId() << "skipForward()" << endl; if( playlist->count() < 2 ) { // there is no "next song" return false; } int savedStatus = getStatus(); if( savedStatus != PLAYING ) { // remove the first song and sync playlist again playlist->removeFirst( ); syncYammi2Player(); emit playlistChanged(); } else { startSongChange(withoutCrossfading); } return true; } /// skip backward in playlist bool NoatunPlayer::skipBackward(bool withoutCrossfading) { kdDebug() << getCurrentPlayerId() << "skipBackward()" << endl; Song* last=playlist->at(0)->song(); // insert pseudo-song to be removed playlist->insert(0, new SongEntryInt(last, 0)); startSongChange(withoutCrossfading); return true; } /// stop playback bool NoatunPlayer::stop() { pause(); jumpTo(0); if(fade < 100) { // if we are currently crossfading, we should stop both players sendDcopCommand("clear()", getOtherPlayerId()); } return true; } PlayerStatus NoatunPlayer::getStatus() { int state=callGetInt("state()"); // case 1: noatun is playing if(state==2) return PLAYING; // case 2: noatun is paused if(state==1) return PAUSED; // case 3: noatun is stopped if(state==0) return STOPPED; kdDebug() << "unknown state received from noatun\n"; return STOPPED; } bool NoatunPlayer::jumpTo(int value) { if(callGetString("title()") == "" && playlist->count() > 0) { kdDebug() << "workaround for seeking if song not active in noatun: play(), pause()\n"; sendDcopCommand("play()"); for(int tries=0; tries<100 && callGetInt("state()") != PLAYING; tries++) { kdDebug() << "waiting for noatun to change state...\n"; } sendDcopCommand("playpause()"); } sendDcopCommandInt("skipTo(int)", value); return true; } QString NoatunPlayer::getCurrentFile() { return callGetString("title()"); } int NoatunPlayer::getCurrentTime() { return callGetInt("position()"); } int NoatunPlayer::getTotalTime() { return callGetInt("length()"); } void NoatunPlayer::syncYammi2Player() { // kdDebug() << getCurrentPlayerId() << "syncYammi2Player()" << endl; bool haveToUpdate = model->skipUnplayableSongs(); if(playlist->count()==0) { // playlist empty sendDcopCommand(QString("clear()")); sendDcopCommand(QString("clear()"), getOtherPlayerId()); if(haveToUpdate) { emit playlistChanged(); } return; } // 1. sync current song QString noatunCurrent=getCurrentFile(); QString location = model->checkAvailability( playlist->at(0)->song() ); QString yammiCurrent=location.right(location.length()-location.findRev('/')-1); if(noatunCurrent!=yammiCurrent) { clearActivePlayerPlaylist(); playlistAdd(location, false); sendDcopCommandInt("setVolume(int)", 100); } if(haveToUpdate) { emit playlistChanged(); } } void NoatunPlayer::quit() { if(getStatus() == PLAYING) { pause(); } } void NoatunPlayer::sendDcopCommandInt(QString command, int param, int id) { if(id==0) { id=getCurrentPlayerId(); } QString str=QString("noatun-%1").arg(id); QByteArray data; QDataStream arg(data, IO_WriteOnly); arg << param; if (!client->send(str.latin1(), "Noatun", command.latin1(), data)) { kdDebug() << "communicating with noatun: some error using DCOP.\n"; } } void NoatunPlayer::sendDcopCommand(QString command, int id) { if(id==0) { id=getCurrentPlayerId(); } QString str=QString("noatun-%1").arg(id); QByteArray data; QDataStream arg(data, IO_WriteOnly); if (!client->send(str.latin1(), "Noatun", command.latin1(), data)) { kdDebug() << "communicating with noatun: some error using DCOP.\n"; } } int NoatunPlayer::callGetInt(QString command, int id) { if(id==0) { id=getCurrentPlayerId(); } QByteArray data, replyData; QCString replyType; QDataStream arg(data, IO_WriteOnly); int result=0; QString str=QString("noatun-%1").arg(id); if (!client->call(str.latin1(), QString("Noatun").latin1(), command.latin1(), data, replyType, replyData)) { kdDebug() << "call() failed, maybe noatun was closed?\n"; kdDebug() << "I will check now and restart noatun if necessary...\n"; ensurePlayerIsRunning(true); } else { QDataStream reply(replyData, IO_ReadOnly); if (replyType == "int") { reply >> result; } else kdDebug() << "unexpected type of dcop reply (int expected): " << replyType << "\n"; } return result; } QString NoatunPlayer::callGetString(QString command, int id) { if(id==0) { id=getCurrentPlayerId(); } QByteArray data, replyData; QCString replyType; QDataStream arg(data, IO_WriteOnly); QString result=""; QString str=QString("noatun-%1").arg(id); if (!client->call(str.latin1(), QString("Noatun").latin1(), command.latin1(), data, replyType, replyData)) kdDebug() << "call() failed\n"; else { QDataStream reply(replyData, IO_ReadOnly); if (replyType == "QString") { reply >> result; } else kdDebug() << "unexpected type of dcop reply (QString expected): " << replyType << "\n"; } return result; }