From 811349c4447fcca70a6182835747fa9fc408b94e Mon Sep 17 00:00:00 2001 From: nymea Date: Tue, 8 Oct 2019 00:38:48 +0200 Subject: [PATCH] improved browsing on TuneIn --- denon/heos.cpp | 109 ++++++++++++++------- denon/heos.h | 7 +- denon/heostypes.h | 7 ++ denon/integrationplugindenon.cpp | 156 +++++++++++++++++++++---------- denon/integrationplugindenon.h | 9 +- 5 files changed, 199 insertions(+), 89 deletions(-) diff --git a/denon/heos.cpp b/denon/heos.cpp index 6ba065b1..81ff604d 100644 --- a/denon/heos.cpp +++ b/denon/heos.cpp @@ -404,7 +404,7 @@ void Heos::browseSourceContainers(const QString &sourceId, const QString &contai queryParams.addQueryItem("cid", containerId); cmd.append(queryParams.toString()); cmd.append("\r\n"); - qCDebug(dcDenon) << "playing station:" << cmd; + qCDebug(dcDenon) << "Browsing container:" << cmd; m_socket->write(cmd); } @@ -459,7 +459,19 @@ void Heos::playUrl(int playerId, const QUrl &mediaUrl) m_socket->write(cmd); } - +void Heos::addContainerToQueue(int playerId, const QString &sourceId, const QString &containerId, ADD_CRITERIA addCriteria) +{ + QByteArray cmd("heos://browse/add_to_queue?"); + QUrlQuery queryParams; + queryParams.addQueryItem("pid", QString::number(playerId)); + queryParams.addQueryItem("sid", sourceId); + queryParams.addQueryItem("cid", containerId); + queryParams.addQueryItem("aid", QString::number(addCriteria)); + cmd.append(queryParams.toString()); + cmd.append("\r\n"); + qCDebug(dcDenon) << "Adding to queue:" << cmd; + m_socket->write(cmd); +} void Heos::onConnected() { @@ -498,9 +510,10 @@ void Heos::readData() if (dataMap.contains("heos")) { QString command = dataMap.value("heos").toMap().value("command").toString(); QUrlQuery message(dataMap.value("heos").toMap().value("message").toString()); + bool success = false; if(dataMap.value("heos").toMap().contains("result")) { //If the message doesn't contain result it is an event message - bool success = dataMap.value("heos").toMap().value("result").toString().contains("success"); + success = dataMap.value("heos").toMap().value("result").toString().contains("success"); if (!success) { qDebug(dcDenon()) << "Command:" << command << "was not successfull. Message:" << message.toString(); } @@ -540,7 +553,8 @@ void Heos::readData() } } - /* 4.2 Player Commands 4.2.1 Get Players + /* 4.2 Player Commands + * 4.2.1 Get Players * 4.2.2 Get Player Info * 4.2.3 Get Play State * 4.2.4 Set Play State @@ -734,28 +748,8 @@ void Heos::readData() qDebug(dcDenon()) << "Get music source request response received" << command; QVariantList payloadVariantList = jsonDoc.toVariant().toMap().value("payload").toList(); QList musicSources; - - foreach (const QVariant &payloadEntryVariant, payloadVariantList) { - MusicSourceObject source; - source.name = payloadEntryVariant.toMap().value("name").toString(); - source.image_url = payloadEntryVariant.toMap().value("image_url").toString(); - source.type = payloadEntryVariant.toMap().value("type").toString(); - source.sourceId = payloadEntryVariant.toMap().value("sid").toInt(); - source.available = payloadEntryVariant.toMap().value("available").toString().contains("true"); - source.serviceUsername = payloadEntryVariant.toMap().value("service_username").toString(); - musicSources.append(source); - } - emit musicSourcesReceived(musicSources); - - } else if (command.contains("browse/browse")) { - QVariantList payloadVariantList = jsonDoc.toVariant().toMap().value("payload").toList(); - - QList musicSources; - QList mediaItems; - - foreach (const QVariant &payloadEntryVariant, payloadVariantList) { - QString type = payloadEntryVariant.toMap().value("type").toString(); - if (type == "source") { + if (success) { + foreach (const QVariant &payloadEntryVariant, payloadVariantList) { MusicSourceObject source; source.name = payloadEntryVariant.toMap().value("name").toString(); source.image_url = payloadEntryVariant.toMap().value("image_url").toString(); @@ -764,14 +758,65 @@ void Heos::readData() source.available = payloadEntryVariant.toMap().value("available").toString().contains("true"); source.serviceUsername = payloadEntryVariant.toMap().value("service_username").toString(); musicSources.append(source); - } else if (type == "container" || type == "album" || type == "song") { - MediaObject media; - media.name = payloadEntryVariant.toMap().value("name").toString(); - media.imageUrl = payloadEntryVariant.toMap().value("image_url").toString(); - mediaItems.append(media); } - emit browseRequestReceived(musicSources, mediaItems); + emit musicSourcesReceived(musicSources); + } + } else if (command.contains("browse/browse")) { + QVariantList payloadVariantList = jsonDoc.toVariant().toMap().value("payload").toList(); + QString sourceId = message.queryItemValue("sid"); + QString containerId = message.queryItemValue("cid"); + + if (message.toString().contains("command under process")){ + qDebug(dcDenon()) << "Browse command is beeing processed"; + return; + } + if (success) { + QList musicSources; + QList mediaItems; + foreach (const QVariant &payloadEntryVariant, payloadVariantList) { + QString type = payloadEntryVariant.toMap().value("type").toString(); + if (type == "source") { + MusicSourceObject source; + source.name = payloadEntryVariant.toMap().value("name").toString(); + source.image_url = payloadEntryVariant.toMap().value("image_url").toString(); + source.type = payloadEntryVariant.toMap().value("type").toString(); + source.sourceId = payloadEntryVariant.toMap().value("sid").toInt(); + qDebug(dcDenon()) << "Source" << source.name << source.type << source.sourceId << payloadEntryVariant.toMap().value("sid"); + //source.available = payloadEntryVariant.toMap().value("available").toString().contains("true"); + //source.serviceUsername = payloadEntryVariant.toMap().value("service_username").toString(); + musicSources.append(source); + } else { + MediaObject media; + qDebug(dcDenon()) << "Media Item" << payloadEntryVariant.toMap().value("mid").toString() << payloadEntryVariant.toMap().value("cid").toString(); + media.name = payloadEntryVariant.toMap().value("name").toString(); + media.containerId = payloadEntryVariant.toMap().value("cid").toString(); + media.mediaId = payloadEntryVariant.toMap().value("mid").toString(); + media.imageUrl = payloadEntryVariant.toMap().value("image_url").toString(); + media.isPlayable = payloadEntryVariant.toMap().value("playable").toString().contains("yes"); + media.isContainer = payloadEntryVariant.toMap().value("container").toString().contains("yes"); + if (type == "artist") { + media.mediaType = MEDIA_TYPE_ARTIST; + } else if (type == "song") { + media.mediaType = MEDIA_TYPE_SONG; + } else if (type == "genre") { + media.mediaType = MEDIA_TYPE_GENRE; + } else if (type == "station") { + media.mediaType = MEDIA_TYPE_STATION; + } else if (type == "album") { + media.mediaType = MEDIA_TYPE_ALBUM; + } else if (type == "container") { + media.mediaType = MEDIA_TYPE_CONTAINER; + } + mediaItems.append(media); + } + } + emit browseRequestReceived(sourceId, containerId, musicSources, mediaItems); + } + else { + int errorId = message.queryItemValue("eid").toInt(); + QString text = message.queryItemValue("text"); + emit browseErrorReceived(sourceId, containerId, errorId, text); } } else if (command.contains("play_preset")) { diff --git a/denon/heos.h b/denon/heos.h index b789301d..85dc27a5 100644 --- a/denon/heos.h +++ b/denon/heos.h @@ -108,6 +108,7 @@ public: void playPresetStation(int playerId, int presetNumber); void playInputSource(int playerId, const QString &inputName); //Validity of Inputs depends on the type of source HEOS devic void playUrl(int playerId, const QUrl &url); + void addContainerToQueue(int playerId, const QString &sourceId, const QString &containerId, ADD_CRITERIA addCriteria); private: bool m_eventRegistered = false; @@ -139,10 +140,10 @@ signals: void sourcesChanged(); void nowPlayingMediaStatusReceived(int playerId, SOURCE_ID source, QString artist, QString album, QString Song, QString artwork); - void musicSourcesReceived(QList musicSources); - void mediaItemsReceived(QList mediaItems); - void browseRequestReceived(QList musicSources, QList mediaItems); + void musicSourcesReceived(QList musicSources); //callback of getMusicSource, not associated to a playerId + void browseRequestReceived(const QString &sourceId, const QString &containerId, QList musicSources, QList mediaItems); //callback of browseSource + void browseErrorReceived(const QString &sourceId, const QString &containerId, int errorId, const QString &errorMessage); void userChanged(bool signedIn, const QString &userName); private slots: diff --git a/denon/heostypes.h b/denon/heostypes.h index 963ff041..379b723b 100644 --- a/denon/heostypes.h +++ b/denon/heostypes.h @@ -93,6 +93,13 @@ enum SEARCH_CRITERIA { // criteria id returned by 'get_search_criteria' com SEARCH_CRITERIA_STATION }; +enum ADD_CRITERIA { + ADD_CRITERIA_PLAY_NOW = 1, + ADD_CRITERIA_PLAY_NEXT, + ADD_CRITERIA_ADD_TO_END, + ADD_CRITERIA_REPLACE_AND_PLAY +}; + enum MEDIA_TYPE { MEDIA_TYPE_SONG, MEDIA_TYPE_STATION, diff --git a/denon/integrationplugindenon.cpp b/denon/integrationplugindenon.cpp index 0f11e96e..ae021b70 100644 --- a/denon/integrationplugindenon.cpp +++ b/denon/integrationplugindenon.cpp @@ -211,6 +211,7 @@ void IntegrationPluginDenon::setupThing(ThingSetupInfo *info) QHostAddress address(thing->paramValue(heosThingIpParamTypeId).toString()); Heos *heos = new Heos(address, this); +<<<<<<< HEAD:denon/integrationplugindenon.cpp connect(heos, &Heos::connectionStatusChanged, this, &IntegrationPluginDenon::onHeosConnectionChanged); connect(heos, &Heos::playerDiscovered, this, &IntegrationPluginDenon::onHeosPlayerDiscovered); @@ -225,6 +226,22 @@ void IntegrationPluginDenon::setupThing(ThingSetupInfo *info) connect(heos, &Heos::browseRequestReceived, this, &IntegrationPluginDenon::onHeosBrowseRequestReceived); m_heos.insert(thing->id(), heos); +======= + connect(heos, &Heos::connectionStatusChanged, this, &DevicePluginDenon::onHeosConnectionChanged); + connect(heos, &Heos::playersChanged, this, &DevicePluginDenon::onHeosPlayersChanged); + connect(heos, &Heos::playerDiscovered, this, &DevicePluginDenon::onHeosPlayerDiscovered); + connect(heos, &Heos::playerPlayStateReceived, this, &DevicePluginDenon::onHeosPlayStateReceived); + connect(heos, &Heos::playerRepeatModeReceived, this, &DevicePluginDenon::onHeosRepeatModeReceived); + connect(heos, &Heos::playerShuffleModeReceived, this, &DevicePluginDenon::onHeosShuffleModeReceived); + connect(heos, &Heos::playerMuteStatusReceived, this, &DevicePluginDenon::onHeosMuteStatusReceived); + connect(heos, &Heos::playerVolumeReceived, this, &DevicePluginDenon::onHeosVolumeStatusReceived); + connect(heos, &Heos::nowPlayingMediaStatusReceived, this, &DevicePluginDenon::onHeosNowPlayingMediaStatusReceived); + connect(heos, &Heos::musicSourcesReceived, this, &DevicePluginDenon::onHeosMusicSourcesReceived); + connect(heos, &Heos::browseRequestReceived, this, &DevicePluginDenon::onHeosBrowseRequestReceived); + connect(heos, &Heos::browseErrorReceived, this, &DevicePluginDenon::onHeosBrowseErrorReceived); + + m_heos.insert(device->id(), heos); +>>>>>>> improved browsing on TuneIn:denon/deviceplugindenon.cpp m_asyncHeosSetups.insert(heos, info); // In case the setup is cancelled before we finish it... connect(info, &QObject::destroyed, this, [this, info, heos]() { m_asyncHeosSetups.remove(heos); }); @@ -590,8 +607,13 @@ void IntegrationPluginDenon::onHeosConnectionChanged(bool status) } } -void IntegrationPluginDenon::onHeosPlayerDiscovered(HeosPlayer *heosPlayer) { +void DevicePluginDenon::onHeosPlayersChanged() +{ + Heos *heos = static_cast(sender()); + heos->getPlayers(); +} +void IntegrationPluginDenon::onHeosPlayerDiscovered(HeosPlayer *heosPlayer) { Heos *heos = static_cast(sender()); Thing *thing = myThings().findById(m_heos.key(heos)); @@ -756,13 +778,12 @@ void IntegrationPluginDenon::onHeosNowPlayingMediaStatusReceived(int playerId, S void IntegrationPluginDenon::onHeosMusicSourcesReceived(QList musicSources) { Heos *heos = static_cast(sender()); - if (m_pendingBrowseResult.contains(heos)) { - BrowseResult *result = m_pendingBrowseResult.take(heos); + if (m_pendingGetSourcesRequest.contains(heos)) { + BrowseResult *result = m_pendingGetSourcesRequest.take(heos); foreach(MusicSourceObject source, musicSources) { BrowserItem item; item.setDisplayName(source.name); - //item.setDescription("test"); - item.setId(QString::number(source.sourceId)); + item.setId("source=" + QString::number(source.sourceId)); item.setThumbnail(source.image_url); item.setExecutable(false); item.setBrowsable(true); @@ -774,24 +795,20 @@ void IntegrationPluginDenon::onHeosMusicSourcesReceived(QList } void IntegrationPluginDenon::onHeosMediaItemsReceived(QList mediaItems) + +void DevicePluginDenon::onHeosBrowseRequestReceived(const QString &sourceId, const QString &containerId, QList musicSources, QList mediaItems) { - Heos *heos = static_cast(sender()); - if (m_pendingBrowseResult.contains(heos)) { - BrowseResult *result = m_pendingBrowseResult.take(heos); - foreach(MediaObject media, mediaItems) { - BrowserItem item; - item.setDisplayName(media.name); - //item.setDescription("test"); - item.setId(media.mediaId); - item.setThumbnail(media.imageUrl); - item.setExecutable(media.isPlayable); - item.setBrowsable(media.isContainer); - result->addItem(item); - qDebug(dcDenon()) << "Media received:" << media.name << media.mediaType << media.mediaId << media.imageUrl; - } - result->finish(Device::DeviceErrorNoError); + QString identifier; + if (containerId.isEmpty()) { + identifier = sourceId; + } else { + identifier = containerId; } -}*/ + if (QUrl(identifier).isValid()) { + identifier = QUrl::fromPercentEncoding(identifier.toUtf8()); + } +} + void IntegrationPluginDenon::onHeosBrowseRequestReceived(QList musicSources, QList mediaItems) { @@ -801,26 +818,49 @@ void IntegrationPluginDenon::onHeosBrowseRequestReceived(QListaddItem(item); - qDebug(dcDenon()) << "Media received:" << media.name << media.mediaType << media.mediaId << media.imageUrl; } foreach(MusicSourceObject source, musicSources) { BrowserItem item; item.setDisplayName(source.name); + qDebug(dcDenon()) << "Adding Item" << source.name << source.sourceId; //item.setDescription("test"); - item.setId(QString::number(source.sourceId)); + item.setId("source=" + QString::number(source.sourceId)); item.setThumbnail(source.image_url); - item.setExecutable(true); + item.setExecutable(false); item.setBrowsable(true); result->addItem(item); } result->finish(Device::DeviceErrorNoError); + } else { + qWarning(dcDenon()) << "Pending browser result doesnt recognize" << identifier << m_pendingBrowseResult.keys(); + } +} + +void IntegrationPluginDenon::onHeosBrowseErrorReceived(const QString &sourceId, const QString &containerId, int errorId, const QString &errorMessage) +{ + QString identifier; + if (containerId.isEmpty()) { + identifier = sourceId; + } else { + identifier = containerId; + } + + if (m_pendingBrowseResult.contains(identifier)) { + BrowseResult *result = m_pendingBrowseResult.take(identifier); + qWarning(dcDenon) << "Browse error" << errorMessage << errorId; + result->finish(Device::DeviceErrorHardwareFailure); } } @@ -851,6 +891,7 @@ void IntegrationPluginDenon::onPluginConfigurationChanged(const ParamTypeId &par } } + void IntegrationPluginDenon::browseDevice(BrowseResult *result) { Heos *heos = m_heos.value(result->device()->parentId()); @@ -858,16 +899,30 @@ void IntegrationPluginDenon::browseDevice(BrowseResult *result) result->finish(Device::DeviceErrorHardwareNotAvailable); return; } - qDebug(dcDenon()) << "Browse device" << result->itemId() << result->locale(); - QUuid requestId; if (result->itemId().isEmpty()) { - requestId = heos->getMusicSources(); - } else { - requestId = heos->browseSource(result->itemId()); + qDebug(dcDenon()) << "Browse source"; + heos->getMusicSources(); + m_pendingGetSourcesRequest.insert(heos, result); + //connect(result, &QObject::destroyed, this, [this, result->itemId()](){ m_pendingBrowseResult.remove(result->itemId());}); + } else if (result->itemId().startsWith("source=")){ + qDebug(dcDenon()) << "Browse source" << result->itemId(); + QString id = result->itemId().remove("source="); + heos->browseSource(id); + m_pendingBrowseResult.insert(id, result); + //connect(result, &QObject::destroyed, this, [this, result->itemId()](){ m_pendingBrowseResult.remove(result->itemId());}); + } else if (result->itemId().startsWith("container=")){ + qDebug(dcDenon()) << "Browse container" << result->itemId(); + QStringList values = result->itemId().split("&"); + if (values.length() == 2) { + QString id = values[0].remove("container="); + heos->browseSourceContainers(values[1], id); + if (QUrl(id).isValid()) { + id = QUrl::fromPercentEncoding(id.toUtf8()); + } + m_pendingBrowseResult.insert(id, result); + } } - m_pendingBrowseResult.insert(requestId, result); - connect(result, &QObject::destroyed, this, [this, requestId](){ m_pendingBrowseResult.remove(requestId);}); } void IntegrationPluginDenon::browserItem(BrowserItemResult *result) @@ -877,7 +932,6 @@ void IntegrationPluginDenon::browserItem(BrowserItemResult *result) result->finish(Device::DeviceErrorHardwareNotAvailable); return; } - qDebug(dcDenon()) << "Browse item called"; return; } @@ -889,16 +943,22 @@ void IntegrationPluginDenon::executeBrowserItem(BrowserActionInfo *info) info->finish(Device::DeviceErrorHardwareNotAvailable); return; } - qDebug(dcDenon()) << "Execute browse item called"; - return; - - /* - int id = kodi->launchBrowserItem(info->browserAction().itemId()); - if (id == -1) { - return info->finish(Device::DeviceErrorHardwareFailure); + /* BrowserAction action = info->browserAction(); + int playerId = info->device()->paramValue(heosPlayerDevicePlayerIdParamTypeId).toInt(); + if (action.itemId()) { + heos->playUrl(playerId, action.itemId()); + } else { + heos->playPresetStation(playerId, presetNumber); } - m_pendingBrowserActions.insert(id, info); - connect(info, &QObject::destroyed, this, [this, id](){ m_pendingBrowserActions.remove(id); });*/ + heos->playInputSource(playerId, inputName); +*/ + + + + + qDebug(dcDenon()) << "Execute browse item called"; + info->finish(Device::DeviceErrorNoError); + return; } void IntegrationPluginDenon::executeBrowserItemAction(BrowserItemActionInfo *info) @@ -908,12 +968,6 @@ void IntegrationPluginDenon::executeBrowserItemAction(BrowserItemActionInfo *inf info->finish(Device::DeviceErrorHardwareNotAvailable); return; } - - - /*int id = kodi->executeBrowserItemAction(info->browserItemAction().itemId(), info->browserItemAction().actionTypeId()); - if (id == -1) { - return info->finish(Device::DeviceErrorHardwareFailure); - } - m_pendingBrowserItemActions.insert(id, info); - connect(info, &QObject::destroyed, this, [this, id](){ m_pendingBrowserItemActions.remove(id); });*/ + qDebug(dcDenon()) << "Execute browse item action called"; + return; } diff --git a/denon/integrationplugindenon.h b/denon/integrationplugindenon.h index 902a5e4d..05572aaf 100644 --- a/denon/integrationplugindenon.h +++ b/denon/integrationplugindenon.h @@ -84,8 +84,9 @@ private: QHash m_asyncActions; QUrl m_notificationUrl; - QHash m_pendingBrowseResult; QHash m_pendingActions; + QHash m_pendingGetSourcesRequest; + QHash m_pendingBrowseResult; // QString = containerId or sourceId QHash m_pendingBrowserActions; QHash m_pendingBrowserItemActions; @@ -93,6 +94,7 @@ private slots: void onPluginTimer(); void onHeosConnectionChanged(bool status); + void onHeosPlayersChanged(); void onHeosPlayerDiscovered(HeosPlayer *heosPlayer); void onHeosPlayStateReceived(int playerId, PLAYER_STATE state); void onHeosShuffleModeReceived(int playerId, bool shuffle); @@ -100,9 +102,10 @@ private slots: void onHeosMuteStatusReceived(int playerId, bool mute); void onHeosVolumeStatusReceived(int playerId, int volume); void onHeosNowPlayingMediaStatusReceived(int playerId, SOURCE_ID source, QString artist, QString album, QString Song, QString artwork); - //void onHeosMusicSourcesReceived(QList musicSources); + void onHeosMusicSourcesReceived(QList musicSources); //void onHeosMediaItemsReceived(QList mediaItems); - void onHeosBrowseRequestReceived(QList musicSources, QList mediaItems); + void onHeosBrowseRequestReceived(const QString &sourceId, const QString &containerId, QList musicSources, QList mediaItems); + void onHeosBrowseErrorReceived(const QString &sourceId, const QString &containerId, int errorId, const QString &errorMessage); void onHeosPlayerNowPlayingChanged(int playerId); void onAvahiServiceEntryAdded(const ZeroConfServiceEntry &serviceEntry);