Go to the documentation of this file.
5 #include <QApplication>
58 setObjectName(
"MusicPlayer");
61 if (playmode.toLower() ==
"random")
63 else if (playmode.toLower() ==
"intelligent")
65 else if (playmode.toLower() ==
"album")
67 else if (playmode.toLower() ==
"artist")
73 if (repeatmode.toLower() ==
"track")
75 else if (repeatmode.toLower() ==
"all")
96 QMap<QString, int>::Iterator i;
264 QMutexLocker locker(
m_lock);
300 decoder->
cond()->wakeAll();
320 ShowOkPopup(tr(
"Got too many track unavailable errors. Maybe the host with the music on is off-line?"));
368 if (adevice ==
"default" || adevice.isEmpty())
377 adevice, pdevice,
FORMAT_S16, 2, AV_CODEC_ID_NONE, 44100,
383 LOG(VB_GENERAL, LOG_ERR,
384 QString(
"MusicPlayer: Cannot open audio output device: %1").arg(adevice));
391 LOG(VB_GENERAL, LOG_ERR,
392 QString(
"MusicPlayer: Cannot open audio output device: %1").arg(adevice));
393 LOG(VB_GENERAL, LOG_ERR,
412 QMutexLocker locker(
m_lock);
472 if (currentTrack >= 0)
515 auto *miniplayer =
new MiniPlayer(popupStack);
517 if (miniplayer->Create())
583 auto *miniplayer =
new MiniPlayer(popupStack);
585 if (miniplayer->Create())
598 auto *me =
dynamic_cast<MythEvent*
>(event);
602 if (me->Message().left(13) ==
"MUSIC_COMMAND")
604 QStringList list = me->
Message().simplified().split(
' ');
608 if (list[2] ==
"PLAY")
610 else if (list[2] ==
"STOP")
612 else if (list[2] ==
"PAUSE")
614 else if (list[2] ==
"SET_VOLUME")
618 int volume = list[3].toInt();
619 if (volume >= 0 && volume <= 100)
623 else if (list[2] ==
"GET_VOLUME")
625 QString message = QString(
"MUSIC_CONTROL ANSWER %1 %2")
630 else if (list[2] ==
"PLAY_FILE")
632 int start = me->Message().indexOf(
"'");
633 int end = me->Message().lastIndexOf(
"'");
635 if (start != -1 && end != -1 && start != end)
637 QString
filename = me->Message().mid(start + 1, end - start - 1);
644 LOG(VB_GENERAL, LOG_ERR,
645 QString(
"MusicPlayer: got invalid MUSIC_COMMAND "
646 "PLAY_FILE - %1").arg(me->Message()));
649 else if (list[2] ==
"PLAY_URL")
651 if (list.size() == 4)
660 LOG(VB_GENERAL, LOG_ERR,
661 QString(
"MusicPlayer: got invalid MUSIC_COMMAND "
662 "PLAY_URL - %1").arg(me->Message()));
665 else if (list[2] ==
"PLAY_TRACK")
667 if (list.size() == 4)
669 int trackID = list[3].toInt();
676 LOG(VB_GENERAL, LOG_ERR,
677 QString(
"MusicPlayer: got invalid MUSIC_COMMAND "
678 "PLAY_TRACK - %1").arg(me->Message()));
681 else if (list[2] ==
"GET_METADATA")
686 mdataStr = QString(
"%1 by %2 from %3").arg(mdata->
Title(), mdata->
Artist(), mdata->
Album());
688 mdataStr =
"Unknown Track2";
690 QString message = QString(
"MUSIC_CONTROL ANSWER %1 %2")
695 else if (list[2] ==
"GET_STATUS")
697 QString statusStr =
"STOPPED";
700 statusStr =
"PLAYING";
702 statusStr =
"PAUSED";
704 QString message = QString(
"MUSIC_CONTROL ANSWER %1 %2")
712 LOG(VB_GENERAL, LOG_ERR,
713 QString(
"MusicPlayer: got unknown/invalid MUSIC_COMMAND "
714 "- %1").arg(me->Message()));
717 else if (me->Message().startsWith(
"MUSIC_SETTINGS_CHANGED"))
721 else if (me->Message().startsWith(
"MUSIC_METADATA_CHANGED"))
725 QStringList list = me->Message().simplified().split(
' ');
726 if (list.size() == 2)
728 int songID = list[1].toInt();
741 else if (me->Message().startsWith(
"MUSIC_SCANNER_STARTED"))
743 QStringList list = me->Message().simplified().split(
' ');
744 if (list.size() == 2)
746 QString host = list[1];
749 tr(
"A music file scan has started on %1").arg(host),
750 tr(
"Music File Scanner"),
751 tr(
"This may take a while I'll give a shout when finished"));
754 else if (me->Message().startsWith(
"MUSIC_SCANNER_FINISHED"))
756 QStringList list = me->Message().simplified().split(
' ');
757 if (list.size() == 6)
759 QString host = list[1];
761 int totalTracks = list[2].toInt();
762 int newTracks = list[3].toInt();
763 int totalCoverart = list[4].toInt();
764 int newCoverart = list[5].toInt();
766 QString summary = QString(
"Total Tracks: %2, new tracks: %3,\nTotal Coverart: %4, New CoverArt %5")
767 .arg(totalTracks).arg(newTracks).arg(totalCoverart).arg(newCoverart);
769 tr(
"A music file scan has finished on %1").arg(host),
770 tr(
"Music File Scanner"), summary);
775 else if (me->Message().startsWith(
"MUSIC_SCANNER_ERROR"))
777 QStringList list = me->Message().simplified().split(
' ');
778 if (list.size() == 3)
780 QString host = list[1];
781 QString
error = list[2];
784 if (
error ==
"Already_Running")
787 tr(
"Music File Scanner"),
788 tr(
"Can't run the music file scanner because it is already running on %1").arg(host));
790 else if (
error ==
"Stalled")
793 tr(
"Music File Scanner"),
794 tr(
"The music file scanner has been running for more than 60 minutes on %1.\nResetting and trying again")
808 LOG(VB_GENERAL, LOG_ERR, QString(
"Audio Output Error: %1").arg(*aoe->errorMessage()));
830 LOG(VB_GENERAL, LOG_ERR, QString(
"Decoder Error: %2").arg(*dxe->errorMessage()));
852 LOG(VB_GENERAL, LOG_ERR, QString(
"Decoder Handler Error - %1").arg(*dhe->getMessage()));
912 auto metadataSecs = duration_cast<std::chrono::seconds>(
getCurrentMetadata()->Length());
916 LOG(VB_GENERAL, LOG_NOTICE, QString(
"MusicPlayer: Updating track length was %1s, should be %2s")
945 QObject::customEvent(event);
1007 for (
int x = 0; x < list->count(); x++)
1012 if (mdata->
ID() ==
id)
1024 if (moveUp && whichTrack <= 0)
1115 decoder->
seek(pos.count());
1128 auto *miniplayer =
new MiniPlayer(popupStack);
1130 if (miniplayer->Create())
1151 LOG(VB_GENERAL, LOG_ERR,
1152 QString(
"MusicPlayer: asked to set the current track to an invalid track no. %1")
1245 int curTrackID = -1;
1258 if (curTrackID != -1)
1292 if (
GetMythDB()->GetNumSetting(
"AllowTagWriting", 0) == 1)
1294 QStringList strList;
1295 strList << QString(
"MUSIC_TAG_UPDATE_VOLATILE")
1419 map[
"volumemute"] =
isMuted() ? tr(
"%1% (Muted)",
"Zero Audio Volume").arg(
getVolume()) :
1421 map[
"volume"] = QString(
"%1").arg(
getVolume());
1422 map[
"volumepercent"] = QString(
"%1%").arg(
getVolume());
1423 map[
"mute"] =
isMuted() ? tr(
"Muted") :
"";
1502 QMutexLocker locker(
m_lock);
1515 LOG(VB_PLAYBACK, LOG_INFO, QString (
"decoder handler is ready, decoding %1")
1516 .arg(decoder->
getURL()));
1519 auto *cddecoder =
dynamic_cast<CdDecoder*
>(decoder);
1537 QMutexLocker locker(
m_lock);
1573 LOG(VB_PLAYBACK, LOG_ERR, QString(
"Cannot initialise decoder for %1")
1574 .arg(decoder->
getURL()));
1628 QString image =
"musicscanner.png";
1630 LOG(VB_GENERAL, LOG_ERR,
"MusicPlayer: sendNotification failed to find the 'musicscanner.png' image");
1633 map[
"asar"] = title;
1634 map[
"minm"] = author;
1639 n->SetId(notificationID);
1642 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.
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)