Go to the documentation of this file.
5 #include <QApplication>
56 setObjectName(
"MusicPlayer");
59 if (playmode.toLower() ==
"random")
61 else if (playmode.toLower() ==
"intelligent")
63 else if (playmode.toLower() ==
"album")
65 else if (playmode.toLower() ==
"artist")
71 if (repeatmode.toLower() ==
"track")
73 else if (repeatmode.toLower() ==
"all")
94 QMap<QString, int>::Iterator i;
262 QMutexLocker locker(
m_lock);
298 decoder->
cond()->wakeAll();
318 ShowOkPopup(tr(
"Got too many track unavailable errors. Maybe the host with the music on is off-line?"));
366 if (adevice ==
"default" || adevice.isEmpty())
375 adevice, pdevice,
FORMAT_S16, 2, AV_CODEC_ID_NONE, 44100,
381 LOG(VB_GENERAL, LOG_ERR,
382 QString(
"MusicPlayer: Cannot open audio output device: %1").
arg(adevice));
389 LOG(VB_GENERAL, LOG_ERR,
390 QString(
"MusicPlayer: Cannot open audio output device: %1").
arg(adevice));
391 LOG(VB_GENERAL, LOG_ERR,
410 QMutexLocker locker(
m_lock);
470 if (currentTrack >= 0)
513 auto *miniplayer =
new MiniPlayer(popupStack);
515 if (miniplayer->Create())
581 auto *miniplayer =
new MiniPlayer(popupStack);
583 if (miniplayer->Create())
596 auto *me =
dynamic_cast<MythEvent*
>(event);
600 if (me->Message().left(13) ==
"MUSIC_COMMAND")
602 QStringList list = me->
Message().simplified().split(
' ');
606 if (list[2] ==
"PLAY")
608 else if (list[2] ==
"STOP")
610 else if (list[2] ==
"PAUSE")
612 else if (list[2] ==
"SET_VOLUME")
614 if (list.size() >= 3)
616 int volume = list[3].toInt();
617 if (volume >= 0 && volume <= 100)
621 else if (list[2] ==
"GET_VOLUME")
623 QString message = QString(
"MUSIC_CONTROL ANSWER %1 %2")
628 else if (list[2] ==
"PLAY_FILE")
630 int start = me->Message().indexOf(
"'");
631 int end = me->Message().lastIndexOf(
"'");
633 if (start != -1 && end != -1 && start != end)
635 QString
filename = me->Message().mid(start + 1, end - start - 1);
642 LOG(VB_GENERAL, LOG_ERR,
643 QString(
"MusicPlayer: got invalid MUSIC_COMMAND "
644 "PLAY_FILE - %1").
arg(me->Message()));
647 else if (list[2] ==
"PLAY_URL")
649 if (list.size() == 4)
658 LOG(VB_GENERAL, LOG_ERR,
659 QString(
"MusicPlayer: got invalid MUSIC_COMMAND "
660 "PLAY_URL - %1").
arg(me->Message()));
663 else if (list[2] ==
"PLAY_TRACK")
665 if (list.size() == 4)
667 int trackID = list[3].toInt();
674 LOG(VB_GENERAL, LOG_ERR,
675 QString(
"MusicPlayer: got invalid MUSIC_COMMAND "
676 "PLAY_TRACK - %1").
arg(me->Message()));
679 else if (list[2] ==
"GET_METADATA")
684 mdataStr = QString(
"%1 by %2 from %3").arg(mdata->
Title()).arg(mdata->
Artist()).arg(mdata->
Album());
686 mdataStr =
"Unknown Track2";
688 QString message = QString(
"MUSIC_CONTROL ANSWER %1 %2")
693 else if (list[2] ==
"GET_STATUS")
695 QString statusStr =
"STOPPED";
698 statusStr =
"PLAYING";
700 statusStr =
"PAUSED";
702 QString message = QString(
"MUSIC_CONTROL ANSWER %1 %2")
710 LOG(VB_GENERAL, LOG_ERR,
711 QString(
"MusicPlayer: got unknown/invalid MUSIC_COMMAND "
712 "- %1").
arg(me->Message()));
715 else if (me->Message().startsWith(
"MUSIC_SETTINGS_CHANGED"))
719 else if (me->Message().startsWith(
"MUSIC_METADATA_CHANGED"))
723 QStringList list = me->Message().simplified().split(
' ');
724 if (list.size() == 2)
726 int songID = list[1].toInt();
739 else if (me->Message().startsWith(
"MUSIC_SCANNER_STARTED"))
741 QStringList list = me->Message().simplified().split(
' ');
742 if (list.size() == 2)
744 QString host = list[1];
747 tr(
"A music file scan has started on %1").
arg(host),
748 tr(
"Music File Scanner"),
749 tr(
"This may take a while I'll give a shout when finished"));
752 else if (me->Message().startsWith(
"MUSIC_SCANNER_FINISHED"))
754 QStringList list = me->Message().simplified().split(
' ');
755 if (list.size() == 6)
757 QString host = list[1];
759 int totalTracks = list[2].toInt();
760 int newTracks = list[3].toInt();
761 int totalCoverart = list[4].toInt();
762 int newCoverart = list[5].toInt();
764 QString summary = QString(
"Total Tracks: %2, new tracks: %3,\nTotal Coverart: %4, New CoverArt %5")
765 .arg(totalTracks).arg(newTracks).arg(totalCoverart).arg(newCoverart);
767 tr(
"A music file scan has finished on %1").
arg(host),
768 tr(
"Music File Scanner"), summary);
773 else if (me->Message().startsWith(
"MUSIC_SCANNER_ERROR"))
775 QStringList list = me->Message().simplified().split(
' ');
776 if (list.size() == 3)
778 QString host = list[1];
779 QString
error = list[2];
782 if (
error ==
"Already_Running")
785 tr(
"Music File Scanner"),
786 tr(
"Can't run the music file scanner because it is already running on %1").
arg(host));
788 else if (
error ==
"Stalled")
791 tr(
"Music File Scanner"),
792 tr(
"The music file scanner has been running for more than 60 minutes on %1.\nResetting and trying again")
806 LOG(VB_GENERAL, LOG_ERR, QString(
"Audio Output Error: %1").
arg(*aoe->errorMessage()));
828 LOG(VB_GENERAL, LOG_ERR, QString(
"Decoder Error: %2").
arg(*dxe->errorMessage()));
850 LOG(VB_GENERAL, LOG_ERR, QString(
"Decoder Handler Error - %1").
arg(*dhe->getMessage()));
910 auto metadataSecs = duration_cast<std::chrono::seconds>(
getCurrentMetadata()->Length());
914 LOG(VB_GENERAL, LOG_NOTICE, QString(
"MusicPlayer: Updating track length was %1s, should be %2s")
943 QObject::customEvent(event);
1005 for (
int x = 0; x < list->count(); x++)
1010 if (mdata->
ID() ==
id)
1022 if (moveUp && whichTrack <= 0)
1113 decoder->
seek(pos.count());
1126 auto *miniplayer =
new MiniPlayer(popupStack);
1128 if (miniplayer->Create())
1149 LOG(VB_GENERAL, LOG_ERR,
1150 QString(
"MusicPlayer: asked to set the current track to an invalid track no. %1")
1243 int curTrackID = -1;
1256 if (curTrackID != -1)
1290 if (
GetMythDB()->GetNumSetting(
"AllowTagWriting", 0) == 1)
1292 QStringList strList;
1293 strList << QString(
"MUSIC_TAG_UPDATE_VOLATILE")
1417 map[
"volumemute"] =
isMuted() ? tr(
"%1% (Muted)",
"Zero Audio Volume").arg(
getVolume()) :
1419 map[
"volume"] = QString(
"%1").arg(
getVolume());
1420 map[
"volumepercent"] = QString(
"%1%").arg(
getVolume());
1421 map[
"mute"] =
isMuted() ? tr(
"Muted") :
"";
1500 QMutexLocker locker(
m_lock);
1513 LOG(VB_PLAYBACK, LOG_INFO, QString (
"decoder handler is ready, decoding %1")
1517 auto *cddecoder =
dynamic_cast<CdDecoder*
>(decoder);
1535 QMutexLocker locker(
m_lock);
1571 LOG(VB_PLAYBACK, LOG_ERR, QString(
"Cannot initialise decoder for %1")
1626 QString image =
"musicscanner.png";
1628 LOG(VB_GENERAL, LOG_ERR,
"MusicPlayer: sendNotification failed to find the 'musicscanner.png' image");
1631 map[
"asar"] =
title;
1632 map[
"minm"] = author;
1637 n->SetId(notificationID);
1640 n->SetFullScreen(
false);
void addVisual(MainVisual *visual)
QString GetError(void) const
void addVisual(MythTV::Visual *v)
virtual void SetCurrentVolume(int value)
bool openOutputDevice(void)
Decoder * getDecoder(void)
AudioOutput * getOutput(void)
MusicMetadata * getSongAt(int pos) const
static Type MythEventMessage
QMap< QString, int > m_notificationMap
void start(QThread::Priority p=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
QString GetMasterHostName(void)
bool setCurrentTrackPos(int pos)
static void error(const char *str,...)
void setVolume(int volume)
void toMap(InfoMap &infoMap) const
virtual void AdjustCurrentVolume(int change)
void sendNotification(int notificationID, const QString &title, const QString &author, const QString &desc)
virtual uint GetCurrentVolume(void) const
ResumeMode getResumeMode(void)
virtual void Reset(void)=0
void loadStreamPlaylist(void)
bool wait(std::chrono::milliseconds time=std::chrono::milliseconds::max())
Wait for the MThread to exit, with a maximum timeout.
void emitTVPlaybackStopped(void)
void shuffleTracks(MusicPlayer::ShuffleMode mode)
This class is used as a container for messages.
arg(title).arg(filename).arg(doDelete))
static Type TrackStatsChangedEvent
void setShuffleMode(ShuffleMode mode)
static Type TrackAddedEvent
bool isPlaying(void) const
MusicPlayer(QObject *parent)
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
std::chrono::seconds m_lastplayDelay
ResumeMode m_resumeModePlayback
static StreamList * getStreamList(void)
static Type MetadataChangedEvent
MuteState getMuteState(void) const
Playlist * getCurrentPlaylist(void)
void dispatch(const MythEvent &event)
Dispatch an event to all listeners.
std::enable_if_t< std::chrono::__is_duration< T >::value, void > SaveDurSetting(const QString &key, T newValue)
void removeListener(QObject *listener)
void removeVisual(MythTV::Visual *v)
void setPlayMode(PlayMode mode)
void updateVolatileMetadata(void)
DecoderHandler * m_decoderHandler
QSet< QObject * > m_listeners
const QString & Message() const
static AudioOutput * OpenAudio(const QString &main_device, const QString &passthru_device, AudioFormat format, int channels, AVCodecID codec, int samplerate, AudioOutputSource source, bool set_initial_vol, bool passthru, int upmixer_startup=0, AudioOutputSettings *custom=nullptr)
void changeCurrentTrack(int trackNo)
change the current track to the given track
ResumeMode m_resumeModeRadio
Playlist * getActive(void)
QHash< QString, QString > InfoMap
void sendTrackStatsChangedEvent(int trackID)
void addListener(QObject *listener)
Add a listener to the observable.
MusicMetadata * getCurrentMetadata(void)
get the metadata for the current track in the playlist
int getNotificationID(const QString &hostname)
Class for starting stream decoding.
void UnregisterForPlayback(QObject *sender)
RepeatMode toggleRepeatMode(void)
Events sent by the DecoderHandler and it's helper classes.
static Type TrackChangeEvent
std::chrono::seconds m_lastTrackStart
static Type PlayedTracksChangedEvent
void removeVisual(MainVisual *visual)
void getBufferStatus(int *available, int *maxSize) const
virtual void Pause(bool paused)=0
virtual void unlock(void)
virtual void ToggleMute(void)
void reloadMusic(void) const
reload music after a scan, rip or import
ShuffleMode toggleShuffleMode(void)
void removeTrack(MusicMetadata::IdType trackID)
void setBufferSize(unsigned int sz)
std::enable_if_t< std::chrono::__is_duration< T >::value, T > GetDurSetting(const QString &key, T defaultval=T::zero())
AllStream * m_all_streams
StreamList * getStreams(void)
Playlist * getStreamPlaylist(void)
void setupDecoderHandler(void)
virtual MuteState GetMuteState(void) const
void sendTrackUnavailableEvent(int trackID)
send a message to the master BE without blocking the UI thread
void addTrack(MusicMetadata::IdType trackID, bool update_display)
Given a tracks ID, add that track to this playlist.
ShuffleMode m_shuffleMode
void disableSaves(void)
whether any changes should be saved to the DB
void RegisterForPlayback(QObject *sender, PlaybackStartCb method)
QString getURL(void) const
void playlistChanged(int playlistID)
void sendMetadataChangedEvent(int trackID)
MusicMetadata * getMetadata(int an_id)
void removeTrack(int trackID)
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
PlaylistContainer * m_all_playlists
void setDevice(const QString &dev)
static Type AllTracksRemovedEvent
void updateLastplay(void)
void showMiniPlayer(void) const
int GetNumSetting(const QString &key, int defaultval=0)
void TVPlaybackAborted(void)
bool GetBoolSetting(const QString &key, bool defaultval=false)
void moveTrackUpDown(bool flag, int where_its_at)
void start(MusicMetadata *mdata)
MusicMetadata * getNextMetadata(void)
get the metadata for the next track in the playlist
void decoderHandlerReady(void)
virtual bool initialize()=0
QSet< QObject * > m_visualisers
bool InWantingPlayback(void)
Returns true if a client has requested playback.
DecoderHandler * getDecoderHandler(void)
static Type TrackUnavailableEvent
void activePlaylistChanged(int trackID, bool deleted)
void setSpeed(float speed)
void setRepeatMode(RepeatMode mode)
void addTrack(int trackID, bool updateUI)
virtual void SetStretchFactor(float factor)
static Type PlaylistChangedEvent
void setOutput(AudioOutput *o)
static Type VolumeChangeEvent
QList< MusicMetadata * > m_playedList
MusicMetadata & getMetadata()
static Type TrackRemovedEvent
void customEvent(QEvent *event) override
MythNotificationCenter * GetNotificationCenter(void)
void UnRegister(void *from, int id, bool closeimemdiately=false)
Unregister the client.
ResumeMode m_resumeModeEditor
void sendVolumeChangedEvent(void)
~MusicPlayer(void) override
MythMainWindow * GetMythMainWindow(void)
bool isRunning(void) const
int getTrackPosition(MusicMetadata::IdType trackID)
static Type CDChangedEvent
void sendAlbumArtChangedEvent(int trackID)
MusicMetadata * m_oneshotMetadata
void restorePosition(void)
MythScreenStack * GetStack(const QString &Stackname)
void WantingPlayback(QObject *sender)
All the objects that have registered using MythCoreContext::RegisterForPlayback but sender will be ca...
void sendCDChangedEvent(void)
void seek(std::chrono::seconds pos)
QString GetHostName(void)
void playFile(const MusicMetadata &mdata)
void removeAllTracks(void)
void stop(bool stopAll=false)
void TVPlaybackStopped(void)
void SaveSetting(const QString &key, int newValue)
QMap< QString, QString > DMAP
void getBufferStatus(int *bufferAvailable, int *bufferSize) const
MythUIHelper * GetMythUI()
static Type AlbumArtChangedEvent
virtual void seek(double)=0
static MThreadPool * globalInstance(void)
uint getVolume(void) const
static constexpr std::chrono::seconds LASTPLAY_DELAY
virtual void AddScreen(MythScreenType *screen, bool allowFade=true)
std::chrono::seconds m_currentTime
void dispatch(const MythEvent &event)
MythConfirmationDialog * ShowOkPopup(const QString &message, bool showCancel)
Non-blocking version of MythPopupBox::showOkPopup()
void removeListener(QObject *listener)
Remove a listener to the observable.
virtual void PauseUntilBuffered(void)=0
virtual bool IsPaused(void) const =0
void start(QRunnable *runnable, const QString &debugName, int priority=0)
void addListener(QObject *listener)
void PauseIdleTimer(bool Pause)
Pause the idle timeout timer.
virtual void SetTimecode(std::chrono::milliseconds timecode)=0
QString GetSetting(const QString &key, const QString &defaultval="")
bool Queue(const MythNotification ¬ification)
Queue a notification Queue() is thread-safe and can be called from anywhere.
void moveTrackUpDown(bool moveUp, int whichTrack)