From fc90d1063651ef2a0048f464b3c8e82962759f1d Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Fri, 30 Nov 2018 16:11:48 +0100 Subject: [PATCH] implement new media interfaces in kodi --- kodi/devicepluginkodi.cpp | 182 ++++++++++++++++++------------- kodi/devicepluginkodi.h | 5 +- kodi/devicepluginkodi.json | 74 +++++++++++-- kodi/kodi.cpp | 216 ++++++++++++++++++++++++++++++------- kodi/kodi.h | 49 ++++++--- kodi/kodiconnection.h | 4 +- kodi/kodijsonhandler.cpp | 80 ++------------ kodi/kodijsonhandler.h | 28 ++--- kodi/kodireply.cpp | 15 +-- kodi/kodireply.h | 7 +- 10 files changed, 408 insertions(+), 252 deletions(-) diff --git a/kodi/devicepluginkodi.cpp b/kodi/devicepluginkodi.cpp index 2067e688..8c47a557 100644 --- a/kodi/devicepluginkodi.cpp +++ b/kodi/devicepluginkodi.cpp @@ -65,6 +65,12 @@ #include "plugin/device.h" #include "plugininfo.h" #include "network/upnp/upnpdiscovery.h" +#include "network/avahi/qtavahiservicebrowser.h" +#include "network/avahi/avahiserviceentry.h" +#include "network/networkaccessmanager.h" + +#include +#include DevicePluginKodi::DevicePluginKodi() { @@ -106,6 +112,56 @@ DeviceManager::DeviceSetupStatus DevicePluginKodi::setupDevice(Device *device) connect(kodi, &Kodi::updateDataReceived, this, &DevicePluginKodi::onSetupFinished); connect(kodi, &Kodi::playbackStatusChanged, this, &DevicePluginKodi::onPlaybackStatusChanged); + connect(kodi, &Kodi::activePlayerChanged, device, [device](const QString &playerType){ + device->setStateValue(kodiPlayerTypeStateTypeId, playerType); + }); + connect(kodi, &Kodi::mediaMetadataChanged, device, [this, device](const QString &title, const QString &artist, const QString &collection, const QString &artwork){ + device->setStateValue(kodiTitleStateTypeId, title); + device->setStateValue(kodiArtistStateTypeId, artist); + device->setStateValue(kodiCollectionStateTypeId, collection); + + + QNetworkRequest request; + QHostAddress hostAddr(device->paramValue(kodiDeviceIpParamTypeId).toString()); + QString addr; + if (hostAddr.protocol() == QAbstractSocket::IPv4Protocol) { + addr = hostAddr.toString(); + } else { + addr = "[" + hostAddr.toString() + "]"; + } + + request.setUrl(QUrl("http://" + addr + ":8080/jsonrpc")); + qCDebug(dcKodi) << "Prepping file dl" << "http://" + addr + ":" + device->paramValue(kodiDevicePortParamTypeId).toString() + "/jsonrpc"; + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + QVariantMap map; + map.insert("jsonrpc", "2.0"); + map.insert("method", "Files.PrepareDownload"); + map.insert("id", QString::number(123)); + QVariantMap params; + params.insert("path", artwork); + 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](){ + 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()); + }); + + }); + + connect(kodi, &Kodi::shuffleChanged, device, [device](bool shuffle){ + device->setStateValue(kodiShuffleStateTypeId, shuffle); + }); + connect(kodi, &Kodi::repeatChanged, device, [device](const QString &repeat){ + if (repeat == "one") { + device->setStateValue(kodiRepeatStateTypeId, "One"); + } else if (repeat == "all") { + device->setStateValue(kodiRepeatStateTypeId, "All"); + } else { + device->setStateValue(kodiRepeatStateTypeId, "None"); + } + }); m_kodis.insert(kodi, device); m_asyncSetups.append(kodi); @@ -127,9 +183,21 @@ DeviceManager::DeviceError DevicePluginKodi::discoverDevices(const DeviceClassId Q_UNUSED(params) Q_UNUSED(deviceClassId) - qCDebug(dcKodi) << "Start UPnP search"; - UpnpDiscoveryReply *reply = hardwareManager()->upnpDiscovery()->discoverDevices(); - connect(reply, &UpnpDiscoveryReply::finished, this, &DevicePluginKodi::onUpnpDiscoveryFinished); + QList descriptors; + foreach (const AvahiServiceEntry &avahiEntry, hardwareManager()->avahiBrowser()->serviceEntries()) { + if (avahiEntry.serviceType() == "_xbmc-jsonrpc._tcp") { + 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()); + descriptor.setParams(params); + descriptors << descriptor; + } + } + if (!descriptors.isEmpty()) { + emit devicesDiscovered(kodiDeviceClassId, descriptors); + } return DeviceManager::DeviceErrorAsync; } @@ -144,52 +212,52 @@ DeviceManager::DeviceError DevicePluginKodi::executeAction(Device *device, const return DeviceManager::DeviceErrorHardwareNotAvailable; } + int commandId = -1; if (action.actionTypeId() == kodiShowNotificationActionTypeId) { - kodi->showNotification(action.param(kodiShowNotificationActionMessageParamTypeId).value().toString(), 8000, action.param(kodiShowNotificationActionTypeParamTypeId).value().toString(), action.id()); - return DeviceManager::DeviceErrorAsync; + commandId = kodi->showNotification(action.param(kodiShowNotificationActionMessageParamTypeId).value().toString(), 8000, action.param(kodiShowNotificationActionTypeParamTypeId).value().toString()); } else if (action.actionTypeId() == kodiVolumeActionTypeId) { - kodi->setVolume(action.param(kodiVolumeActionVolumeParamTypeId).value().toInt(), action.id()); - return DeviceManager::DeviceErrorAsync; + commandId = kodi->setVolume(action.param(kodiVolumeActionVolumeParamTypeId).value().toInt()); } else if (action.actionTypeId() == kodiMuteActionTypeId) { - kodi->setMuted(action.param(kodiMuteActionMuteParamTypeId).value().toBool(), action.id()); - return DeviceManager::DeviceErrorAsync; + commandId = kodi->setMuted(action.param(kodiMuteActionMuteParamTypeId).value().toBool()); } else if (action.actionTypeId() == kodiPressButtonActionTypeId) { - kodi->pressButton(action.param(kodiPressButtonActionButtonParamTypeId).value().toString(), action.id()); - return DeviceManager::DeviceErrorAsync; + commandId = kodi->pressButton(action.param(kodiPressButtonActionButtonParamTypeId).value().toString()); } else if (action.actionTypeId() == kodiSystemActionTypeId) { - kodi->systemCommand(action.param(kodiSystemActionSystemCommandParamTypeId).value().toString(), action.id()); - return DeviceManager::DeviceErrorAsync; + commandId = kodi->systemCommand(action.param(kodiSystemActionSystemCommandParamTypeId).value().toString()); } else if (action.actionTypeId() == kodiVideoLibraryActionTypeId) { - kodi->videoLibrary(action.param(kodiVideoLibraryActionVideoCommandParamTypeId).value().toString(), action.id()); - return DeviceManager::DeviceErrorAsync; + commandId = kodi->videoLibrary(action.param(kodiVideoLibraryActionVideoCommandParamTypeId).value().toString()); } else if (action.actionTypeId() == kodiAudioLibraryActionTypeId) { - kodi->audioLibrary(action.param(kodiAudioLibraryActionAudioCommandParamTypeId).value().toString(), action.id()); - return DeviceManager::DeviceErrorAsync; + commandId = kodi->audioLibrary(action.param(kodiAudioLibraryActionAudioCommandParamTypeId).value().toString()); } else if(action.actionTypeId() == kodiSkipBackActionTypeId) { - kodi->pressButton("skipprevious", action.id()); - return DeviceManager::DeviceErrorAsync; - } else if(action.actionTypeId() == kodiRewindActionTypeId) { - kodi->pressButton("rewind", action.id()); - return DeviceManager::DeviceErrorAsync; + commandId = kodi->pressButton("skipprevious"); + } else if(action.actionTypeId() == kodiFastRewindActionTypeId) { + commandId = kodi->pressButton("rewind"); } else if(action.actionTypeId() == kodiStopActionTypeId) { - kodi->pressButton("stop", action.id()); - return DeviceManager::DeviceErrorAsync; + commandId = kodi->pressButton("stop"); } else if(action.actionTypeId() == kodiPlayActionTypeId) { - kodi->pressButton("play", action.id()); - return DeviceManager::DeviceErrorAsync; + commandId = kodi->pressButton("play"); } else if(action.actionTypeId() == kodiPauseActionTypeId) { - kodi->pressButton("pause", action.id()); - return DeviceManager::DeviceErrorAsync; + commandId = kodi->pressButton("pause"); } else if(action.actionTypeId() == kodiFastForwardActionTypeId) { - kodi->pressButton("fastforward", action.id()); - return DeviceManager::DeviceErrorAsync; + commandId = kodi->pressButton("fastforward"); } else if(action.actionTypeId() == kodiSkipNextActionTypeId) { - kodi->pressButton("skipnext", action.id()); - return DeviceManager::DeviceErrorAsync; + commandId = kodi->pressButton("skipnext"); + } else if (action.actionTypeId() == kodiShuffleActionTypeId) { + commandId = kodi->setShuffle(action.param(kodiShuffleActionShuffleParamTypeId).value().toBool()); + } else if (action.actionTypeId() == kodiRepeatActionTypeId) { + QString repeat = action.param(kodiRepeatActionRepeatParamTypeId).value().toString(); + if (repeat == "One") { + commandId = kodi->setRepeat("one"); + } else if (repeat == "All") { + commandId = kodi->setRepeat("all"); + } else { + commandId = kodi->setRepeat("off"); + } } else { qWarning(dcKodi()) << "Unhandled action type" << action.actionTypeId(); + return DeviceManager::DeviceErrorActionTypeNotFound; } - return DeviceManager::DeviceErrorActionTypeNotFound; + m_pendingActions.insert(commandId, action.id()); + return DeviceManager::DeviceErrorAsync; } return DeviceManager::DeviceErrorDeviceClassNotFound; } @@ -204,43 +272,6 @@ void DevicePluginKodi::onPluginTimer() } } -void DevicePluginKodi::onUpnpDiscoveryFinished() -{ - qCDebug(dcKodi()) << "Upnp discovery finished"; - UpnpDiscoveryReply *reply = static_cast(sender()); - if (reply->error() != UpnpDiscoveryReply::UpnpDiscoveryReplyErrorNoError) { - qCWarning(dcKodi()) << "Upnp discovery error" << reply->error(); - } - reply->deleteLater(); - - QList deviceDescriptors; - foreach (const UpnpDeviceDescriptor &upnpDescriptor, reply->deviceDescriptors()) { - if (upnpDescriptor.modelName().contains("Kodi")) { - - // check if we already found the kodi on this ip - bool alreadyAdded = false; - foreach (const DeviceDescriptor dDescriptor, deviceDescriptors) { - if (dDescriptor.params().paramValue(kodiDeviceIpParamTypeId).toString() == upnpDescriptor.hostAddress().toString()) { - alreadyAdded = true; - break; - } - } - if (alreadyAdded) - continue; - - qCDebug(dcKodi) << upnpDescriptor; - DeviceDescriptor deviceDescriptor(kodiDeviceClassId, "Kodi - Media Center", upnpDescriptor.hostAddress().toString()); - ParamList params; - params.append(Param(kodiDeviceNameParamTypeId, upnpDescriptor.friendlyName())); - params.append(Param(kodiDeviceIpParamTypeId, upnpDescriptor.hostAddress().toString())); - params.append(Param(kodiDevicePortParamTypeId, 9090)); - deviceDescriptor.setParams(params); - deviceDescriptors.append(deviceDescriptor); - } - } - emit devicesDiscovered(kodiDeviceClassId, deviceDescriptors); -} - void DevicePluginKodi::onConnectionChanged() { Kodi *kodi = static_cast(sender()); @@ -267,13 +298,12 @@ void DevicePluginKodi::onStateChanged() device->setStateValue(kodiMuteStateTypeId, kodi->muted()); } -void DevicePluginKodi::onActionExecuted(const ActionId &actionId, const bool &success) +void DevicePluginKodi::onActionExecuted(int actionId, bool success) { - if (success) { - emit actionExecutionFinished(actionId, DeviceManager::DeviceErrorNoError); - } else { - emit actionExecutionFinished(actionId, DeviceManager::DeviceErrorInvalidParameter); + if (!m_pendingActions.contains(actionId)) { + return; } + emit actionExecutionFinished(m_pendingActions.value(actionId), success ? DeviceManager::DeviceErrorNoError : DeviceManager::DeviceErrorInvalidParameter); } void DevicePluginKodi::versionDataReceived(const QVariantMap &data) @@ -304,7 +334,7 @@ void DevicePluginKodi::onSetupFinished(const QVariantMap &data) emit deviceSetupFinished(device, DeviceManager::DeviceSetupStatusSuccess); - kodi->showNotification("Connected", 2000, "info", ActionId()); + kodi->showNotification("Connected", 2000, "info"); } void DevicePluginKodi::onPlaybackStatusChanged(const QString &playbackStatus) diff --git a/kodi/devicepluginkodi.h b/kodi/devicepluginkodi.h index cc16db0f..c5d06668 100644 --- a/kodi/devicepluginkodi.h +++ b/kodi/devicepluginkodi.h @@ -52,12 +52,13 @@ private: QHash m_kodis; QList m_asyncSetups; + QHash m_pendingActions; + private slots: void onPluginTimer(); - void onUpnpDiscoveryFinished(); void onConnectionChanged(); void onStateChanged(); - void onActionExecuted(const ActionId &actionId, const bool &success); + void onActionExecuted(int actionId, bool success); void versionDataReceived(const QVariantMap &data); void onSetupFinished(const QVariantMap &data); diff --git a/kodi/devicepluginkodi.json b/kodi/devicepluginkodi.json index a230e1b6..50b5f597 100644 --- a/kodi/devicepluginkodi.json +++ b/kodi/devicepluginkodi.json @@ -13,7 +13,7 @@ "name": "kodi", "displayName": "Kodi", "deviceIcon": "Tv", - "interfaces": ["mediacontroller", "extendedvolumecontroller"], + "interfaces": ["mediaplayer", "extendedmediacontroller", "extendedvolumecontroller", "mediametadataprovider", "shufflerepeat"], "basicTags": [ "Service", "Multimedia", @@ -22,13 +22,6 @@ "createMethods": ["user", "discovery"], "criticalStateTypeId": "09dfbd40-c97c-4a20-9ecd-f80e389a4864", "paramTypes": [ - { - "id": "a704beb1-b0b0-46fc-91fc-3aac01e1a364", - "name": "name", - "displayName": "name", - "type": "QString", - "inputType": "TextLine" - }, { "id": "1a897065-57c6-49b3-bac9-1e5db27859e5", "name": "ip", @@ -89,8 +82,69 @@ "displayNameEvent": "playback status changed", "displayNameAction": "set playback status", "writable": true + }, + { + "id": "0af58b87-4e45-4f0a-9ef2-0ade74c7c22c", + "type": "QString", + "name": "playerType", + "displayName": "Active player type", + "possibleValues": ["audio", "video"], + "displayNameEvent": "Active player changed", + "defaultValue": "audio" + }, + { + "id": "f2209fec-cceb-46ad-8189-4caf42166e6b", + "type": "QString", + "name": "title", + "displayName": "Title", + "displayNameEvent": "Title changed", + "defaultValue": "" + }, + { + "id": "8cb920a3-3bf1-4231-92d4-8ac27e7b3d65", + "type": "QString", + "name": "artist", + "displayName": "Artist", + "displayNameEvent": "Artist changed", + "defaultValue": "" + }, + { + "id": "ce399eec-9f6a-4903-9916-0e90e38b255e", + "type": "QString", + "name": "collection", + "displayName": "Collection", + "displayNameEvent": "Collection changed", + "defaultValue": "" + }, + { + "id": "44304c82-c2f6-433b-b62b-815382617d0b", + "type": "QString", + "name": "artwork", + "displayName": "Artwork", + "displayNameEvent": "Artwork changed", + "defaultValue": "" + }, + { + "id": "5913aa2a-629d-4de5-bf44-a4a1f130c118", + "type": "bool", + "name": "shuffle", + "displayName": "Shuffle", + "displayNameEvent": "Shuffle changed", + "displayNameAction": "Set shuffle", + "defaultValue": false, + "writable": true + }, + { + "id": "bc02c28e-3f5d-4de4-b9b5-c0b1576c6e7e", + "type": "QString", + "name": "repeat", + "displayName": "Repeat", + "displayNameEvent": "Repeat changed", + "displayNameAction": "Set repeat", + "possibleValues": ["None", "One", "All"], + "defaultValue": "None", + "writable": true } - ], "eventTypes": [ { @@ -117,7 +171,7 @@ }, { "id": "7e70b47b-7e79-4521-be34-04a3c427e5b1", - "name": "rewind", + "name": "fastRewind", "displayName": "rewind" }, { diff --git a/kodi/kodi.cpp b/kodi/kodi.cpp index 514c1073..e38ed177 100644 --- a/kodi/kodi.cpp +++ b/kodi/kodi.cpp @@ -23,6 +23,7 @@ #include "kodi.h" #include #include "extern-plugininfo.h" +#include Kodi::Kodi(const QHostAddress &hostAddress, const int &port, QObject *parent) : QObject(parent), @@ -33,14 +34,8 @@ Kodi::Kodi(const QHostAddress &hostAddress, const int &port, QObject *parent) : connect (m_connection, &KodiConnection::connectionStatusChanged, this, &Kodi::connectionStatusChanged); m_jsonHandler = new KodiJsonHandler(m_connection, this); - connect(m_jsonHandler, &KodiJsonHandler::volumeChanged, this, &Kodi::onVolumeChanged); - connect(m_jsonHandler, &KodiJsonHandler::actionExecuted, this, &Kodi::actionExecuted); - connect(m_jsonHandler, &KodiJsonHandler::versionDataReceived, this, &Kodi::versionDataReceived); - connect(m_jsonHandler, &KodiJsonHandler::updateDataReceived, this, &Kodi::updateDataReceived); - connect(m_jsonHandler, &KodiJsonHandler::updateDataReceived, this, &Kodi::onUpdateFinished); - connect(m_jsonHandler, &KodiJsonHandler::playbackStatusChanged, this, &Kodi::playbackStatusChanged); - connect(m_jsonHandler, &KodiJsonHandler::activePlayersChanged, this, &Kodi::activePlayersChanged); - connect(m_jsonHandler, &KodiJsonHandler::playerPropertiesReveived, this, &Kodi::playerPropertiesReceived); + connect(m_jsonHandler, &KodiJsonHandler::notificationReceived, this, &Kodi::processNotification); + connect(m_jsonHandler, &KodiJsonHandler::replyReceived, this, &Kodi::processResponse); } QHostAddress Kodi::hostAddress() const @@ -58,12 +53,12 @@ bool Kodi::connected() const return m_connection->connected(); } -void Kodi::setMuted(const bool &muted, const ActionId &actionId) +int Kodi::setMuted(const bool &muted) { QVariantMap params; params.insert("mute", muted); - m_jsonHandler->sendData("Application.SetMute", params, actionId); + return m_jsonHandler->sendData("Application.SetMute", params); } bool Kodi::muted() const @@ -71,12 +66,12 @@ bool Kodi::muted() const return m_muted; } -void Kodi::setVolume(const int &volume, const ActionId &actionId) +int Kodi::setVolume(const int &volume) { QVariantMap params; params.insert("volume", volume); - m_jsonHandler->sendData("Application.SetVolume", params, actionId); + return m_jsonHandler->sendData("Application.SetVolume", params); } int Kodi::volume() const @@ -84,7 +79,23 @@ int Kodi::volume() const return m_volume; } -void Kodi::showNotification(const QString &message, const int &displayTime, const QString ¬ificationType, const ActionId &actionId) +int Kodi::setShuffle(bool shuffle) +{ + QVariantMap params; + params.insert("playerid", m_activePlayer); + params.insert("shuffle", shuffle); + return m_jsonHandler->sendData("Player.SetShuffle", params); +} + +int Kodi::setRepeat(const QString &repeat) +{ + QVariantMap params; + params.insert("playerid", m_activePlayer); + params.insert("repeat", repeat); + return m_jsonHandler->sendData("Player.SetRepeat", params); +} + +int Kodi::showNotification(const QString &message, const int &displayTime, const QString ¬ificationType) { QVariantMap params; params.insert("title", "nymea notification"); @@ -92,17 +103,17 @@ void Kodi::showNotification(const QString &message, const int &displayTime, cons params.insert("displaytime", displayTime); params.insert("image", notificationType); - m_jsonHandler->sendData("GUI.ShowNotification", params, actionId); + return m_jsonHandler->sendData("GUI.ShowNotification", params); } -void Kodi::pressButton(const QString &button, const ActionId &actionId) +int Kodi::pressButton(const QString &button) { QVariantMap params; params.insert("action", button); - m_jsonHandler->sendData("Input.ExecuteAction", params, actionId); + return m_jsonHandler->sendData("Input.ExecuteAction", params); } -void Kodi::systemCommand(const QString &command, const ActionId &actionId) +int Kodi::systemCommand(const QString &command) { QString method; if (command == "hibernate") { @@ -117,10 +128,10 @@ void Kodi::systemCommand(const QString &command, const ActionId &actionId) // already checkt with allowed values } - m_jsonHandler->sendData("System." + method, QVariantMap(), actionId); + return m_jsonHandler->sendData("System." + method, QVariantMap()); } -void Kodi::videoLibrary(const QString &command, const ActionId &actionId) +int Kodi::videoLibrary(const QString &command) { QString method; if (command == "scan") { @@ -131,10 +142,10 @@ void Kodi::videoLibrary(const QString &command, const ActionId &actionId) // already checkt with allowed values } - m_jsonHandler->sendData("VideoLibrary." + method, QVariantMap(), actionId); + return m_jsonHandler->sendData("VideoLibrary." + method, QVariantMap()); } -void Kodi::audioLibrary(const QString &command, const ActionId &actionId) +int Kodi::audioLibrary(const QString &command) { QString method; if (command == "scan") { @@ -145,7 +156,7 @@ void Kodi::audioLibrary(const QString &command, const ActionId &actionId) // already checkt with allowed values } - m_jsonHandler->sendData("AudioLibrary." + method, QVariantMap(), actionId); + return m_jsonHandler->sendData("AudioLibrary." + method, QVariantMap()); } void Kodi::update() @@ -158,15 +169,15 @@ void Kodi::update() properties.append("version"); params.insert("properties", properties); - m_jsonHandler->sendData("Application.GetProperties", params, ActionId()); + m_jsonHandler->sendData("Application.GetProperties", params); params.clear(); - m_jsonHandler->sendData("Player.GetActivePlayers", params, ActionId()); + m_jsonHandler->sendData("Player.GetActivePlayers", params); } void Kodi::checkVersion() { - m_jsonHandler->sendData("JSONRPC.Version", QVariantMap(), ActionId()); + m_jsonHandler->sendData("JSONRPC.Version", QVariantMap()); } void Kodi::connectKodi() @@ -202,29 +213,162 @@ void Kodi::onUpdateFinished(const QVariantMap &data) void Kodi::activePlayersChanged(const QVariantList &data) { - qCDebug(dcKodi()) << "active players changed" << data.count(); + qCDebug(dcKodi()) << "active players changed" << data.count() << data; m_activePlayerCount = data.count(); if (m_activePlayerCount == 0) { - emit playbackStatusChanged("Stopped"); + onPlaybackStatusChanged("Stopped"); return; } - int activePlayer = data.first().toMap().value("playerid").toInt(); - QVariantMap params; - params.insert("playerid", activePlayer); - QVariantList properties; - properties.append("speed"); - params.insert("properties", properties); - m_jsonHandler->sendData("Player.GetProperties", params, ActionId()); + m_activePlayer = data.first().toMap().value("playerid").toInt(); + qCDebug(dcKodi) << "Active Player changed:" << m_activePlayer << data.first().toMap().value("type").toString(); + emit activePlayerChanged(data.first().toMap().value("type").toString()); + + updatePlayerProperties(); } void Kodi::playerPropertiesReceived(const QVariantMap &properties) { qCDebug(dcKodi()) << "player props received" << properties; + if (m_activePlayerCount > 0) { if (properties.value("speed").toDouble() > 0) { - emit playbackStatusChanged("Playing"); + onPlaybackStatusChanged("Playing"); } else { - emit playbackStatusChanged("Paused"); + onPlaybackStatusChanged("Paused"); } } + + emit shuffleChanged(properties.value("shuffled").toBool()); + emit repeatChanged(properties.value("repeat").toString()); + +} + +void Kodi::mediaMetaDataReceived(const QVariantMap &data) +{ + QVariantMap item = data.value("item").toMap(); + + QString title = item.value("title").toString(); + QString artist; + QString collection; + if (item.value("type").toString() == "song") { + artist = !item.value("artist").toList().isEmpty() ? item.value("artist").toList().first().toString() : ""; + collection = item.value("album").toString(); + } else if (item.value("type").toString() == "episode") { + collection = item.value("showtitle").toString(); + } else if (item.value("type").toString() == "unknown") { + artist = item.value("channel").toString(); + collection = item.value("showtitle").toString(); + } else if (item.value("type").toString() == "channel") { + artist = item.value("channel").toString(); + collection = item.value("showtitle").toString(); + } else if (item.value("type").toString() == "movie") { + artist = item.value("director").toStringList().join(", "); + collection = item.value("year").toString(); + } + + QString artwork = item.value("thumbnail").toString(); + if (artwork.isEmpty()) { + artwork = item.value("fanart").toString(); + } + qCDebug(dcKodi) << "title:" << title << artwork; + emit mediaMetadataChanged(title, artist, collection, artwork); +} + +void Kodi::onPlaybackStatusChanged(const QString &playbackState) +{ + if (playbackState != "Stopped") { + updateMetadata(); + } else { + emit mediaMetadataChanged(QString(), QString(), QString(), QString()); + } + emit playbackStatusChanged(playbackState); +} + +void Kodi::processNotification(const QString &method, const QVariantMap ¶ms) +{ + qCDebug(dcKodi) << "got notification" << method << params; + + if (method == "Application.OnVolumeChanged") { + QVariantMap data = params.value("data").toMap(); + onVolumeChanged(data.value("volume").toInt(), data.value("muted").toBool()); + } else if (method == "Player.OnPlay" || method == "Player.OnResume") { + emit activePlayersChanged(QVariantList() << params.value("data").toMap().value("player")); + onPlaybackStatusChanged("Playing"); + } else if (method == "Player.OnPause") { + emit playbackStatusChanged("Paused"); + } else if (method == "Player.OnStop") { + emit playbackStatusChanged("Stopped"); + emit activePlayersChanged(QVariantList()); + } +} + +void Kodi::processResponse(int id, const QString &method, const QVariantMap &response) +{ + + 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") { + //qCDebug(dcKodi) << "got update response" << reply.method(); + emit updateDataReceived(response.value("result").toMap()); + return; + } + + if (method == "JSONRPC.Version") { + qCDebug(dcKodi) << "got version response" << method; + emit versionDataReceived(response.value("result").toMap()); + return; + } + + if (method == "Player.GetActivePlayers") { + qCDebug(dcKodi) << "Active players changed" << response; + emit activePlayersChanged(response.value("result").toList()); + return; + } + + if (method == "Player.GetProperties") { + qCDebug(dcKodi) << "Player properties received" << response; + playerPropertiesReceived(response.value("result").toMap()); + return; + } + + if (method == "Player.GetItem") { + qCDebug(dcKodi) << "Played item received" << response; + emit mediaMetaDataReceived(response.value("result").toMap()); + return; + } + + if (method == "Player.SetShuffle" || method == "Player.SetRepeat") { + updatePlayerProperties(); + } + + emit actionExecuted(id, true); + + qCDebug(dcKodi()) << "unhandled reply" << method << response; +} + +void Kodi::updatePlayerProperties() +{ + QVariantMap params; + params.insert("playerid", m_activePlayer); + QVariantList properties; + properties << "speed" << "shuffled" << "repeat"; + params.insert("properties", properties); + m_jsonHandler->sendData("Player.GetProperties", params); +} + +void Kodi::updateMetadata() +{ + QVariantMap params; + params.insert("playerid", m_activePlayer); + QVariantList fields; + fields << "title" << "artist" << "album" << "director" << "thumbnail" << "showtitle" << "fanart" << "channel" << "year"; + params.insert("properties", fields); + m_jsonHandler->sendData("Player.GetItem", params); } diff --git a/kodi/kodi.h b/kodi/kodi.h index 96792334..46db56d9 100644 --- a/kodi/kodi.h +++ b/kodi/kodi.h @@ -34,7 +34,7 @@ class Kodi : public QObject Q_OBJECT public: - explicit Kodi(const QHostAddress &hostAddress, const int &port = 9090, QObject *parent = 0); + explicit Kodi(const QHostAddress &hostAddress, const int &port = 9090, QObject *parent = nullptr); QHostAddress hostAddress() const; int port() const; @@ -42,18 +42,21 @@ public: bool connected() const; // propertys - void setMuted(const bool &muted, const ActionId &actionId); + int setMuted(const bool &muted); bool muted() const; - void setVolume(const int &volume, const ActionId &actionId); + int setVolume(const int &volume); int volume() const; + int setShuffle(bool shuffle); + int setRepeat(const QString &repeat); + // actions - void showNotification(const QString &message, const int &displayTime, const QString ¬ificationType, const ActionId &actionId); - void pressButton(const QString &button, const ActionId &actionId); - void systemCommand(const QString &command, const ActionId &actionId); - void videoLibrary(const QString &command, const ActionId &actionId); - void audioLibrary(const QString &command, const ActionId &actionId); + int showNotification(const QString &message, const int &displayTime, const QString ¬ificationType); + int pressButton(const QString &button); + int systemCommand(const QString &command); + int videoLibrary(const QString &command); + int audioLibrary(const QString &command); void update(); void checkVersion(); @@ -61,26 +64,40 @@ public: void connectKodi(); void disconnectKodi(); -private: - KodiConnection *m_connection; - 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) - signals: void connectionStatusChanged(); void stateChanged(); - void actionExecuted(const ActionId &actionId, const bool &success); + void activePlayerChanged(const QString &playerType); + void actionExecuted(int actionId, const 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); private slots: void onVolumeChanged(const int &volume, const bool &muted); void onUpdateFinished(const QVariantMap &data); void activePlayersChanged(const QVariantList &data); void playerPropertiesReceived(const QVariantMap &properties); + void mediaMetaDataReceived(const QVariantMap &data); + void onPlaybackStatusChanged(const QString &plabackState); + + void processNotification(const QString &method, const QVariantMap ¶ms); + void processResponse(int id, const QString &method, const QVariantMap &response); + + void updatePlayerProperties(); + void updateMetadata(); + +private: + KodiConnection *m_connection; + 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; + }; #endif // KODI_H diff --git a/kodi/kodiconnection.h b/kodi/kodiconnection.h index 3ab0769e..cb633c48 100644 --- a/kodi/kodiconnection.h +++ b/kodi/kodiconnection.h @@ -32,7 +32,7 @@ class KodiConnection : public QObject { Q_OBJECT public: - explicit KodiConnection(const QHostAddress &hostAddress, const int &port = 9090, QObject *parent = 0); + explicit KodiConnection(const QHostAddress &hostAddress, const int &port = 9090, QObject *parent = nullptr); void connectKodi(); void disconnectKodi(); @@ -62,6 +62,8 @@ signals: public slots: void sendData(const QByteArray &message); + + }; #endif // KODICONNECTION_H diff --git a/kodi/kodijsonhandler.cpp b/kodi/kodijsonhandler.cpp index a510fdb1..3ad6944b 100644 --- a/kodi/kodijsonhandler.cpp +++ b/kodi/kodijsonhandler.cpp @@ -33,82 +33,22 @@ KodiJsonHandler::KodiJsonHandler(KodiConnection *connection, QObject *parent) : connect(m_connection, &KodiConnection::dataReady, this, &KodiJsonHandler::processResponse); } -void KodiJsonHandler::sendData(const QString &method, const QVariantMap ¶ms, const ActionId &actionId) +int KodiJsonHandler::sendData(const QString &method, const QVariantMap ¶ms) { + m_id++; + QVariantMap package; package.insert("id", m_id); package.insert("method", method); package.insert("params", params); package.insert("jsonrpc", "2.0"); - m_replys.insert(m_id, KodiReply(method, params, actionId)); + m_replys.insert(m_id, KodiReply(method, params)); QJsonDocument jsonDoc = QJsonDocument::fromVariant(package); m_connection->sendData(jsonDoc.toJson()); //qCDebug(dcKodi) << "sending data" << jsonDoc.toJson(); - m_id++; -} - -void KodiJsonHandler::processNotification(const QString &method, const QVariantMap ¶ms) -{ - qCDebug(dcKodi) << "got notification" << method; - - if (method == "Application.OnVolumeChanged") { - QVariantMap data = params.value("data").toMap(); - emit volumeChanged(data.value("volume").toInt(), data.value("muted").toBool()); - } else if (method == "Player.OnPlay" || method == "Player.OnResume") { - emit playbackStatusChanged("Playing"); - } else if (method == "Player.OnPause") { - emit playbackStatusChanged("Paused"); - } else if (method == "Player.OnStop") { - emit playbackStatusChanged("Stopped"); - } -} - -void KodiJsonHandler::processActionResponse(const KodiReply &reply, const QVariantMap &response) -{ - if (response.contains("error")) { - //qCDebug(dcKodi) << QJsonDocument::fromVariant(response).toJson(); - qCWarning(dcKodi) << "got error response for action" << reply.method() << ":" << response.value("error").toMap().value("message").toString(); - emit actionExecuted(reply.actionId(), false); - } else { - emit actionExecuted(reply.actionId(), true); - } -} - -void KodiJsonHandler::processRequestResponse(const KodiReply &reply, const QVariantMap &response) -{ - if (response.contains("error")) { - //qCDebug(dcKodi) << QJsonDocument::fromVariant(response).toJson(); - qCWarning(dcKodi) << "got error response for request " << reply.method() << ":" << response.value("error").toMap().value("message").toString(); - return; - } - - if (reply.method() == "Application.GetProperties") { - //qCDebug(dcKodi) << "got update response" << reply.method(); - emit updateDataReceived(response.value("result").toMap()); - return; - } - - if (reply.method() == "JSONRPC.Version") { - qCDebug(dcKodi) << "got version response" << reply.method(); - emit versionDataReceived(response.value("result").toMap()); - return; - } - - if (reply.method() == "Player.GetActivePlayers") { - qCDebug(dcKodi) << "Active players changed" << response; - emit activePlayersChanged(response.value("result").toList()); - return; - } - - if (reply.method() == "Player.GetProperties") { - qCDebug(dcKodi) << "Player properties received" << response; - emit playerPropertiesReveived(response.value("result").toMap()); - return; - } - - qCDebug(dcKodi()) << "unhandled reply" << reply.method() << response; + return m_id; } void KodiJsonHandler::processResponse(const QByteArray &data) @@ -138,18 +78,12 @@ void KodiJsonHandler::processResponse(const QByteArray &data) qCWarning(dcKodi) << "method missing in message" << data; } - processNotification(message.value("method").toString(), message.value("params").toMap()); + emit notificationReceived(message.value("method").toString(), message.value("params").toMap()); return; } int id = message.value("id").toInt(); KodiReply reply = m_replys.take(id); - // check if this message is a response to an action call - if (reply.actionId() != ActionId()) { - processActionResponse(reply, message); - return; - } - - processRequestResponse(reply, message); + emit replyReceived(id, reply.method(), message); } diff --git a/kodi/kodijsonhandler.h b/kodi/kodijsonhandler.h index 1bde5cd5..947b20a8 100644 --- a/kodi/kodijsonhandler.h +++ b/kodi/kodijsonhandler.h @@ -35,32 +35,22 @@ class KodiJsonHandler : public QObject { Q_OBJECT public: - explicit KodiJsonHandler(KodiConnection *connection = 0, QObject *parent = 0); + explicit KodiJsonHandler(KodiConnection *connection, QObject *parent = nullptr); - void sendData(const QString &method, const QVariantMap ¶ms, const ActionId &actionId); + int sendData(const QString &method, const QVariantMap ¶ms); + +signals: + void notificationReceived(const QString &method, const QVariantMap ¶ms); + void replyReceived(int id, const QString &method, const QVariantMap ¶ms); + +private slots: + void processResponse(const QByteArray &data); private: KodiConnection *m_connection; int m_id; QHash m_replys; - void processNotification(const QString &method, const QVariantMap ¶ms); - void processActionResponse(const KodiReply &reply, const QVariantMap &response); - void processRequestResponse(const KodiReply &reply, const QVariantMap &response); - -signals: - void volumeChanged(const int &volume, const bool &muted); - void actionExecuted(const ActionId &actionId, const bool &success); - void updateDataReceived(const QVariantMap &data); - void versionDataReceived(const QVariantMap &data); - void activePlayersChanged(const QVariantList &data); - void playerPropertiesReveived(const QVariantMap &properties); - - void playbackStatusChanged(const QString &playbackStatus); - -private slots: - void processResponse(const QByteArray &data); - }; #endif // KODIJSONHANDLER_H diff --git a/kodi/kodireply.cpp b/kodi/kodireply.cpp index 2f6c9f91..eb7b12fe 100644 --- a/kodi/kodireply.cpp +++ b/kodi/kodireply.cpp @@ -26,23 +26,12 @@ KodiReply::KodiReply() { } -KodiReply::KodiReply(const QString &method, const QVariantMap ¶ms, const ActionId &actionId) : +KodiReply::KodiReply(const QString &method, const QVariantMap ¶ms) : m_method(method), - m_params(params), - m_actionId(actionId) + m_params(params) { } -void KodiReply::setActionId(const ActionId &actionId) -{ - m_actionId = actionId; -} - -ActionId KodiReply::actionId() const -{ - return m_actionId; -} - void KodiReply::setMethod(const QString &method) { m_method = method; diff --git a/kodi/kodireply.h b/kodi/kodireply.h index 1a921325..ac79d1ce 100644 --- a/kodi/kodireply.h +++ b/kodi/kodireply.h @@ -32,10 +32,7 @@ class KodiReply { public: KodiReply(); - KodiReply(const QString &method, const QVariantMap ¶ms, const ActionId &actionId = ActionId()); - - void setActionId(const ActionId &actionId); - ActionId actionId() const; + KodiReply(const QString &method, const QVariantMap ¶ms); void setMethod(const QString &method); QString method() const; @@ -46,8 +43,6 @@ public: private: QString m_method; QVariantMap m_params; - ActionId m_actionId; - }; #endif // KODIREPLY_H