/src/libtomahawk/audio/AudioEngine.cpp

http://github.com/tomahawk-player/tomahawk · C++ · 1390 lines · 1049 code · 287 blank · 54 comment · 217 complexity · d2f9fab5bf67ed1d0aa57c8ecd20e49d MD5 · raw file

  1. /* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
  2. *
  3. * Copyright 2010-2016, Christian Muehlhaeuser <muesli@tomahawk-player.org>
  4. * Copyright 2010-2012, Jeff Mitchell <jeff@tomahawk-player.org>
  5. * Copyright 2013, Teo Mrnjavac <teo@kde.org>
  6. *
  7. * Tomahawk is free software: you can redistribute it and/or modify
  8. * it under the terms of the GNU General Public License as published by
  9. * the Free Software Foundation, either version 3 of the License, or
  10. * (at your option) any later version.
  11. *
  12. * Tomahawk is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
  19. */
  20. #include "AudioEngine.h"
  21. #include "AudioEngine_p.h"
  22. #include "config.h"
  23. #include "audio/Qnr_IoDeviceStream.h"
  24. #include "filemetadata/MusicScanner.h"
  25. #include "jobview/JobStatusView.h"
  26. #include "jobview/JobStatusModel.h"
  27. #include "jobview/ErrorStatusMessage.h"
  28. #include "network/Servent.h"
  29. #include "playlist/SingleTrackPlaylistInterface.h"
  30. #include "utils/Closure.h"
  31. #include "utils/Logger.h"
  32. #include "utils/NetworkReply.h"
  33. #include "utils/NetworkAccessManager.h"
  34. #include "Album.h"
  35. #include "Artist.h"
  36. #include "Pipeline.h"
  37. #include "PlaylistEntry.h"
  38. #include "SourceList.h"
  39. #include "TomahawkSettings.h"
  40. #include "UrlHandler.h"
  41. #include "resolvers/ScriptJob.h"
  42. #include <QDir>
  43. using namespace Tomahawk;
  44. #define AUDIO_VOLUME_STEP 5
  45. static const uint_fast8_t UNDERRUNTHRESHOLD = 2;
  46. static QString s_aeInfoIdentifier = QString( "AUDIOENGINE" );
  47. void
  48. AudioEnginePrivate::onStateChanged( AudioOutput::AudioState newState, AudioOutput::AudioState oldState )
  49. {
  50. tDebug() << Q_FUNC_INFO << oldState << newState << q_ptr->state();
  51. AudioEngine::AudioState previousState = q_ptr->state();
  52. if ( newState == AudioOutput::Loading )
  53. {
  54. // We don't emit this state to listeners - yet.
  55. state = AudioEngine::Loading;
  56. }
  57. else if ( newState == AudioOutput::Buffering )
  58. {
  59. if ( underrunCount > UNDERRUNTHRESHOLD && !underrunNotified )
  60. {
  61. underrunNotified = true;
  62. //FIXME: Actually notify
  63. }
  64. else
  65. underrunCount++;
  66. }
  67. else if ( newState == AudioOutput::Error )
  68. {
  69. q_ptr->setState( AudioEngine::Stopped );
  70. tDebug() << Q_FUNC_INFO << "AudioOutput Error";
  71. emit q_ptr->error( AudioEngine::UnknownError );
  72. q_ptr->setState( AudioEngine::Error );
  73. }
  74. else if ( newState == AudioOutput::Playing )
  75. {
  76. bool emitSignal = false;
  77. if ( q_ptr->state() != AudioEngine::Paused && q_ptr->state() != AudioEngine::Playing )
  78. {
  79. underrunCount = 0;
  80. underrunNotified = false;
  81. emitSignal = true;
  82. }
  83. q_ptr->setState( AudioEngine::Playing );
  84. audioRetryCounter = 0;
  85. if ( emitSignal )
  86. emit q_ptr->started( currentTrack );
  87. }
  88. else if ( newState == AudioOutput::Paused )
  89. {
  90. q_ptr->setState( AudioEngine::Paused );
  91. }
  92. else if ( newState == AudioOutput::Stopped )
  93. {
  94. q_ptr->setState( AudioEngine::Stopped );
  95. }
  96. if ( previousState != AudioEngine::Stopped &&
  97. ( oldState == AudioOutput::Playing || oldState == AudioOutput::Loading ) )
  98. {
  99. bool retry = false;
  100. if ( newState == AudioOutput::Error )
  101. {
  102. retry = ( audioRetryCounter < 2 );
  103. audioRetryCounter++;
  104. if ( !retry )
  105. {
  106. q_ptr->stop( AudioEngine::UnknownError );
  107. }
  108. }
  109. if ( newState == AudioOutput::Stopped || retry )
  110. {
  111. tDebug() << Q_FUNC_INFO << "Finding next track." << oldState << newState;
  112. if ( q_ptr->canGoNext() )
  113. {
  114. q_ptr->loadNextTrack();
  115. }
  116. else
  117. {
  118. if ( !playlist.isNull() && playlist.data()->retryMode() == Tomahawk::PlaylistModes::Retry )
  119. waitingOnNewTrack = true;
  120. q_ptr->stop();
  121. }
  122. }
  123. }
  124. }
  125. AudioEngine* AudioEnginePrivate::s_instance = 0;
  126. AudioEngine*
  127. AudioEngine::instance()
  128. {
  129. return AudioEnginePrivate::s_instance;
  130. }
  131. AudioEngine::AudioEngine()
  132. : QObject()
  133. , d_ptr( new AudioEnginePrivate( this ) )
  134. {
  135. Q_D( AudioEngine );
  136. d->timeElapsed = 0;
  137. d->waitingOnNewTrack = false;
  138. d->state = Stopped;
  139. d->coverTempFile = 0;
  140. d->s_instance = this;
  141. tDebug() << "Init AudioEngine";
  142. d->audioOutput = new AudioOutput( this );
  143. connect( d->audioOutput, SIGNAL( initialized() ), this, SIGNAL( initialized() ) );
  144. connect( d->audioOutput, SIGNAL( stateChanged( AudioOutput::AudioState, AudioOutput::AudioState ) ), d_func(), SLOT( onStateChanged( AudioOutput::AudioState, AudioOutput::AudioState ) ) );
  145. connect( d->audioOutput, SIGNAL( tick( qint64 ) ), SLOT( timerTriggered( qint64 ) ) );
  146. connect( d->audioOutput, SIGNAL( positionChanged( float ) ), SLOT( onPositionChanged( float ) ) );
  147. connect( d->audioOutput, SIGNAL( volumeChanged( qreal ) ), SLOT( onVolumeChanged( qreal ) ) );
  148. connect( d->audioOutput, SIGNAL( mutedChanged( bool ) ), SIGNAL( mutedChanged( bool ) ) );
  149. if ( TomahawkSettings::instance()->muted() )
  150. {
  151. mute();
  152. }
  153. setVolume( TomahawkSettings::instance()->volume() );
  154. qRegisterMetaType< AudioErrorCode >("AudioErrorCode");
  155. qRegisterMetaType< AudioState >("AudioState");
  156. }
  157. AudioEngine::~AudioEngine()
  158. {
  159. tDebug() << Q_FUNC_INFO;
  160. TomahawkSettings::instance()->setVolume( volume() );
  161. TomahawkSettings::instance()->setMuted( isMuted() );
  162. delete d_ptr;
  163. }
  164. void
  165. AudioEngine::playPause()
  166. {
  167. if ( QThread::currentThread() != thread() )
  168. {
  169. QMetaObject::invokeMethod( this, "playPause", Qt::QueuedConnection );
  170. return;
  171. }
  172. if ( isPlaying() )
  173. pause();
  174. else
  175. play();
  176. }
  177. void
  178. AudioEngine::play()
  179. {
  180. if ( QThread::currentThread() != thread() )
  181. {
  182. QMetaObject::invokeMethod( this, "play", Qt::QueuedConnection );
  183. return;
  184. }
  185. Q_D( AudioEngine );
  186. tDebug( LOGEXTRA ) << Q_FUNC_INFO;
  187. if ( isPaused() )
  188. {
  189. d->audioOutput->play();
  190. emit resumed();
  191. sendNowPlayingNotification( Tomahawk::InfoSystem::InfoNowResumed );
  192. }
  193. else
  194. {
  195. if ( !d->currentTrack && d->playlist && d->playlist->nextResult() )
  196. {
  197. loadNextTrack();
  198. }
  199. else
  200. next();
  201. }
  202. }
  203. void
  204. AudioEngine::pause()
  205. {
  206. if ( QThread::currentThread() != thread() )
  207. {
  208. QMetaObject::invokeMethod( this, "pause", Qt::QueuedConnection );
  209. return;
  210. }
  211. Q_D( AudioEngine );
  212. tDebug( LOGEXTRA ) << Q_FUNC_INFO;
  213. d->audioOutput->pause();
  214. emit paused();
  215. Tomahawk::InfoSystem::InfoSystem::instance()->pushInfo( Tomahawk::InfoSystem::InfoPushData( s_aeInfoIdentifier, Tomahawk::InfoSystem::InfoNowPaused, QVariant(), Tomahawk::InfoSystem::PushNoFlag ) );
  216. }
  217. void
  218. AudioEngine::stop( AudioErrorCode errorCode )
  219. {
  220. if ( QThread::currentThread() != thread() )
  221. {
  222. QMetaObject::invokeMethod( this, "stop", Qt::QueuedConnection );
  223. return;
  224. }
  225. Q_D( AudioEngine );
  226. tDebug() << Q_FUNC_INFO << errorCode << isStopped();
  227. if ( errorCode == NoError )
  228. setState( Stopped );
  229. else
  230. setState( Error );
  231. if ( d->audioOutput->state() != AudioOutput::Stopped )
  232. d->audioOutput->stop();
  233. emit stopped();
  234. if ( !d->playlist.isNull() )
  235. d->playlist.data()->reset();
  236. if ( !d->currentTrack.isNull() )
  237. emit timerPercentage( ( (double)d->timeElapsed / (double)d->currentTrack->track()->duration() ) * 100.0 );
  238. setCurrentTrack( Tomahawk::result_ptr() );
  239. if ( d->waitingOnNewTrack )
  240. sendWaitingNotification();
  241. if ( d->audioOutput->isInitialized() )
  242. {
  243. Tomahawk::InfoSystem::InfoPushData pushData( s_aeInfoIdentifier, Tomahawk::InfoSystem::InfoNowStopped, QVariant(), Tomahawk::InfoSystem::PushNoFlag );
  244. Tomahawk::InfoSystem::InfoSystem::instance()->pushInfo( pushData );
  245. }
  246. }
  247. void
  248. AudioEngine::previous()
  249. {
  250. if ( QThread::currentThread() != thread() )
  251. {
  252. QMetaObject::invokeMethod( this, "previous", Qt::QueuedConnection );
  253. return;
  254. }
  255. tDebug( LOGEXTRA ) << Q_FUNC_INFO;
  256. if ( canGoPrevious() )
  257. loadPreviousTrack();
  258. }
  259. void
  260. AudioEngine::next()
  261. {
  262. if ( QThread::currentThread() != thread() )
  263. {
  264. QMetaObject::invokeMethod( this, "next", Qt::QueuedConnection );
  265. return;
  266. }
  267. tDebug( LOGEXTRA ) << Q_FUNC_INFO;
  268. if ( canGoNext() )
  269. loadNextTrack();
  270. }
  271. bool
  272. AudioEngine::canGoNext()
  273. {
  274. Q_D( AudioEngine );
  275. tDebug( LOGVERBOSE ) << Q_FUNC_INFO;
  276. if ( d->queue && d->queue->trackCount() )
  277. return true;
  278. if ( d->playlist.isNull() )
  279. return false;
  280. if ( d->playlist.data()->skipRestrictions() == PlaylistModes::NoSkip ||
  281. d->playlist.data()->skipRestrictions() == PlaylistModes::NoSkipForwards )
  282. {
  283. return false;
  284. }
  285. if ( !d->currentTrack.isNull() && !d->playlist->hasNextResult() &&
  286. ( d->playlist->currentItem().isNull() || ( d->currentTrack->id() == d->playlist->currentItem()->id() ) ) )
  287. {
  288. //For instance, when doing a catch-up while listening along, but the person
  289. //you're following hasn't started a new track yet...don't do anything
  290. tDebug( LOGEXTRA ) << Q_FUNC_INFO << "Catch up, but same track or can't move on because don't have next track or it wasn't resolved";
  291. return false;
  292. }
  293. return ( d->currentTrack && d->playlist.data()->hasNextResult() &&
  294. !d->playlist.data()->nextResult().isNull() &&
  295. d->playlist.data()->nextResult()->isOnline() );
  296. }
  297. bool
  298. AudioEngine::canGoPrevious()
  299. {
  300. Q_D( AudioEngine );
  301. if ( d->playlist.isNull() )
  302. return false;
  303. if ( d->playlist.data()->skipRestrictions() == PlaylistModes::NoSkip ||
  304. d->playlist.data()->skipRestrictions() == PlaylistModes::NoSkipBackwards )
  305. return false;
  306. return ( d->currentTrack && d->playlist.data()->hasPreviousResult() && d->playlist.data()->previousResult()->isOnline() );
  307. }
  308. bool
  309. AudioEngine::canSeek()
  310. {
  311. Q_D( AudioEngine );
  312. return !d->playlist.isNull() && ( d->playlist.data()->seekRestrictions() != PlaylistModes::NoSeek );
  313. }
  314. void
  315. AudioEngine::seek( qint64 ms )
  316. {
  317. Q_D( AudioEngine );
  318. /*if ( !canSeek() )
  319. {
  320. tDebug( LOGEXTRA ) << "Could not seek!";
  321. return;
  322. }*/
  323. if ( isPlaying() || isPaused() )
  324. {
  325. tDebug( LOGVERBOSE ) << Q_FUNC_INFO << ms;
  326. d->audioOutput->seek( ms );
  327. emit seeked( ms );
  328. }
  329. }
  330. void
  331. AudioEngine::seek( int ms )
  332. {
  333. seek( (qint64) ms );
  334. }
  335. void
  336. AudioEngine::setVolume( int percentage )
  337. {
  338. Q_D( AudioEngine );
  339. tDebug() << Q_FUNC_INFO << percentage;
  340. percentage = qBound( 0, percentage, 100 );
  341. d->audioOutput->setVolume( (qreal)percentage / 100.0 );
  342. if ( percentage > 0 && d->audioOutput->isMuted() )
  343. d->audioOutput->setMuted( false );
  344. emit volumeChanged( percentage );
  345. }
  346. void
  347. AudioEngine::lowerVolume()
  348. {
  349. setVolume( volume() - AUDIO_VOLUME_STEP );
  350. }
  351. void
  352. AudioEngine::raiseVolume()
  353. {
  354. setVolume( volume() + AUDIO_VOLUME_STEP );
  355. }
  356. bool
  357. AudioEngine::isMuted() const
  358. {
  359. return d_func()->audioOutput->isMuted();
  360. }
  361. void
  362. AudioEngine::mute()
  363. {
  364. Q_D( AudioEngine );
  365. d->audioOutput->setMuted( true );
  366. emit volumeChanged( volume() );
  367. }
  368. void
  369. AudioEngine::toggleMute()
  370. {
  371. Q_D( AudioEngine );
  372. d->audioOutput->setMuted( !d->audioOutput->isMuted() );
  373. emit volumeChanged( volume() );
  374. }
  375. void
  376. AudioEngine::sendWaitingNotification() const
  377. {
  378. tDebug( LOGVERBOSE ) << Q_FUNC_INFO;
  379. //since it's async, after this is triggered our result could come in, so don't show the popup in that case
  380. if ( d_func()->playlist && d_func()->playlist->nextResult() && d_func()->playlist->nextResult()->isOnline() )
  381. return;
  382. Tomahawk::InfoSystem::InfoPushData pushData (
  383. s_aeInfoIdentifier, Tomahawk::InfoSystem::InfoTrackUnresolved,
  384. QVariant(),
  385. Tomahawk::InfoSystem::PushNoFlag );
  386. Tomahawk::InfoSystem::InfoSystem::instance()->pushInfo( pushData );
  387. }
  388. void
  389. AudioEngine::sendNowPlayingNotification( const Tomahawk::InfoSystem::InfoType type )
  390. {
  391. Q_D( AudioEngine );
  392. if ( d->currentTrack.isNull() )
  393. return;
  394. if ( d->currentTrack->track()->coverLoaded() )
  395. {
  396. onNowPlayingInfoReady( type );
  397. }
  398. else
  399. {
  400. NewClosure( d->currentTrack->track().data(), SIGNAL( coverChanged() ), const_cast< AudioEngine* >( this ), SLOT( sendNowPlayingNotification( const Tomahawk::InfoSystem::InfoType ) ), type );
  401. d->currentTrack->track()->cover( QSize( 0, 0 ), true );
  402. }
  403. }
  404. void
  405. AudioEngine::onNowPlayingInfoReady( const Tomahawk::InfoSystem::InfoType type )
  406. {
  407. Q_D( AudioEngine );
  408. if ( d->currentTrack.isNull() ||
  409. d->currentTrack->track()->artist().isEmpty() )
  410. return;
  411. QVariantMap playInfo;
  412. QImage cover;
  413. cover = d->currentTrack->track()->cover( QSize( 0, 0 ) ).toImage();
  414. if ( !cover.isNull() )
  415. {
  416. playInfo["cover"] = cover;
  417. delete d->coverTempFile;
  418. d->coverTempFile = new QTemporaryFile( QDir::toNativeSeparators( QDir::tempPath() + "/" + d->currentTrack->track()->artist() + "_" + d->currentTrack->track()->album() + "_tomahawk_cover.png" ) );
  419. if ( !d->coverTempFile->open() )
  420. {
  421. tDebug() << Q_FUNC_INFO << "WARNING: could not write temporary file for cover art!";
  422. }
  423. else
  424. {
  425. // Finally, save the image to the new temp file
  426. if ( cover.save( d->coverTempFile, "PNG" ) )
  427. {
  428. tDebug() << Q_FUNC_INFO << "Saving cover image to:" << QFileInfo( *d->coverTempFile ).absoluteFilePath();
  429. playInfo["coveruri"] = QFileInfo( *d->coverTempFile ).absoluteFilePath();
  430. }
  431. else
  432. tDebug() << Q_FUNC_INFO << "Failed to save cover image!";
  433. }
  434. }
  435. else
  436. tDebug() << Q_FUNC_INFO << "Cover from query is null!";
  437. Tomahawk::InfoSystem::InfoStringHash trackInfo;
  438. trackInfo["title"] = d->currentTrack->track()->track();
  439. trackInfo["artist"] = d->currentTrack->track()->artist();
  440. trackInfo["album"] = d->currentTrack->track()->album();
  441. trackInfo["duration"] = QString::number( d->currentTrack->track()->duration() );
  442. trackInfo["albumpos"] = QString::number( d->currentTrack->track()->albumpos() );
  443. playInfo["trackinfo"] = QVariant::fromValue< Tomahawk::InfoSystem::InfoStringHash >( trackInfo );
  444. playInfo["private"] = TomahawkSettings::instance()->privateListeningMode();
  445. Tomahawk::InfoSystem::InfoPushData pushData( s_aeInfoIdentifier, type, playInfo, Tomahawk::InfoSystem::PushShortUrlFlag );
  446. Tomahawk::InfoSystem::InfoSystem::instance()->pushInfo( pushData );
  447. }
  448. void
  449. AudioEngine::loadTrack( const Tomahawk::result_ptr& result )
  450. {
  451. Q_D( AudioEngine );
  452. tDebug( LOGEXTRA ) << Q_FUNC_INFO << ( result.isNull() ? QString() : result->url() );
  453. if ( !d->audioOutput->isInitialized() )
  454. {
  455. return;
  456. }
  457. if ( !result )
  458. {
  459. stop();
  460. return;
  461. }
  462. // We do this to stop the audio as soon as a user activated another track
  463. // If we don't block the audioOutput signals, the state change will trigger
  464. // loading yet another track
  465. d->audioOutput->blockSignals( true );
  466. d->audioOutput->stop();
  467. d->audioOutput->blockSignals( false );
  468. setCurrentTrack( result );
  469. ScriptJob* job = result->resolvedBy()->getStreamUrl( result );
  470. connect( job, SIGNAL( done( QVariantMap ) ), SLOT( gotStreamUrl( QVariantMap ) ) );
  471. job->setProperty( "result", QVariant::fromValue( result ) );
  472. job->start();
  473. }
  474. void
  475. AudioEngine::gotStreamUrl( const QVariantMap& data )
  476. {
  477. QString streamUrl = data[ "url" ].toString();
  478. QVariantMap headers = data[ "headers" ].toMap();
  479. Tomahawk::result_ptr result = sender()->property( "result" ).value<result_ptr>();
  480. if ( streamUrl.isEmpty() || headers.isEmpty() ||
  481. !( TomahawkUtils::isHttpResult( streamUrl ) || TomahawkUtils::isHttpsResult( streamUrl ) ) )
  482. {
  483. // We can't supply custom headers to VLC - but prefer using its HTTP streaming due to improved seeking ability
  484. // Not an RTMP or HTTP-with-headers URL, get IO device
  485. QSharedPointer< QIODevice > sp;
  486. performLoadIODevice( result, streamUrl );
  487. }
  488. else
  489. {
  490. // We need our own QIODevice for streaming
  491. // TODO: just make this part of the http(s) IoDeviceFactory (?)
  492. QUrl url = QUrl::fromEncoded( streamUrl.toUtf8() );
  493. QNetworkRequest req( url );
  494. QMap<QString, QString> parsedHeaders;
  495. foreach ( const QString& key, headers.keys() )
  496. {
  497. Q_ASSERT_X( headers[key].canConvert( QVariant::String ), Q_FUNC_INFO, "Expected a Map of string for additional headers" );
  498. if ( headers[key].canConvert( QVariant::String ) )
  499. {
  500. parsedHeaders.insert( key, headers[key].toString() );
  501. }
  502. }
  503. foreach ( const QString& key, parsedHeaders.keys() )
  504. {
  505. req.setRawHeader( key.toLatin1(), parsedHeaders[key].toLatin1() );
  506. }
  507. tDebug() << "Creating a QNetworkReply with url:" << req.url().toString();
  508. NetworkReply* reply = new NetworkReply( Tomahawk::Utils::nam()->get( req ) );
  509. NewClosure( reply, SIGNAL( finalUrlReached() ), this, SLOT( gotRedirectedStreamUrl( Tomahawk::result_ptr, NetworkReply* ) ), result, reply );
  510. }
  511. sender()->deleteLater();
  512. }
  513. void
  514. AudioEngine::gotRedirectedStreamUrl( const Tomahawk::result_ptr& result, NetworkReply* reply )
  515. {
  516. // std::functions cannot accept temporaries as parameters
  517. QSharedPointer< QIODevice > sp ( reply->reply(), &QObject::deleteLater );
  518. QString url = reply->reply()->url().toString();
  519. reply->disconnectFromReply();
  520. reply->deleteLater();
  521. performLoadTrack( result, url, sp );
  522. }
  523. void
  524. AudioEngine::onPositionChanged( float new_position )
  525. {
  526. // tDebug() << Q_FUNC_INFO << new_position << state();
  527. emit trackPosition( new_position );
  528. }
  529. void
  530. AudioEngine::performLoadIODevice( const result_ptr& result, const QString& url )
  531. {
  532. tDebug( LOGEXTRA ) << Q_FUNC_INFO << ( result.isNull() ? QString() : url );
  533. if ( !TomahawkUtils::isLocalResult( url ) && !TomahawkUtils::isHttpResult( url )
  534. && !TomahawkUtils::isRtmpResult( url ) )
  535. {
  536. std::function< void ( const QString, QSharedPointer< QIODevice > ) > callback =
  537. std::bind( &AudioEngine::performLoadTrack, this, result,
  538. std::placeholders::_1,
  539. std::placeholders::_2 );
  540. Tomahawk::UrlHandler::getIODeviceForUrl( result, url, callback );
  541. }
  542. else
  543. {
  544. QSharedPointer< QIODevice > io;
  545. performLoadTrack( result, url, io );
  546. }
  547. }
  548. void
  549. AudioEngine::performLoadTrack( const Tomahawk::result_ptr result, const QString& url, QSharedPointer< QIODevice > io )
  550. {
  551. if ( QThread::currentThread() != thread() )
  552. {
  553. QMetaObject::invokeMethod( this, "performLoadTrack", Qt::QueuedConnection,
  554. Q_ARG( const Tomahawk::result_ptr, result ),
  555. Q_ARG( const QString, url ),
  556. Q_ARG( QSharedPointer< QIODevice >, io )
  557. );
  558. return;
  559. }
  560. Q_D( AudioEngine );
  561. if ( currentTrack() != result )
  562. {
  563. tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Track loaded too late, skip.";
  564. return;
  565. }
  566. tDebug( LOGEXTRA ) << Q_FUNC_INFO << ( result.isNull() ? QString() : result->url() );
  567. QSharedPointer< QIODevice > ioToKeep = io;
  568. bool err = false;
  569. {
  570. if ( !( TomahawkUtils::isLocalResult( url ) || TomahawkUtils::isHttpResult( url ) || TomahawkUtils::isRtmpResult( url ) )
  571. && ( !io || io.isNull() ) )
  572. {
  573. tLog() << Q_FUNC_INFO << "Error getting iodevice for" << result->url();
  574. err = true;
  575. }
  576. if ( !err )
  577. {
  578. tLog() << Q_FUNC_INFO << "Starting new song:" << url;
  579. d->state = Loading;
  580. emit loading( d->currentTrack );
  581. if ( !TomahawkUtils::isLocalResult( url )
  582. && !( TomahawkUtils::isHttpResult( url ) && io.isNull() )
  583. && !TomahawkUtils::isRtmpResult( url ) )
  584. {
  585. QSharedPointer<QNetworkReply> qnr = io.objectCast<QNetworkReply>();
  586. if ( !qnr.isNull() )
  587. {
  588. d->audioOutput->setCurrentSource( new QNR_IODeviceStream( qnr, this ) );
  589. // We keep track of the QNetworkReply in QNR_IODeviceStream
  590. // and AudioOutput handles the deletion of the
  591. // QNR_IODeviceStream object
  592. ioToKeep.clear();
  593. d->audioOutput->setAutoDelete( true );
  594. }
  595. else
  596. {
  597. d->audioOutput->setCurrentSource( io.data() );
  598. // We handle the deletion via tracking in d->input
  599. d->audioOutput->setAutoDelete( false );
  600. }
  601. }
  602. else
  603. {
  604. /*
  605. * TODO: Do we need this anymore as we now do HTTP streaming ourselves?
  606. * Maybe this can be useful for letting VLC do other protocols?
  607. */
  608. if ( !TomahawkUtils::isLocalResult( url ) )
  609. {
  610. QUrl furl = url;
  611. if ( url.contains( "?" ) )
  612. {
  613. furl = QUrl( url.left( url.indexOf( '?' ) ) );
  614. TomahawkUtils::urlSetQuery( furl, QString( url.mid( url.indexOf( '?' ) + 1 ) ) );
  615. }
  616. tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Passing to VLC:" << furl;
  617. d->audioOutput->setCurrentSource( furl );
  618. }
  619. else
  620. {
  621. QString furl = url;
  622. if ( furl.startsWith( "file://" ) )
  623. furl = furl.right( furl.length() - 7 );
  624. tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Passing to VLC:" << QUrl::fromLocalFile( furl );
  625. d->audioOutput->setCurrentSource( QUrl::fromLocalFile( furl ) );
  626. }
  627. d->audioOutput->setAutoDelete( true );
  628. }
  629. if ( !d->input.isNull() )
  630. {
  631. d->input->close();
  632. d->input.clear();
  633. }
  634. d->input = ioToKeep;
  635. d->audioOutput->play();
  636. if ( TomahawkSettings::instance()->privateListeningMode() != TomahawkSettings::FullyPrivate )
  637. {
  638. d->currentTrack->track()->startPlaying();
  639. }
  640. sendNowPlayingNotification( Tomahawk::InfoSystem::InfoNowPlaying );
  641. }
  642. }
  643. if ( err )
  644. {
  645. stop();
  646. return;
  647. }
  648. d->waitingOnNewTrack = false;
  649. return;
  650. }
  651. void
  652. AudioEngine::loadPreviousTrack()
  653. {
  654. if ( QThread::currentThread() != thread() )
  655. {
  656. QMetaObject::invokeMethod( this, "loadPreviousTrack", Qt::QueuedConnection );
  657. return;
  658. }
  659. Q_D( AudioEngine );
  660. tDebug( LOGEXTRA ) << Q_FUNC_INFO;
  661. if ( d->playlist.isNull() )
  662. {
  663. stop();
  664. return;
  665. }
  666. Tomahawk::result_ptr result;
  667. if ( d->playlist.data()->previousResult() )
  668. {
  669. result = d->playlist.data()->setSiblingResult( -1 );
  670. setCurrentTrackPlaylist( d->playlist );
  671. }
  672. if ( result )
  673. loadTrack( result );
  674. else
  675. stop();
  676. }
  677. void
  678. AudioEngine::loadNextTrack()
  679. {
  680. if ( QThread::currentThread() != thread() )
  681. {
  682. QMetaObject::invokeMethod( this, "loadNextTrack", Qt::QueuedConnection );
  683. return;
  684. }
  685. Q_D( AudioEngine );
  686. tDebug( LOGEXTRA ) << Q_FUNC_INFO;
  687. Tomahawk::result_ptr result;
  688. if ( d->stopAfterTrack && d->currentTrack )
  689. {
  690. if ( d->stopAfterTrack->track()->equals( d->currentTrack->track() ) )
  691. {
  692. d->stopAfterTrack.clear();
  693. stop();
  694. return;
  695. }
  696. }
  697. if ( d->queue && d->queue->trackCount() )
  698. {
  699. query_ptr query = d->queue->tracks().first();
  700. if ( query && query->numResults() )
  701. result = query->results().first();
  702. }
  703. if ( !d->playlist.isNull() && result.isNull() )
  704. {
  705. tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "Loading playlist's next item" << d->playlist.data() << d->playlist->shuffled();
  706. if ( d->playlist.data()->nextResult() )
  707. {
  708. result = d->playlist.data()->setSiblingResult( 1 );
  709. setCurrentTrackPlaylist( d->playlist );
  710. }
  711. }
  712. if ( result )
  713. {
  714. tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "Got next item, loading track";
  715. loadTrack( result );
  716. }
  717. else
  718. {
  719. if ( !d->playlist.isNull() && d->playlist.data()->retryMode() == Tomahawk::PlaylistModes::Retry )
  720. d->waitingOnNewTrack = true;
  721. stop();
  722. }
  723. }
  724. void
  725. AudioEngine::play( const QUrl& url )
  726. {
  727. tDebug() << Q_FUNC_INFO << url;
  728. const QVariantMap tags = MusicScanner::readTags( QFileInfo( url.toLocalFile() ) ).toMap();
  729. track_ptr t;
  730. if ( !tags.isEmpty() )
  731. {
  732. t = Track::get( tags["artist"].toString(), tags["track"].toString(), tags["album"].toString(),
  733. tags["albumArtist"].toString(), tags["duration"].toInt(), tags["composer"].toString(),
  734. tags["albumpos"].toUInt(), tags["discnumber"].toUInt() );
  735. }
  736. else
  737. {
  738. t = Tomahawk::Track::get( "Unknown Artist", "Unknown Track" );
  739. }
  740. result_ptr result = Result::get( url.toString(), t );
  741. if ( !tags.isEmpty() )
  742. {
  743. result->setSize( tags["size"].toUInt() );
  744. result->setBitrate( tags["bitrate"].toUInt() );
  745. result->setModificationTime( tags["mtime"].toUInt() );
  746. result->setMimetype( tags["mimetype"].toString() );
  747. }
  748. result->setResolvedByCollection( SourceList::instance()->getLocal()->collections().first(), false );
  749. // Tomahawk::query_ptr qry = Tomahawk::Query::get( t );
  750. playItem( playlistinterface_ptr(), result, query_ptr() );
  751. }
  752. void
  753. AudioEngine::playItem( Tomahawk::playlistinterface_ptr playlist, const Tomahawk::result_ptr& result, const Tomahawk::query_ptr& fromQuery )
  754. {
  755. Q_D( AudioEngine );
  756. tDebug( LOGEXTRA ) << Q_FUNC_INFO << ( result.isNull() ? QString() : result->url() );
  757. if ( !d->playlist.isNull() )
  758. d->playlist.data()->reset();
  759. setPlaylist( playlist );
  760. if ( !playlist && fromQuery )
  761. {
  762. setCurrentTrackPlaylist( playlistinterface_ptr( new SingleTrackPlaylistInterface( fromQuery ) ) );
  763. }
  764. else
  765. {
  766. setCurrentTrackPlaylist( playlist );
  767. }
  768. if ( result )
  769. {
  770. loadTrack( result );
  771. }
  772. else if ( !d->playlist.isNull() && d->playlist.data()->retryMode() == PlaylistModes::Retry )
  773. {
  774. d->waitingOnNewTrack = true;
  775. if ( isStopped() )
  776. emit sendWaitingNotification();
  777. else
  778. stop();
  779. }
  780. }
  781. void
  782. AudioEngine::playItem( Tomahawk::playlistinterface_ptr playlist, const Tomahawk::query_ptr& query )
  783. {
  784. if ( query->resolvingFinished() || query->numResults( true ) )
  785. {
  786. if ( query->numResults( true ) )
  787. {
  788. playItem( playlist, query->results().first(), query );
  789. return;
  790. }
  791. JobStatusView::instance()->model()->addJob(
  792. new ErrorStatusMessage( tr( "Sorry, %applicationName couldn't find the track '%1' by %2" ).arg( query->queryTrack()->track() ).arg( query->queryTrack()->artist() ), 15 ) );
  793. if ( isStopped() )
  794. emit stopped(); // we do this so the original caller knows we couldn't find this track
  795. }
  796. else
  797. {
  798. Pipeline::instance()->resolve( query );
  799. NewClosure( query.data(), SIGNAL( resultsChanged() ),
  800. const_cast<AudioEngine*>(this), SLOT( playItem( Tomahawk::playlistinterface_ptr, Tomahawk::query_ptr ) ), playlist, query );
  801. }
  802. }
  803. void
  804. AudioEngine::playItem( const Tomahawk::artist_ptr& artist )
  805. {
  806. playlistinterface_ptr pli = artist->playlistInterface( Mixed );
  807. if ( pli->isFinished() )
  808. {
  809. if ( pli->tracks().isEmpty() )
  810. {
  811. JobStatusView::instance()->model()->addJob(
  812. new ErrorStatusMessage( tr( "Sorry, %applicationName couldn't find the artist '%1'" ).arg( artist->name() ), 15 ) );
  813. if ( isStopped() )
  814. emit stopped(); // we do this so the original caller knows we couldn't find this track
  815. }
  816. else
  817. playPlaylistInterface( pli );
  818. }
  819. else
  820. {
  821. NewClosure( artist.data(), SIGNAL( tracksAdded( QList<Tomahawk::query_ptr>, Tomahawk::ModelMode, Tomahawk::collection_ptr ) ),
  822. const_cast<AudioEngine*>(this), SLOT( playItem( Tomahawk::artist_ptr ) ), artist );
  823. pli->tracks();
  824. }
  825. }
  826. void
  827. AudioEngine::playItem( const Tomahawk::album_ptr& album )
  828. {
  829. playlistinterface_ptr pli = album->playlistInterface( Mixed );
  830. if ( pli->isFinished() )
  831. {
  832. if ( pli->tracks().isEmpty() )
  833. {
  834. JobStatusView::instance()->model()->addJob(
  835. new ErrorStatusMessage( tr( "Sorry, %applicationName couldn't find the album '%1' by %2" ).arg( album->name() ).arg( album->artist()->name() ), 15 ) );
  836. if ( isStopped() )
  837. emit stopped(); // we do this so the original caller knows we couldn't find this track
  838. }
  839. else
  840. playPlaylistInterface( pli );
  841. }
  842. else
  843. {
  844. NewClosure( album.data(), SIGNAL( tracksAdded( QList<Tomahawk::query_ptr>, Tomahawk::ModelMode, Tomahawk::collection_ptr ) ),
  845. const_cast<AudioEngine*>(this), SLOT( playItem( Tomahawk::album_ptr ) ), album );
  846. pli->tracks();
  847. }
  848. }
  849. void
  850. AudioEngine::playPlaylistInterface( const Tomahawk::playlistinterface_ptr& playlist )
  851. {
  852. if ( !playlist->hasFirstPlayableTrack() )
  853. {
  854. NewClosure( playlist.data(), SIGNAL( foundFirstPlayableTrack() ),
  855. const_cast<AudioEngine*>(this), SLOT( playPlaylistInterface( Tomahawk::playlistinterface_ptr ) ), playlist );
  856. return;
  857. }
  858. foreach ( const Tomahawk::query_ptr& query, playlist->tracks() )
  859. {
  860. if ( query->playable() )
  861. {
  862. playItem( playlist, query );
  863. return;
  864. }
  865. }
  866. // No playable track found
  867. JobStatusView::instance()->model()->addJob( new ErrorStatusMessage( tr( "Sorry, couldn't find any playable tracks" ), 15 ) );
  868. }
  869. void
  870. AudioEngine::onPlaylistNextTrackAvailable()
  871. {
  872. Q_D( AudioEngine );
  873. tDebug() << Q_FUNC_INFO;
  874. {
  875. // If in real-time and you have a few seconds left, you're probably lagging -- finish it up
  876. if ( d->playlist && d->playlist->latchMode() == PlaylistModes::RealTime && ( d->waitingOnNewTrack || d->currentTrack.isNull() || d->currentTrack->id() == 0 || ( currentTrackTotalTime() - currentTime() > 6000 ) ) )
  877. {
  878. d->waitingOnNewTrack = false;
  879. loadNextTrack();
  880. return;
  881. }
  882. if ( !d->waitingOnNewTrack )
  883. return;
  884. d->waitingOnNewTrack = false;
  885. loadNextTrack();
  886. }
  887. }
  888. void
  889. AudioEngine::timerTriggered( qint64 time )
  890. {
  891. Q_D( AudioEngine );
  892. emit timerMilliSeconds( time );
  893. if ( d->timeElapsed != time / 1000 )
  894. {
  895. d->timeElapsed = time / 1000;
  896. emit timerSeconds( d->timeElapsed );
  897. if ( !d->currentTrack.isNull() )
  898. {
  899. if ( d->currentTrack->track()->duration() == 0 )
  900. {
  901. emit timerPercentage( 0 );
  902. }
  903. else
  904. {
  905. emit timerPercentage( ( (double) d->timeElapsed / (double) d->currentTrack->track()->duration() ) * 100.0 );
  906. }
  907. }
  908. }
  909. }
  910. void
  911. AudioEngine::setQueue( const playlistinterface_ptr& queue )
  912. {
  913. Q_D( AudioEngine );
  914. if ( d->queue )
  915. {
  916. disconnect( d->queue.data(), SIGNAL( previousTrackAvailable( bool ) ), this, SIGNAL( controlStateChanged() ) );
  917. disconnect( d->queue.data(), SIGNAL( nextTrackAvailable( bool ) ), this, SIGNAL( controlStateChanged() ) );
  918. }
  919. d->queue = queue;
  920. if ( d->queue )
  921. {
  922. connect( d->queue.data(), SIGNAL( previousTrackAvailable( bool ) ), SIGNAL( controlStateChanged() ) );
  923. connect( d->queue.data(), SIGNAL( nextTrackAvailable( bool ) ), SIGNAL( controlStateChanged() ) );
  924. }
  925. }
  926. void
  927. AudioEngine::setPlaylist( Tomahawk::playlistinterface_ptr playlist )
  928. {
  929. Q_D( AudioEngine );
  930. if ( d->playlist == playlist )
  931. return;
  932. if ( !d->playlist.isNull() )
  933. {
  934. if ( d->playlist.data() )
  935. {
  936. disconnect( d->playlist.data(), SIGNAL( previousTrackAvailable( bool ) ) );
  937. disconnect( d->playlist.data(), SIGNAL( nextTrackAvailable( bool ) ) );
  938. disconnect( d->playlist.data(), SIGNAL( shuffleModeChanged( bool ) ) );
  939. disconnect( d->playlist.data(), SIGNAL( repeatModeChanged( Tomahawk::PlaylistModes::RepeatMode ) ) );
  940. }
  941. d->playlist.data()->reset();
  942. }
  943. if ( playlist.isNull() )
  944. {
  945. d->playlist.clear();
  946. emit playlistChanged( playlist );
  947. return;
  948. }
  949. d->playlist = playlist;
  950. d->stopAfterTrack.clear();
  951. if ( !d->playlist.isNull() )
  952. {
  953. connect( d->playlist.data(), SIGNAL( nextTrackAvailable( bool ) ), SLOT( onPlaylistNextTrackAvailable() ) );
  954. connect( d->playlist.data(), SIGNAL( previousTrackAvailable( bool ) ), SIGNAL( controlStateChanged() ) );
  955. connect( d->playlist.data(), SIGNAL( nextTrackAvailable( bool ) ), SIGNAL( controlStateChanged() ) );
  956. connect( d->playlist.data(), SIGNAL( shuffleModeChanged( bool ) ), SIGNAL( shuffleModeChanged( bool ) ) );
  957. connect( d->playlist.data(), SIGNAL( repeatModeChanged( Tomahawk::PlaylistModes::RepeatMode ) ), SIGNAL( repeatModeChanged( Tomahawk::PlaylistModes::RepeatMode ) ) );
  958. emit shuffleModeChanged( d->playlist.data()->shuffled() );
  959. emit repeatModeChanged( d->playlist.data()->repeatMode() );
  960. }
  961. emit playlistChanged( playlist );
  962. }
  963. void
  964. AudioEngine::setRepeatMode( Tomahawk::PlaylistModes::RepeatMode mode )
  965. {
  966. Q_D( AudioEngine );
  967. if ( !d->playlist.isNull() )
  968. {
  969. d->playlist.data()->setRepeatMode( mode );
  970. }
  971. }
  972. void
  973. AudioEngine::setShuffled( bool enabled )
  974. {
  975. Q_D( AudioEngine );
  976. if ( !d->playlist.isNull() )
  977. {
  978. d->playlist.data()->setShuffled( enabled );
  979. }
  980. }
  981. void
  982. AudioEngine::setStopAfterTrack( const query_ptr& query )
  983. {
  984. Q_D( AudioEngine );
  985. if ( d->stopAfterTrack != query )
  986. {
  987. d->stopAfterTrack = query;
  988. emit stopAfterTrackChanged();
  989. }
  990. }
  991. void
  992. AudioEngine::setCurrentTrack( const Tomahawk::result_ptr& result )
  993. {
  994. Q_D( AudioEngine );
  995. if ( !d->currentTrack.isNull() )
  996. {
  997. if ( d->state != Error && TomahawkSettings::instance()->privateListeningMode() == TomahawkSettings::PublicListening )
  998. {
  999. d->currentTrack->track()->finishPlaying( d->timeElapsed );
  1000. }
  1001. emit finished( d->currentTrack );
  1002. }
  1003. d->currentTrack = result;
  1004. if ( result )
  1005. {
  1006. if ( d->playlist && d->playlist->currentItem() != result )
  1007. {
  1008. d->playlist->setCurrentIndex( d->playlist->indexOfResult( result ) );
  1009. }
  1010. }
  1011. }
  1012. void
  1013. AudioEngine::setState( AudioState state )
  1014. {
  1015. Q_D( AudioEngine );
  1016. AudioState oldState = (AudioState) d->state;
  1017. d->state = state;
  1018. emit stateChanged( state, oldState );
  1019. }
  1020. qint64
  1021. AudioEngine::currentTime() const
  1022. {
  1023. return d_func()->audioOutput->currentTime();
  1024. }
  1025. qint64
  1026. AudioEngine::currentTrackTotalTime() const
  1027. {
  1028. Q_D( const AudioEngine );
  1029. // FIXME : This is too hacky. The problem is that I don't know why
  1030. // libVLC doesn't report total duration for stream data (imem://)
  1031. // But it's not a real problem for playback, since EndOfStream is emitted by libVLC itself
  1032. // This value is only used by AudioOutput to evaluate if it's close to end of stream
  1033. if ( d->audioOutput->totalTime() <= 0 && d->currentTrack && d->currentTrack->track() ) {
  1034. return d->currentTrack->track()->duration() * 1000 + 1000;
  1035. }
  1036. return d->audioOutput->totalTime();
  1037. }
  1038. unsigned int
  1039. AudioEngine::volume() const
  1040. {
  1041. return d_func()->audioOutput->volume() * 100.0;
  1042. }
  1043. AudioEngine::AudioState
  1044. AudioEngine::state() const
  1045. {
  1046. return (AudioState) d_func()->state;
  1047. }
  1048. bool
  1049. AudioEngine::isPlaying() const
  1050. {
  1051. return d_func()->state == Playing;
  1052. }
  1053. bool
  1054. AudioEngine::isPaused() const
  1055. {
  1056. return d_func()->state == Paused;
  1057. }
  1058. bool
  1059. AudioEngine::isStopped() const
  1060. {
  1061. return d_func()->state == Stopped;
  1062. }
  1063. playlistinterface_ptr
  1064. AudioEngine::currentTrackPlaylist() const
  1065. {
  1066. return d_func()->currentTrackPlaylist;
  1067. }
  1068. playlistinterface_ptr
  1069. AudioEngine::playlist() const
  1070. {
  1071. return d_func()->playlist;
  1072. }
  1073. result_ptr
  1074. AudioEngine::currentTrack() const
  1075. {
  1076. return d_func()->currentTrack;
  1077. }
  1078. query_ptr
  1079. AudioEngine::stopAfterTrack() const
  1080. {
  1081. return d_func()->stopAfterTrack;
  1082. }
  1083. void
  1084. AudioEngine::onVolumeChanged( qreal volume )
  1085. {
  1086. emit volumeChanged( volume * 100 );
  1087. }
  1088. void
  1089. AudioEngine::setCurrentTrackPlaylist( const playlistinterface_ptr& playlist )
  1090. {
  1091. Q_D( AudioEngine );
  1092. if ( d->currentTrackPlaylist != playlist )
  1093. {
  1094. d->currentTrackPlaylist = playlist;
  1095. emit currentTrackPlaylistChanged( d->currentTrackPlaylist );
  1096. }
  1097. }
  1098. void
  1099. AudioEngine::setDspCallback( std::function< void( int state, int frameNumber, float* samples, int nb_channels, int nb_samples ) > cb )
  1100. {
  1101. Q_D( AudioEngine );
  1102. d->audioOutput->setDspCallback( cb );
  1103. }