Add media browser support to kodi

This commit is contained in:
Michael Zanetti 2019-07-19 21:46:44 +02:00
parent 3206f285ba
commit 593b93c845
9 changed files with 650 additions and 11 deletions

View File

@ -63,7 +63,10 @@ void DevicePluginKodi::init()
Device::DeviceSetupStatus DevicePluginKodi::setupDevice(Device *device)
{
qCDebug(dcKodi) << "Setup Kodi device" << device->paramValue(kodiDeviceIpParamTypeId).toString();
Kodi *kodi= new Kodi(QHostAddress(device->paramValue(kodiDeviceIpParamTypeId).toString()), 9090, this);
QString ipString = device->paramValue(kodiDeviceIpParamTypeId).toString();
int port = device->paramValue(kodiDevicePortParamTypeId).toInt();
int httpPort = device->paramValue(kodiDeviceHttpPortParamTypeId).toInt();
Kodi *kodi= new Kodi(QHostAddress(ipString), port, httpPort, this);
connect(kodi, &Kodi::connectionStatusChanged, this, &DevicePluginKodi::onConnectionChanged);
connect(kodi, &Kodi::stateChanged, this, &DevicePluginKodi::onStateChanged);
@ -71,6 +74,8 @@ Device::DeviceSetupStatus DevicePluginKodi::setupDevice(Device *device)
connect(kodi, &Kodi::versionDataReceived, this, &DevicePluginKodi::versionDataReceived);
connect(kodi, &Kodi::updateDataReceived, this, &DevicePluginKodi::onSetupFinished);
connect(kodi, &Kodi::playbackStatusChanged, this, &DevicePluginKodi::onPlaybackStatusChanged);
connect(kodi, &Kodi::browseResult, this, &DevicePluginKodi::browseRequestFinished);
connect(kodi, &Kodi::browserItemResult, this, &DevicePluginKodi::browserItemRequestFinished);
connect(kodi, &Kodi::activePlayerChanged, device, [device](const QString &playerType){
device->setStateValue(kodiPlayerTypeStateTypeId, playerType);
@ -105,7 +110,10 @@ Device::DeviceSetupStatus DevicePluginKodi::setupDevice(Device *device)
connect(reply, &QNetworkReply::finished, device, [device, reply, addr](){
reply->deleteLater();
QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll());
device->setStateValue(kodiArtworkStateTypeId, "http://" + addr + ":8080/" + jsonDoc.toVariant().toMap().value("result").toMap().value("details").toMap().value("path").toString());
QString fileUrl = "http://" + addr + ":8080/" + jsonDoc.toVariant().toMap().value("result").toMap().value("details").toMap().value("path").toString();
qCDebug(dcKodi()) << "DL result:" << jsonDoc.toJson();
qCDebug(dcKodi()) << "Resolved url:" << fileUrl;
device->setStateValue(kodiArtworkStateTypeId, fileUrl);
});
});
@ -222,6 +230,43 @@ Device::DeviceError DevicePluginKodi::executeAction(Device *device, const Action
return Device::DeviceErrorDeviceClassNotFound;
}
Device::BrowseResult DevicePluginKodi::browseDevice(Device *device, Device::BrowseResult result, const QString &itemId, const QLocale &locale)
{
Q_UNUSED(locale)
Kodi *kodi = m_kodis.key(device);
if (!kodi) {
result.status = Device::DeviceErrorHardwareNotAvailable;
return result;
}
return kodi->browse(itemId, result);
}
Device::BrowserItemResult DevicePluginKodi::browserItem(Device *device, Device::BrowserItemResult result, const QString &itemId, const QLocale &locale)
{
Q_UNUSED(locale)
Kodi *kodi = m_kodis.key(device);
if (!kodi) {
result.status = Device::DeviceErrorHardwareNotAvailable;
return result;
}
return kodi->browserItem(itemId, result);
}
Device::DeviceError DevicePluginKodi::executeBrowserItem(Device *device, const BrowserAction &browserAction)
{
Kodi *kodi = m_kodis.key(device);
if (!kodi) {
return Device::DeviceErrorHardwareNotAvailable;
}
return kodi->launchBrowserItem(browserAction.itemId());
}
void DevicePluginKodi::onPluginTimer()
{
foreach (Kodi *kodi, m_kodis.keys()) {

View File

@ -47,6 +47,11 @@ public:
Device::DeviceError discoverDevices(const DeviceClassId &deviceClassId, const ParamList &params) override;
Device::DeviceError executeAction(Device *device, const Action &action) override;
Device::BrowseResult browseDevice(Device *device, Device::BrowseResult result, const QString &itemId, const QLocale &locale) override;
Device::BrowserItemResult browserItem(Device *device, Device::BrowserItemResult result, const QString &itemId, const QLocale &locale) override;
Device::DeviceError executeBrowserItem(Device *device, const BrowserAction &browserAction) override;
// Device::DeviceError executeBrowserItemAction(Device *device, const BrowserItemAction &browserItemAction) override;
private:
PluginTimer *m_pluginTimer;
QHash<Kodi *, Device *> m_kodis;

View File

@ -14,19 +14,27 @@
"displayName": "Kodi",
"interfaces": ["mediaplayer", "extendedmediacontroller", "extendedvolumecontroller", "mediametadataprovider", "shufflerepeat", "connectable"],
"createMethods": ["user", "discovery"],
"browsable": true,
"paramTypes": [
{
"id": "1a897065-57c6-49b3-bac9-1e5db27859e5",
"name": "ip",
"displayName": "ip",
"displayName": "IP Address",
"type" : "QString",
"inputType": "IPv4Address"
},
{
"id": "660fb4d7-9479-4c9d-a900-ce221d2b8ae4",
"name": "port",
"displayName": "port",
"displayName": "Port",
"type" : "int"
},
{
"id": "27ea7e46-80f0-49ea-9352-b57c78905c67",
"name": "httpPort",
"displayName": "HTTP port",
"type" : "int",
"defaultValue": 8080
}
],
"stateTypes": [

View File

@ -25,8 +25,9 @@
#include "extern-plugininfo.h"
#include <QUrl>
Kodi::Kodi(const QHostAddress &hostAddress, const int &port, QObject *parent) :
Kodi::Kodi(const QHostAddress &hostAddress, int port, int httpPort, QObject *parent) :
QObject(parent),
m_httpPort(httpPort),
m_muted(false),
m_volume(-1)
{
@ -36,6 +37,113 @@ Kodi::Kodi(const QHostAddress &hostAddress, const int &port, QObject *parent) :
m_jsonHandler = new KodiJsonHandler(m_connection, this);
connect(m_jsonHandler, &KodiJsonHandler::notificationReceived, this, &Kodi::processNotification);
connect(m_jsonHandler, &KodiJsonHandler::replyReceived, this, &Kodi::processResponse);
// Init FS
m_virtualFs = new VirtualFsNode(BrowserItem());
QVariantMap sort;
sort.insert("method", "label");
sort.insert("ignorearticle", true);
QVariantList properties;
properties.append("thumbnail");
// Music
BrowserItem item = BrowserItem("audiolibrary", tr("Music library"), true);
item.setDescription(tr(""));
item.setIcon(BrowserItem::BrowserIconFolder);
VirtualFsNode *audioLibrary = new VirtualFsNode(item);
m_virtualFs->addChild(audioLibrary);
item = BrowserItem("artists", tr("Artists"), true);
item.setDescription(tr(""));
item.setIcon(BrowserItem::BrowserIconFolder);
VirtualFsNode *artists = new VirtualFsNode(item);
artists->getMethod = "AudioLibrary.GetArtists";
artists->getParams.insert("sort", sort);
artists->getParams.insert("properties", properties);
audioLibrary->addChild(artists);
item = BrowserItem("albums", tr("Albums"), true);
item.setDescription(tr(""));
item.setIcon(BrowserItem::BrowserIconFolder);
VirtualFsNode *albums = new VirtualFsNode(item);
albums->getMethod = "AudioLibrary.GetAlbums";
albums->getParams.insert("sort", sort);
albums->getParams.insert("properties", properties);
audioLibrary->addChild(albums);
item = BrowserItem("songs", tr("Songs"), true);
item.setDescription(tr(""));
item.setIcon(BrowserItem::BrowserIconFolder);
VirtualFsNode *songs = new VirtualFsNode(item);
songs->getMethod = "AudioLibrary.GetSongs";
songs->getParams.insert("sort", sort);
songs->getParams.insert("properties", properties);
audioLibrary->addChild(songs);
// Video
item = BrowserItem("videolibrary", tr("Video library"), true);
item.setDescription(tr(""));
item.setIcon(BrowserItem::BrowserIconFolder);
VirtualFsNode *videoLibrary = new VirtualFsNode(item);
m_virtualFs->addChild(videoLibrary);
item = BrowserItem("movies", tr("Movies"), true);
item.setDescription(tr(""));
item.setIcon(BrowserItem::BrowserIconFolder);
VirtualFsNode *movies = new VirtualFsNode(item);
movies->getMethod = "VideoLibrary.GetMovies";
movies->getParams.insert("sort", sort);
movies->getParams.insert("properties", properties);
videoLibrary->addChild(movies);
item = BrowserItem("tvshows", tr("TV Shows"), true);
item.setDescription(tr(""));
item.setIcon(BrowserItem::BrowserIconFolder);
VirtualFsNode *tvShows = new VirtualFsNode(item);
tvShows->getMethod = "VideoLibrary.GetTVShows";
tvShows->getParams.insert("sort", sort);
tvShows->getParams.insert("properties", properties);
videoLibrary->addChild(tvShows);
item = BrowserItem("musicvideos", tr("Music Videos"), true);
item.setDescription(tr(""));
item.setIcon(BrowserItem::BrowserIconFolder);
VirtualFsNode *musicVideos = new VirtualFsNode(item);
musicVideos->getMethod = "VideoLibrary.GetMusicVideos";
musicVideos->getParams.insert("sort", sort);
musicVideos->getParams.insert("properties", properties);
videoLibrary->addChild(musicVideos);
item = BrowserItem("addons", tr("Add-ons"), true);
item.setDescription(tr(""));
item.setIcon(BrowserItem::BrowserIconFolder);
VirtualFsNode *addons = new VirtualFsNode(item);
m_virtualFs->addChild(addons);
item = BrowserItem("videoaddons", tr("Video add-ons"), true);
item.setDescription(tr(""));
item.setIcon(BrowserItem::BrowserIconFolder);
VirtualFsNode *videoAddons = new VirtualFsNode(item);
videoAddons->getMethod = "Files.GetDirectory";
videoAddons->getParams.insert("directory", "addons://sources/video");
videoAddons->getParams.insert("sort", sort);
videoAddons->getParams.insert("properties", properties);
addons->addChild(videoAddons);
item = BrowserItem("musicaddons", tr("Music add-ons"), true);
item.setDescription(tr(""));
item.setIcon(BrowserItem::BrowserIconFolder);
VirtualFsNode *musicAddons = new VirtualFsNode(item);
musicAddons->getMethod = "Files.GetDirectory";
musicAddons->getParams.insert("directory", "addons://sources/audio");
musicAddons->getParams.insert("sort", sort);
musicAddons->getParams.insert("properties", properties);
addons->addChild(musicAddons);
}
QHostAddress Kodi::hostAddress() const
@ -190,6 +298,213 @@ void Kodi::disconnectKodi()
m_connection->disconnectKodi();
}
Device::BrowseResult Kodi::browse(const QString &itemId, Device::BrowseResult &result)
{
// m_jsonHandler->sendData()
VirtualFsNode *node = m_virtualFs->findNode(itemId);
if (node) {
if (node->getMethod.isEmpty()) {
foreach (VirtualFsNode *child, node->childs) {
result.items.append(child->item);
}
return result;
}
qCDebug(dcKodi()) << "Sending:" << node->getMethod << node->getParams;
int id = m_jsonHandler->sendData(node->getMethod, node->getParams);
m_pendingBrowseRequests.insert(id, result);
result.status = Device::DeviceErrorAsync;
return result;
}
QVariantMap sort;
sort.insert("method", "label");
sort.insert("ignorearticle", true);
QVariantList properties;
properties.append("thumbnail");
if (itemId.startsWith("artist:")) {
QString idString = itemId;
idString.remove(QRegExp("^artist:"));
QVariantMap filter;
filter.insert("artistid", idString.toInt());
QVariantMap params;
params.insert("filter", filter);
params.insert("properties", properties);
int id = m_jsonHandler->sendData("AudioLibrary.GetAlbums", params);
m_pendingBrowseRequests.insert(id, result);
result.status = Device::DeviceErrorAsync;
return result;
}
if (itemId.startsWith("album:")) {
QString idString = itemId;
idString.remove(QRegExp("^album:"));
QVariantMap filter;
filter.insert("albumid", idString.toInt());
QVariantMap params;
params.insert("filter", filter);
QVariantList properties;
properties.append("thumbnail");
properties.append("albumid");
params.insert("properties", properties);
int id = m_jsonHandler->sendData("AudioLibrary.GetSongs", params);
m_pendingBrowseRequests.insert(id, result);
result.status = Device::DeviceErrorAsync;
return result;
}
if (itemId.startsWith("tvshow:")) {
QString idString = itemId;
idString.remove(QRegExp("^tvshow:"));
QVariantMap params;
params.insert("tvshowid", idString.toInt());
QVariantList properties;
properties.append("tvshowid");
properties.append("season");
properties.append("thumbnail");
params.insert("properties", properties);
int id = m_jsonHandler->sendData("VideoLibrary.GetSeasons", params);
m_pendingBrowseRequests.insert(id, result);
result.status = Device::DeviceErrorAsync;
return result;
}
if (itemId.startsWith("season:")) {
QString idString = itemId;
idString.remove(QRegExp("^season:"));
int seasonId = idString.left(idString.indexOf(",")).toInt();
idString.remove(QRegExp("^[0-9]*,tvshow:"));
int tvShowId = idString.toInt();
QVariantMap params;
params.insert("tvshowid", tvShowId);
params.insert("season", seasonId);
params.insert("properties", properties);
qCDebug(dcKodi()) << "getting episodes:" << params;
int id = m_jsonHandler->sendData("VideoLibrary.GetEpisodes", params);
m_pendingBrowseRequests.insert(id, result);
result.status = Device::DeviceErrorAsync;
return result;
}
if (itemId.startsWith("addon:")) {
QString idString = itemId;
idString.remove(QRegExp("^addon:"));
QVariantMap params;
params.insert("directory", "plugin://" + idString);
// QVariantList properties;
// properties.append("tvshowid");
// properties.append("season");
// params.insert("properties", properties);
qCDebug(dcKodi()) << "Sending" << params;
int id = m_jsonHandler->sendData("Files.GetDirectory", params);
m_pendingBrowseRequests.insert(id, result);
result.status = Device::DeviceErrorAsync;
return result;
}
if (itemId.startsWith("file:")) {
QString idString = itemId;
idString.remove(QRegExp("^file:"));
QVariantMap params;
params.insert("directory", idString);
params.insert("properties", properties);
qCDebug(dcKodi()) << "Sending" << params;
int id = m_jsonHandler->sendData("Files.GetDirectory", params);
m_pendingBrowseRequests.insert(id, result);
result.status = Device::DeviceErrorAsync;
return result;
}
result.status = Device::DeviceErrorItemNotFound;
return result;
}
Device::BrowserItemResult Kodi::browserItem(const QString &itemId, Device::BrowserItemResult &result)
{
qCDebug(dcKodi()) << "Getting details for" << itemId;
QString idString = itemId;
QString method;
QVariantMap params;
if (idString.startsWith("song:")) {
idString.remove(QRegExp("^song:"));
params.insert("songid", idString.toInt());
method = "AudioLibrary.GetSongDetails";
} else if (idString.startsWith("movie:")) {
idString.remove(QRegExp("^movie:"));
params.insert("movieid", idString.toInt());
method = "VideoLibrary.GetMovieDetails";
} else if (idString.startsWith("episode:")) {
idString.remove(QRegExp("^episode:"));
params.insert("episodeid", idString.toInt());
method = "VideoLibrary.GetEpisodeDetails";
} else if (idString.startsWith("musicvideo:")) {
idString.remove(QRegExp("^musicvideo:"));
params.insert("musicvideoid", idString.toInt());
method = "VideoLibrary.GetMusicVideoDetails";
} else {
qCWarning(dcKodi()) << "Unhandled browserItem request!" << itemId;
result.status = Device::DeviceErrorUnsupportedFeature;
return result;
}
int id = m_jsonHandler->sendData(method, params);
m_pendingBrowserItemRequests.insert(id, result);
result.status = Device::DeviceErrorAsync;
return result;
}
Device::DeviceError Kodi::launchBrowserItem(const QString &itemId)
{
qCDebug(dcKodi()) << "Launching" << itemId;
QVariantMap playlistItem;
QString idString = itemId;
if (idString.startsWith("song:")) {
idString.remove(QRegExp("^song:"));
int idx = idString.indexOf(",album:");
if (idx > 0) {
int position = idString.left(idx).toInt();
idString.remove(QRegExp("^[0-9]*,album:"));
int albumId = idString.toInt();
QVariantMap params;
params.insert("playlistid", 0);
m_jsonHandler->sendData("Playlist.Clear", params);
QVariantMap item;
item.insert("albumid", albumId);
params.insert("item", item);
m_jsonHandler->sendData("Playlist.Add", params);
playlistItem.insert("playlistid", 0);
playlistItem.insert("position", position);
} else {
playlistItem.insert("songid", idString.toInt());
}
} else if (idString.startsWith("movie:")) {
idString.remove(QRegExp("^movie:"));
playlistItem.insert("movieid", idString.toInt());
} else if (idString.startsWith("episode:")) {
idString.remove(QRegExp("^episode:"));
playlistItem.insert("episodeid", idString.toInt());
} else if (idString.startsWith("file:")) {
idString.remove(QRegExp("^file:"));
playlistItem.insert("file", idString);
} else {
qCWarning(dcKodi()) << "Unhandled launchBrowserItem request!" << itemId;
return Device::DeviceErrorItemNotFound;
}
QVariantMap params;
params.clear();
params.insert("item", playlistItem);
qCDebug(dcKodi()) << "Player.Open" << params;
m_jsonHandler->sendData("Player.Open", params);
return Device::DeviceErrorNoError;
}
void Kodi::onVolumeChanged(const int &volume, const bool &muted)
{
if (m_volume != volume || m_muted != muted) {
@ -346,11 +661,213 @@ void Kodi::processResponse(int id, const QString &method, const QVariantMap &res
if (method == "Player.SetShuffle" || method == "Player.SetRepeat") {
updatePlayerProperties();
emit actionExecuted(id, true);
return;
}
emit actionExecuted(id, true);
if (method == "AudioLibrary.GetArtists") {
Device::BrowseResult result = m_pendingBrowseRequests.take(id);
foreach (const QVariant &artistVariant, response.value("result").toMap().value("artists").toList()) {
QVariantMap artist = artistVariant.toMap();
qCDebug(dcKodi()) << "Entry:" << artist;
BrowserItem item("artist:" + artist.value("artistid").toString(), artist.value("label").toString());
item.setBrowsable(true);
item.setIcon(BrowserItem::BrowserIconFolder);
item.setThumbnail(prepareThumbnail(artist.value("thumbnail").toString()));
qCDebug(dcKodi()) << "Thumbnail" << item.thumbnail();
result.items.append(item);
}
emit browseResult(result);
return;
}
qCDebug(dcKodi()) << "unhandled reply" << method << response;
if (method == "AudioLibrary.GetAlbums") {
Device::BrowseResult result = m_pendingBrowseRequests.take(id);
foreach (const QVariant &albumVariant, response.value("result").toMap().value("albums").toList()) {
QVariantMap album = albumVariant.toMap();
BrowserItem item("album:" + album.value("albumid").toString(), album.value("label").toString());
item.setBrowsable(true);
item.setIcon(BrowserItem::BrowserIconFolder);
item.setThumbnail(prepareThumbnail(album.value("thumbnail").toString()));
result.items.append(item);
}
emit browseResult(result);
return;
}
if (method == "AudioLibrary.GetSongs") {
Device::BrowseResult result = m_pendingBrowseRequests.take(id);
int i = 0;
foreach (const QVariant &songVariant, response.value("result").toMap().value("songs").toList()) {
QVariantMap song = songVariant.toMap();
qCDebug(dcKodi()) << "Entry:" << song;
QString newId = "song:";
if (song.contains("albumid")) {
newId += QString::number(i);
newId += ",album:" + song.value("albumid").toString();
} else {
newId += song.value("songid").toString();
}
BrowserItem item(newId, song.value("label").toString());
item.setExecutable(true);
item.setIcon(BrowserItem::BrowserIconMusic);
item.setThumbnail(prepareThumbnail(song.value("thumbnail").toString()));
result.items.append(item);
i++;
}
emit browseResult(result);
return;
}
if (method == "VideoLibrary.GetMovies") {
Device::BrowseResult result = m_pendingBrowseRequests.take(id);
foreach (const QVariant &movieVariant, response.value("result").toMap().value("movies").toList()) {
QVariantMap movie = movieVariant.toMap();
qCDebug(dcKodi()) << "Entry:" << movie;
BrowserItem item("movie:" + movie.value("movieid").toString(), movie.value("label").toString());
item.setExecutable(true);
item.setIcon(BrowserItem::BrowserIconVideo);
item.setThumbnail(prepareThumbnail(movie.value("thumbnail").toString()));
result.items.append(item);
}
emit browseResult(result);
return;
}
if (method == "VideoLibrary.GetTVShows") {
Device::BrowseResult result = m_pendingBrowseRequests.take(id);
foreach (const QVariant &tvShowVariant, response.value("result").toMap().value("tvshows").toList()) {
QVariantMap tvShow = tvShowVariant.toMap();
qCDebug(dcKodi()) << "Entry:" << tvShow;
BrowserItem item("tvshow:" + tvShow.value("tvshowid").toString(), tvShow.value("label").toString());
item.setBrowsable(true);
item.setIcon(BrowserItem::BrowserIconFolder);
item.setThumbnail(prepareThumbnail(tvShow.value("thumbnail").toString()));
result.items.append(item);
}
emit browseResult(result);
return;
}
if (method == "VideoLibrary.GetSeasons") {
Device::BrowseResult result = m_pendingBrowseRequests.take(id);
foreach (const QVariant &seasonVariant, response.value("result").toMap().value("seasons").toList()) {
QVariantMap season = seasonVariant.toMap();
qCDebug(dcKodi()) << "Entry:" << season;
BrowserItem item("season:" + season.value("season").toString() + ",tvshow:" + season.value("tvshowid").toString(), season.value("label").toString());
item.setBrowsable(true);
item.setIcon(BrowserItem::BrowserIconFolder);
item.setThumbnail(prepareThumbnail(season.value("thumbnail").toString()));
result.items.append(item);
}
emit browseResult(result);
return;
}
if (method == "VideoLibrary.GetEpisodes") {
Device::BrowseResult result = m_pendingBrowseRequests.take(id);
foreach (const QVariant &episodeVariant, response.value("result").toMap().value("episodes").toList()) {
QVariantMap episode = episodeVariant.toMap();
qCDebug(dcKodi()) << "Entry:" << episode;
BrowserItem item("episode:" + episode.value("episodeid").toString(), episode.value("label").toString());
item.setExecutable(true);
item.setIcon(BrowserItem::BrowserIconVideo);
item.setThumbnail(prepareThumbnail(episode.value("thumbnail").toString()));
result.items.append(item);
}
emit browseResult(result);
return;
}
if (method == "VideoLibrary.GetMusicVideos") {
Device::BrowseResult result = m_pendingBrowseRequests.take(id);
foreach (const QVariant &musicVideoVariant, response.value("result").toMap().value("musicvideos").toList()) {
QVariantMap musicVideo = musicVideoVariant.toMap();
qCDebug(dcKodi()) << "Entry:" << musicVideo;
BrowserItem item("musicvideo:" + musicVideo.value("musicvideoid").toString(), musicVideo.value("label").toString());
item.setExecutable(true);
item.setIcon(BrowserItem::BrowserIconVideo);
item.setThumbnail(prepareThumbnail(musicVideo.value("thumbnail").toString()));
result.items.append(item);
}
emit browseResult(result);
return;
}
if (method == "Addons.GetAddons") {
Device::BrowseResult result = m_pendingBrowseRequests.take(id);
foreach (const QVariant &addonVariant, response.value("result").toMap().value("addons").toList()) {
QVariantMap addon = addonVariant.toMap();
qCDebug(dcKodi()) << "Entry:" << addon;
BrowserItem item("addon:" + addon.value("addonid").toString(), addon.value("name").toString());
item.setBrowsable(true);
item.setIcon(BrowserItem::BrowserIconApplication);
result.items.append(item);
}
emit browseResult(result);
return;
}
if (method == "Files.GetDirectory") {
Device::BrowseResult result = m_pendingBrowseRequests.take(id);
foreach (const QVariant &fileVariant, response.value("result").toMap().value("files").toList()) {
QVariantMap file = fileVariant.toMap();
qCDebug(dcKodi()) << "Entry:" << file;
BrowserItem item("file:" + file.value("file").toString(), file.value("label").toString());
if (file.value("type").toString() == "directory" || file.value("type").toString() == "unknown") {
item.setBrowsable(true);
item.setIcon(BrowserItem::BrowserIconFolder);
} else if (file.value("type").toString() == "episode" || file.value("type").toString() == "movie") {
item.setExecutable(true);
item.setIcon(BrowserItem::BrowserIconVideo);
} else if (file.value("type").toString() == "song") {
item.setExecutable(true);
item.setIcon(BrowserItem::BrowserIconMusic);
}
result.items.append(item);
}
emit browseResult(result);
return;
}
if (method == "AudioLibrary.GetSongDetails") {
Device::BrowserItemResult result = m_pendingBrowserItemRequests.take(id);
result.item.setId("song:" + response.value("result").toMap().value("songdetails").toMap().value("songid").toString());
result.item.setDisplayName(response.value("result").toMap().value("songdetails").toMap().value("label").toString());
qCDebug(dcKodi()) << "Song details:" << result.item.displayName();
emit browserItemResult(result);
return;
}
if (method == "VideoLibrary.GetMovieDetails") {
Device::BrowserItemResult result = m_pendingBrowserItemRequests.take(id);
result.item.setId("movie:" + response.value("result").toMap().value("moviedetails").toMap().value("movieid").toString());
result.item.setDisplayName(response.value("result").toMap().value("moviedetails").toMap().value("label").toString());
qCDebug(dcKodi()) << "Movie details:" << result.item.displayName();
emit browserItemResult(result);
return;
}
if (method == "VideoLibrary.GetEpisodeDetails") {
Device::BrowserItemResult result = m_pendingBrowserItemRequests.take(id);
result.item.setId("movie:" + response.value("result").toMap().value("episodedetails").toMap().value("episodeid").toString());
result.item.setDisplayName(response.value("result").toMap().value("episodedetails").toMap().value("label").toString());
qCDebug(dcKodi()) << "Episode details:" << result.item.displayName();
emit browserItemResult(result);
return;
}
if (method == "VideoLibrary.GetMusicVideoDetails") {
Device::BrowserItemResult result = m_pendingBrowserItemRequests.take(id);
result.item.setId("movie:" + response.value("result").toMap().value("musicvideodetails").toMap().value("musicvideoid").toString());
result.item.setDisplayName(response.value("result").toMap().value("musicvideodetails").toMap().value("label").toString());
qCDebug(dcKodi()) << "Episode details:" << result.item.displayName();
emit browserItemResult(result);
return;
}
qCWarning(dcKodi()) << "unhandled reply" << method << response;
}
void Kodi::updatePlayerProperties()
@ -372,3 +889,15 @@ void Kodi::updateMetadata()
params.insert("properties", fields);
m_jsonHandler->sendData("Player.GetItem", params);
}
QString Kodi::prepareThumbnail(const QString &thumbnail)
{
if (thumbnail.isEmpty()) {
return QString();
}
return QString("http://%1:%2/image/%3")
.arg(m_connection->hostAddress().toString())
.arg(m_httpPort)
.arg(QString(thumbnail.toUtf8().toPercentEncoding()));
}

View File

@ -29,12 +29,15 @@
#include "kodiconnection.h"
#include "kodijsonhandler.h"
#include "types/browseritem.h"
#include "devices/device.h"
class Kodi : public QObject
{
Q_OBJECT
public:
explicit Kodi(const QHostAddress &hostAddress, const int &port = 9090, QObject *parent = nullptr);
explicit Kodi(const QHostAddress &hostAddress, int port = 9090, int httpPort = 8080, QObject *parent = nullptr);
QHostAddress hostAddress() const;
int port() const;
@ -64,6 +67,10 @@ public:
void connectKodi();
void disconnectKodi();
Device::BrowseResult browse(const QString &itemId, Device::BrowseResult &result);
Device::BrowserItemResult browserItem(const QString &itemId, Device::BrowserItemResult &result);
Device::DeviceError launchBrowserItem(const QString &itemId);
signals:
void connectionStatusChanged();
void stateChanged();
@ -75,6 +82,8 @@ signals:
void mediaMetadataChanged(const QString &title, const QString &artist, const QString &collection, const QString &artwork);
void shuffleChanged(bool shuffle);
void repeatChanged(const QString &repeat);
void browseResult(const Device::BrowseResult &result);
void browserItemResult(const Device::BrowserItemResult &result);
private slots:
void onVolumeChanged(const int &volume, const bool &muted);
@ -90,14 +99,40 @@ private slots:
void updatePlayerProperties();
void updateMetadata();
private:
QString prepareThumbnail(const QString &thumbnail);
private:
KodiConnection *m_connection;
int m_httpPort;
KodiJsonHandler *m_jsonHandler;
bool m_muted;
int m_volume;
int m_activePlayerCount = 0; // if it's > 0, there is something playing (either music or video or slideshow)
int m_activePlayer = -1;
class VirtualFsNode {
public:
VirtualFsNode(const BrowserItem &item):item(item) {}
BrowserItem item;
QList<VirtualFsNode*> childs;
QString getMethod;
QVariantMap getParams;
void addChild(VirtualFsNode* child) {childs.append(child); }
VirtualFsNode *findNode(const QString &id) {
if (item.id() == id) return this;
foreach (VirtualFsNode *child, childs) {
VirtualFsNode *node = child->findNode(id);
if (node) return node;
}
return nullptr;
}
};
VirtualFsNode* m_virtualFs = nullptr;
QHash<int, Device::BrowseResult> m_pendingBrowseRequests;
QHash<int, Device::BrowserItemResult> m_pendingBrowserItemRequests;
};
#endif // KODI_H

View File

@ -26,7 +26,7 @@
#include <QPixmap>
KodiConnection::KodiConnection(const QHostAddress &hostAddress, const int &port, QObject *parent) :
KodiConnection::KodiConnection(const QHostAddress &hostAddress, int port, QObject *parent) :
QObject(parent),
m_hostAddress(hostAddress),
m_port(port),

View File

@ -32,13 +32,14 @@ class KodiConnection : public QObject
{
Q_OBJECT
public:
explicit KodiConnection(const QHostAddress &hostAddress, const int &port = 9090, QObject *parent = nullptr);
explicit KodiConnection(const QHostAddress &hostAddress, int port = 9090, QObject *parent = nullptr);
void connectKodi();
void disconnectKodi();
QHostAddress hostAddress() const;
int port() const;
int httpPort() const;
bool connected();

View File

@ -53,14 +53,29 @@ int KodiJsonHandler::sendData(const QString &method, const QVariantMap &params)
void KodiJsonHandler::processResponse(const QByteArray &data)
{
m_dataBuffer.append(data);
QByteArray packet = m_dataBuffer;
int pos = packet.indexOf("}{");
if (pos > 0) {
packet = m_dataBuffer.left(pos + 1);
}
if (!packet.endsWith("}")) {
return; // Won't parse for sure, likely not enough data yet
}
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
QJsonDocument jsonDoc = QJsonDocument::fromJson(packet, &error);
if(error.error != QJsonParseError::NoError) {
qCWarning(dcKodi) << "failed to parse JSON data:" << data << ":" << error.errorString();
return;
}
// Ok, we managed to parse a complete packet, remove it from the input buffer
m_dataBuffer.remove(0, packet.length());
//qCDebug(dcKodi) << "data received:" << jsonDoc.toJson();
QVariantMap message = jsonDoc.toVariant().toMap();

View File

@ -50,6 +50,7 @@ private:
KodiConnection *m_connection;
int m_id;
QHash<int, KodiReply> m_replys;
QByteArray m_dataBuffer;
};