diff --git a/sonos/devicepluginsonos.cpp b/sonos/devicepluginsonos.cpp index 15388364..16c0ce99 100644 --- a/sonos/devicepluginsonos.cpp +++ b/sonos/devicepluginsonos.cpp @@ -48,44 +48,6 @@ void DevicePluginSonos::setupDevice(DeviceSetupInfo *info) { Device *device = info->device(); - if (!m_pluginTimer5sec) { - m_pluginTimer5sec = hardwareManager()->pluginTimerManager()->registerTimer(5); - connect(m_pluginTimer5sec, &PluginTimer::timeout, this, [this]() { - - foreach (Device *connectionDevice, myDevices().filterByDeviceClassId(sonosConnectionDeviceClassId)) { - Sonos *sonos = m_sonosConnections.value(connectionDevice); - if (!sonos) { - qWarning(dcSonos()) << "No sonos connection found to device" << connectionDevice->name(); - continue; - } - foreach (Device *groupDevice, myDevices().filterByParentDeviceId(connectionDevice->id())) { - if (groupDevice->deviceClassId() == sonosGroupDeviceClassId) { - //get playback status of each group - QString groupId = groupDevice->paramValue(sonosGroupDeviceGroupIdParamTypeId).toString(); - sonos->getGroupPlaybackStatus(groupId); - sonos->getGroupMetadataStatus(groupId); - sonos->getGroupVolume(groupId); - } - } - } - }); - } - - if (!m_pluginTimer60sec) { - m_pluginTimer60sec = hardwareManager()->pluginTimerManager()->registerTimer(60); - connect(m_pluginTimer60sec, &PluginTimer::timeout, this, [this]() { - foreach (Device *device, myDevices().filterByDeviceClassId(sonosConnectionDeviceClassId)) { - Sonos *sonos = m_sonosConnections.value(device); - if (!sonos) { - qWarning(dcSonos()) << "No sonos connection found to device" << device->name(); - continue; - } - //get groups for each household in order to add or remove groups - sonos->getHouseholds(); - } - }); - } - if (device->deviceClassId() == sonosConnectionDeviceClassId) { Sonos *sonos; if (m_setupSonosConnections.keys().contains(device->id())) { @@ -126,6 +88,7 @@ void DevicePluginSonos::setupDevice(DeviceSetupInfo *info) connect(sonos, &Sonos::volumeReceived, this, &DevicePluginSonos::onVolumeReceived); connect(sonos, &Sonos::actionExecuted, this, &DevicePluginSonos::onActionExecuted); connect(sonos, &Sonos::authenticationStatusChanged, this, &DevicePluginSonos::onAuthenticationStatusChanged); + connect(sonos, &Sonos::favouritesReceived, this, &DevicePluginSonos::onFavouritesReceived); sonos->getAccessTokenFromRefreshToken(refreshToken); m_sonosConnections.insert(device, sonos); return info->finish(Device::DeviceErrorNoError); @@ -204,6 +167,44 @@ void DevicePluginSonos::confirmPairing(DevicePairingInfo *info, const QString &u void DevicePluginSonos::postSetupDevice(Device *device) { + if (!m_pluginTimer5sec) { + m_pluginTimer5sec = hardwareManager()->pluginTimerManager()->registerTimer(5); + connect(m_pluginTimer5sec, &PluginTimer::timeout, this, [this]() { + + foreach (Device *connectionDevice, myDevices().filterByDeviceClassId(sonosConnectionDeviceClassId)) { + Sonos *sonos = m_sonosConnections.value(connectionDevice); + if (!sonos) { + qWarning(dcSonos()) << "No sonos connection found to device" << connectionDevice->name(); + continue; + } + foreach (Device *groupDevice, myDevices().filterByParentDeviceId(connectionDevice->id())) { + if (groupDevice->deviceClassId() == sonosGroupDeviceClassId) { + //get playback status of each group + QString groupId = groupDevice->paramValue(sonosGroupDeviceGroupIdParamTypeId).toString(); + sonos->getGroupPlaybackStatus(groupId); + sonos->getGroupMetadataStatus(groupId); + sonos->getGroupVolume(groupId); + } + } + } + }); + } + + if (!m_pluginTimer60sec) { + m_pluginTimer60sec = hardwareManager()->pluginTimerManager()->registerTimer(60); + connect(m_pluginTimer60sec, &PluginTimer::timeout, this, [this]() { + foreach (Device *device, myDevices().filterByDeviceClassId(sonosConnectionDeviceClassId)) { + Sonos *sonos = m_sonosConnections.value(device); + if (!sonos) { + qWarning(dcSonos()) << "No sonos connection found to device" << device->name(); + continue; + } + //get groups for each household in order to add or remove groups + sonos->getHouseholds(); + } + }); + } + if (device->deviceClassId() == sonosConnectionDeviceClassId) { Sonos *sonos = m_sonosConnections.value(device); sonos->getHouseholds(); @@ -331,6 +332,55 @@ void DevicePluginSonos::executeAction(DeviceActionInfo *info) info->finish(Device::DeviceErrorDeviceClassNotFound); } +void DevicePluginSonos::browseDevice(BrowseResult *result) +{ + Device *parentDevice = myDevices().findById(result->device()->parentId()); + Sonos *sonosConnection = m_sonosConnections.value(parentDevice); + if (!sonosConnection) + return; + + qDebug(dcSonos()) << "Browse Device" << result->itemId(); + QString householdId = result->device()->paramValue(sonosGroupDeviceHouseholdIdParamTypeId).toString(); + if (result->itemId().isEmpty()){ + BrowserItem item; + item.setId("favorites"); + item.setIcon(BrowserItem::BrowserIconFavorites); + item.setExecutable(false); + item.setBrowsable(true); + item.setDisplayName("Favorites"); + result->addItem(item); + result->finish(Device::DeviceErrorNoError); + } else if (result->itemId() == "favorites") { + sonosConnection->getFavorites(householdId); + m_pendingBrowseResult.insert(householdId, result); + } else { + //TODO add media browsing + result->finish(Device::DeviceErrorItemNotFound); + } +} + +void DevicePluginSonos::browserItem(BrowserItemResult *result) +{ + Q_UNUSED(result) +} + +void DevicePluginSonos::executeBrowserItem(BrowserActionInfo *info) +{ + Device *parentDevice = myDevices().findById(info->device()->parentId()); + Sonos *sonosConnection = m_sonosConnections.value(parentDevice); + if (!sonosConnection) + return; + + QString groupId = info->device()->paramValue(sonosGroupDeviceGroupIdParamTypeId).toString(); + QUuid requestId = sonosConnection->loadFavorite(groupId, info->browserAction().itemId()); + m_pendingBrowserExecution.insert(requestId, info); +} + +void DevicePluginSonos::executeBrowserItemAction(BrowserItemActionInfo *info) +{ + Q_UNUSED(info) +} + void DevicePluginSonos::onConnectionChanged(bool connected) { Sonos *sonos = static_cast(sender()); @@ -366,25 +416,45 @@ void DevicePluginSonos::onHouseholdIdsReceived(QList householdIds) Sonos *sonos = static_cast(sender()); foreach(QString householdId, householdIds) { sonos->getGroups(householdId); - sonos->getFavorites(householdId); sonos->getPlaylists(householdId); } } void DevicePluginSonos::onFavouritesReceived(const QString &householdId, QList favourites) { - Q_UNUSED(householdId); - foreach(Sonos::FavouriteObject favourite, favourites) { - qDebug(dcSonos()) << "Favourite: " << favourite.name << favourite.description; + if (m_pendingBrowseResult.contains(householdId)) { + BrowseResult *result = m_pendingBrowseResult.take(householdId); + if (!result) + return; + + foreach(Sonos::FavouriteObject favourite, favourites) { + BrowserItem item; + item.setId(favourite.id); + item.setExecutable(true); + item.setBrowsable(false); + if (!favourite.imageUrl.isEmpty()) { + item.setThumbnail(favourite.imageUrl); + } else { + item.setIcon(BrowserItem::BrowserIconFavorites); + } + item.setDisplayName(favourite.name); + item.setDescription(favourite.description); + result->addItem(item); + qDebug(dcSonos()) << "Favourite: " << favourite.name << favourite.description; + } + result->finish(Device::DeviceErrorNoError); + } else { + qDebug(dcSonos()) << "Received unhandled favourites list"; } } void DevicePluginSonos::onPlaylistsReceived(const QString &householdId, QList playlists) { Sonos *sonos = static_cast(sender()); + foreach(Sonos::PlaylistObject playlist, playlists) { qDebug(dcSonos()) << "Playlist: " << playlist.name << playlist.type << playlist.trackCount; - sonos->getPlaylist(householdId, playlist.id); + sonos->getPlaylist(householdId, playlist.id); //Get the playlist details } } @@ -399,7 +469,6 @@ void DevicePluginSonos::onPlaylistSummaryReceived(const QString &householdId, So void DevicePluginSonos::onGroupsReceived(const QString &householdId, QList groupObjects) { - Q_UNUSED(householdId); Sonos *sonos = static_cast(sender()); Device *parentDevice = m_sonosConnections.key(sonos); if (!parentDevice) @@ -409,12 +478,16 @@ void DevicePluginSonos::onGroupsReceived(const QString &householdId, QListsetName(groupObject.displayName); + if (groupDevice->name() != groupObject.displayName) { + qDebug(dcSonos()) << "Updating group name" << groupDevice->name() << "to" << groupObject.displayName; + groupDevice->setName(groupObject.displayName); + } } else { //new device, add to the system DeviceDescriptor deviceDescriptor(sonosGroupDeviceClassId, groupObject.displayName, "Sonos Group", parentDevice->id()); ParamList params; params.append(Param(sonosGroupDeviceGroupIdParamTypeId, groupObject.groupId)); + params.append(Param(sonosGroupDeviceHouseholdIdParamTypeId, householdId)); deviceDescriptor.setParams(params); deviceDescriptors.append(deviceDescriptor); } @@ -478,8 +551,11 @@ void DevicePluginSonos::onMetadataStatusReceived(const QString &groupId, Sonos:: device->setStateValue(sonosGroupTitleStateTypeId, metaDataStatus.currentItem.track.name); device->setStateValue(sonosGroupArtistStateTypeId, metaDataStatus.currentItem.track.artist.name); device->setStateValue(sonosGroupCollectionStateTypeId, metaDataStatus.currentItem.track.album.name); - //device->setStateValue(sonosGroupArtworkStateTypeId, metaDataStatus.currentItem.track.imageUrl); - device->setStateValue(sonosGroupArtworkStateTypeId, metaDataStatus.container.imageUrl); + if (!metaDataStatus.currentItem.track.imageUrl.isEmpty()){ + device->setStateValue(sonosGroupArtworkStateTypeId, metaDataStatus.currentItem.track.imageUrl); + } else { + device->setStateValue(sonosGroupArtworkStateTypeId, metaDataStatus.container.imageUrl); + } } void DevicePluginSonos::onVolumeReceived(const QString &groupId, Sonos::VolumeObject groupVolume) @@ -506,4 +582,18 @@ void DevicePluginSonos::onActionExecuted(QUuid sonosActionId, bool success) info->finish(Device::DeviceErrorHardwareFailure); } } + + if (m_pendingBrowserExecution.contains(sonosActionId)) { + BrowserActionInfo *info = m_pendingBrowserExecution.value(sonosActionId); + if (!info) { + qCWarning(dcSonos()) << "BrowseActionInfo has disappeared. Did it time out?"; + return; + } + + if (success) { + info->finish(Device::DeviceErrorNoError); + } else { + info->finish(Device::DeviceErrorHardwareFailure); + } + } } diff --git a/sonos/devicepluginsonos.h b/sonos/devicepluginsonos.h index f72d4796..97e6c6d6 100644 --- a/sonos/devicepluginsonos.h +++ b/sonos/devicepluginsonos.h @@ -49,11 +49,16 @@ public: void deviceRemoved(Device *device) override; void executeAction(DeviceActionInfo *info) override; + void browseDevice(BrowseResult *result) override; + void browserItem(BrowserItemResult *result) override; + void executeBrowserItem(BrowserActionInfo *info) override; + void executeBrowserItemAction(BrowserItemActionInfo *info) override; + private: PluginTimer *m_pluginTimer5sec = nullptr; PluginTimer *m_pluginTimer60sec = nullptr; - QHash m_setupSonosConnections; + QHash m_setupSonosConnections; QHash m_sonosConnections; QList m_householdIds; @@ -61,6 +66,9 @@ private: QByteArray m_sonosConnectionRefreshToken; QHash > m_pendingActions; + QHash m_pendingBrowseResult; + QHash m_pendingBrowserExecution; + private slots: void onConnectionChanged(bool connected); diff --git a/sonos/devicepluginsonos.json b/sonos/devicepluginsonos.json index c9087780..2621d84e 100644 --- a/sonos/devicepluginsonos.json +++ b/sonos/devicepluginsonos.json @@ -22,8 +22,8 @@ { "id": "5aa4360c-61de-47d0-a72e-a19d57712e1c", "name": "connected", - "displayName": "connected", - "displayNameEvent": "connected changed", + "displayName": "Connected", + "displayNameEvent": "Connected changed", "defaultValue": true, "type": "bool" }, @@ -51,28 +51,35 @@ "displayName": "Sonos group", "interfaces": ["extendedvolumecontroller", "mediametadataprovider", "shufflerepeat", "connectable"], "createMethods": ["auto"], + "browsable": true, "paramTypes": [ { "id": "defc44cd-2ffb-4af1-b348-d6a3474c7515", "name": "groupId", "displayName": "Group id", "type" : "QString" + }, + { + "id": "f8a5d3d8-fad9-441d-b345-3484524490a0", + "name": "householdId", + "displayName": "Household id", + "type" : "QString" } ], "stateTypes": [ { "id": "09dfbd40-c97c-4a20-9ecd-f80e389a4864", "name": "connected", - "displayName": "connected", - "displayNameEvent": "connected changed", + "displayName": "Connected", + "displayNameEvent": "Connected changed", "defaultValue": false, "type": "bool" }, { "id": "bc98cdb0-4d0e-48ca-afc7-922e49bb7813", "name": "mute", - "displayName": "mute", - "displayNameEvent": "mute changed", + "displayName": "Mute", + "displayNameEvent": "Mute changed", "displayNameAction": "Set mute", "type": "bool", "defaultValue": true, @@ -81,8 +88,8 @@ { "id": "9dfe5d78-4c3f-497c-bab1-bb9fdf7e93a9", "name": "volume", - "displayName": "volume", - "displayNameEvent": "volume changed", + "displayName": "Volume", + "displayNameEvent": "Volume changed", "displayNameAction": "Set volume", "unit": "Percentage", "type": "int", @@ -94,12 +101,12 @@ { "id": "2dd512b7-40c2-488e-8d4f-6519edaa6f74", "name": "playbackStatus", - "displayName": "playback status", + "displayName": "Playback status", "type": "QString", "possibleValues": ["Playing", "Paused", "Stopped"], "defaultValue": "Stopped", - "displayNameEvent": "playback status changed", - "displayNameAction": "set playback status", + "displayNameEvent": "Playback status changed", + "displayNameAction": "Set playback status", "writable": true }, { @@ -160,49 +167,56 @@ { "id": "2535a1eb-7643-4874-98f6-b027fdff6311", "name": "onPlayerPlay", - "displayName": "player play" + "displayName": "Group play" }, { "id": "99498b1c-e9c0-480a-9e91-662ee79ba976", "name": "onPlayerPause", - "displayName": "player pause" + "displayName": "Group pause" }, { "id": "a02ce255-3abb-435d-a92e-7f99c952ecb2", "name": "onPlayerStop", - "displayName": "player stop" + "displayName": "Group stop" } ], "actionTypes": [ { "id": "a180807d-1265-4831-9d86-a421767418dd", "name": "skipBack", - "displayName": "skip back" + "displayName": "Skip back" }, { "id": "7e70b47b-7e79-4521-be34-04a3c427e5b1", "name": "fastRewind", - "displayName": "rewind" + "displayName": "Rewind" }, { "id": "ae3cbe03-ee3e-410e-abbd-efabc2402198", "name": "stop", - "displayName": "stop" + "displayName": "Stop" }, { "id": "4d2ee668-a2e3-4795-8b96-0c800b703b46", "name": "play", - "displayName": "play" + "displayName": "Play" }, { "id": "3cf341cb-fe63-40bc-a450-9678d18e91e3", "name": "pause", - "displayName": "pause" + "displayName": "Pause" }, { "id": "85d7126a-b123-4a28-aeb4-d84bcfb4d14f", "name": "skipNext", - "displayName": "skipNext" + "displayName": "Skip Next" + } + ], + "browserItemActionTypes": [ + { + "id": "b056af0f-4d5b-4f75-b0dd-e73246d3a83f", + "name": "updateLibrary", + "displayName": "Update library" } ] } diff --git a/sonos/sonos.cpp b/sonos/sonos.cpp index 356a0f80..e533fe41 100644 --- a/sonos/sonos.cpp +++ b/sonos/sonos.cpp @@ -111,7 +111,7 @@ void Sonos::getHouseholds() QList households; foreach (const QVariant &variant, data.toVariant().toMap().value("households").toList()) { QVariantMap obj = variant.toMap(); - qDebug(dcSonos()) << "Household ID received:" << obj["id"].toString(); + //qDebug(dcSonos()) << "Household ID received:" << obj["id"].toString(); households.append(obj["id"].toString()); } emit householdIdsReceived(households); @@ -125,12 +125,14 @@ QUuid Sonos::loadFavorite(const QString &groupId, const QString &favouriteId) request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); request.setRawHeader("Authorization", "Bearer " + m_accessToken); request.setRawHeader("X-Sonos-Api-Key", m_clientKey); - request.setUrl(QUrl(m_baseControlUrl + "/groups/" + groupId + "/favourites")); + request.setUrl(QUrl(m_baseControlUrl + "/groups/" + groupId + "/favorites")); QUuid actionId = QUuid::createUuid(); QJsonObject object; - object.insert("favoriteId", QJsonValue::fromVariant(favouriteId)); + object.insert("favoriteId", favouriteId); + object.insert("playOnCompletion", true); QJsonDocument doc(object); + qDebug(dcSonos()) << "Sending request" << doc.toJson(); QNetworkReply *reply = m_networkManager->post(request, doc.toJson(QJsonDocument::Compact)); connect(reply, &QNetworkReply::finished, this, [reply, actionId, this] { @@ -193,15 +195,16 @@ void Sonos::getFavorites(const QString &householdId) return; QVariantList array = data.toVariant().toMap().value("items").toList(); - QList favourites - ; + //qDebug(dcSonos()) << "Favourites received:" << data.toJson(); + + QList favourites; foreach (const QVariant &variant, array) { QVariantMap itemObject = variant.toMap(); - qDebug(dcSonos()) << "Item ID received:" << itemObject["id"].toString(); FavouriteObject favourite; favourite.id = itemObject["id"].toString(); favourite.name = itemObject["name"].toString(); favourite.description = itemObject["description"].toString(); + favourite.imageUrl = itemObject["imageUrl"].toString(); favourites.append(favourite); } emit favouritesReceived(householdId, favourites); @@ -248,10 +251,16 @@ void Sonos::getGroups(const QString &householdId) QList groupObjects; foreach (const QVariant &value, array) { QVariantMap obj = value.toMap(); - qDebug(dcSonos()) << "Group ID received:" << obj["id"].toString(); + //qDebug(dcSonos()) << "Group ID received:" << obj["id"].toString(); GroupObject group; group.groupId = obj["id"].toString(); group.displayName = obj["name"].toString(); + group.CoordinatorId = obj["coordinatorId"].toString(); + group.playbackState = obj["playbackState"].toString(); + QVariantList players = obj.value("playerIds").toList(); + foreach (const QVariant &value, players) { + group.playerIds.append(value.toByteArray()); + } groupObjects.append(group); } emit groupsReceived(householdId, groupObjects); @@ -1289,7 +1298,6 @@ void Sonos::getPlaylist(const QString &householdId, const QString &playlistId) QVariantList array = variant["tracks"].toList(); foreach (const QVariant &value, array) { QVariantMap itemObject = value.toMap(); - qDebug(dcSonos()) << "Item ID received:" << itemObject["id"].toString(); PlaylistTrackObject track; track.name = itemObject["name"].toString(); track.album = itemObject["album"].toString(); @@ -1341,7 +1349,6 @@ void Sonos::getPlaylists(const QString &householdId) QList playlists; foreach (const QVariant &value, array) { QVariantMap itemObject = value.toMap(); - qDebug(dcSonos()) << "Item ID received:" << itemObject["id"].toString(); PlaylistObject playlist; playlist.id = itemObject["id"].toString(); playlist.name = itemObject["name"].toString();