diff --git a/app/ui/tools/audio/AudioInfoView.qml b/app/ui/tools/audio/AudioInfoView.qml index 71545199..d2b4a240 100644 --- a/app/ui/tools/audio/AudioInfoView.qml +++ b/app/ui/tools/audio/AudioInfoView.qml @@ -1,8 +1,9 @@ pragma ComponentBehavior: Bound - import QtQuick import QtQuick.Controls import src +import IconFonts +import common Item { id: audio_info_frame @@ -67,13 +68,27 @@ Item { delegate: Item { id: playlist_delegate - // TODO: type required property AudioFile modelData required property int index - width: playlist_view.width + width: error_indicator.visible ? playlist_view.width + error_indicator.width : playlist_view.width height: playlist_text.height + 10 + Text { + id: error_indicator + + visible: playlist_delegate.modelData.hadError + + font: FontAwesome.fontSolid + text: FontAwesome.triangleExclamation + color: SettingsManager.colors.error + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.leftMargin: 5 + verticalAlignment: Text.AlignVCenter + } + Text { id: playlist_text clip: true @@ -83,6 +98,7 @@ Item { font.pointSize: 10 anchors.centerIn: parent width: parent.width - 10 + leftPadding: error_indicator.visible ? error_indicator.width + error_indicator.anchors.leftMargin : 0 } ToolTip { diff --git a/src/tools/audio/audiotool.cpp b/src/tools/audio/audiotool.cpp index 2b484695..00854c7d 100644 --- a/src/tools/audio/audiotool.cpp +++ b/src/tools/audio/audiotool.cpp @@ -185,7 +185,7 @@ void AudioTool::next() { if (m_musicElementType == AudioElement::Type::Music) { - musicPlayer.next(); + musicPlayer.next(false); } } diff --git a/src/tools/audio/metadata/metadatareader.cpp b/src/tools/audio/metadata/metadatareader.cpp index 5f71b99c..725db49b 100644 --- a/src/tools/audio/metadata/metadatareader.cpp +++ b/src/tools/audio/metadata/metadatareader.cpp @@ -38,6 +38,12 @@ void MetaDataReader::setMetaData(QMediaMetaData::Key key, const QVariant &value) case QMediaMetaData::CoverArtImage: { if (m_coverFile) m_coverFile->deleteLater(); + if (value.value().isNull()) + { + m_metaData.cover({}); + break; + } + m_coverFile = std::make_unique(); if (m_coverFile->open()) @@ -144,7 +150,7 @@ void MetaDataReader::loadMetaData(const QString &path, const QByteArray &data) if (auto artist = tag->artist(); !artist.isEmpty()) { - m_metaData.artist(QString::fromStdString(artist.to8Bit(true)).split(", ")); + m_metaData.artist(QString::fromStdString(artist.to8Bit(true)).split(", "_L1)); } if (auto album = tag->album(); !album.isEmpty()) diff --git a/src/tools/audio/players/audioplayer.h b/src/tools/audio/players/audioplayer.h index 90fa6e0c..eacbc963 100644 --- a/src/tools/audio/players/audioplayer.h +++ b/src/tools/audio/players/audioplayer.h @@ -31,7 +31,7 @@ public slots: virtual void stop() = 0; virtual void setVolume(int linear, int logarithmic) = 0; virtual void again() = 0; - virtual void next() = 0; + virtual void next(bool withError) = 0; protected: static auto normalizeVolume(int volume) -> float; diff --git a/src/tools/audio/players/bufferedaudioplayer.cpp b/src/tools/audio/players/bufferedaudioplayer.cpp index 7f72073a..ba81f635 100644 --- a/src/tools/audio/players/bufferedaudioplayer.cpp +++ b/src/tools/audio/players/bufferedaudioplayer.cpp @@ -53,7 +53,7 @@ void BufferedAudioPlayer::setIndex(qsizetype index) } else { - next(); + next(true); } } } @@ -110,14 +110,14 @@ auto BufferedAudioPlayer::loadPlaylist() -> QFuture void BufferedAudioPlayer::handleUnsupportedMediaSource(const AudioFile &file) { qCWarning(gmAudioBufferedPlayer()) << "Media type" << file.source() << "is currently not supported."; - next(); + next(true); } void BufferedAudioPlayer::onFileReceived(const Files::FileDataResult &result) { if (!result.success() || result.data().isEmpty()) { - next(); + next(true); return; } @@ -149,8 +149,6 @@ void BufferedAudioPlayer::stop() m_mediaPlayer.stop(); state(AudioPlayer::State::Stopped); - - m_playlist->clear(); } void BufferedAudioPlayer::setVolume(int linear, int logarithmic) @@ -164,7 +162,7 @@ void BufferedAudioPlayer::again() m_mediaPlayer.setPosition(0); } -void BufferedAudioPlayer::next() +void BufferedAudioPlayer::next(bool withError) { if (!m_element || m_playlist->isEmpty()) { @@ -172,6 +170,21 @@ void BufferedAudioPlayer::next() return; } + if (withError) + { + auto *file = m_playlist->at(playlistIndex()); + if (file) + { + file->hadError(true); + } + + if (!m_playlist->hasElementsWithoutErrors()) + { + stop(); + return; + } + } + // Complete random if (m_element->mode() == AudioElement::Mode::Random) { @@ -201,7 +214,16 @@ void BufferedAudioPlayer::next() void BufferedAudioPlayer::onMediaPlayerPlaybackStateChanged(QMediaPlayer::PlaybackState newState) { qCDebug(gmAudioBufferedPlayer) << "Media player playback state changed:" << newState; - if (newState == QMediaPlayer::PlayingState) state(State::Playing); + if (newState == QMediaPlayer::PlayingState) + { + state(State::Playing); + + auto *file = m_playlist->at(playlistIndex()); + if (file) + { + file->hadError(false); + } + } } void BufferedAudioPlayer::onMediaStatusChanged(QMediaPlayer::MediaStatus status) @@ -212,7 +234,7 @@ void BufferedAudioPlayer::onMediaStatusChanged(QMediaPlayer::MediaStatus status) { case QMediaPlayer::EndOfMedia: qCDebug(gmAudioBufferedPlayer()) << "End of media was reached, playing next file ..."; - next(); + next(false); break; case QMediaPlayer::BufferingMedia: state(State::Loading); @@ -234,7 +256,7 @@ void BufferedAudioPlayer::onMediaPlayerErrorOccurred(QMediaPlayer::Error error, if (error != QMediaPlayer::NoError) { - next(); + next(true); } } @@ -244,7 +266,7 @@ void BufferedAudioPlayer::startPlaying() if (m_element->mode() == AudioElement::Mode::Random) { - next(); + next(false); return; } @@ -298,6 +320,13 @@ void BufferedAudioPlayer::loadWebFile(const QString &url) m_mediaPlayer.setSource(QUrl(url)); m_mediaPlayer.play(); m_audioOutput.setMuted(false); + + QMediaMetaData metaData; + metaData.insert(QMediaMetaData::Key::Title, "-"); + metaData.insert(QMediaMetaData::Key::Author, "-"); + metaData.insert(QMediaMetaData::Key::AlbumTitle, "-"); + metaData.insert(QMediaMetaData::Key::CoverArtImage, QImage()); + emit metaDataChanged(metaData); } void BufferedAudioPlayer::loadYouTubeFile(AudioFile &file) @@ -305,7 +334,7 @@ void BufferedAudioPlayer::loadYouTubeFile(AudioFile &file) const Services::VideoId id(file.url()); if (!id.isValid()) { - next(); + next(true); return; } @@ -314,7 +343,7 @@ void BufferedAudioPlayer::loadYouTubeFile(AudioFile &file) .then([this, &file](const Services::YouTubeVideo &video) { if (video.audioStreamUrl.isEmpty()) { - next(); + next(true); return; } @@ -333,7 +362,7 @@ void BufferedAudioPlayer::loadYouTubeFile(AudioFile &file) emit metaDataChanged(metaData); }); }) - .onCanceled([this]() { next(); }); + .onCanceled([this]() { next(true); }); } void BufferedAudioPlayer::applyShuffleMode() diff --git a/src/tools/audio/players/bufferedaudioplayer.h b/src/tools/audio/players/bufferedaudioplayer.h index 3c7fa456..b013c6fb 100644 --- a/src/tools/audio/players/bufferedaudioplayer.h +++ b/src/tools/audio/players/bufferedaudioplayer.h @@ -42,7 +42,7 @@ public slots: void stop() override; void setVolume(int linear, int logarithmic) override; void again() override; - void next() override; + void next(bool withError) override; signals: void playlistChanged(); diff --git a/src/tools/audio/players/musicplayer.cpp b/src/tools/audio/players/musicplayer.cpp index 5ec14f25..c2d4598c 100644 --- a/src/tools/audio/players/musicplayer.cpp +++ b/src/tools/audio/players/musicplayer.cpp @@ -81,13 +81,19 @@ void MusicPlayer::handleUnsupportedMediaSource(const AudioFile &file) break; default: qCCritical(gmAudioMusic()) << "loadMedia() is not implemented for type" << file.source(); - next(); + next(true); break; } } void MusicPlayer::loadSpotifyFile(const AudioFile &file) { + if (!SpotifyPlayer::canPlay()) + { + next(true); + return; + } + m_spotifyPlayer.play(file.url()); state(State::Playing); } @@ -95,7 +101,7 @@ void MusicPlayer::loadSpotifyFile(const AudioFile &file) void MusicPlayer::onSpotifySongEnded() { qCDebug(gmAudioMusic()) << "Spotify song ended, starting next song ..."; - next(); + next(false); } void MusicPlayer::onSpotifyStateChanged(State state) diff --git a/src/tools/audio/players/soundplayercontroller.h b/src/tools/audio/players/soundplayercontroller.h index 9650341a..995274d8 100644 --- a/src/tools/audio/players/soundplayercontroller.h +++ b/src/tools/audio/players/soundplayercontroller.h @@ -33,8 +33,10 @@ public slots: emit stopAll(); } void setVolume(int linear, int logarithmic) override; - void next() override - { /* Not Implemented */ + void next(bool withError) override + { + /* Not Implemented */ + Q_UNUSED(withError) } void again() override { /* Not Implemented */ diff --git a/src/tools/audio/players/spotifyplayer.cpp b/src/tools/audio/players/spotifyplayer.cpp index a7d61799..bfcaacd6 100644 --- a/src/tools/audio/players/spotifyplayer.cpp +++ b/src/tools/audio/players/spotifyplayer.cpp @@ -18,6 +18,11 @@ SpotifyPlayer::SpotifyPlayer(MetaDataReader &mDReader, QObject *parent) connect(&m_metaDataTimer, &QTimer::timeout, this, &SpotifyPlayer::onMetaDataTimerTimeout); } +auto SpotifyPlayer::canPlay() -> bool +{ + return isSpotifyAvailable(); +} + /// The current song has ended, stop any spotify activity and notify music player void SpotifyPlayer::onDurationTimerTimeout() { @@ -42,7 +47,7 @@ auto SpotifyPlayer::isSpotifyAvailable() -> bool return false; } - qCWarning(gmAudioSpotify) << "Spotify connection is disabled."; + qCWarning(gmAudioSpotify) << "Not connected to Spotify."; return false; } @@ -153,7 +158,7 @@ void SpotifyPlayer::pausePlay() /** * @brief Switch to next song in playlist */ -void SpotifyPlayer::next() +void SpotifyPlayer::next(bool /*withError*/) { qCDebug(gmAudioSpotify) << "Skipping to next track ..."; diff --git a/src/tools/audio/players/spotifyplayer.h b/src/tools/audio/players/spotifyplayer.h index ac380119..6819b9ee 100644 --- a/src/tools/audio/players/spotifyplayer.h +++ b/src/tools/audio/players/spotifyplayer.h @@ -13,13 +13,15 @@ class SpotifyPlayer : public AudioPlayer public: SpotifyPlayer(MetaDataReader &mDReader, QObject *parent = nullptr); + [[nodiscard]] static auto canPlay() -> bool; + public slots: void play(const QString &uri); void play() override; void pause() override; void stop() override; void pausePlay(); - void next() override; + void next(bool withError) override; void again() override; void setVolume(int linear, int logarithmic) override; diff --git a/src/tools/audio/playlist/audioplaylist.cpp b/src/tools/audio/playlist/audioplaylist.cpp index 449489bf..80863e72 100644 --- a/src/tools/audio/playlist/audioplaylist.cpp +++ b/src/tools/audio/playlist/audioplaylist.cpp @@ -87,3 +87,13 @@ void AudioPlaylist::clear() { m_files.clear(); } + +auto AudioPlaylist::hasElementsWithoutErrors() const -> bool +{ + foreach (const auto *file, m_files) + { + if (!file->hadError()) return true; + } + + return false; +} diff --git a/src/tools/audio/playlist/audioplaylist.h b/src/tools/audio/playlist/audioplaylist.h index e7b95e3c..5f99ec31 100644 --- a/src/tools/audio/playlist/audioplaylist.h +++ b/src/tools/audio/playlist/audioplaylist.h @@ -37,6 +37,8 @@ class AudioPlaylist void shuffle(); void clear(); + [[nodiscard]] auto hasElementsWithoutErrors() const -> bool; + private: QList m_files; Type m_type = Type::Undefined; diff --git a/src/tools/audio/playlist/resolvingaudioplaylist.cpp b/src/tools/audio/playlist/resolvingaudioplaylist.cpp index 4ecc35a9..bf2200c7 100644 --- a/src/tools/audio/playlist/resolvingaudioplaylist.cpp +++ b/src/tools/audio/playlist/resolvingaudioplaylist.cpp @@ -38,7 +38,7 @@ auto ResolvingAudioPlaylist::resolve() -> QFuture auto ResolvingAudioPlaylist::unwrapEntries() -> QFuture { QList> futures; - const AudioFile *audioFile = nullptr; + AudioFile *audioFile = nullptr; for (qsizetype i = 0; i < length(); i++) { @@ -73,12 +73,13 @@ auto ResolvingAudioPlaylist::unwrapEntries() -> QFuture }); } -auto ResolvingAudioPlaylist::unwrapPlaylistFile(qsizetype index, const AudioFile &file) -> QFuture +auto ResolvingAudioPlaylist::unwrapPlaylistFile(qsizetype index, AudioFile &file) -> QFuture { - auto callback = [this, index](const QByteArray &data) { + auto callback = [this, index, &file](const QByteArray &data) { if (data.isEmpty()) { qCWarning(gmAudioPlaylistResolving()) << "Error: File is empty!"; + file.hadError(true); return; } @@ -86,6 +87,7 @@ auto ResolvingAudioPlaylist::unwrapPlaylistFile(qsizetype index, const AudioFile if (playlist->isEmpty()) { qCWarning(gmAudioPlaylistResolving()) << "Playlist is empty"; + file.hadError(true); return; } @@ -116,12 +118,18 @@ auto ResolvingAudioPlaylist::unwrapPlaylistFile(qsizetype index, const AudioFile } } -auto ResolvingAudioPlaylist::unwrapSpotify(qsizetype index, const AudioFile &file) -> QFuture +auto ResolvingAudioPlaylist::unwrapSpotify(qsizetype index, AudioFile &file) -> QFuture { const auto uri = SpotifyUtils::makeUri(file.url()); const auto type = SpotifyUtils::getUriType(uri); const auto id = SpotifyUtils::getIdFromUri(uri); + if (!Spotify::instance()->connected()) + { + file.hadError(true); + return QtFuture::makeReadyFuture(); + } + if (!SpotifyUtils::isContainerType(type)) return QtFuture::makeReadyFuture(); const auto callback = [this, index](const SpotifyTrackList &tracklist) { @@ -160,7 +168,7 @@ auto ResolvingAudioPlaylist::unwrapSpotify(qsizetype index, const AudioFile &fil } } -auto ResolvingAudioPlaylist::unwrapYouTube(qsizetype index, const AudioFile &file) -> QFuture +auto ResolvingAudioPlaylist::unwrapYouTube(qsizetype index, AudioFile &file) -> QFuture { const PlaylistId id(file.url()); if (!id.isValid()) return QtFuture::makeReadyFuture(); @@ -177,7 +185,8 @@ auto ResolvingAudioPlaylist::unwrapYouTube(qsizetype index, const AudioFile &fil files << new AudioFile(video.id.toUrl(), AudioFile::Source::Youtube, video.title, &m_fileParent); } replace(index, files); - }); + }) + .onCanceled([&file]() { file.hadError(true); }); } void ResolvingAudioPlaylist::loadTitles() diff --git a/src/tools/audio/playlist/resolvingaudioplaylist.h b/src/tools/audio/playlist/resolvingaudioplaylist.h index 73e92afd..7f8617bf 100644 --- a/src/tools/audio/playlist/resolvingaudioplaylist.h +++ b/src/tools/audio/playlist/resolvingaudioplaylist.h @@ -14,9 +14,9 @@ class ResolvingAudioPlaylist : public AudioPlaylist private: auto unwrapEntries() -> QFuture; - auto unwrapPlaylistFile(qsizetype index, const AudioFile &file) -> QFuture; - auto unwrapSpotify(qsizetype index, const AudioFile &file) -> QFuture; - auto unwrapYouTube(qsizetype index, const AudioFile &file) -> QFuture; + auto unwrapPlaylistFile(qsizetype index, AudioFile &file) -> QFuture; + auto unwrapSpotify(qsizetype index, AudioFile &file) -> QFuture; + auto unwrapYouTube(qsizetype index, AudioFile &file) -> QFuture; void loadTitles(); void loadSpotifyTitles(const QList &tracks) const; diff --git a/src/tools/audio/project/audiofile.cpp b/src/tools/audio/project/audiofile.cpp index 0696cb9e..ce3e698d 100644 --- a/src/tools/audio/project/audiofile.cpp +++ b/src/tools/audio/project/audiofile.cpp @@ -6,12 +6,14 @@ using namespace Qt::Literals::StringLiterals; AudioFile::AudioFile(const QString &url, Source source, const QString &title, QObject *parent) : QObject(parent), a_url(url), a_title(title), a_source(source) { + connect(this, &AudioFile::missingChanged, this, [this](bool missing) { + if (missing) hadError(true); + }); } -AudioFile::AudioFile(const QJsonObject &object, QObject *parent) : QObject(parent) +AudioFile::AudioFile(const QJsonObject &object, QObject *parent) + : AudioFile(object["url"_L1].toString(), static_cast(object["source"_L1].toInt()), u""_s, parent) { - url(object["url"_L1].toString()); - a_source = static_cast(object["source"_L1].toInt()); } AudioFile::AudioFile(const AudioFile &other) : AudioFile(other.url(), other.source(), other.title(), other.parent()) diff --git a/src/tools/audio/project/audiofile.h b/src/tools/audio/project/audiofile.h index c012297b..c0562e3b 100644 --- a/src/tools/audio/project/audiofile.h +++ b/src/tools/audio/project/audiofile.h @@ -37,6 +37,7 @@ class AudioFile : public QObject AUTO_PROPERTY(QString, title) AUTO_PROPERTY_VAL(Source, source) AUTO_PROPERTY_VAL2(bool, missing, false) + AUTO_PROPERTY_VAL2(bool, hadError, false) Q_PROPERTY(QString printableUrl READ printableUrl NOTIFY urlChanged) };