Merge PR #136: Kodi: Add support for media browsing
This commit is contained in:
commit
f62ae3d378
@ -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,9 @@ 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::browserItemActionExecuted, this, &DevicePluginKodi::onBrowserItemActionExecuted);
|
||||
|
||||
connect(kodi, &Kodi::activePlayerChanged, device, [device](const QString &playerType){
|
||||
device->setStateValue(kodiPlayerTypeStateTypeId, playerType);
|
||||
@ -89,8 +95,9 @@ Device::DeviceSetupStatus DevicePluginKodi::setupDevice(Device *device)
|
||||
} else {
|
||||
addr = "[" + hostAddr.toString() + "]";
|
||||
}
|
||||
QString port = device->paramValue(kodiDeviceHttpPortParamTypeId).toString();
|
||||
|
||||
request.setUrl(QUrl("http://" + addr + ":8080/jsonrpc"));
|
||||
request.setUrl(QUrl(QString("http://%1:%2/jsonrpc").arg(addr).arg(port)));
|
||||
qCDebug(dcKodi) << "Prepping file dl" << "http://" + addr + ":" + device->paramValue(kodiDevicePortParamTypeId).toString() + "/jsonrpc";
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
QVariantMap map;
|
||||
@ -102,10 +109,13 @@ Device::DeviceSetupStatus DevicePluginKodi::setupDevice(Device *device)
|
||||
map.insert("params", params);
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromVariant(map);
|
||||
QNetworkReply *reply = hardwareManager()->networkManager()->post(request, jsonDoc.toJson(QJsonDocument::Compact));
|
||||
connect(reply, &QNetworkReply::finished, device, [device, reply, addr](){
|
||||
connect(reply, &QNetworkReply::finished, device, [device, reply, addr, port](){
|
||||
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 + ":" + port + "/" + 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);
|
||||
});
|
||||
|
||||
});
|
||||
@ -144,19 +154,68 @@ Device::DeviceError DevicePluginKodi::discoverDevices(const DeviceClassId &devic
|
||||
Q_UNUSED(deviceClassId)
|
||||
|
||||
ZeroConfServiceBrowser *serviceBrowser = hardwareManager()->zeroConfController()->createServiceBrowser("_xbmc-jsonrpc._tcp");
|
||||
QTimer::singleShot(5000, this, [this, serviceBrowser](){
|
||||
QList<DeviceDescriptor> descriptors;
|
||||
ZeroConfServiceBrowser *httpServiceBrowser = hardwareManager()->zeroConfController()->createServiceBrowser("_http._tcp");
|
||||
QTimer::singleShot(5000, this, [this, serviceBrowser, httpServiceBrowser](){
|
||||
QHash<QString, DeviceDescriptor> descriptors;
|
||||
|
||||
foreach (const ZeroConfServiceEntry avahiEntry, serviceBrowser->serviceEntries()) {
|
||||
|
||||
QString uuid;
|
||||
foreach (const QString &txt, avahiEntry.txt()) {
|
||||
if (txt.startsWith("uuid")) {
|
||||
uuid = txt.split("=").last();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (descriptors.contains(uuid)) {
|
||||
// Might appear multiple times, IPv4 and IPv6
|
||||
continue;
|
||||
}
|
||||
|
||||
qCDebug(dcKodi) << "Zeroconf entry:" << avahiEntry;
|
||||
DeviceDescriptor descriptor(kodiDeviceClassId, avahiEntry.name(), avahiEntry.hostName() + " (" + avahiEntry.hostAddress().toString() + ")");
|
||||
ParamList params;
|
||||
params << Param(kodiDeviceIpParamTypeId, avahiEntry.hostAddress().toString());
|
||||
params << Param(kodiDevicePortParamTypeId, avahiEntry.port());
|
||||
params << Param(kodiDeviceUuidParamTypeId, uuid);
|
||||
descriptor.setParams(params);
|
||||
descriptors << descriptor;
|
||||
|
||||
Devices existing = myDevices().filterByParam(kodiDeviceUuidParamTypeId, uuid);
|
||||
if (existing.count() > 0) {
|
||||
descriptor.setDeviceId(existing.first()->id());
|
||||
}
|
||||
|
||||
descriptors.insert(uuid, descriptor);
|
||||
}
|
||||
emit devicesDiscovered(kodiDeviceClassId, descriptors);
|
||||
|
||||
foreach (const ZeroConfServiceEntry avahiEntry, httpServiceBrowser->serviceEntries()) {
|
||||
// qCDebug(dcKodi) << "Zeroconf http entry:" << avahiEntry;
|
||||
QString uuid;
|
||||
foreach (const QString &txt, avahiEntry.txt()) {
|
||||
if (txt.startsWith("uuid")) {
|
||||
uuid = txt.split("=").last();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!descriptors.contains(uuid)) {
|
||||
continue;
|
||||
}
|
||||
qCDebug(dcKodi()) << "Updating http parameter:" << avahiEntry.port();
|
||||
DeviceDescriptor descriptor = descriptors.value(uuid);
|
||||
ParamList params = descriptor.params();
|
||||
params << Param(kodiDeviceHttpPortParamTypeId, avahiEntry.port());
|
||||
descriptor.setParams(params);
|
||||
descriptors[uuid] = descriptor;
|
||||
}
|
||||
|
||||
|
||||
foreach (const DeviceDescriptor &d, descriptors.values()) {
|
||||
qCDebug(dcKodi()) << "Returning descritpor:" << d.params();
|
||||
}
|
||||
emit devicesDiscovered(kodiDeviceClassId, descriptors.values());
|
||||
serviceBrowser->deleteLater();
|
||||
httpServiceBrowser->deleteLater();
|
||||
});
|
||||
|
||||
return Device::DeviceErrorAsync;
|
||||
@ -173,34 +232,34 @@ Device::DeviceError DevicePluginKodi::executeAction(Device *device, const Action
|
||||
}
|
||||
|
||||
int commandId = -1;
|
||||
if (action.actionTypeId() == kodiShowNotificationActionTypeId) {
|
||||
commandId = kodi->showNotification(action.param(kodiShowNotificationActionMessageParamTypeId).value().toString(), 8000, action.param(kodiShowNotificationActionTypeParamTypeId).value().toString());
|
||||
if (action.actionTypeId() == kodiNotifyActionTypeId) {
|
||||
commandId = kodi->showNotification(
|
||||
action.param(kodiNotifyActionTitleParamTypeId).value().toString(),
|
||||
action.param(kodiNotifyActionBodyParamTypeId).value().toString(),
|
||||
8000,
|
||||
action.param(kodiNotifyActionTypeParamTypeId).value().toString());
|
||||
} else if (action.actionTypeId() == kodiVolumeActionTypeId) {
|
||||
commandId = kodi->setVolume(action.param(kodiVolumeActionVolumeParamTypeId).value().toInt());
|
||||
} else if (action.actionTypeId() == kodiMuteActionTypeId) {
|
||||
commandId = kodi->setMuted(action.param(kodiMuteActionMuteParamTypeId).value().toBool());
|
||||
} else if (action.actionTypeId() == kodiPressButtonActionTypeId) {
|
||||
commandId = kodi->pressButton(action.param(kodiPressButtonActionButtonParamTypeId).value().toString());
|
||||
} else if (action.actionTypeId() == kodiNavigateActionTypeId) {
|
||||
commandId = kodi->navigate(action.param(kodiNavigateActionToParamTypeId).value().toString());
|
||||
} else if (action.actionTypeId() == kodiSystemActionTypeId) {
|
||||
commandId = kodi->systemCommand(action.param(kodiSystemActionSystemCommandParamTypeId).value().toString());
|
||||
} else if (action.actionTypeId() == kodiVideoLibraryActionTypeId) {
|
||||
commandId = kodi->videoLibrary(action.param(kodiVideoLibraryActionVideoCommandParamTypeId).value().toString());
|
||||
} else if (action.actionTypeId() == kodiAudioLibraryActionTypeId) {
|
||||
commandId = kodi->audioLibrary(action.param(kodiAudioLibraryActionAudioCommandParamTypeId).value().toString());
|
||||
} else if(action.actionTypeId() == kodiSkipBackActionTypeId) {
|
||||
commandId = kodi->pressButton("skipprevious");
|
||||
commandId = kodi->navigate("skipprevious");
|
||||
} else if(action.actionTypeId() == kodiFastRewindActionTypeId) {
|
||||
commandId = kodi->pressButton("rewind");
|
||||
commandId = kodi->navigate("rewind");
|
||||
} else if(action.actionTypeId() == kodiStopActionTypeId) {
|
||||
commandId = kodi->pressButton("stop");
|
||||
commandId = kodi->navigate("stop");
|
||||
} else if(action.actionTypeId() == kodiPlayActionTypeId) {
|
||||
commandId = kodi->pressButton("play");
|
||||
commandId = kodi->navigate("play");
|
||||
} else if(action.actionTypeId() == kodiPauseActionTypeId) {
|
||||
commandId = kodi->pressButton("pause");
|
||||
commandId = kodi->navigate("pause");
|
||||
} else if(action.actionTypeId() == kodiFastForwardActionTypeId) {
|
||||
commandId = kodi->pressButton("fastforward");
|
||||
commandId = kodi->navigate("fastforward");
|
||||
} else if(action.actionTypeId() == kodiSkipNextActionTypeId) {
|
||||
commandId = kodi->pressButton("skipnext");
|
||||
commandId = kodi->navigate("skipnext");
|
||||
} else if (action.actionTypeId() == kodiShuffleActionTypeId) {
|
||||
commandId = kodi->setShuffle(action.param(kodiShuffleActionShuffleParamTypeId).value().toBool());
|
||||
} else if (action.actionTypeId() == kodiRepeatActionTypeId) {
|
||||
@ -222,6 +281,58 @@ 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());
|
||||
}
|
||||
|
||||
Device::DeviceError DevicePluginKodi::executeBrowserItemAction(Device *device, const BrowserItemAction &browserItemAction)
|
||||
{
|
||||
Kodi *kodi = m_kodis.key(device);
|
||||
if (!kodi) {
|
||||
return Device::DeviceErrorHardwareNotAvailable;
|
||||
}
|
||||
|
||||
int id = kodi->executeBrowserItemAction(browserItemAction.itemId(), browserItemAction.actionTypeId());
|
||||
if (id == -1) {
|
||||
return Device::DeviceErrorHardwareFailure;
|
||||
}
|
||||
m_pendingBrowserItemActions.insert(id, browserItemAction.id());
|
||||
return Device::DeviceErrorAsync;
|
||||
}
|
||||
|
||||
void DevicePluginKodi::onPluginTimer()
|
||||
{
|
||||
foreach (Kodi *kodi, m_kodis.keys()) {
|
||||
@ -263,7 +374,15 @@ void DevicePluginKodi::onActionExecuted(int actionId, bool success)
|
||||
if (!m_pendingActions.contains(actionId)) {
|
||||
return;
|
||||
}
|
||||
emit actionExecutionFinished(m_pendingActions.value(actionId), success ? Device::DeviceErrorNoError : Device::DeviceErrorInvalidParameter);
|
||||
emit actionExecutionFinished(m_pendingActions.take(actionId), success ? Device::DeviceErrorNoError : Device::DeviceErrorInvalidParameter);
|
||||
}
|
||||
|
||||
void DevicePluginKodi::onBrowserItemActionExecuted(int actionId, bool success)
|
||||
{
|
||||
if (!m_pendingBrowserItemActions.contains(actionId)) {
|
||||
return;
|
||||
}
|
||||
emit browserItemActionExecutionFinished(m_pendingBrowserItemActions.take(actionId), success ? Device::DeviceErrorNoError : Device::DeviceErrorHardwareFailure);
|
||||
}
|
||||
|
||||
void DevicePluginKodi::versionDataReceived(const QVariantMap &data)
|
||||
@ -294,7 +413,7 @@ void DevicePluginKodi::onSetupFinished(const QVariantMap &data)
|
||||
|
||||
emit deviceSetupFinished(device, Device::DeviceSetupStatusSuccess);
|
||||
|
||||
kodi->showNotification("Connected", 2000, "info");
|
||||
kodi->showNotification("nymea", tr("Connected"), 2000, "info");
|
||||
}
|
||||
|
||||
void DevicePluginKodi::onPlaybackStatusChanged(const QString &playbackStatus)
|
||||
|
||||
@ -47,18 +47,25 @@ public:
|
||||
Device::DeviceError discoverDevices(const DeviceClassId &deviceClassId, const ParamList ¶ms) 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;
|
||||
QList<Kodi *> m_asyncSetups;
|
||||
|
||||
QHash<int, ActionId> m_pendingActions;
|
||||
QHash<int, ActionId> m_pendingBrowserItemActions;
|
||||
|
||||
private slots:
|
||||
void onPluginTimer();
|
||||
void onConnectionChanged();
|
||||
void onStateChanged();
|
||||
void onActionExecuted(int actionId, bool success);
|
||||
void onBrowserItemActionExecuted(int actionId, bool success);
|
||||
void versionDataReceived(const QVariantMap &data);
|
||||
void onSetupFinished(const QVariantMap &data);
|
||||
|
||||
|
||||
@ -12,21 +12,35 @@
|
||||
"id": "d09953e3-c5bd-415b-973b-0d0bf2be3f69",
|
||||
"name": "kodi",
|
||||
"displayName": "Kodi",
|
||||
"interfaces": ["mediaplayer", "extendedmediacontroller", "extendedvolumecontroller", "mediametadataprovider", "shufflerepeat", "connectable"],
|
||||
"interfaces": ["mediaplayer", "extendedmediacontroller", "extendedvolumecontroller", "mediametadataprovider", "shufflerepeat", "notifications", "extendednavigationpad", "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
|
||||
},
|
||||
{
|
||||
"id": "692eb6e0-7f4e-4f43-92da-8347372287ce",
|
||||
"name": "uuid",
|
||||
"displayName": "UUID",
|
||||
"type": "QString"
|
||||
}
|
||||
],
|
||||
"stateTypes": [
|
||||
@ -190,12 +204,19 @@
|
||||
},
|
||||
{
|
||||
"id": "dc0aa3b5-4eae-4e58-a4ac-d4c124da53f1",
|
||||
"name": "showNotification",
|
||||
"name": "notify",
|
||||
"displayName": "show notification",
|
||||
"paramTypes": [
|
||||
{
|
||||
"id": "798f720a-cc4f-40e7-91d7-2ef5957ca7ad",
|
||||
"name": "message",
|
||||
"name": "title",
|
||||
"displayName": "title",
|
||||
"type": "QString",
|
||||
"inputType": "TextLine"
|
||||
},
|
||||
{
|
||||
"id": "c92d79ad-3b74-4cb6-a21b-d6a0a3cfd3e1",
|
||||
"name": "body",
|
||||
"displayName": "message",
|
||||
"type": "QString",
|
||||
"inputType": "TextLine"
|
||||
@ -216,45 +237,24 @@
|
||||
},
|
||||
{
|
||||
"id": "28060803-aa85-44a4-9dec-ee669dfb629f",
|
||||
"name": "pressButton",
|
||||
"displayName": "press button",
|
||||
"name": "navigate",
|
||||
"displayName": "Navigate",
|
||||
"paramTypes": [
|
||||
{
|
||||
"id": "93861dac-0c24-4a3b-903d-d1be44eae611",
|
||||
"name": "button",
|
||||
"displayName": "button",
|
||||
"name": "to",
|
||||
"displayName": "to",
|
||||
"type": "QString",
|
||||
"allowedValues": [
|
||||
"left",
|
||||
"right",
|
||||
"up",
|
||||
"down",
|
||||
"pageup",
|
||||
"pagedown",
|
||||
"select",
|
||||
"left",
|
||||
"right",
|
||||
"enter",
|
||||
"back",
|
||||
"menu",
|
||||
"info",
|
||||
"pause",
|
||||
"stop",
|
||||
"skipnext",
|
||||
"skipprevious",
|
||||
"stepforward",
|
||||
"stepback",
|
||||
"osd",
|
||||
"play",
|
||||
"playpause",
|
||||
"fastforward",
|
||||
"rewind",
|
||||
"fullscreen",
|
||||
"mute",
|
||||
"volumeup",
|
||||
"volumedown",
|
||||
"channelup",
|
||||
"channeldown",
|
||||
"red",
|
||||
"green",
|
||||
"yellow",
|
||||
"blue"
|
||||
"home"
|
||||
]
|
||||
}
|
||||
]
|
||||
@ -278,42 +278,18 @@
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"browserItemActionTypes": [
|
||||
{
|
||||
"id": "3fed69c5-dddf-4500-a674-c79015f63974",
|
||||
"name": "updateLibrary",
|
||||
"displayName": "Update library"
|
||||
},
|
||||
{
|
||||
"id": "59f7ad10-16eb-40b7-a88b-c8393ae8e413",
|
||||
"name": "videoLibrary",
|
||||
"displayName": "video library",
|
||||
"paramTypes": [
|
||||
{
|
||||
"id": "3219855b-e043-43aa-91ae-794b474379bf",
|
||||
"name": "videoCommand",
|
||||
"displayName": "command",
|
||||
"type": "QString",
|
||||
"defaultValue": "scan",
|
||||
"allowedValues": [
|
||||
"scan",
|
||||
"clean"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "3d5120eb-1007-46c3-b76e-d9d4b105b9f2",
|
||||
"name": "audioLibrary",
|
||||
"displayName": "audio library",
|
||||
"paramTypes": [
|
||||
{
|
||||
"id": "ba89d098-56d7-40a2-b499-c02499c1ec0c",
|
||||
"name": "audioCommand",
|
||||
"displayName": "command",
|
||||
"type": "QString",
|
||||
"defaultValue": "scan",
|
||||
"allowedValues": [
|
||||
"scan",
|
||||
"clean"
|
||||
]
|
||||
}
|
||||
]
|
||||
"id": "dbc2c455-ae75-493e-9d8b-659e951b55a1",
|
||||
"name": "cleanLibrary",
|
||||
"displayName": "Clean library"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
710
kodi/kodi.cpp
710
kodi/kodi.cpp
@ -24,9 +24,11 @@
|
||||
#include <QDebug>
|
||||
#include "extern-plugininfo.h"
|
||||
#include <QUrl>
|
||||
#include <QTime>
|
||||
|
||||
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 +38,134 @@ 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");
|
||||
|
||||
// Video
|
||||
BrowserItem item = BrowserItem("videolibrary", tr("Video library"), true);
|
||||
item.setDescription(tr(""));
|
||||
item.setIcon(BrowserItem::BrowserIconFolder);
|
||||
item.setActionTypeIds({kodiUpdateLibraryBrowserItemActionTypeId, kodiCleanLibraryBrowserItemActionTypeId});
|
||||
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);
|
||||
QVariantList movieProperties = properties;
|
||||
movieProperties.append("year");
|
||||
movieProperties.append("rating");
|
||||
movieProperties.append("runtime");
|
||||
movies->getParams.insert("properties", movieProperties);
|
||||
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);
|
||||
QVariantList tvShowProperties = properties;
|
||||
tvShowProperties.append("year");
|
||||
tvShowProperties.append("rating");
|
||||
tvShowProperties.append("season");
|
||||
tvShows->getParams.insert("properties", tvShowProperties);
|
||||
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);
|
||||
|
||||
// Music
|
||||
item = BrowserItem("audiolibrary", tr("Music library"), true);
|
||||
item.setDescription(tr(""));
|
||||
item.setIcon(BrowserItem::BrowserIconFolder);
|
||||
item.setActionTypeIds({kodiUpdateLibraryBrowserItemActionTypeId, kodiCleanLibraryBrowserItemActionTypeId});
|
||||
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);
|
||||
QVariantList artistProperties = properties;
|
||||
artistProperties.append("formed");
|
||||
artistProperties.append("genre");
|
||||
artists->getParams.insert("properties", artistProperties);
|
||||
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);
|
||||
QVariantList albumProperties = properties;
|
||||
albumProperties.append("artist");
|
||||
albumProperties.append("year");
|
||||
albums->getParams.insert("properties", albumProperties);
|
||||
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);
|
||||
QVariantList songProperties = properties;
|
||||
songProperties.append("artist");
|
||||
songProperties.append("album");
|
||||
songProperties.append("year");
|
||||
songs->getParams.insert("properties", songProperties);
|
||||
audioLibrary->addChild(songs);
|
||||
|
||||
// Add-ons
|
||||
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
|
||||
@ -95,10 +225,10 @@ int Kodi::setRepeat(const QString &repeat)
|
||||
return m_jsonHandler->sendData("Player.SetRepeat", params);
|
||||
}
|
||||
|
||||
int Kodi::showNotification(const QString &message, const int &displayTime, const QString ¬ificationType)
|
||||
int Kodi::showNotification(const QString &title, const QString &message, const int &displayTime, const QString ¬ificationType)
|
||||
{
|
||||
QVariantMap params;
|
||||
params.insert("title", "nymea notification");
|
||||
params.insert("title", title);
|
||||
params.insert("message", message);
|
||||
params.insert("displaytime", displayTime);
|
||||
params.insert("image", notificationType);
|
||||
@ -106,10 +236,19 @@ int Kodi::showNotification(const QString &message, const int &displayTime, const
|
||||
return m_jsonHandler->sendData("GUI.ShowNotification", params);
|
||||
}
|
||||
|
||||
int Kodi::pressButton(const QString &button)
|
||||
int Kodi::navigate(const QString &to)
|
||||
{
|
||||
qCDebug(dcKodi()) << "Navigate:" << to;
|
||||
if (to == "home") {
|
||||
return m_jsonHandler->sendData("Input.Home", QVariantMap());
|
||||
}
|
||||
|
||||
QVariantMap params;
|
||||
params.insert("action", button);
|
||||
QString mappedTo = to;
|
||||
if (to == "enter") {
|
||||
mappedTo = "select";
|
||||
}
|
||||
params.insert("action", mappedTo);
|
||||
return m_jsonHandler->sendData("Input.ExecuteAction", params);
|
||||
}
|
||||
|
||||
@ -131,34 +270,6 @@ int Kodi::systemCommand(const QString &command)
|
||||
return m_jsonHandler->sendData("System." + method, QVariantMap());
|
||||
}
|
||||
|
||||
int Kodi::videoLibrary(const QString &command)
|
||||
{
|
||||
QString method;
|
||||
if (command == "scan") {
|
||||
method = "Scan";
|
||||
} else if (command == "clean") {
|
||||
method = "Clean";
|
||||
} else {
|
||||
// already checkt with allowed values
|
||||
}
|
||||
|
||||
return m_jsonHandler->sendData("VideoLibrary." + method, QVariantMap());
|
||||
}
|
||||
|
||||
int Kodi::audioLibrary(const QString &command)
|
||||
{
|
||||
QString method;
|
||||
if (command == "scan") {
|
||||
method = "Scan";
|
||||
} else if (command == "clean") {
|
||||
method = "Clean";
|
||||
} else {
|
||||
// already checkt with allowed values
|
||||
}
|
||||
|
||||
return m_jsonHandler->sendData("AudioLibrary." + method, QVariantMap());
|
||||
}
|
||||
|
||||
void Kodi::update()
|
||||
{
|
||||
QVariantMap params;
|
||||
@ -190,6 +301,246 @@ 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);
|
||||
QVariantList albumProperties = properties;
|
||||
albumProperties.append("artist");
|
||||
albumProperties.append("year");
|
||||
params.insert("properties", albumProperties);
|
||||
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 songProperties = properties;
|
||||
songProperties.append("albumid");
|
||||
songProperties.append("artist");
|
||||
songProperties.append("album");
|
||||
songProperties.append("year");
|
||||
params.insert("properties", songProperties);
|
||||
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");
|
||||
properties.append("showtitle");
|
||||
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);
|
||||
QVariantList properties;
|
||||
properties.append("thumbnail");
|
||||
properties.append("showtitle");
|
||||
properties.append("season");
|
||||
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;
|
||||
}
|
||||
|
||||
int Kodi::executeBrowserItemAction(const QString &itemId, const ActionTypeId &actionTypeId)
|
||||
{
|
||||
QString scope;
|
||||
QString method;
|
||||
if (actionTypeId == kodiUpdateLibraryBrowserItemActionTypeId) {
|
||||
method = "Scan";
|
||||
} else if (actionTypeId == kodiCleanLibraryBrowserItemActionTypeId) {
|
||||
method = "Clean";
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (itemId == "audiolibrary") {
|
||||
scope = "AudioLibrary";
|
||||
} else if (itemId == "videolibrary") {
|
||||
scope = "VideoLibrary";
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return m_jsonHandler->sendData(scope + "." + method, QVariantMap());
|
||||
}
|
||||
|
||||
void Kodi::onVolumeChanged(const int &volume, const bool &muted)
|
||||
{
|
||||
if (m_volume != volume || m_muted != muted) {
|
||||
@ -308,10 +659,7 @@ void Kodi::processResponse(int id, const QString &method, const QVariantMap &res
|
||||
qCDebug(dcKodi) << "response received:" << method << response;
|
||||
|
||||
if (response.contains("error")) {
|
||||
//qCDebug(dcKodi) << QJsonDocument::fromVariant(response).toJson();
|
||||
qCWarning(dcKodi) << "got error response for request " << method << ":" << response.value("error").toMap().value("message").toString();
|
||||
emit actionExecuted(id, false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (method == "Application.GetProperties") {
|
||||
@ -346,11 +694,281 @@ void Kodi::processResponse(int id, const QString &method, const QVariantMap &res
|
||||
|
||||
if (method == "Player.SetShuffle" || method == "Player.SetRepeat") {
|
||||
updatePlayerProperties();
|
||||
emit actionExecuted(id, !response.contains("error"));
|
||||
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()));
|
||||
QStringList description;
|
||||
if (!artist.value("formed").toString().isEmpty()) {
|
||||
description.append(artist.value("formed").toString());
|
||||
}
|
||||
if (!artist.value("genre").toStringList().isEmpty()) {
|
||||
description.append(artist.value("genre").toStringList().join(", "));
|
||||
}
|
||||
item.setDescription(description.join(" - "));
|
||||
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()));
|
||||
QStringList description;
|
||||
if (!album.value("artist").toStringList().isEmpty()) {
|
||||
description.append(album.value("artist").toStringList().join(", "));
|
||||
}
|
||||
if (album.value("year").toInt() != 0) {
|
||||
description.append(album.value("year").toString());
|
||||
}
|
||||
item.setDescription(description.join(" - "));
|
||||
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()));
|
||||
QStringList description;
|
||||
if (!song.value("artist").toStringList().isEmpty()) {
|
||||
description.append(song.value("artist").toStringList().join(","));
|
||||
}
|
||||
if (!song.value("album").toString().isEmpty()) {
|
||||
description.append(song.value("album").toString());
|
||||
} else if (!song.value("year").toString().isEmpty()) {
|
||||
description.append(song.value("year").toString());
|
||||
}
|
||||
item.setDescription(description.join(" - "));
|
||||
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()));
|
||||
QString rating;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if (qRound(movie.value("rating").toDouble() / 2) >= i) {
|
||||
rating += "★";
|
||||
} else {
|
||||
rating += "☆";
|
||||
}
|
||||
}
|
||||
|
||||
int runtime = movie.value("runtime").toInt();
|
||||
int hours = runtime / 60 / 60;
|
||||
int minutes = (runtime / 60) % 60;
|
||||
QString duration;
|
||||
duration = QString("%1:%2").arg(hours).arg(minutes, 2, 10, QChar('0'));
|
||||
item.setDescription(movie.value("year").toString() + " - " + duration + " - " + rating);
|
||||
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()));
|
||||
QString rating;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if (qRound(tvShow.value("rating").toDouble() / 2) >= i) {
|
||||
rating += "★";
|
||||
} else {
|
||||
rating += "☆";
|
||||
}
|
||||
}
|
||||
item.setDescription(tvShow.value("year").toString() + " - " + tr("%1 seasons").arg(tvShow.value("season").toInt()) + " - " + rating);
|
||||
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()));
|
||||
item.setDescription(season.value("showtitle").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()));
|
||||
if (!episode.value("season").toString().isEmpty()) {
|
||||
item.setDescription(episode.value("showtitle").toString() + " - " + tr("Season %1").arg(episode.value("season").toString()));
|
||||
} else {
|
||||
item.setDescription(episode.value("showtitle").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);
|
||||
item.setThumbnail(prepareThumbnail(addon.value("thumbnail").toString()));
|
||||
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);
|
||||
}
|
||||
item.setThumbnail(prepareThumbnail(file.value("thumbnail").toString()));
|
||||
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;
|
||||
}
|
||||
|
||||
if (method == "GUI.ShowNotification" || method == "Input.ExecuteAction") {
|
||||
emit actionExecuted(id, !response.contains("error"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (method == "VideoLibrary.Scan" || method == "VideoLibrary.Clean" || method == "AudioLibrary.Scan" || method == "AudioLibrary.Clean") {
|
||||
emit browserItemActionExecuted(id, !response.contains("error"));
|
||||
return;
|
||||
}
|
||||
|
||||
qCWarning(dcKodi()) << "unhandled reply" << method << response;
|
||||
}
|
||||
|
||||
void Kodi::updatePlayerProperties()
|
||||
@ -372,3 +990,19 @@ void Kodi::updateMetadata()
|
||||
params.insert("properties", fields);
|
||||
m_jsonHandler->sendData("Player.GetItem", params);
|
||||
}
|
||||
|
||||
QString Kodi::prepareThumbnail(const QString &thumbnail)
|
||||
{
|
||||
if (thumbnail.isEmpty()) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString addr = m_connection->hostAddress().toString();
|
||||
if (m_connection->hostAddress().protocol() == QAbstractSocket::IPv6Protocol) {
|
||||
addr = '[' + addr + ']';
|
||||
}
|
||||
return QString("http://%1:%2/image/%3")
|
||||
.arg(addr)
|
||||
.arg(m_httpPort)
|
||||
.arg(QString(thumbnail.toUtf8().toPercentEncoding()));
|
||||
}
|
||||
|
||||
48
kodi/kodi.h
48
kodi/kodi.h
@ -29,12 +29,16 @@
|
||||
#include "kodiconnection.h"
|
||||
#include "kodijsonhandler.h"
|
||||
|
||||
#include "types/browseritem.h"
|
||||
#include "types/browseritemaction.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;
|
||||
@ -52,11 +56,9 @@ public:
|
||||
int setRepeat(const QString &repeat);
|
||||
|
||||
// actions
|
||||
int showNotification(const QString &message, const int &displayTime, const QString ¬ificationType);
|
||||
int pressButton(const QString &button);
|
||||
int showNotification(const QString &title, const QString &message, const int &displayTime, const QString ¬ificationType);
|
||||
int navigate(const QString &to);
|
||||
int systemCommand(const QString &command);
|
||||
int videoLibrary(const QString &command);
|
||||
int audioLibrary(const QString &command);
|
||||
|
||||
void update();
|
||||
void checkVersion();
|
||||
@ -64,17 +66,25 @@ 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);
|
||||
int executeBrowserItemAction(const QString &itemId, const ActionTypeId &actionTypeId);
|
||||
|
||||
signals:
|
||||
void connectionStatusChanged();
|
||||
void stateChanged();
|
||||
void activePlayerChanged(const QString &playerType);
|
||||
void actionExecuted(int actionId, const bool &success);
|
||||
void actionExecuted(int actionId, bool success);
|
||||
void updateDataReceived(const QVariantMap &data);
|
||||
void versionDataReceived(const QVariantMap &data);
|
||||
void playbackStatusChanged(const QString &playbackState);
|
||||
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);
|
||||
void browserItemActionExecuted(int actionId, bool success);
|
||||
|
||||
private slots:
|
||||
void onVolumeChanged(const int &volume, const bool &muted);
|
||||
@ -90,14 +100,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
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -53,14 +53,29 @@ int KodiJsonHandler::sendData(const QString &method, const QVariantMap ¶ms)
|
||||
|
||||
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();
|
||||
|
||||
@ -50,6 +50,7 @@ private:
|
||||
KodiConnection *m_connection;
|
||||
int m_id;
|
||||
QHash<int, KodiReply> m_replys;
|
||||
QByteArray m_dataBuffer;
|
||||
|
||||
};
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user