Compare commits

..
Author SHA1 Message Date
Jonah Brüchert f9948bf3c3 WIP 2022-07-29 02:57:13 +02:00
Jonah Brüchert 94c3e653f7 Begin work on MPRIS integration
This code should eventually become a library, therefore we already have
the restriction that the mpris2 static library may not link against
other parts of the AudioTube code.
2022-07-19 16:23:41 +02:00
29 changed files with 1182 additions and 379 deletions

No files matched your search

+1 -4
View File
@@ -32,15 +32,12 @@ ecm_setup_version(${PROJECT_VERSION}
)
kde_enable_exceptions()
add_compile_options(-fsanitize=address)
add_link_options(-fsanitize=address)
find_package(Qt5 ${QT5_MIN_VERSION} REQUIRED COMPONENTS Core Gui Qml QuickControls2 Svg Sql)
find_package(Qt5 ${QT5_MIN_VERSION} REQUIRED COMPONENTS Core Gui Qml QuickControls2 Svg Sql DBus)
find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Kirigami2 I18n CoreAddons Crash)
find_package(pybind11 REQUIRED)
find_package(Ytdlp REQUIRED RUNTIME)
find_package(YTMusicAPI REQUIRED RUNTIME)
find_package(QCoro5 REQUIRED COMPONENTS Core Qml Network)
ki18n_install(po)
-4
View File
@@ -22,7 +22,6 @@ SPDX-License-Identifier: CC0-1.0
<name xml:lang="it">AudioTube</name>
<name xml:lang="ko">AudioTube</name>
<name xml:lang="nl">AudioTube</name>
<name xml:lang="nn">AudioTube</name>
<name xml:lang="pa">ਆਡੀਓ-ਟਿਊਬ</name>
<name xml:lang="pl">AudioTube</name>
<name xml:lang="pt">AudioTube</name>
@@ -50,7 +49,6 @@ SPDX-License-Identifier: CC0-1.0
<summary xml:lang="it">Client YouTube Music convergente</summary>
<summary xml:lang="ko">올인원 YouTube Music 클라이언트</summary>
<summary xml:lang="nl">Convergente muziekclient van YouTube</summary>
<summary xml:lang="nn">YouTube Music-klient, tilpassa ulike brukarflater</summary>
<summary xml:lang="pl">Adaptacyjny program do YouTube Music</summary>
<summary xml:lang="pt">Cliente de música do YouTube convergente</summary>
<summary xml:lang="pt-BR">Um cliente convergente para o YouTube Music</summary>
@@ -79,7 +77,6 @@ SPDX-License-Identifier: CC0-1.0
<developer_name xml:lang="it">Comunità KDE</developer_name>
<developer_name xml:lang="ko">KDE 커뮤니티</developer_name>
<developer_name xml:lang="nl">KDE-gemeenschap</developer_name>
<developer_name xml:lang="nn">KDE-fellesskapet</developer_name>
<developer_name xml:lang="pa">ਕੇਡੀਈ ਕਮਿਊਨਟੀ</developer_name>
<developer_name xml:lang="pl">Społeczność KDE</developer_name>
<developer_name xml:lang="pt">Comunidade do KDE</developer_name>
@@ -162,7 +159,6 @@ SPDX-License-Identifier: CC0-1.0
<p xml:lang="it">AudioTube può eseguire ricerche su YouTube Music, elencare album e artisti, riprodurre scalette e album generati automaticamente e ti consente di creare la tua scaletta.</p>
<p xml:lang="ko">AudioTube는 YouTube Music을 검색하고, 앨범과 아티스트를 표시하고, 자동 생성된 재생 목록, 앨범을 재생하고, 사용자 정의 재생 목록을 만들 수 있습니다.</p>
<p xml:lang="nl">AudioTube kan in YouTube Music zoeken, een lijst maken van albums en artiesten, automatically gegenereerde afspeellijsten, albums afspelen en biedt u het samenstellen van uw eigen afspeellijst.</p>
<p xml:lang="nn">Med AudioTube kan du søkja på YouTube Music, visa album og artistar, spela automatiske genererte spelelister og album samt laga eigne spelelister.</p>
<p xml:lang="pl">AudioTube może przeszukiwać serwis YouTube Music, wypisać albumy i wykonawców, odtwarzać samoczynnie utworzone listy odtwarzania, albumy oraz umożliwia tworzenie własnych list odtwarzania.</p>
<p xml:lang="pt">O AudioTube consegue pesquisar no YouTube Music, enumerar os álbuns e artistas, reproduzir listas geradas automaticamente, álbuns e permite-lhe reproduzir as suas próprias listas de reprodução.</p>
<p xml:lang="pt-BR">O AudioTube pode pesquisar no YouTube Music, listar álbuns e artistas, rodar listas de reprodução geradas automaticamente, álbuns inteiros, e criar a sua própria lista de reprodução.</p>
-2
View File
@@ -15,7 +15,6 @@ Name[hi]=ओडियो-ट्यूब
Name[it]=AudioTube
Name[ko]=AudioTube
Name[nl]=AudioTube
Name[nn]=AudioTube
Name[pa]=ਆਡੀਓ-ਟਿਊਬ
Name[pl]=AudioTube
Name[pt]=AudioTube
@@ -44,7 +43,6 @@ Comment[hi]=यूट्यूब संगीत क्लाएंट
Comment[it]=Client YouTube Music
Comment[ko]=YouTube Music 클라이언트
Comment[nl]=Muziekclient van YouTube
Comment[nn]=YouTube Music-klient
Comment[pa]=ਯੂਟਿਊਬ ਸੰਗੀਤ ਕਲਾਈਂਟ
Comment[pl]=Program do YouTube Music
Comment[pt]=Cliente do YouTube Music
+1 -1
View File
@@ -120,7 +120,7 @@ msgstr "Cançons reproduïdes"
#: contents/ui/main.qml:39
#, kde-format
msgid "Back"
msgstr "Arrere"
msgstr "Enrere"
#: contents/ui/main.qml:73 contents/ui/SearchPage.qml:150
#, kde-format
+14 -13
View File
@@ -7,7 +7,7 @@ msgstr ""
"Project-Id-Version: audiotube\n"
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
"POT-Creation-Date: 2022-07-12 00:48+0000\n"
"PO-Revision-Date: 2022-07-22 15:55+0100\n"
"PO-Revision-Date: 2022-01-01 15:21+0000\n"
"Last-Translator: Steve Allewell <[email protected]>\n"
"Language-Team: British English <[email protected]>\n"
"Language: en_GB\n"
@@ -15,7 +15,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Lokalize 22.04.3\n"
"X-Generator: Lokalize 21.12.0\n"
#, kde-format
msgctxt "NAME OF TRANSLATORS"
@@ -92,32 +92,32 @@ msgstr "Cancel"
#: contents/ui/LibraryPage.qml:27
#, kde-format
msgid "Favourites"
msgstr "Favourites"
msgstr ""
#: contents/ui/LibraryPage.qml:37 contents/ui/LibraryPage.qml:108
#, kde-format
msgid "Show All"
msgstr "Show All"
msgstr ""
#: contents/ui/LibraryPage.qml:42
#, kde-format
msgid "Favourite Songs"
msgstr "Favourite Songs"
msgstr ""
#: contents/ui/LibraryPage.qml:98
#, kde-format
msgid "Most played"
msgstr "Most played"
msgstr ""
#: contents/ui/LibraryPage.qml:113
#, kde-format
msgid "Played Songs"
msgstr "Played Songs"
msgstr ""
#: contents/ui/main.qml:39
#, kde-format
msgid "Back"
msgstr "Back"
msgstr ""
#: contents/ui/main.qml:73 contents/ui/SearchPage.qml:150
#, kde-format
@@ -130,14 +130,15 @@ msgid "AudioTube"
msgstr "AudioTube"
#: contents/ui/MaximizedPlayerPage.qml:92
#, kde-format
#, fuzzy, kde-format
#| msgid "No media playing"
msgid "Now Playing"
msgstr "Now Playing"
msgstr "No media playing"
#: contents/ui/MaximizedPlayerPage.qml:117
#, kde-format
msgid "Queue"
msgstr "Queue"
msgstr ""
#: contents/ui/MaximizedPlayerPage.qml:222
#, kde-format
@@ -153,12 +154,12 @@ msgstr "No media playing"
#: contents/ui/PlaybackHistory.qml:15
#, kde-format
msgid "Unknown list of songs"
msgstr "Unknown list of songs"
msgstr ""
#: contents/ui/PlaybackHistory.qml:25
#, kde-format
msgid "No songs here yet"
msgstr "No songs here yet"
msgstr ""
#: contents/ui/SearchPage.qml:27
#, kde-format
-192
View File
@@ -1,192 +0,0 @@
# Translation of audiotube to Norwegian Nynorsk
#
# Øystein Steffensen-Alværvik <[email protected]>, 2022.
# Karl Ove Hufthammer <[email protected]>, 2022.
msgid ""
msgstr ""
"Project-Id-Version: audiotube\n"
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
"POT-Creation-Date: 2022-07-12 00:48+0000\n"
"PO-Revision-Date: 2022-08-07 12:08+0200\n"
"Last-Translator: Karl Ove Hufthammer <[email protected]>\n"
"Language-Team: Norwegian Nynorsk <[email protected]>\n"
"Language: nn\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Lokalize 22.04.3\n"
"X-Environment: kde\n"
"X-Accelerator-Marker: &\n"
"X-Text-Markup: kde4\n"
#, kde-format
msgctxt "NAME OF TRANSLATORS"
msgid "Your names"
msgstr "Øystein Steffensen-Alværvik"
#, kde-format
msgctxt "EMAIL OF TRANSLATORS"
msgid "Your emails"
msgstr "[email protected]"
#: contents/ui/AlbumPage.qml:19 contents/ui/PlaylistPage.qml:19
#, kde-format
msgid "Play"
msgstr "Spel av"
#: contents/ui/AlbumPage.qml:26 contents/ui/PlaylistPage.qml:26
#, kde-format
msgid "Shuffle"
msgstr "Stokk om"
#: contents/ui/AlbumPage.qml:68 contents/ui/ArtistPage.qml:101
#: contents/ui/PlaybackHistory.qml:74 contents/ui/PlaylistPage.qml:67
#, kde-format
msgid "Play Next"
msgstr "Spel som neste"
#: contents/ui/AlbumPage.qml:73 contents/ui/ArtistPage.qml:107
#: contents/ui/PlaybackHistory.qml:79 contents/ui/PlaylistPage.qml:72
#: contents/ui/SearchPage.qml:123
#, kde-format
msgid "Add to Playlist"
msgstr "Legg til speleliste"
#: contents/ui/ArtistPage.qml:39 contents/ui/SearchPage.qml:67
#, kde-format
msgid ""
"Video playback is not supported yet. Do you want to play only the audio of "
"\"%1\"?"
msgstr "Videoavspeling er endå ikkje støtta. Vil du spela av lyden til «%1»?"
#: contents/ui/ArtistPage.qml:55 contents/ui/SearchPage.qml:25
#, kde-format
msgid "Albums"
msgstr "Album"
#: contents/ui/ArtistPage.qml:57
#, kde-format
msgid "Singles"
msgstr "Singelplater"
#: contents/ui/ArtistPage.qml:59 contents/ui/SearchPage.qml:31
#, kde-format
msgid "Songs"
msgstr "Songar"
#: contents/ui/ArtistPage.qml:61 contents/ui/SearchPage.qml:33
#, kde-format
msgid "Videos"
msgstr "Videoar"
#: contents/ui/ConfirmationMessage.qml:16
#, kde-format
msgid "OK"
msgstr "OK"
#: contents/ui/ConfirmationMessage.qml:21
#, kde-format
msgid "Cancel"
msgstr "Avbryt"
#: contents/ui/LibraryPage.qml:27
#, kde-format
msgid "Favourites"
msgstr "Favorittar"
#: contents/ui/LibraryPage.qml:37 contents/ui/LibraryPage.qml:108
#, kde-format
msgid "Show All"
msgstr "Vis alle"
#: contents/ui/LibraryPage.qml:42
#, kde-format
msgid "Favourite Songs"
msgstr "Favorittsongar"
#: contents/ui/LibraryPage.qml:98
#, kde-format
msgid "Most played"
msgstr "Oftast avspela"
#: contents/ui/LibraryPage.qml:113
#, kde-format
msgid "Played Songs"
msgstr "Avspela songar"
#: contents/ui/main.qml:39
#, kde-format
msgid "Back"
msgstr "Tilbake"
#: contents/ui/main.qml:73 contents/ui/SearchPage.qml:150
#, kde-format
msgid "Search"
msgstr "Søk"
#: contents/ui/main.qml:85
#, kde-format
msgid "AudioTube"
msgstr "AudioTube"
#: contents/ui/MaximizedPlayerPage.qml:92
#, kde-format
msgid "Now Playing"
msgstr "Spelar no"
#: contents/ui/MaximizedPlayerPage.qml:117
#, kde-format
msgid "Queue"
msgstr "Kø"
#: contents/ui/MaximizedPlayerPage.qml:222
#, kde-format
msgid "Remove Track"
msgstr "Fjern spor"
#: contents/ui/MaximizedPlayerPage.qml:242
#: contents/ui/MinimizedPlayerControls.qml:96
#, kde-format
msgid "No media playing"
msgstr "Inkje medium vert spela"
#: contents/ui/PlaybackHistory.qml:15
#, kde-format
msgid "Unknown list of songs"
msgstr "Ukjend songliste"
#: contents/ui/PlaybackHistory.qml:25
#, kde-format
msgid "No songs here yet"
msgstr "Ingen songar enno"
#: contents/ui/SearchPage.qml:27
#, kde-format
msgid "Artists"
msgstr "Artistar"
#: contents/ui/SearchPage.qml:29
#, kde-format
msgid "Playlists"
msgstr "Spelelister"
#: contents/ui/SearchPage.qml:36
#, kde-format
msgid "Unknown"
msgstr "Ukjend"
#: contents/ui/SearchPage.qml:117
#, kde-format
msgid "Play next"
msgstr "Spel som neste"
#: contents/ui/SearchPage.qml:129
#, kde-format
msgid "Radio"
msgstr "Radio"
#: contents/ui/SearchPage.qml:147
#, kde-format
msgid "Find music on YouTube Music"
msgstr "Søk etter musikk på YouTube Music"
+1 -1
View File
@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: kdeorg\n"
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
"POT-Creation-Date: 2022-07-12 00:48+0000\n"
"PO-Revision-Date: 2022-08-13 03:53\n"
"PO-Revision-Date: 2022-07-16 05:20\n"
"Last-Translator: \n"
"Language-Team: Chinese Simplified\n"
"Language: zh_CN\n"
+2 -3
View File
@@ -19,6 +19,7 @@ target_include_directories(asyncdatabase PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(asyncdatabase PUBLIC Qt5::Sql)
add_subdirectory(example)
add_subdirectory(mpris2)
add_executable(audiotube
main.cpp
@@ -48,11 +49,9 @@ target_link_libraries(audiotube
KF5::I18n
KF5::CoreAddons
KF5::Crash
QCoro5::Core
QCoro5::Qml
QCoro5::Network
ytm
asyncdatabase
mpris2
)
target_compile_definitions(audiotube PRIVATE -DQT_NO_KEYWORDS -DQT_NO_URL_CAST_FROM_STRING)
+13 -30
View File
@@ -4,43 +4,26 @@
#include "albummodel.h"
#include <QCoro/QCoroFuture>
#include <QCoro/QCoroTask>
#define AWAIT(coroutine) \
({ \
bool __abort = false; \
connect(this, &QObject::destroyed, [&]() { \
__abort = true; \
}); \
auto val = co_await coroutine; \
if (abort) { \
co_return; \
} \
val; \
})
#include <QDebug>
AlbumModel::AlbumModel(QObject *parent)
: AbstractYTMusicModel(parent)
{
connect(this, &AlbumModel::browseIdChanged, this, [this]() -> QCoro::Task<> {
connect(this, &AlbumModel::browseIdChanged, this, [this] {
setLoading(true);
auto future = YTMusicThread::instance()->fetchAlbum(m_browseId);
connectFuture(future, this, [=, this](const album::Album &album) {
setLoading(false);
auto album = co_await YTMusicThread::instance()->fetchAlbum(m_browseId).withContext(this);
beginResetModel();
m_album = album;
endResetModel();
std::sort(m_album.thumbnails.begin(), m_album.thumbnails.end());
setLoading(false);
beginResetModel();
m_album = album;
endResetModel();
std::sort(m_album.thumbnails.begin(), m_album.thumbnails.end());
Q_EMIT titleChanged();
Q_EMIT thumbnailUrlChanged();
Q_EMIT playlistIdChanged();
Q_EMIT titleChanged();
Q_EMIT thumbnailUrlChanged();
Q_EMIT playlistIdChanged();
});
});
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::errorOccurred, this, [=, this] {
+18 -19
View File
@@ -6,38 +6,37 @@
#include <asyncytmusic.h>
#include <QCoro/QCoroTask>
#include <QCoro/QCoroFuture>
ArtistModel::ArtistModel(QObject *parent)
: AbstractYTMusicModel(parent)
, m_view(albums, singles, songs, videos)
{
connect(this, &ArtistModel::channelIdChanged, this, [this]() -> QCoro::Task<> {
connect(this, &ArtistModel::channelIdChanged, this, [this] {
if (m_channelId.isEmpty()) {
co_return;
return;
}
setLoading(true);
auto artist = co_await YTMusicThread::instance()->fetchArtist(m_channelId);
setLoading(false);
auto future = YTMusicThread::instance()->fetchArtist(m_channelId);
connectFuture(future, this, [=, this](const artist::Artist &artist) {
setLoading(false);
beginResetModel();
m_artist = artist;
std::sort(m_artist.thumbnails.begin(), m_artist.thumbnails.end());
beginResetModel();
m_artist = artist;
std::sort(m_artist.thumbnails.begin(), m_artist.thumbnails.end());
albums = m_artist.albums ? m_artist.albums->results : std::vector<artist::Artist::Album>();
singles = m_artist.singles ? m_artist.singles->results : std::vector<artist::Artist::Single>();
songs = m_artist.songs ? m_artist.songs->results : std::vector<artist::Artist::Song>();
videos = m_artist.videos ? m_artist.videos->results : std::vector<artist::Artist::Video>();
albums = m_artist.albums ? m_artist.albums->results : std::vector<artist::Artist::Album>();
singles = m_artist.singles ? m_artist.singles->results : std::vector<artist::Artist::Single>();
songs = m_artist.songs ? m_artist.songs->results : std::vector<artist::Artist::Song>();
videos = m_artist.videos ? m_artist.videos->results : std::vector<artist::Artist::Video>();
// std::span can't know if the data pointer underneath it was changed, so re-create
m_view = MultiIterableView(albums, singles, songs, videos);
endResetModel();
// std::span can't know if the data pointer underneath it was changed, so re-create
m_view = MultiIterableView(albums, singles, songs, videos);
endResetModel();
Q_EMIT titleChanged();
Q_EMIT thumbnailUrlChanged();
Q_EMIT titleChanged();
Q_EMIT thumbnailUrlChanged();
});
});
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::errorOccurred, this, [this] {
setLoading(false);
+8 -8
View File
@@ -60,7 +60,7 @@ AsyncYTMusic::AsyncYTMusic(QObject *parent)
//
// search
//
QCoro::Task<std::vector<search::SearchResultItem>> AsyncYTMusic::search(const QString &query)
QFuture<std::vector<search::SearchResultItem>> AsyncYTMusic::search(const QString &query)
{
return invokeAndCatchOnThread([=, this]() {
return m_ytm->search(query.toStdString());
@@ -70,7 +70,7 @@ QCoro::Task<std::vector<search::SearchResultItem>> AsyncYTMusic::search(const QS
//
// fetchArtist
//
QCoro::Task<artist::Artist> AsyncYTMusic::fetchArtist(const QString &channelId)
QFuture<artist::Artist> AsyncYTMusic::fetchArtist(const QString &channelId)
{
return invokeAndCatchOnThread([=, this]() {
return m_ytm->get_artist(channelId.toStdString());
@@ -80,7 +80,7 @@ QCoro::Task<artist::Artist> AsyncYTMusic::fetchArtist(const QString &channelId)
//
// fetchAlbum
//
QCoro::Task<album::Album> AsyncYTMusic::fetchAlbum(const QString &browseId)
QFuture<album::Album> AsyncYTMusic::fetchAlbum(const QString &browseId)
{
return invokeAndCatchOnThread([=, this]() {
return m_ytm->get_album(browseId.toStdString());
@@ -90,7 +90,7 @@ QCoro::Task<album::Album> AsyncYTMusic::fetchAlbum(const QString &browseId)
//
// fetchSong
//
QCoro::Task<std::optional<song::Song>> AsyncYTMusic::fetchSong(const QString &videoId)
QFuture<std::optional<song::Song>> AsyncYTMusic::fetchSong(const QString &videoId)
{
return invokeAndCatchOnThread([=, this]() {
return m_ytm->get_song(videoId.toStdString());
@@ -100,7 +100,7 @@ QCoro::Task<std::optional<song::Song>> AsyncYTMusic::fetchSong(const QString &vi
//
// fetchPlaylist
//
QCoro::Task<playlist::Playlist> AsyncYTMusic::fetchPlaylist(const QString &playlistId) {
QFuture<playlist::Playlist> AsyncYTMusic::fetchPlaylist(const QString &playlistId) {
return invokeAndCatchOnThread([=, this]() {
return m_ytm->get_playlist(playlistId.toStdString());
});
@@ -109,7 +109,7 @@ QCoro::Task<playlist::Playlist> AsyncYTMusic::fetchPlaylist(const QString &playl
//
// fetchArtistAlbum
//
QCoro::Task<std::vector<artist::Artist::Album>> AsyncYTMusic::fetchArtistAlbums(const QString &channelId, const QString &params)
QFuture<std::vector<artist::Artist::Album>> AsyncYTMusic::fetchArtistAlbums(const QString &channelId, const QString &params)
{
return invokeAndCatchOnThread([=, this]() {
return m_ytm->get_artist_albums(channelId.toStdString(), params.toStdString());
@@ -119,7 +119,7 @@ QCoro::Task<std::vector<artist::Artist::Album>> AsyncYTMusic::fetchArtistAlbums(
//
// extractVideoInfo
//
QCoro::Task<video_info::VideoInfo> AsyncYTMusic::extractVideoInfo(const QString &videoId)
QFuture<video_info::VideoInfo> AsyncYTMusic::extractVideoInfo(const QString &videoId)
{
return invokeAndCatchOnThread([=, this]() {
return m_ytm->extract_video_info(videoId.toStdString());
@@ -129,7 +129,7 @@ QCoro::Task<video_info::VideoInfo> AsyncYTMusic::extractVideoInfo(const QString
//
// fetchWatchPlaylist
//
QCoro::Task<watch::Playlist> AsyncYTMusic::fetchWatchPlaylist(const std::optional<QString> &videoId, const std::optional<QString> &playlistId)
QFuture<watch::Playlist> AsyncYTMusic::fetchWatchPlaylist(const std::optional<QString> &videoId, const std::optional<QString> &playlistId)
{
return invokeAndCatchOnThread([=, this]() {
return m_ytm->get_watch_playlist(
+10 -13
View File
@@ -9,9 +9,6 @@
#include <QFuture>
#include <QFutureWatcher>
#include <QCoroFuture>
#include <QCoroTask>
#include <vector>
#include <ytmusic.h>
@@ -103,21 +100,21 @@ class AsyncYTMusic : public QObject
public:
// public functions need to be thread safe
QCoro::Task<std::vector<search::SearchResultItem>> search(const QString &query);
QFuture<std::vector<search::SearchResultItem>> search(const QString &query);
QCoro::Task<artist::Artist> fetchArtist(const QString &channelId);
QFuture<artist::Artist> fetchArtist(const QString &channelId);
QCoro::Task<album::Album> fetchAlbum(const QString &browseId);
QFuture<album::Album> fetchAlbum(const QString &browseId);
QCoro::Task<std::optional<song::Song> > fetchSong(const QString &videoId);
QFuture<std::optional<song::Song> > fetchSong(const QString &videoId);
QCoro::Task<playlist::Playlist> fetchPlaylist(const QString &playlistId);
QFuture<playlist::Playlist> fetchPlaylist(const QString &playlistId);
QCoro::Task<std::vector<artist::Artist::Album>> fetchArtistAlbums(const QString &channelId, const QString &params);
QFuture<std::vector<artist::Artist::Album>> fetchArtistAlbums(const QString &channelId, const QString &params);
QCoro::Task<video_info::VideoInfo> extractVideoInfo(const QString &videoId);
QFuture<video_info::VideoInfo> extractVideoInfo(const QString &videoId);
QCoro::Task<watch::Playlist> fetchWatchPlaylist(const std::optional<QString> &videoId = std::nullopt ,
QFuture<watch::Playlist> fetchWatchPlaylist(const std::optional<QString> &videoId = std::nullopt ,
const std::optional<QString> &playlistId = std::nullopt);
Q_SIGNAL void errorOccurred(const QString &error);
@@ -128,7 +125,7 @@ protected:
private:
/// Invokes the given function on the thread of the YTMusic object, and handles exceptions that occur while invoking it.
template <typename Func>
QCoro::Task<std::invoke_result_t<Func>> invokeAndCatchOnThread(Func fun) {
QFuture<std::invoke_result_t<Func>> invokeAndCatchOnThread(Func fun) {
using ReturnType = std::invoke_result_t<Func>;
auto interface = std::make_shared<QFutureInterface<ReturnType>>();
QMetaObject::invokeMethod(this, [=, this]() {
@@ -141,7 +138,7 @@ private:
Q_EMIT errorOccurred(QString::fromLocal8Bit(err.what()));
}
});
co_return co_await interface->future();
return interface->future();
}
// Python interpreter will be initialized from the thread calling the methods
+11 -1
View File
@@ -98,7 +98,17 @@ Flickable {
videoId: UserPlaylistModel.currentVideoId
onTitleChanged: Library.addPlaybackHistoryItem(info.videoId, info.title)
}
MprisPlayer {
id: mpris
trackTitle: info.title
album: "Test Album"
playbackState: MprisPlayer.PlaybackRunning
playerName: "AudioTube"
trackDuration: audio.duration
}
property var audioPlayer: Audio {
id: audio
+44 -59
View File
@@ -12,18 +12,8 @@
#include "asyncdatabase.h"
#include <QCoroTask>
#include <QCoroFuture>
#include <QCoroQmlTask>
#include <QCoroNetworkReply>
namespace ranges = std::ranges;
template <typename T>
QCoro::Task<T> toTask(QFuture<T> &&future) {
co_return co_await future;
}
Library::Library(QObject *parent)
: QObject{parent}
, m_database(ThreadedDatabase::establishConnection([]() -> DatabaseConfiguration {
@@ -51,23 +41,19 @@ Library &Library::instance()
FavouritesModel *Library::favourites()
{
auto future = m_database->getResults<Song>("select * from favourites natural join songs");
return new FavouritesModel(toTask(std::move(future)), this);
return new FavouritesModel(std::move(future), this);
}
QCoro::QmlTask Library::addFavourite(const QString &videoId, const QString &title)
void Library::addFavourite(const QString &videoId, const QString &title)
{
return [=, this]() -> QCoro::Task<> {
co_await addSong(videoId, title);
co_await m_database->execute("insert or ignore into favourites (video_id) values (?)", videoId);
}();
connectFuture(addSong(videoId, title), this, [=, this] {
connectFuture(m_database->execute("insert or ignore into favourites (video_id) values (?)", videoId), this, &Library::favouritesChanged);
});
}
QCoro::QmlTask Library::removeFavourite(const QString &videoId)
void Library::removeFavourite(const QString &videoId)
{
return [=, this]() -> QCoro::Task<> {
co_await m_database->execute("delete from favourites where video_id = ?", videoId);
Library::favouritesChanged();
}();
connectFuture(m_database->execute("delete from favourites where video_id = ?", videoId), this, &Library::favouritesChanged);
}
FavouriteWatcher *Library::favouriteWatcher(const QString &videoId)
@@ -80,7 +66,7 @@ FavouriteWatcher *Library::favouriteWatcher(const QString &videoId)
SearchHistoryModel *Library::searches()
{
return new SearchHistoryModel(toTask(m_database->getResults<SingleValue<QString>>("select (search_query) from searches order by search_id desc")), this);
return new SearchHistoryModel(m_database->getResults<SingleValue<QString>>("select (search_query) from searches order by search_id desc"), this);
}
void Library::addSearch(const QString &text)
@@ -91,22 +77,22 @@ void Library::addSearch(const QString &text)
PlaybackHistoryModel *Library::playbackHistory()
{
auto future = m_database->getResults<PlayedSong>("select * from played_songs natural join songs");
return new PlaybackHistoryModel(toTask(std::move(future)), this);
return new PlaybackHistoryModel(std::move(future), this);
}
QCoro::QmlTask Library::addPlaybackHistoryItem(const QString videoId, const QString title)
void Library::addPlaybackHistoryItem(const QString &videoId, const QString &title)
{
return [=, this]() -> QCoro::Task<> {
co_await addSong(videoId, title);
co_await m_database->execute("insert or ignore into played_songs (video_id, plays) values (?, ?)", videoId, 0);
co_await m_database->execute("update played_songs set plays = plays + 1 where video_id = ? ", videoId);
}();
connectFuture(addSong(videoId, title), this, [=, this] {
connectFuture(m_database->execute("insert or ignore into played_songs (video_id, plays) values (?, ?)", videoId, 0), this, [=, this] {
connectFuture(m_database->execute("update played_songs set plays = plays + 1 where video_id = ? ", videoId), this, &Library::playbackHistoryChanged);
});
});
}
PlaybackHistoryModel *Library::mostPlayed()
{
auto future = m_database->getResults<PlayedSong>("select * from played_songs natural join songs order by plays desc limit 10");
return new PlaybackHistoryModel(toTask(std::move(future)), this);
return new PlaybackHistoryModel(std::move(future), this);
}
QNetworkAccessManager &Library::nam()
@@ -114,14 +100,14 @@ QNetworkAccessManager &Library::nam()
return m_networkImageCacher;
}
QCoro::Task<> Library::addSong(const QString videoId, const QString title)
QFuture<void> Library::addSong(const QString &videoId, const QString &title)
{
co_await m_database->execute("insert or ignore into songs (video_id, title, artist, album) values (?, ?, null, null)", videoId, title);
return m_database->execute("insert or ignore into songs (video_id, title, artist, album) values (?, ?, null, null)", videoId, title);
}
QCoro::Task<> ThumbnailSource::setVideoId(const QString &id) {
void ThumbnailSource::setVideoId(const QString &id) {
if (m_videoId == id) {
co_return;
return;
}
m_videoId = id;
@@ -133,21 +119,25 @@ QCoro::Task<> ThumbnailSource::setVideoId(const QString &id) {
if (QFile::exists(cacheLocation)) {
setCachedPath(QUrl::fromLocalFile(cacheLocation));
co_return;
return;
}
auto info = co_await YTMusicThread::instance()->extractVideoInfo(id);
auto *reply = co_await Library::instance().nam().get(QNetworkRequest(QUrl(QString::fromStdString(info.thumbnail))));
QFile file(cacheLocation);
file.open(QFile::WriteOnly);
file.write(reply->readAll());
setCachedPath(QUrl::fromLocalFile(cacheLocation));
auto future = YTMusicThread::instance()->extractVideoInfo(id);
connectFuture(future, this, [this, id, cacheLocation](video_info::VideoInfo &&info) {
auto *reply = Library::instance().nam().get(QNetworkRequest(QUrl(QString::fromStdString(info.thumbnail))));
connect(reply, &QNetworkReply::finished, this, [id, reply, this, cacheLocation]() {
QFile file(cacheLocation);
file.open(QFile::WriteOnly);
file.write(reply->readAll());
setCachedPath(QUrl::fromLocalFile(cacheLocation));
});
});
}
PlaybackHistoryModel::PlaybackHistoryModel(QCoro::Task<std::vector<PlayedSong> > &&songs, QObject *parent)
PlaybackHistoryModel::PlaybackHistoryModel(QFuture<std::vector<PlayedSong> > &&songs, QObject *parent)
: QAbstractListModel(parent)
{
songs.then([this](const auto songs) {
connectFuture(songs, this, [this](const auto songs) {
beginResetModel();
m_playedSongs = songs;
endResetModel();
@@ -182,10 +172,10 @@ QVariant PlaybackHistoryModel::data(const QModelIndex &index, int role) const {
Q_UNREACHABLE();
}
FavouritesModel::FavouritesModel(QCoro::Task<std::vector<Song>> &&songs, QObject *parent)
FavouritesModel::FavouritesModel(QFuture<std::vector<Song>> &&songs, QObject *parent)
: QAbstractListModel(parent)
{
songs.then([this](const auto songs) {
connectFuture(songs, this, [this](const auto songs) {
beginResetModel();
m_favouriteSongs = songs;
endResetModel();
@@ -217,21 +207,16 @@ QVariant FavouritesModel::data(const QModelIndex &index, int role) const {
Q_UNREACHABLE();
}
template <typename T>
QCoro::Task<T> toTask(QFuture<T> &future) {
auto result = co_await future;
co_return result;
}
FavouriteWatcher::FavouriteWatcher(Library *library, const QString &videoId)
: QObject(library), m_videoId(videoId), m_library(library)
{
auto update = [this]() -> QCoro::Task<> {
auto count = co_await toTask(m_library->database().getResult<SingleValue<bool>>("select count(*) > 0 from favourites where video_id = ?", m_videoId)).withContext(this);
if (count) {
m_isFavourite = count->value;
Q_EMIT isFavouriteChanged();
}
auto update = [this] {
connectFuture(m_library->database().getResult<SingleValue<bool>>("select count(*) > 0 from favourites where video_id = ?", m_videoId), this, [this](auto count) {
if (count) {
m_isFavourite = count->value;
Q_EMIT isFavouriteChanged();
}
});
};
update();
connect(library, &Library::favouritesChanged, this, update);
@@ -241,10 +226,10 @@ bool FavouriteWatcher::isFavourite() const {
return m_isFavourite;
}
SearchHistoryModel::SearchHistoryModel(QCoro::Task<std::vector<SingleValue<QString>>> &&historyFuture, QObject *parent)
SearchHistoryModel::SearchHistoryModel(QFuture<std::vector<SingleValue<QString>>> &&historyFuture, QObject *parent)
: QAbstractListModel(parent)
{
historyFuture.then([this](const auto history) {
connectFuture(historyFuture, this, [this](const auto history) {
beginResetModel();
m_history = history;
endResetModel();
+8 -10
View File
@@ -9,8 +9,6 @@
#include <QNetworkAccessManager>
#include <QAbstractListModel>
#include <QCoroQmlTask>
#include <memory>
#include "asyncytmusic.h"
@@ -42,7 +40,7 @@ class FavouritesModel : public QAbstractListModel {
};
public:
FavouritesModel(QCoro::Task<std::vector<Song>> &&songs, QObject *parent = nullptr);
FavouritesModel(QFuture<std::vector<Song>> &&songs, QObject *parent = nullptr);
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex &parent) const override;
@@ -78,7 +76,7 @@ class PlaybackHistoryModel : public QAbstractListModel {
};
public:
PlaybackHistoryModel(QCoro::Task<std::vector<PlayedSong>> &&songs, QObject *parent = nullptr);
PlaybackHistoryModel(QFuture<std::vector<PlayedSong>> &&songs, QObject *parent = nullptr);
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex &parent) const override;
@@ -92,7 +90,7 @@ class SearchHistoryModel : public QAbstractListModel {
Q_OBJECT
public:
SearchHistoryModel(QCoro::Task<std::vector<SingleValue<QString>>> &&historyFuture, QObject *parent = nullptr);
SearchHistoryModel(QFuture<std::vector<SingleValue<QString>>> &&historyFuture, QObject *parent = nullptr);
int rowCount(const QModelIndex &parent) const override;
QVariant data(const QModelIndex &index, int role) const override;
@@ -116,8 +114,8 @@ public:
FavouritesModel *favourites();
Q_SIGNAL void favouritesChanged();
Q_INVOKABLE QCoro::QmlTask addFavourite(const QString &videoId, const QString &title);
Q_INVOKABLE QCoro::QmlTask removeFavourite(const QString &videoId);
Q_INVOKABLE void addFavourite(const QString &videoId, const QString &title);
Q_INVOKABLE void removeFavourite(const QString &videoId);
Q_INVOKABLE FavouriteWatcher *favouriteWatcher(const QString &videoId);
SearchHistoryModel *searches();
@@ -126,7 +124,7 @@ public:
PlaybackHistoryModel *playbackHistory();
Q_SIGNAL void playbackHistoryChanged();
Q_INVOKABLE QCoro::QmlTask addPlaybackHistoryItem(const QString videoId, const QString title);
Q_INVOKABLE void addPlaybackHistoryItem(const QString &videoId, const QString &title);
PlaybackHistoryModel *mostPlayed();
@@ -136,7 +134,7 @@ public:
}
private:
QCoro::Task<> addSong(const QString videoId, const QString title);
QFuture<void> addSong(const QString &videoId, const QString &title);
QNetworkAccessManager m_networkImageCacher;
std::unique_ptr<ThreadedDatabase> m_database;
@@ -152,7 +150,7 @@ class ThumbnailSource : public QObject {
QString videoId() const {
return m_videoId;
}
QCoro::Task<> setVideoId(const QString &id);
void setVideoId(const QString &id);
Q_SIGNAL void videoIdChanged();
QUrl cachedPath() const {
+4 -5
View File
@@ -11,8 +11,8 @@
#include <KCrash>
#include <KAboutData>
#include <QCoroQml>
#include "declarativemprisplayer.h"
#include "mpris2.h"
#include "searchmodel.h"
#include "albummodel.h"
#include "videoinfoextractor.h"
@@ -23,7 +23,7 @@
#include "playerutils.h"
#include "library.h"
#include "asyncdatabase.h"
#include <mediaplayer2.h>
constexpr auto URI = "org.kde.ytmusic";
@@ -40,8 +40,6 @@ Q_DECL_EXPORT int main(int argc, char *argv[])
KAboutData::setApplicationData(KAboutData(QStringLiteral("audiotube"), QStringLiteral("AudioTube")));
KCrash::initialize();
QCoro::Qml::registerTypes();
QQmlApplicationEngine engine;
qmlRegisterType<SearchModel>(URI, 1, 0, "SearchModel");
@@ -49,6 +47,7 @@ Q_DECL_EXPORT int main(int argc, char *argv[])
qmlRegisterType<VideoInfoExtractor>(URI, 1, 0, "VideoInfoExtractor");
qmlRegisterType<ArtistModel>(URI, 1, 0, "ArtistModel");
qmlRegisterType<PlaylistModel>(URI, 1, 0, "PlaylistModel");
qmlRegisterType<DeclarativeMprisPlayer>(URI, 1, 0, "MprisPlayer");
qmlRegisterSingletonType<UserPlaylistModel>(URI, 1, 0, "UserPlaylistModel", [](QQmlEngine *, QJSEngine *) {
return new UserPlaylistModel();
});
+16
View File
@@ -0,0 +1,16 @@
# SPDX-FileCopyrightText: 2021 Jonah Brüchert <[email protected]>
#
# SPDX-License-Identifier: BSD-2-Clause
add_library(mpris2 STATIC
mediaplayer2.cpp
mediaplayer2.h
mediaplayer2player.cpp
mediaplayer2player.h
mpris2.cpp
mpris2.h
declarativemprisplayer.cpp
declarativemprisplayer.h
)
target_link_libraries(mpris2 PUBLIC Qt::Core Qt::Gui Qt::DBus KF5::CoreAddons)
+61
View File
@@ -0,0 +1,61 @@
#include "declarativemprisplayer.h"
#include <QTimer>
DeclarativeMprisPlayer::DeclarativeMprisPlayer()
{
QTimer::singleShot(0, [this]() {
connect(m_mpris.player(), &MediaPlayer2Player::play, this, &DeclarativeMprisPlayer::play);
connect(m_mpris.player(), &MediaPlayer2Player::playPause, this, &DeclarativeMprisPlayer::playPause);
connect(m_mpris.player(), &MediaPlayer2Player::Stop, this, &DeclarativeMprisPlayer::stop);
});
}
QString DeclarativeMprisPlayer::playerName() const {
return m_mpris.playerName();
}
void DeclarativeMprisPlayer::setPlayerName(const QString &name) {
m_mpris.setPlayerName(name);
Q_EMIT playerNameChanged();
}
DeclarativeMprisPlayer::PlaybackState DeclarativeMprisPlayer::playbackState() {
return PlaybackState(m_mpris.player()->playbackState());
}
void DeclarativeMprisPlayer::setPlaybackState(PlaybackState state) {
m_mpris.player()->setPlaybackState(MediaPlayer2Player::PlaybackState(state));
}
void DeclarativeMprisPlayer::setAlbum(const QString &album) {
m_mpris.player()->setCurrentTrackAlbum(album);
}
QString DeclarativeMprisPlayer::album() const {
return m_mpris.player()->currentTrackAlbum();
}
QUrl DeclarativeMprisPlayer::albumArt() const {
return m_mpris.player()->currentTrackCoverArt();
}
void DeclarativeMprisPlayer::setAlbumArt(const QUrl &url) {
m_mpris.player()->setCurrentTrackCoverArt(url);
}
QString DeclarativeMprisPlayer::trackTitle() const
{
return m_mpris.player()->currentTrackTitle();
}
void DeclarativeMprisPlayer::setTrackTitle(const QString &title)
{
m_mpris.player()->setCurrentTrackTitle(title);
}
void DeclarativeMprisPlayer::setTrackDuration(qlonglong trackDuration)
{
return m_mpris.player()->setDuration(trackDuration);
}
qlonglong DeclarativeMprisPlayer::trackDuration() const { return 0; }
+66
View File
@@ -0,0 +1,66 @@
#pragma once
#include <QObject>
#include "mediaplayer2player.h"
#include "mpris2.h"
class DeclarativeMprisPlayer : public QObject
{
Q_OBJECT
Q_PROPERTY(QString playerName READ playerName WRITE setPlayerName NOTIFY playerNameChanged)
Q_PROPERTY(PlaybackState playbackState READ playbackState WRITE setPlaybackState NOTIFY playbackStateChanged)
Q_PROPERTY(QString album READ album WRITE setAlbum NOTIFY albumChanged)
Q_PROPERTY(QUrl albumArt READ albumArt WRITE setAlbumArt NOTIFY albumArtChanged)
Q_PROPERTY(QString trackTitle READ trackTitle WRITE setTrackTitle NOTIFY trackTitleChanged);
Q_PROPERTY(qlonglong trackDuration READ trackDuration WRITE setTrackDuration NOTIFY trackDurationChanged);
public:
enum PlaybackState {
PlaybackStopped = MediaPlayer2Player::PlaybackStopped,
PlaybackRunning = MediaPlayer2Player::PlaybackRunning,
PlaybackPaused = MediaPlayer2Player::PlaybackPaused
};
Q_ENUM(PlaybackState)
DeclarativeMprisPlayer();
QString playerName() const;
void setPlayerName(const QString &name);
Q_SIGNAL void playerNameChanged();
PlaybackState playbackState();
void setPlaybackState(PlaybackState state);
Q_SIGNAL void playbackStateChanged();
void setAlbum(const QString &album);
QString album() const;
Q_SIGNAL void albumChanged();
QUrl albumArt() const;
void setAlbumArt(const QUrl &url);
Q_SIGNAL void albumArtChanged();
QString trackTitle() const;
void setTrackTitle(const QString &title);
Q_SIGNAL void trackTitleChanged();
qlonglong trackDuration() const;
void setTrackDuration(qlonglong trackDuration);
Q_SIGNAL void trackDurationChanged();
Q_SIGNAL void changeVolume();
Q_SIGNAL void play();
Q_SIGNAL void playPause();
Q_SIGNAL void stop();
Q_SIGNAL void goPrevious();
Q_SIGNAL void goNext();
private:
Mpris2 m_mpris;
};
+71
View File
@@ -0,0 +1,71 @@
// SPDX-FileCopyrightText: 2014 (c) Sujith Haridasan <[email protected]>
// SPDX-FileCopyrightText: 2014 (c) Ashish Madeti <[email protected]>
// SPDX-FileCopyrightText: 2016 (c) Matthieu Gallien <[email protected]>
// SPDX-License-Identifier: GPL-3.0-or-later
#include "mediaplayer2.h"
#include <QCoreApplication>
#include <KAboutData>
MediaPlayer2::MediaPlayer2(QObject* parent)
: QDBusAbstractAdaptor(parent)
{
}
MediaPlayer2::~MediaPlayer2()
= default;
bool MediaPlayer2::CanQuit() const
{
return true;
}
bool MediaPlayer2::CanRaise() const
{
return true;
}
bool MediaPlayer2::HasTrackList() const
{
return false;
}
void MediaPlayer2::Quit()
{
QCoreApplication::quit();
}
void MediaPlayer2::Raise()
{
Q_EMIT raisePlayer();
}
QString MediaPlayer2::Identity() const
{
return KAboutData::applicationData().displayName();
}
QString MediaPlayer2::DesktopEntry() const
{
return KAboutData::applicationData().desktopFileName();
}
QStringList MediaPlayer2::SupportedUriSchemes() const
{
return QStringList() << QStringLiteral("file");
}
QStringList MediaPlayer2::SupportedMimeTypes() const
{
// KService::Ptr app = KService::serviceByDesktopName(KCmdLineArgs::aboutData()->appName());
// if (app) {
// return app->mimeTypes();
// }
return QStringList();
}
#include "moc_mediaplayer2.cpp"
+51
View File
@@ -0,0 +1,51 @@
/***************************************************************************
SPDX-FileCopyrightText: 2014 (c) Sujith Haridasan <[email protected]>
SPDX-FileCopyrightText: 2014 (c) Ashish Madeti <[email protected]>
SPDX-FileCopyrightText: 2016 (c) Matthieu Gallien <[email protected]>
SPDX-License-Identifier: GPL-3.0-or-later
***************************************************************************/
#pragma once
#include <QDBusAbstractAdaptor>
#include <QStringList>
class MediaPlayer2 : public QDBusAbstractAdaptor
{
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "org.mpris.MediaPlayer2") // Docs: https://specifications.freedesktop.org/mpris-spec/latest/Media_Player.html
Q_PROPERTY(bool CanQuit READ CanQuit CONSTANT)
Q_PROPERTY(bool CanRaise READ CanRaise CONSTANT)
Q_PROPERTY(bool HasTrackList READ HasTrackList CONSTANT)
Q_PROPERTY(QString Identity READ Identity CONSTANT)
Q_PROPERTY(QString DesktopEntry READ DesktopEntry CONSTANT)
Q_PROPERTY(QStringList SupportedUriSchemes READ SupportedUriSchemes CONSTANT)
Q_PROPERTY(QStringList SupportedMimeTypes READ SupportedMimeTypes CONSTANT)
public:
explicit MediaPlayer2(QObject* parent = nullptr);
~MediaPlayer2() override;
[[nodiscard]] bool CanQuit() const;
[[nodiscard]] bool CanRaise() const;
[[nodiscard]] bool HasTrackList() const;
[[nodiscard]] QString Identity() const;
[[nodiscard]] QString DesktopEntry() const;
[[nodiscard]] QStringList SupportedUriSchemes() const;
[[nodiscard]] QStringList SupportedMimeTypes() const;
public Q_SLOTS:
void Quit();
void Raise();
Q_SIGNALS:
void raisePlayer();
};
+383
View File
@@ -0,0 +1,383 @@
// SPDX-FileCopyrightText: 2014 (c) Sujith Haridasan <[email protected]>
// SPDX-FileCopyrightText: 2014 (c) Ashish Madeti <[email protected]>
// SPDX-FileCopyrightText: 2016 (c) Matthieu Gallien <[email protected]>
// SPDX-FileCopyrightText: 2022 (c) Jonah Brüchert <[email protected]>
// SPDX-License-Identifier: GPL-3.0-or-later
#include "mediaplayer2player.h"
#include <QStringList>
#include <QStringBuilder>
#include <QDBusConnection>
#include <QMetaClassInfo>
#include <QImage>
#include <QBuffer>
constexpr double MAX_RATE = 1.0;
constexpr const double MIN_RATE = 1.0;
MediaPlayer2Player::MediaPlayer2Player(QObject* parent)
: QDBusAbstractAdaptor(parent)
{
m_volume = 0; // TODO
m_canPlay = true; // TODO
signalPropertiesChange(QStringLiteral("Volume"), Volume());
m_mediaPlayerPresent = 1;
}
MediaPlayer2Player::~MediaPlayer2Player() = default;
QString MediaPlayer2Player::PlaybackStatus() const
{
switch (m_playbackState) {
case PlaybackStopped:
return QStringLiteral("Stopped");
case PlaybackRunning:
return QStringLiteral("Playing");
case PlaybackPaused:
return QStringLiteral("Paused");
}
Q_UNREACHABLE();
}
bool MediaPlayer2Player::CanGoNext() const
{
return m_canGoNext;
}
void MediaPlayer2Player::Next()
{
Q_EMIT next();
}
bool MediaPlayer2Player::CanGoPrevious() const
{
return m_canGoPrevious;
}
void MediaPlayer2Player::Previous()
{
Q_EMIT previous();
}
bool MediaPlayer2Player::CanPause() const
{
return m_canPlay;
}
void MediaPlayer2Player::Pause()
{
Q_EMIT pause();
}
void MediaPlayer2Player::PlayPause()
{
Q_EMIT playPause();
}
void MediaPlayer2Player::Stop()
{
Q_EMIT stop();
}
bool MediaPlayer2Player::CanPlay() const
{
return m_canPlay;
}
void MediaPlayer2Player::Play()
{
Q_EMIT play();
}
double MediaPlayer2Player::Volume() const
{
return m_volume;
}
void MediaPlayer2Player::setVolume(double volume)
{
m_volume = qBound(0.0, volume, 1.0);
Q_EMIT volumeChanged(m_volume);
signalPropertiesChange(QStringLiteral("Volume"), Volume());
}
void MediaPlayer2Player::setPropertyPosition(int newPositionInMs)
{
// TODO
}
QVariantMap MediaPlayer2Player::Metadata() const
{
return m_metadata;
}
qlonglong MediaPlayer2Player::Position() const
{
return m_position;
}
double MediaPlayer2Player::Rate() const
{
return m_rate;
}
void MediaPlayer2Player::setRate(double newRate)
{
if (newRate <= 0.0001 && newRate >= -0.0001) {
Pause();
} else {
m_rate = qBound(MinimumRate(), newRate, MaximumRate());
Q_EMIT rateChanged(m_rate);
signalPropertiesChange(QStringLiteral("Rate"), Rate());
}
}
double MediaPlayer2Player::MinimumRate() const
{
return MIN_RATE;
}
double MediaPlayer2Player::MaximumRate() const
{
return MAX_RATE;
}
bool MediaPlayer2Player::CanSeek() const
{
return m_playerIsSeekableChanged;
}
bool MediaPlayer2Player::CanControl() const
{
return true;
}
void MediaPlayer2Player::Seek(qlonglong Offset)
{
Q_EMIT seek(Offset);
}
void MediaPlayer2Player::SetPosition(const QDBusObjectPath &trackId, qlonglong pos)
{
Q_EMIT positionRequested(pos);
}
void MediaPlayer2Player::OpenUri(const QString &uri)
{
Q_UNUSED(uri);
}
//void MediaPlayer2Player::playerSourceChanged()
//{
// setCurrentTrack(m_manageAudioPlayer->playListPosition());
//}
void MediaPlayer2Player::playControlEnabledChanged()
{
signalPropertiesChange(QStringLiteral("CanPause"), CanPause());
signalPropertiesChange(QStringLiteral("CanPlay"), CanPlay());
Q_EMIT canPauseChanged();
Q_EMIT canPlayChanged();
}
void MediaPlayer2Player::skipBackwardControlEnabledChanged()
{
signalPropertiesChange(QStringLiteral("CanGoPrevious"), CanGoPrevious());
Q_EMIT canGoPreviousChanged();
}
void MediaPlayer2Player::skipForwardControlEnabledChanged()
{
signalPropertiesChange(QStringLiteral("CanGoNext"), CanGoNext());
Q_EMIT canGoNextChanged();
}
void MediaPlayer2Player::playerPlaybackStateChanged()
{
signalPropertiesChange(QStringLiteral("PlaybackStatus"), PlaybackStatus());
Q_EMIT playbackStatusChanged();
playerIsSeekableChanged();
}
void MediaPlayer2Player::playerIsSeekableChanged()
{
signalPropertiesChange(QStringLiteral("CanSeek"), CanSeek());
Q_EMIT canSeekChanged();
}
void MediaPlayer2Player::audioPositionChanged()
{
setPropertyPosition(m_position);
}
void MediaPlayer2Player::playerSeeked(qint64 position)
{
Q_EMIT Seeked(position * 1000);
}
void MediaPlayer2Player::audioDurationChanged()
{
m_metadata = getMetadataOfCurrentTrack();
signalPropertiesChange(QStringLiteral("Metadata"), Metadata());
skipBackwardControlEnabledChanged();
skipForwardControlEnabledChanged();
playerPlaybackStateChanged();
playerIsSeekableChanged();
setPropertyPosition(m_position);
}
QVariantMap MediaPlayer2Player::getMetadataOfCurrentTrack()
{
auto result = QVariantMap();
if (m_currentTrackId.isEmpty()) {
return {};
}
result[QStringLiteral("mpris:trackid")] = QVariant::fromValue<QDBusObjectPath>(QDBusObjectPath(m_currentTrackId));
result[QStringLiteral("mpris:length")] = m_duration * 1000;
//convert milli-seconds into micro-seconds
result[QStringLiteral("xesam:title")] = m_currentTrackTitle;
result[QStringLiteral("xesam:url")] = m_currentTrackUrl;
result[QStringLiteral("xesam:album")] = m_currentTrackAlbum;
result[QStringLiteral("xesam:artist")] = m_currentTrackArtist;
result[QStringLiteral("mpris:artUrl")] = m_coverArtUrl;
return result;
}
int MediaPlayer2Player::mediaPlayerPresent() const
{
return m_mediaPlayerPresent;
}
void MediaPlayer2Player::setDuration(qlonglong duration) {
m_duration = duration;
}
void MediaPlayer2Player::setPlaybackState(PlaybackState newState)
{
m_playbackState = newState;
}
void MediaPlayer2Player::setCurrentTrackTitle(const QString &title)
{
m_currentTrackTitle = title;
}
QString MediaPlayer2Player::currentTrackTitle() const
{
return m_currentTrackTitle;
}
void MediaPlayer2Player::setCurrentTrackUrl(const QUrl &url)
{
m_currentTrackUrl = url;
}
void MediaPlayer2Player::setCurrentTrackAlbum(const QString &album)
{
m_currentTrackAlbum = album;
}
QString MediaPlayer2Player::currentTrackAlbum() const
{
return m_currentTrackAlbum;
}
void MediaPlayer2Player::setCurrentTrackArtist(const QString &artist)
{
m_currentTrackArtist = artist;
}
QString MediaPlayer2Player::currentTrackArtist() const
{
return m_currentTrackArtist;
}
void MediaPlayer2Player::setCurrentTrackCoverArt(const QUrl &url)
{
m_coverArtUrl = url;
}
QUrl MediaPlayer2Player::currentTrackCoverArt() const
{
return m_coverArtUrl;
}
void MediaPlayer2Player::setCurrentTrackCoverArt(const QImage &image)
{
QByteArray data;
QBuffer buffer(&data);
image.save(&buffer);
m_coverArtUrl = QUrl(QString("data:image/png;base64," % data));
}
void MediaPlayer2Player::setCanGoPrevious(bool value)
{
m_canGoPrevious = value;
}
void MediaPlayer2Player::canGoForward(bool value)
{
m_canGoNext = value;
}
void MediaPlayer2Player::canPause(bool value)
{
m_canPause = value;
}
void MediaPlayer2Player::canPlay(bool value)
{
m_canPlay = value;
}
void MediaPlayer2Player::setPosition(qlonglong position) {
m_position = position;
}
void MediaPlayer2Player::setMediaPlayerPresent(int status)
{
if (m_mediaPlayerPresent != status) {
m_mediaPlayerPresent = status;
Q_EMIT mediaPlayerPresentChanged();
signalPropertiesChange(QStringLiteral("CanGoNext"), CanGoNext());
signalPropertiesChange(QStringLiteral("CanGoPrevious"), CanGoPrevious());
signalPropertiesChange(QStringLiteral("CanPause"), CanPause());
signalPropertiesChange(QStringLiteral("CanPlay"), CanPlay());
Q_EMIT canGoNextChanged();
Q_EMIT canGoPreviousChanged();
Q_EMIT canPauseChanged();
Q_EMIT canPlayChanged();
}
}
void MediaPlayer2Player::signalPropertiesChange(const QString &property, const QVariant &value)
{
QVariantMap properties;
properties[property] = value;
const int ifaceIndex = metaObject()->indexOfClassInfo("D-Bus Interface");
QDBusMessage msg = QDBusMessage::createSignal(QStringLiteral("/org/mpris/MediaPlayer2"),
QStringLiteral("org.freedesktop.DBus.Properties"), QStringLiteral("PropertiesChanged"));
msg << QLatin1String(metaObject()->classInfo(ifaceIndex).value());
msg << properties;
msg << QStringList();
QDBusConnection::sessionBus().send(msg);
}
#include "moc_mediaplayer2player.cpp"
+244
View File
@@ -0,0 +1,244 @@
// SPDX-FileCopyrightText: 2014 (c) Sujith Haridasan <[email protected]>
// SPDX-FileCopyrightText: 2014 (c) Ashish Madeti <[email protected]>
// SPDX-FileCopyrightText: 2016 (c) Matthieu Gallien <[email protected]>
// SPDX-FileCopyrightText: 2022 (c) Jonah Brüchert <[email protected]>
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <QDBusAbstractAdaptor>
#include <QDBusObjectPath>
#include <QDBusMessage>
#include <QUrl>
class MediaPlayListProxyModel;
class ManageAudioPlayer;
class ManageMediaPlayerControl;
class ManageHeaderBar;
class AudioWrapper;
class MediaPlayer2Player : public QDBusAbstractAdaptor
{
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "org.mpris.MediaPlayer2.Player") // Docs: https://specifications.freedesktop.org/mpris-spec/latest/Player_Interface.html
Q_PROPERTY(QString PlaybackStatus READ PlaybackStatus NOTIFY playbackStatusChanged)
Q_PROPERTY(double Rate READ Rate WRITE setRate NOTIFY rateChanged)
Q_PROPERTY(QVariantMap Metadata READ Metadata NOTIFY playbackStatusChanged)
Q_PROPERTY(double Volume READ Volume WRITE setVolume NOTIFY volumeChanged)
Q_PROPERTY(qlonglong Position READ Position WRITE setPropertyPosition NOTIFY playbackStatusChanged)
Q_PROPERTY(double MinimumRate READ MinimumRate CONSTANT)
Q_PROPERTY(double MaximumRate READ MaximumRate CONSTANT)
Q_PROPERTY(bool CanGoNext READ CanGoNext NOTIFY canGoNextChanged)
Q_PROPERTY(bool CanGoPrevious READ CanGoPrevious NOTIFY canGoPreviousChanged)
Q_PROPERTY(bool CanPlay READ CanPlay NOTIFY canPlayChanged)
Q_PROPERTY(bool CanPause READ CanPause NOTIFY canPauseChanged)
Q_PROPERTY(bool CanControl READ CanControl NOTIFY canControlChanged)
Q_PROPERTY(bool CanSeek READ CanSeek NOTIFY canSeekChanged)
Q_PROPERTY(int mediaPlayerPresent READ mediaPlayerPresent WRITE setMediaPlayerPresent NOTIFY mediaPlayerPresentChanged)
public:
explicit MediaPlayer2Player(QObject* parent = nullptr);
~MediaPlayer2Player() override;
QString PlaybackStatus() const;
double Rate() const;
QVariantMap Metadata() const;
double Volume() const;
qlonglong Position() const;
double MinimumRate() const;
double MaximumRate() const;
bool CanGoNext() const;
bool CanGoPrevious() const;
bool CanPlay() const;
bool CanPause() const;
bool CanSeek() const;
bool CanControl() const;
int currentTrack() const;
int mediaPlayerPresent() const;
void setDesktopFileName(const QString &fileName);
///
/// Signal to the mpris interface that the playback is now at a new position
///
void setPosition(qlonglong pos);
///
/// Signal the length of the current track to the mpris interface
///
void setDuration(qlonglong duration);
enum PlaybackState {
PlaybackStopped,
PlaybackRunning,
PlaybackPaused
};
///
/// Set the current playback state
///
void setPlaybackState(PlaybackState newState);
PlaybackState playbackState() const {
return m_playbackState;
}
///
/// Set the current track's title
///
void setCurrentTrackTitle(const QString &title);
QString currentTrackTitle() const;
///
/// Set the current track's URL
///
void setCurrentTrackUrl(const QUrl &url);
///
/// Set the album of the currently playing track
///
void setCurrentTrackAlbum(const QString &album);
QString currentTrackAlbum() const;
///
/// Set current track's artist
///
void setCurrentTrackArtist(const QString &artist);
QString currentTrackArtist() const;
///
/// Set cover art url
///
void setCurrentTrackCoverArt(const QUrl &url);
QUrl currentTrackCoverArt() const;
///
/// Set cover art image
///
void setCurrentTrackCoverArt(const QImage &image);
void setCanGoPrevious(bool);
void canGoForward(bool);
void canPause(bool value);
void canPlay(bool value);
Q_SIGNALS:
void Seeked(qlonglong Position);
void rateChanged(double newRate);
///
/// Emitted when the volume should be changed to newVol
///
void volumeChanged(double newVol);
void playbackStatusChanged();
void canGoNextChanged();
void canGoPreviousChanged();
void canPlayChanged();
void canPauseChanged();
void canControlChanged();
void canSeekChanged();
void currentTrackChanged();
void mediaPlayerPresentChanged();
///
/// Emitted when the player should skip to the next track
///
void next();
///
/// Emitted when it is requested to skip to the previous track
/// over the MPRIS interface
///
void previous();
///
/// Emitted when play / pause should be toggled
///
void playPause();
///
/// Emitted when the playback should be stopped
///
void stop();
///
/// Emitted when playback should be resumed
///
void play();
///
/// Emitted when the player should seek
///
void seek(qlonglong offset);
///
/// Emitted when the player should skip to the given position
///
void positionRequested(qlonglong position);
public Q_SLOTS:
void Next();
void Previous();
void Pause();
void PlayPause();
void Stop();
void Play();
void Seek(qlonglong Offset);
void SetPosition(const QDBusObjectPath &trackId, qlonglong pos);
void OpenUri(const QString &uri);
private Q_SLOTS:
void playControlEnabledChanged();
void skipBackwardControlEnabledChanged();
void skipForwardControlEnabledChanged();
void playerPlaybackStateChanged();
void playerIsSeekableChanged();
void audioPositionChanged();
void playerSeeked(qint64 position);
void audioDurationChanged();
private:
void signalPropertiesChange(const QString &property, const QVariant &value);
void setMediaPlayerPresent(int status);
void setRate(double newRate);
void setVolume(double volume);
void setPropertyPosition(int newPositionInMs);
QVariantMap getMetadataOfCurrentTrack();
QVariantMap m_metadata;
QString m_currentTrack;
QString m_currentTrackId;
double m_rate = 1.0;
double m_volume = 0.0;
int m_mediaPlayerPresent = 0;
bool m_canPause = true;
bool m_canPlay = false;
bool m_canGoNext = false;
bool m_canGoPrevious = false;
qlonglong m_position = 0;
bool m_playerIsSeekableChanged = false;
mutable QDBusMessage mProgressIndicatorSignal;
int mPreviousProgressPosition = 0;
// Current track metadata
qlonglong m_duration;
PlaybackState m_playbackState = PlaybackStopped;
QString m_currentTrackTitle;
QUrl m_currentTrackUrl;
QString m_currentTrackAlbum;
QString m_currentTrackArtist;
QUrl m_coverArtUrl;
};
+79
View File
@@ -0,0 +1,79 @@
/***************************************************************************
SPDX-FileCopyrightText: 2014 (c) Sujith Haridasan <[email protected]>
SPDX-FileCopyrightText: 2014 (c) Ashish Madeti <[email protected]>
SPDX-FileCopyrightText: 2016 (c) Matthieu Gallien <[email protected]>
SPDX-License-Identifier: GPL-3.0-or-later
***************************************************************************/
#include "mpris2.h"
#include "mediaplayer2.h"
#include "mediaplayer2player.h"
#include <QDBusConnection>
#if defined Q_OS_WIN
#include <Windows.h>
#else
#include <unistd.h>
#endif
Mpris2::Mpris2(QObject* parent)
: QObject(parent)
{
}
void Mpris2::initDBusService()
{
QString mspris2Name(QStringLiteral("org.mpris.MediaPlayer2.") + m_playerName);
bool success = QDBusConnection::sessionBus().registerService(mspris2Name);
// If the above failed, it's likely because we're not the first instance
// or the name is already taken. In that event the MPRIS2 spec wants the
// following:
if (!success) {
#if defined Q_OS_WIN
success = QDBusConnection::sessionBus().registerService(mspris2Name + QLatin1String(".instance") + QString::number(GetCurrentProcessId()));
#else
success = QDBusConnection::sessionBus().registerService(mspris2Name + QLatin1String(".instance") + QString::number(getpid()));
#endif
}
if (success) {
m_mp2 = std::make_unique<MediaPlayer2>(this);
m_mp2p = std::make_unique<MediaPlayer2Player>(this);
QDBusConnection::sessionBus().registerObject(QStringLiteral("/org/mpris/MediaPlayer2"), this, QDBusConnection::ExportAdaptors);
connect(m_mp2.get(), &MediaPlayer2::raisePlayer, this, &Mpris2::raisePlayer);
}
}
Mpris2::~Mpris2()
= default;
QString Mpris2::playerName() const
{
return m_playerName;
}
void Mpris2::setPlayerName(const QString &playerName)
{
if (m_playerName == playerName) {
return;
}
m_playerName = playerName;
if (!m_playerName.isEmpty()) {
if (!m_mp2) {
initDBusService();
}
}
Q_EMIT playerNameChanged();
}
#include "moc_mpris2.cpp"
+63
View File
@@ -0,0 +1,63 @@
/***************************************************************************
SPDX-FileCopyrightText: 2014 (c) Sujith Haridasan <[email protected]>
SPDX-FileCopyrightText: 2014 (c) Ashish Madeti <[email protected]>
SPDX-FileCopyrightText: 2016 (c) Matthieu Gallien <[email protected]>
SPDX-License-Identifier: GPL-3.0-or-later
***************************************************************************/
#pragma once
#include <QObject>
#include <memory>
class MediaPlayer2;
class MediaPlayer2Player;
class Mpris2 : public QObject
{
Q_OBJECT
Q_PROPERTY(QString playerName
READ playerName
WRITE setPlayerName
NOTIFY playerNameChanged)
public:
explicit Mpris2(QObject* parent = nullptr);
~Mpris2() override;
[[nodiscard]] QString playerName() const;
MediaPlayer2Player *player() const {
return m_mp2p.get();
}
void setPlayerName(const QString &playerName);
Q_SIGNALS:
void raisePlayer();
void playerNameChanged();
void playListModelChanged();
void audioPlayerManagerChanged();
void manageMediaPlayerControlChanged();
void headerBarManagerChanged();
void audioPlayerChanged();
void showProgressOnTaskBarChanged();
private:
void initDBusService();
std::unique_ptr<MediaPlayer2> m_mp2;
std::unique_ptr<MediaPlayer2Player> m_mp2p;
QString m_playerName;
bool mShowProgressOnTaskBar = true;
};
+1 -1
View File
@@ -16,7 +16,7 @@ PlaylistModel::PlaylistModel(QObject *parent)
connect(this, &PlaylistModel::playlistIdChanged, this, [=, this] {
setLoading(true);
auto future = YTMusicThread::instance()->fetchPlaylist(m_playlistId);
future.then(this, [=, this](const playlist::Playlist &playlist) {
connectFuture(future, this, [=, this](const playlist::Playlist &playlist) {
setLoading(false);
beginResetModel();
m_playlist = playlist;
+9 -10
View File
@@ -4,26 +4,25 @@
#include "searchmodel.h"
#include <QCoroFuture>
#include <QCoroTask>
SearchModel::SearchModel(QObject *parent)
: AbstractYTMusicModel(parent)
{
connect(this, &SearchModel::searchQueryChanged, this, [this]() -> QCoro::Task<> {
connect(this, &SearchModel::searchQueryChanged, this, [this] {
if (m_searchQuery.isEmpty()) {
beginResetModel();
m_searchResults.clear();
endResetModel();
co_return;
return;
}
setLoading(true);
auto results = co_await YTMusicThread::instance()->search(m_searchQuery);
beginResetModel();
setLoading(false);
m_searchResults = results;
endResetModel();
auto future = YTMusicThread::instance()->search(m_searchQuery);
connectFuture(future, this, [=, this](const std::vector<search::SearchResultItem> &results) {
beginResetModel();
setLoading(false);
m_searchResults = results;
endResetModel();
});
});
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::errorOccurred, this, [this] {
setLoading(false);
+2 -2
View File
@@ -41,7 +41,7 @@ UserPlaylistModel::UserPlaylistModel(QObject *parent)
setLoading(true);
auto future = YTMusicThread::instance()->fetchWatchPlaylist(m_initialVideoId);
future.then(this, handleResult);
connectFuture(future, this, handleResult);
});
connect(this, &UserPlaylistModel::playlistIdChanged, this, [=, this] {
if (m_playlistId.isEmpty()) {
@@ -50,7 +50,7 @@ UserPlaylistModel::UserPlaylistModel(QObject *parent)
setLoading(true);
auto future = YTMusicThread::instance()->fetchWatchPlaylist(std::nullopt, m_playlistId);
future.then(this, handleResult);
connectFuture(future, this, handleResult);;
});
connect(&YTMusicThread::instance().get(), &AsyncYTMusic::errorOccurred, this, [this] {
setLoading(false);
+1 -1
View File
@@ -27,7 +27,7 @@ VideoInfoExtractor::VideoInfoExtractor(QObject *parent)
setLoading(true);
auto future = YTMusicThread::instance()->extractVideoInfo(QString::fromStdString(m_videoId.toStdString()));
future.then(this, [this](const video_info::VideoInfo &videoInfo) {
connectFuture(future, this, [this](const video_info::VideoInfo &videoInfo) {
m_videoInfo = videoInfo;
setLoading(false);
Q_EMIT audioUrlChanged();