MythTV  0.27pre
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Groups Pages
musicplayer.cpp
Go to the documentation of this file.
1 // ANSI C includes
2 #include <cstdlib>
3 
4 // qt
5 #include <QApplication>
6 #include <QWidget>
7 #include <QFile>
8 #include <QList>
9 #include <QDir>
10 
11 // mythtv
12 #include <mythcontext.h>
13 #include <audiooutput.h>
14 #include <mythdb.h>
15 #include <mythdialogbox.h>
16 #include <mythmainwindow.h>
17 #include <musicutils.h>
18 
19 // mythmusic
20 #include "musicdata.h"
21 #include "musicplayer.h"
22 #include "decoder.h"
23 #include "decoderhandler.h"
24 #include "metaio.h"
25 #ifdef HAVE_CDIO
26 #include "cddecoder.h"
27 #endif
28 #include "constants.h"
29 #include "mainvisual.h"
30 #include "miniplayer.h"
31 #include "playlistcontainer.h"
32 
33 // how long to wait before updating the lastplay and playcount fields
34 #define LASTPLAY_DELAY 15
35 
37 QString gCDdevice = "";
38 
40 
41 QEvent::Type MusicPlayerEvent::TrackChangeEvent = (QEvent::Type) QEvent::registerEventType();
42 QEvent::Type MusicPlayerEvent::VolumeChangeEvent = (QEvent::Type) QEvent::registerEventType();
43 QEvent::Type MusicPlayerEvent::TrackAddedEvent = (QEvent::Type) QEvent::registerEventType();
44 QEvent::Type MusicPlayerEvent::TrackRemovedEvent = (QEvent::Type) QEvent::registerEventType();
45 QEvent::Type MusicPlayerEvent::AllTracksRemovedEvent = (QEvent::Type) QEvent::registerEventType();
46 QEvent::Type MusicPlayerEvent::MetadataChangedEvent = (QEvent::Type) QEvent::registerEventType();
47 QEvent::Type MusicPlayerEvent::TrackStatsChangedEvent = (QEvent::Type) QEvent::registerEventType();
48 QEvent::Type MusicPlayerEvent::AlbumArtChangedEvent = (QEvent::Type) QEvent::registerEventType();
49 QEvent::Type MusicPlayerEvent::CDChangedEvent = (QEvent::Type) QEvent::registerEventType();
50 QEvent::Type MusicPlayerEvent::PlaylistChangedEvent = (QEvent::Type) QEvent::registerEventType();
51 QEvent::Type MusicPlayerEvent::PlayedTracksChangedEvent = (QEvent::Type) QEvent::registerEventType();
52 
53 MusicPlayer::MusicPlayer(QObject *parent)
54  :QObject(parent)
55 {
56  setObjectName("MusicPlayer");
57 
58  m_output = NULL;
59  m_decoderHandler = NULL;
60  m_currentPlaylist = NULL;
61  m_currentTrack = -1;
62 
63  m_currentTime = 0;
64  m_lastTrackStart = 0;
65 
67  m_bufferSize = 0;
68 
69  m_currentMetadata = NULL;
70  m_oneshotMetadata = NULL;
71 
72  m_isAutoplay = false;
73  m_isPlaying = false;
75  m_canShowPlayer = true;
76  m_wasPlaying = true;
77  m_updatedLastplay = false;
78  m_allowRestorePos = true;
79 
80  m_playSpeed = 1.0;
81 
82  QString playmode = gCoreContext->GetSetting("PlayMode", "none");
83  if (playmode.toLower() == "random")
85  else if (playmode.toLower() == "intelligent")
87  else if (playmode.toLower() == "album")
89  else if (playmode.toLower() == "artist")
91  else
93 
94  QString repeatmode = gCoreContext->GetSetting("RepeatMode", "all");
95  if (repeatmode.toLower() == "track")
97  else if (repeatmode.toLower() == "all")
99  else
101 
102  loadSettings();
103 
104  gCoreContext->addListener(this);
105 }
106 
108 {
109  if (!hasClient())
110  savePosition();
111 
113 
114  stop(true);
115 
116  if (m_decoderHandler)
117  {
119  m_decoderHandler->deleteLater();
120  m_decoderHandler = NULL;
121  }
122 
123  if (m_oneshotMetadata)
124  {
125  delete m_oneshotMetadata;
126  m_oneshotMetadata = NULL;
127  }
128 
129  while (!m_playedList.empty())
130  {
131  delete m_playedList.back();
132  m_playedList.pop_back();
133  }
134 
136  gCoreContext->SaveSetting("PlayMode", "intelligent");
137  else if (m_shuffleMode == SHUFFLE_RANDOM)
138  gCoreContext->SaveSetting("PlayMode", "random");
139  else if (m_shuffleMode == SHUFFLE_ALBUM)
140  gCoreContext->SaveSetting("PlayMode", "album");
141  else if (m_shuffleMode == SHUFFLE_ARTIST)
142  gCoreContext->SaveSetting("PlayMode", "artist");
143  else
144  gCoreContext->SaveSetting("PlayMode", "none");
145 
146  if (m_repeatMode == REPEAT_TRACK)
147  gCoreContext->SaveSetting("RepeatMode", "track");
148  else if (m_repeatMode == REPEAT_ALL)
149  gCoreContext->SaveSetting("RepeatMode", "all");
150  else
151  gCoreContext->SaveSetting("RepeatMode", "none");
152 
153  gCoreContext->SaveSetting("MusicAutoShowPlayer",
154  (m_autoShowPlayer ? "1" : "1"));
155 }
156 
157 void MusicPlayer::addListener(QObject *listener)
158 {
159  if (listener && m_output)
160  m_output->addListener(listener);
161 
162  if (listener && getDecoder())
163  getDecoder()->addListener(listener);
164 
165  if (listener && m_decoderHandler)
166  m_decoderHandler->addListener(listener);
167 
168  MythObservable::addListener(listener);
169 
171 }
172 
173 void MusicPlayer::removeListener(QObject *listener)
174 {
175  if (listener && m_output)
176  m_output->removeListener(listener);
177 
178  if (listener && getDecoder())
179  getDecoder()->removeListener(listener);
180 
181  if (listener && m_decoderHandler)
182  m_decoderHandler->removeListener(listener);
183 
185 
187 }
188 
190 {
191  if (visual && !m_visualisers.contains(visual))
192  {
193  if (m_output)
194  {
195  m_output->addListener(visual);
196  m_output->addVisual(visual);
197  }
198 
199  m_visualisers.insert(visual);
200  }
201 }
202 
204 {
205  if (visual)
206  {
207  if (m_output)
208  {
209  m_output->removeListener(visual);
210  m_output->removeVisual(visual);
211  }
212 
213  m_visualisers.remove(visual);
214  }
215 }
216 
218 {
219  QString resumestring = gCoreContext->GetSetting("ResumeMode", "off");
220  if (resumestring.toLower() == "off")
222  else if (resumestring.toLower() == "track")
224  else
226 
227  m_lastplayDelay = gCoreContext->GetNumSetting("MusicLastPlayDelay", LASTPLAY_DELAY);
228 
229  m_autoShowPlayer = (gCoreContext->GetNumSetting("MusicAutoShowPlayer", 1) > 0);
230 }
231 
232 // this stops playing the playlist and plays the file pointed to by mdata
234 {
235  if (m_oneshotMetadata)
236  {
237  delete m_oneshotMetadata;
238  m_oneshotMetadata = NULL;
239  }
240 
242  *m_oneshotMetadata = mdata;
243 
244  play();
245 }
246 
247 void MusicPlayer::stop(bool stopAll)
248 {
249  stopDecoder();
250 
251  if (m_output)
252  {
253  if (m_output->IsPaused())
254  pause();
255  m_output->Reset();
256  }
257 
258  m_isPlaying = false;
259 
260  if (stopAll && getDecoder())
261  {
262  getDecoder()->removeListener(this);
263 
264  // remove any listeners from the decoder
265  {
266  QMutexLocker locker(m_lock);
267  QSet<QObject*>::const_iterator it = m_listeners.begin();
268  for (; it != m_listeners.end() ; ++it)
269  {
270  getDecoder()->removeListener(*it);
271  }
272  }
273  }
274 
275  if (stopAll && m_output)
276  {
277  m_output->removeListener(this);
278  delete m_output;
279  m_output = NULL;
280  }
281 
282  // because we don't actually stop the audio output we have to fake a Stopped
283  // event so any listeners can act on it
285  dispatch(oe);
286 
288 }
289 
291 {
292  if (m_output)
293  {
296  }
297  // wake up threads
298  if (getDecoder())
299  {
300  getDecoder()->lock();
301  getDecoder()->cond()->wakeAll();
302  getDecoder()->unlock();
303  }
304 
306 }
307 
309 {
311  if (!meta)
312  return;
313 
314  stopDecoder();
315 
316 
317  if (!m_output)
318  {
319  if (!openOutputDevice())
320  return;
321  }
322 
323  if (!getDecoderHandler())
325 
326  getDecoderHandler()->start(meta);
327 
329 }
330 
332 {
333  if (getDecoderHandler())
334  getDecoderHandler()->stop();
335 }
336 
338 {
339  QString adevice, pdevice;
340 
341  if (gCoreContext->GetSetting("MusicAudioDevice") == "default")
342  adevice = gCoreContext->GetSetting("AudioOutputDevice");
343  else
344  adevice = gCoreContext->GetSetting("MusicAudioDevice");
345 
346  pdevice = gCoreContext->GetNumSetting("PassThruDeviceOverride", false) ?
347  gCoreContext->GetSetting("PassThruOutputDevice") : "auto";
348 
350  adevice, pdevice, FORMAT_S16, 2, 0, 44100,
351  AUDIOOUTPUT_MUSIC, true, false,
352  gCoreContext->GetNumSetting("MusicDefaultUpmix", 0) + 1);
353 
354  if (!m_output)
355  {
356  LOG(VB_GENERAL, LOG_ERR,
357  QString("MusicPlayer: Cannot open audio output device: %1").arg(adevice));
358 
359  return false;
360  }
361 
362  if (!m_output->GetError().isEmpty())
363  {
364  LOG(VB_GENERAL, LOG_ERR,
365  QString("MusicPlayer: Cannot open audio output device: %1").arg(adevice));
366  LOG(VB_GENERAL, LOG_ERR,
367  QString("Error was: %1").arg(m_output->GetError()));
368 
369  delete m_output;
370  m_output = NULL;
371 
372  return false;
373  }
374 
375  m_output->setBufferSize(256 * 1024);
376 
377  m_output->addListener(this);
378 
379  // add any visuals to the audio output
380  QSet<QObject*>::const_iterator it = m_visualisers.begin();
381 
382  for (; it != m_visualisers.end() ; ++it)
383  {
385  }
386 
387  // add any listeners to the audio output
388  QMutexLocker locker(m_lock);
389  it = m_listeners.begin();
390  for (; it != m_listeners.end() ; ++it)
391  {
392  m_output->addListener(*it);
393  }
394 
395  return true;
396 }
397 
399 {
400  int currentTrack = m_currentTrack;
401 
402  if (!m_currentPlaylist)
403  return;
404 
405  if (m_oneshotMetadata)
406  {
407  delete m_oneshotMetadata;
408  m_oneshotMetadata = NULL;
409  }
410  else
411  currentTrack++;
412 
413  if (currentTrack >= m_currentPlaylist->getSongs().size())
414  {
415  if (m_repeatMode == REPEAT_ALL)
416  {
417  // start playing again from first track
418  currentTrack = 0;
419  }
420  else
421  {
422  stop();
423  return;
424  }
425  }
426 
427  changeCurrentTrack(currentTrack);
428 
429  if (m_currentMetadata)
430  play();
431  else
432  stop();
433 }
434 
436 {
437  int currentTrack = m_currentTrack;
438 
439  if (!m_currentPlaylist)
440  return;
441 
442  if (m_oneshotMetadata)
443  {
444  delete m_oneshotMetadata;
445  m_oneshotMetadata = NULL;
446  }
447  else
448  currentTrack--;
449 
450  if (currentTrack >= 0)
451  {
452  changeCurrentTrack(currentTrack);
453 
454  if (m_currentMetadata)
455  play();
456  else
457  return;//stop();
458  }
459  else
460  {
461  // FIXME take repeat mode into account
462  return; //stop();
463  }
464 }
465 
467 {
468  if (!m_currentPlaylist)
469  return;
470 
471  if (m_oneshotMetadata)
472  {
473  delete m_oneshotMetadata;
474  m_oneshotMetadata = NULL;
475  play();
476  return;
477  }
478 
479  if (m_repeatMode == REPEAT_TRACK)
480  {
481  play();
482  return;
483  }
484  else
485  {
486  if (!m_decoderHandler->next())
487  next();
488  }
489 
490  // if we don't already have a gui attached show the miniplayer if configured to do so
492  {
493  MythScreenStack *popupStack =
494  GetMythMainWindow()->GetStack("popup stack");
495 
496  MiniPlayer *miniplayer = new MiniPlayer(popupStack);
497 
498  if (miniplayer->Create())
499  popupStack->AddScreen(miniplayer);
500  else
501  delete miniplayer;
502  }
503 }
504 
505 void MusicPlayer::customEvent(QEvent *event)
506 {
507  // handle decoderHandler events
508  if (event->type() == DecoderHandlerEvent::Ready)
509  {
511  }
512  else if (event->type() == DecoderHandlerEvent::Meta)
513  {
514  DecoderHandlerEvent *dhe = dynamic_cast<DecoderHandlerEvent*>(event);
515  if (!dhe)
516  return;
517 
518  MusicMetadata *mdata = new MusicMetadata(*dhe->getMetadata());
519 
521 
522  mdata->setID(m_currentMetadata->ID());
523  mdata->setTrack(m_playedList.count() + 1);
526 
527  m_playedList.append(mdata);
528 
530 
532  {
533  MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
534 
535  MiniPlayer *miniplayer = new MiniPlayer(popupStack);
536 
537  if (miniplayer->Create())
538  popupStack->AddScreen(miniplayer);
539  else
540  delete miniplayer;
541  }
542 
543  // tell any listeners we've added a new track to the played list
544  MusicPlayerEvent me(MusicPlayerEvent::PlayedTracksChangedEvent, mdata->ID());
545  dispatch(me);
546  }
547  // handle MythEvent events
548  else if (event->type() == MythEvent::MythEventMessage)
549  {
550  MythEvent *me = dynamic_cast<MythEvent*>(event);
551 
552  if (!me)
553  return;
554 
555  if (me->Message().left(14) == "PLAYBACK_START")
556  {
558  QString hostname = me->Message().mid(15);
559 
560  if (hostname == gCoreContext->GetHostName())
561  {
562  if (m_isPlaying)
563  savePosition();
564  stop(true);
565  }
566  }
567  else if (me->Message().left(12) == "PLAYBACK_END")
568  {
569  if (m_wasPlaying)
570  {
571  QString hostname = me->Message().mid(13);
572  if (hostname == gCoreContext->GetHostName())
573  {
574  play();
576  "MusicBookmarkPosition", 0));
577  gCoreContext->SaveSetting("MusicBookmark", "");
578  gCoreContext->SaveSetting("MusicBookmarkPosition", 0);
579  }
580 
581  m_wasPlaying = false;
582  }
583  }
584  else if (me->Message().left(13) == "MUSIC_COMMAND")
585  {
586  QStringList list = me->Message().simplified().split(' ');
587 
588  if (list.size() >= 3 && list[1] == gCoreContext->GetHostName())
589  {
590  if (list[2] == "PLAY")
591  play();
592  else if (list[2] == "STOP")
593  stop();
594  else if (list[2] == "PAUSE")
595  pause();
596  else if (list[2] == "SET_VOLUME")
597  {
598  if (list.size() >= 3)
599  {
600  int volume = list[3].toInt();
601  if (volume >= 0 && volume <= 100)
602  setVolume(volume);
603  }
604  }
605  else if (list[2] == "GET_VOLUME")
606  {
607  QString message = QString("MUSIC_CONTROL ANSWER %1 %2")
608  .arg(gCoreContext->GetHostName()).arg(getVolume());
609  MythEvent me(message);
610  gCoreContext->dispatch(me);
611  }
612  else if (list[2] == "PLAY_FILE")
613  {
614  int start = me->Message().indexOf("'");
615  int end = me->Message().lastIndexOf("'");
616 
617  if (start != -1 && end != -1 && start != end)
618  {
619  QString filename = me->Message().mid(start + 1, end - start - 1);
620  MusicMetadata mdata;
621  mdata.setFilename(filename);
622  playFile(mdata);
623  }
624  else
625  LOG(VB_GENERAL, LOG_ERR,
626  QString("MusicPlayer: got invalid MUSIC_COMMAND "
627  "PLAY_FILE - %1").arg(me->Message()));
628  }
629  else if (list[2] == "PLAY_URL")
630  {
631  if (list.size() == 4)
632  {
633  QString filename = list[3];
634  MusicMetadata mdata;
635  mdata.setFilename(filename);
636  playFile(mdata);
637  }
638  else
639  LOG(VB_GENERAL, LOG_ERR,
640  QString("MusicPlayer: got invalid MUSIC_COMMAND "
641  "PLAY_URL - %1").arg(me->Message()));
642  }
643  else if (list[2] == "PLAY_TRACK")
644  {
645  if (list.size() == 4)
646  {
647  int trackID = list[3].toInt();
648  MusicMetadata *mdata = gMusicData->all_music->getMetadata(trackID);
649  if (mdata)
650  playFile(*mdata);
651  }
652  else
653  LOG(VB_GENERAL, LOG_ERR,
654  QString("MusicPlayer: got invalid MUSIC_COMMAND "
655  "PLAY_TRACK - %1").arg(me->Message()));
656  }
657  else if (list[2] == "GET_METADATA")
658  {
659  QString mdataStr;
661  if (mdata)
662  mdataStr = QString("%1 by %2 from %3").arg(mdata->Title()).arg(mdata->Artist()).arg(mdata->Album());
663  else
664  mdataStr = "Unknown Track2";
665 
666  QString message = QString("MUSIC_CONTROL ANSWER %1 %2")
667  .arg(gCoreContext->GetHostName()).arg(mdataStr);
668  MythEvent me(message);
669  gCoreContext->dispatch(me);
670  }
671  }
672  else
673  LOG(VB_GENERAL, LOG_ERR,
674  QString("MusicPlayer: got unknown/invalid MUSIC_COMMAND "
675  "- %1").arg(me->Message()));
676  }
677  else if (me->Message().startsWith("MUSIC_SETTINGS_CHANGED"))
678  {
679  QString startdir = gCoreContext->GetSetting("MusicLocation");
680  startdir = QDir::cleanPath(startdir);
681  if (!startdir.isEmpty() && !startdir.endsWith("/"))
682  startdir += "/";
683 
684  setMusicDirectory(startdir);
685 
686  loadSettings();
687  }
688  else if (me->Message().startsWith("MUSIC_METADATA_CHANGED"))
689  {
690  if (gMusicData->initialized)
691  {
692  QStringList list = me->Message().simplified().split(' ');
693  if (list.size() == 2)
694  {
695  int songID = list[1].toInt();
696  MusicMetadata *mdata = gMusicData->all_music->getMetadata(songID);
697 
698  if (mdata)
699  {
700  mdata->reloadMetadata();
701 
702  // tell any listeners the metadata has changed for this track
703  sendMetadataChangedEvent(songID);
704  }
705  }
706  }
707  }
708  }
709 
710  if (m_isAutoplay)
711  {
712  if (event->type() == OutputEvent::Error)
713  {
714  OutputEvent *aoe = dynamic_cast<OutputEvent*>(event);
715 
716  if (!aoe)
717  return;
718 
719  LOG(VB_GENERAL, LOG_ERR, QString("Output Error - %1")
720  .arg(*aoe->errorMessage()));
721 
722  ShowOkPopup(QString("MythMusic has encountered the following error:\n%1")
723  .arg(*aoe->errorMessage()));
724  stop(true);
725  }
726  else if (event->type() == DecoderEvent::Error)
727  {
728  stop(true);
729 
730  QApplication::sendPostedEvents();
731 
732  DecoderEvent *dxe = dynamic_cast<DecoderEvent*>(event);
733 
734  if (!dxe)
735  return;
736 
737  LOG(VB_GENERAL, LOG_ERR, QString("Decoder Error - %1")
738  .arg(*dxe->errorMessage()));
739  ShowOkPopup(QString("MythMusic has encountered the following error:\n%1")
740  .arg(*dxe->errorMessage()));
741  }
742  else if (event->type() == DecoderHandlerEvent::Error)
743  {
744  DecoderHandlerEvent *dhe = dynamic_cast<DecoderHandlerEvent*>(event);
745  if (!dhe)
746  return;
747 
748  LOG(VB_GENERAL, LOG_ERR, QString("Output Error - %1")
749  .arg(*dhe->getMessage()));
750 
751  ShowOkPopup(QString("MythMusic has encountered the following error:\n%1")
752  .arg(*dhe->getMessage()));
753  stop(true);
754  }
755  }
756 
757  if (event->type() == OutputEvent::Info)
758  {
759  OutputEvent *oe = dynamic_cast<OutputEvent*>(event);
760 
761  if (!oe)
762  return;
763 
766  else
768 
770  {
771  // we update the lastplay and playcount after playing
772  // for m_lastplayDelay seconds or half the total track time
774  (m_currentMetadata->Length() / 1000) / 2) ||
776  {
777  updateLastplay();
778  }
779  }
780 
781  // update the current tracks time in the last played list
782  if (m_playMode == PLAYMODE_RADIO)
783  {
784  if (!m_playedList.isEmpty())
785  {
786  m_playedList.last()->setLength(m_currentTime * 1000);
787  // this will update any track lengths displayed on screen
788  gPlayer->sendMetadataChangedEvent(m_playedList.last()->ID());
789  }
790  }
791  }
792  else if (event->type() == DecoderEvent::Finished)
793  {
796  {
797  LOG(VB_GENERAL, LOG_NOTICE, QString("MusicPlayer: Updating track length was %1s, should be %2s")
798  .arg(m_currentMetadata->Length() / 1000).arg(m_currentTime));
799 
802 
803  // this will update any track lengths displayed on screen
805 
806  // this will force the playlist stats to update
808  dispatch(me);
809  }
810 
811  nextAuto();
812  }
813  else if (event->type() == DecoderEvent::Stopped)
814  {
815  }
816  else if (event->type() == DecoderHandlerEvent::BufferStatus)
817  {
818  DecoderHandlerEvent *dhe = dynamic_cast<DecoderHandlerEvent*>(event);
819  if (!dhe)
820  return;
821 
823  }
824 
825  QObject::customEvent(event);
826 }
827 
828 void MusicPlayer::getBufferStatus(int *bufferAvailable, int *bufferSize)
829 {
830  *bufferAvailable = m_bufferAvailable;
831  *bufferSize = m_bufferSize;
832 }
833 
835 {
836  if (m_playMode == mode)
837  return;
838 
839  savePosition();
840 
841  m_playMode = mode;
842 
843  loadPlaylist();
844 }
845 
847 {
848  if (m_playMode == PLAYMODE_RADIO)
849  {
851 
853  {
854  int bookmark = gCoreContext->GetNumSetting("MusicRadioBookmark", 0);
855  if (bookmark < 0 || bookmark >= m_currentPlaylist->getSongs().size())
856  bookmark = 0;
857 
858  m_currentTrack = bookmark;
859  }
860  else
861  m_currentTrack = 0;
862 
864  }
865  else
866  {
868 
870  {
871  int bookmark = gCoreContext->GetNumSetting("MusicBookmark", 0);
872  if (bookmark < 0 || bookmark >= m_currentPlaylist->getSongs().size())
873  bookmark = 0;
874 
875  m_currentTrack = bookmark;
876  }
877  else
878  m_currentTrack = 0;
879  }
880 
881  m_currentMetadata = NULL;
882 }
883 
885 {
886  // create the radio playlist
890 
891  for (int x = 0; x < list->count(); x++)
892  {
893  MusicMetadata *mdata = list->at(x);
894  gMusicData->all_playlists->getStreamPlaylist()->addTrack(mdata->ID(), false);
895  }
896 
898 }
899 
900 void MusicPlayer::moveTrackUpDown(bool moveUp, int whichTrack)
901 {
902  if (moveUp && whichTrack <= 0)
903  return;
904 
905  if (!moveUp && whichTrack >= m_currentPlaylist->getSongs().size() - 1)
906  return;
907 
909 
910  m_currentPlaylist->moveTrackUpDown(moveUp, whichTrack);
911 
912  m_currentTrack = m_currentPlaylist->getSongs().indexOf(currTrack);
913 }
914 
916 {
917  changeCurrentTrack(pos);
918 
919  if (!m_currentMetadata)
920  {
921  stop();
922  return false;
923  }
924 
925  play();
926 
927  return true;
928 }
929 
931 {
932  // can't save a bookmark if we don't know what we are playing
933  if (!m_currentMetadata)
934  return;
935 
936  if (m_playMode == PLAYMODE_RADIO)
937  {
938  gCoreContext->SaveSetting("MusicRadioBookmark", m_currentMetadata->ID());
939  }
940  else
941  {
942  gCoreContext->SaveSetting("MusicBookmark", m_currentMetadata->ID());
943  gCoreContext->SaveSetting("MusicBookmarkPosition", m_currentTime);
944  }
945 }
946 
948 {
949  // if we are switching views we don't wont to restore the position
950  if (!m_allowRestorePos)
951  return;
952 
953  m_currentTrack = 0;
954  uint id = -1;
955 
956  if (gPlayer->getResumeMode() > MusicPlayer::RESUME_OFF)
957  {
958  if (m_playMode == PLAYMODE_RADIO)
959  id = gCoreContext->GetNumSetting("MusicRadioBookmark", 0);
960  else
961  id = gCoreContext->GetNumSetting("MusicBookmark", 0);
962  }
963 
964  for (int x = 0; x < m_currentPlaylist->getSongs().size(); x++)
965  {
966  if (m_currentPlaylist->getSongs().at(x)->ID() == id)
967  {
968  m_currentTrack = x;
969  break;
970  }
971  }
972 
974 
975  if (m_currentMetadata)
976  {
977  play();
978 
980  seek(gCoreContext->GetNumSetting("MusicBookmarkPosition", 0));
981  }
982 }
983 
984 void MusicPlayer::seek(int pos)
985 {
986  if (m_output)
987  {
988  if (getDecoder() && getDecoder()->isRunning())
989  getDecoder()->seek(pos);
990 
991  m_output->SetTimecode(pos*1000);
992  }
993 }
994 
996 {
997  if (m_canShowPlayer)
998  {
999  MythScreenStack *popupStack =
1000  GetMythMainWindow()->GetStack("popup stack");
1001 
1002  MiniPlayer *miniplayer = new MiniPlayer(popupStack);
1003 
1004  if (miniplayer->Create())
1005  popupStack->AddScreen(miniplayer);
1006  else
1007  delete miniplayer;
1008  }
1009 }
1010 
1013 {
1014  if (!m_currentPlaylist)
1015  return;
1016 
1017  // check to see if we need to save the current tracks volatile metadata (playcount, last played etc)
1019 
1020  m_currentTrack = trackNo;
1021 
1022  // sanity check the current track
1023  if (m_currentTrack < 0 || m_currentTrack >= m_currentPlaylist->getSongs().size())
1024  {
1025  LOG(VB_GENERAL, LOG_ERR,
1026  QString("MusicPlayer: asked to set the current track to an invalid track no. %1")
1027  .arg(trackNo));
1028  m_currentTrack = -1;
1029  m_currentMetadata = NULL;
1030  return;
1031  }
1032 
1034 }
1035 
1038 {
1039  if (m_oneshotMetadata)
1040  return m_oneshotMetadata;
1041 
1042  if (m_currentMetadata)
1043  return m_currentMetadata;
1044 
1046  return NULL;
1047 
1049 
1050  return m_currentMetadata;
1051 }
1052 
1055 {
1056  if (m_playMode == PLAYMODE_RADIO)
1057  return NULL;
1058 
1059  if (m_oneshotMetadata)
1060  return m_currentMetadata;
1061 
1063  return NULL;
1064 
1065  if (m_repeatMode == REPEAT_TRACK)
1066  return getCurrentMetadata();
1067 
1068  // if we are not playing the last track then just return the next track
1069  if (m_currentTrack < m_currentPlaylist->getSongs().size() - 1)
1071  else
1072  {
1073  // if we are playing the last track then we need to take the
1074  // repeat mode into account
1075  if (m_repeatMode == REPEAT_ALL)
1076  return m_currentPlaylist->getSongAt(0);
1077  else
1078  return NULL;
1079  }
1080 
1081  return NULL;
1082 }
1083 
1085 {
1086  switch (m_repeatMode)
1087  {
1088  case REPEAT_OFF:
1090  break;
1091  case REPEAT_TRACK:
1093  break;
1094  case REPEAT_ALL:
1096  break;
1097  default:
1099  break;
1100  }
1101 
1102  return m_repeatMode;
1103 }
1104 
1106 {
1107  switch (m_shuffleMode)
1108  {
1109  case SHUFFLE_OFF:
1111  break;
1112  case SHUFFLE_RANDOM:
1114  break;
1115  case SHUFFLE_INTELLIGENT:
1117  break;
1118  case SHUFFLE_ALBUM:
1120  break;
1121  case SHUFFLE_ARTIST:
1123  break;
1124  default:
1126  break;
1127  }
1128 
1130 
1131  return m_shuffleMode;
1132 }
1133 
1135 {
1136  int curTrackID = -1;
1137  if (getCurrentMetadata())
1138  curTrackID = getCurrentMetadata()->ID();
1139 
1140  m_shuffleMode = mode;
1141 
1142  if (m_currentPlaylist)
1144 
1145  if (curTrackID != -1)
1146  {
1147  for (int x = 0; x < getPlaylist()->getSongs().size(); x++)
1148  {
1149  MusicMetadata *mdata = getPlaylist()->getSongs().at(x);
1150  if (mdata && mdata->ID() == (MusicMetadata::IdType) curTrackID)
1151  {
1152  m_currentTrack = x;
1153  break;
1154  }
1155  }
1156  }
1157 }
1158 
1160 {
1162  {
1165  }
1166 
1167  m_updatedLastplay = true;
1168 }
1169 
1171 {
1173  {
1175  {
1177 
1178  // only write the playcount & rating to the tag if it's enabled by the user
1179  if (GetMythDB()->GetNumSetting("AllowTagWriting", 0) == 1)
1180  {
1182 
1183  if (tagger)
1184  {
1186  delete tagger;
1187  }
1188  }
1189 
1191  }
1192  }
1193 }
1194 
1195 void MusicPlayer::setSpeed(float newspeed)
1196 {
1197  if (m_output)
1198  {
1199  m_playSpeed = newspeed;
1201  }
1202 }
1203 
1205 {
1206  m_playSpeed += 0.05;
1208 }
1209 
1211 {
1212  m_playSpeed -= 0.05;
1214 }
1215 
1217 {
1218  MusicPlayerEvent me(MusicPlayerEvent::VolumeChangeEvent, getVolume(), isMuted());
1219  dispatch(me);
1220 }
1221 
1223 {
1224  MusicPlayerEvent me(MusicPlayerEvent::MetadataChangedEvent, trackID);
1225  dispatch(me);
1226 }
1227 
1229 {
1230  MusicPlayerEvent me(MusicPlayerEvent::TrackStatsChangedEvent, trackID);
1231  dispatch(me);
1232 }
1233 
1235 {
1236  MusicPlayerEvent me(MusicPlayerEvent::AlbumArtChangedEvent, trackID);
1237  dispatch(me);
1238 }
1239 
1241 {
1242  MusicPlayerEvent me(MusicPlayerEvent::CDChangedEvent, -1);
1243  dispatch(me);
1244 }
1245 
1247 {
1248  if (getOutput())
1249  {
1252  }
1253 }
1254 
1256 {
1257  if (getOutput())
1258  {
1261  }
1262 }
1263 
1264 void MusicPlayer::setVolume(int volume)
1265 {
1266  if (getOutput())
1267  {
1268  getOutput()->SetCurrentVolume(volume);
1270  }
1271 }
1272 
1274 {
1275  if (m_output)
1276  return m_output->GetCurrentVolume();
1277  return 0;
1278 }
1279 
1281 {
1282  if (m_output)
1283  {
1284  m_output->ToggleMute();
1286  }
1287 }
1288 
1290 {
1291  if (m_output)
1292  return m_output->GetMuteState();
1293  return kMuteOff;
1294 }
1295 
1296 void MusicPlayer::toMap(QHash<QString, QString> &map)
1297 {
1298  map["volumemute"] = isMuted() ? tr("%1% (Muted)", "Zero Audio Volume").arg(getVolume()) :
1299  QString("%1%").arg(getVolume());
1300  map["volume"] = QString("%1").arg(getVolume());
1301  map["volumepercent"] = QString("%1%").arg(getVolume());
1302  map["mute"] = isMuted() ? tr("Muted") : "";
1303 }
1304 
1305 void MusicPlayer::activePlaylistChanged(int trackID, bool deleted)
1306 {
1307  if (trackID == -1)
1308  {
1309  if (deleted)
1310  {
1311  // all tracks were removed
1312  m_currentTrack = -1;
1313  m_currentMetadata = NULL;
1314  stop(true);
1315  MusicPlayerEvent me(MusicPlayerEvent::AllTracksRemovedEvent, 0);
1316  dispatch(me);
1317  }
1318  else
1319  {
1320  MusicPlayerEvent me(MusicPlayerEvent::TrackAddedEvent, trackID);
1321  dispatch(me);
1322  }
1323  }
1324  else
1325  {
1326  if (deleted)
1327  {
1328  MusicPlayerEvent me(MusicPlayerEvent::TrackRemovedEvent, trackID);
1329  dispatch(me);
1330  }
1331  else
1332  {
1333  MusicPlayerEvent me(MusicPlayerEvent::TrackAddedEvent, trackID);
1334  dispatch(me);
1335  }
1336  }
1337 }
1338 
1339 void MusicPlayer::playlistChanged(int playlistID)
1340 {
1341  MusicPlayerEvent me(MusicPlayerEvent::PlaylistChangedEvent, playlistID);
1342  dispatch(me);
1343 }
1344 
1346 {
1349 
1350  // add any listeners to the decoderHandler
1351  {
1352  QMutexLocker locker(m_lock);
1353  QSet<QObject*>::const_iterator it = m_listeners.begin();
1354  for (; it != m_listeners.end() ; ++it)
1355  {
1357  }
1358  }
1359 }
1360 
1362 {
1363  LOG(VB_PLAYBACK, LOG_INFO, QString ("decoder handler is ready, decoding %1")
1364  .arg(getDecoder()->getFilename()));
1365 
1366 #ifdef HAVE_CDIO
1367  CdDecoder *cddecoder = dynamic_cast<CdDecoder*>(getDecoder());
1368  if (cddecoder)
1369  cddecoder->setDevice(gCDdevice);
1370 #endif
1371 
1373  //getDecoder()-> setBlockSize(2 * 1024);
1374  getDecoder()->addListener(this);
1375 
1376  // add any listeners to the decoder
1377  {
1378  QMutexLocker locker(m_lock);
1379  QSet<QObject*>::const_iterator it = m_listeners.begin();
1380  for (; it != m_listeners.end() ; ++it)
1381  {
1382  getDecoder()->addListener(*it);
1383  }
1384  }
1385 
1386  m_currentTime = 0;
1387  m_lastTrackStart = 0;
1388 
1389  QSet<QObject*>::const_iterator it = m_visualisers.begin();
1390  for (; it != m_visualisers.end() ; ++it)
1391  {
1392  //m_output->addVisual((MythTV::Visual*)(*it));
1393  //(*it)->setDecoder(getDecoder());
1394  //m_visual->setOutput(m_output);
1395  }
1396 
1397  if (getDecoder()->initialize())
1398  {
1399  if (m_output)
1400  m_output->Reset();
1401 
1402  getDecoder()->start();
1403 
1404  if (m_resumeMode == RESUME_EXACT &&
1405  gCoreContext->GetNumSetting("MusicBookmarkPosition", 0) > 0)
1406  {
1407  seek(gCoreContext->GetNumSetting("MusicBookmarkPosition", 0));
1408  gCoreContext->SaveSetting("MusicBookmarkPosition", 0);
1409  }
1410 
1411  m_isPlaying = true;
1412  m_updatedLastplay = false;
1413  }
1414  else
1415  {
1416  LOG(VB_PLAYBACK, LOG_ERR, QString("Cannot initialise decoder for %1")
1417  .arg(getDecoder()->getFilename()));
1418  return;
1419  }
1420 
1421  // tell any listeners we've started playing a new track
1423  dispatch(me);
1424 }
1425 
1426 void MusicPlayer::removeTrack(int trackID)
1427 {
1428  MusicMetadata *mdata = gMusicData->all_music->getMetadata(trackID);
1429  if (mdata)
1430  {
1431  int trackPos = gPlayer->getPlaylist()->getSongs().indexOf(mdata);
1432  if (m_currentTrack > 0 && m_currentTrack >= trackPos)
1433  m_currentTrack--;
1434 
1435  getPlaylist()->removeTrack(trackID);
1436  }
1437 }
1438 
1439 void MusicPlayer::addTrack(int trackID, bool updateUI)
1440 {
1441  getPlaylist()->addTrack(trackID, updateUI);
1442 }
1443 
1445 {
1446  return gMusicData->all_streams->getStreams();
1447 }