diff --git a/sonos/devicepluginsonos.cpp b/sonos/devicepluginsonos.cpp index f071275d..5441b669 100644 --- a/sonos/devicepluginsonos.cpp +++ b/sonos/devicepluginsonos.cpp @@ -38,15 +38,49 @@ DevicePluginSonos::DevicePluginSonos() DevicePluginSonos::~DevicePluginSonos() { - hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer); + hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer5sec); + hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer60sec); } Device::DeviceSetupStatus DevicePluginSonos::setupDevice(Device *device) { - if (!m_pluginTimer) { - hardwareManager()->pluginTimerManager()->registerTimer(10); - connect(m_pluginTimer, &PluginTimer::timeout, this, &DevicePluginSonos::onPluginTimer); + 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(!m_tokenRefreshTimer) { @@ -74,8 +108,6 @@ Device::DeviceSetupStatus DevicePluginSonos::setupDevice(Device *device) } if (device->deviceClassId() == sonosGroupDeviceClassId) { - - } return Device::DeviceSetupStatusSuccess; } @@ -213,8 +245,10 @@ void DevicePluginSonos::deviceRemoved(Device *device) { qCDebug(dcSonos) << "Delete " << device->name(); if (myDevices().empty()) { - hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer); - m_pluginTimer = nullptr; + hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer5sec); + hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer60sec); + m_pluginTimer5sec = nullptr; + m_pluginTimer60sec = nullptr; } } @@ -222,31 +256,32 @@ void DevicePluginSonos::deviceRemoved(Device *device) Device::DeviceError DevicePluginSonos::executeAction(Device *device, const Action &action) { if (device->deviceClassId() == sonosGroupDeviceClassId) { - Sonos *sonos = m_sonosConnections.value(device); - QByteArray groupId = device->paramValue(sonosGroupDeviceGroupIdParamTypeId).toByteArray(); + Sonos *sonos = m_sonosConnections.value(myDevices().findById(device->parentId())); + QString groupId = device->paramValue(sonosGroupDeviceGroupIdParamTypeId).toString(); - if (!sonos) + if (!sonos) { + qWarning(dcSonos()) << "Action cannot be executed: Sonos connection not available"; return Device::DeviceErrorInvalidParameter; + } if (action.actionTypeId() == sonosGroupPlayActionTypeId) { - sonos->groupPlay(groupId); + m_pendingActions.insert(sonos->groupPlay(groupId), action.id()); return Device::DeviceErrorAsync; } if (action.actionTypeId() == sonosGroupShuffleActionTypeId) { bool shuffle = action.param(sonosGroupShuffleActionShuffleParamTypeId).value().toBool(); - sonos->groupSetShuffle(groupId, shuffle); + m_pendingActions.insert(sonos->groupSetShuffle(groupId, shuffle), action.id()); return Device::DeviceErrorAsync; } if (action.actionTypeId() == sonosGroupRepeatActionTypeId) { - if (action.param(sonosGroupRepeatActionRepeatParamTypeId).value().toString() == "None") { - sonos->groupSetRepeat(groupId, Sonos::RepeatModeNone); - } else if (action.param(sonosGroupShuffleActionShuffleParamTypeId).value().toString() == "One") { - sonos->groupSetRepeat(groupId, Sonos::RepeatModeOne); - } else if (action.param(sonosGroupShuffleActionShuffleParamTypeId).value().toString() == "All") { - sonos->groupSetRepeat(groupId, Sonos::RepeatModeAll); + m_pendingActions.insert(sonos->groupSetRepeat(groupId, Sonos::RepeatModeNone), action.id()); + } else if (action.param(sonosGroupRepeatActionRepeatParamTypeId).value().toString() == "One") { + m_pendingActions.insert(sonos->groupSetRepeat(groupId, Sonos::RepeatModeOne), action.id()); + } else if (action.param(sonosGroupRepeatActionRepeatParamTypeId).value().toString() == "All") { + m_pendingActions.insert(sonos->groupSetRepeat(groupId, Sonos::RepeatModeAll), action.id()); } else { return Device::DeviceErrorHardwareFailure; } @@ -269,6 +304,13 @@ Device::DeviceError DevicePluginSonos::executeAction(Device *device, const Actio return Device::DeviceErrorAsync; } + + if (action.actionTypeId() == sonosGroupVolumeActionTypeId) { + int volume = action.param(sonosGroupVolumeActionVolumeParamTypeId).value().toInt(); + m_pendingActions.insert(sonos->setGroupVolume(groupId, volume), action.id()); + return Device::DeviceErrorAsync; + } + if (action.actionTypeId() == sonosGroupSkipNextActionTypeId) { m_pendingActions.insert(sonos->groupSkipToNextTrack(groupId), action.id()); return Device::DeviceErrorAsync; @@ -282,11 +324,11 @@ Device::DeviceError DevicePluginSonos::executeAction(Device *device, const Actio if (action.actionTypeId() == sonosGroupPlaybackStatusActionTypeId) { QString playbackStatus = action.param(sonosGroupPlaybackStatusActionPlaybackStatusParamTypeId).value().toString(); if (playbackStatus == "Playing") { - sonos->groupPlay(groupId); + m_pendingActions.insert(sonos->groupPlay(groupId), action.id()); } else if(playbackStatus == "Stopped") { - sonos->groupPause(groupId); + m_pendingActions.insert(sonos->groupPause(groupId), action.id()); } else if(playbackStatus == "Paused") { - sonos->groupPause(groupId); + m_pendingActions.insert(sonos->groupPause(groupId), action.id()); } return Device::DeviceErrorAsync; } @@ -295,28 +337,6 @@ Device::DeviceError DevicePluginSonos::executeAction(Device *device, const Actio return Device::DeviceErrorDeviceClassNotFound; } -void DevicePluginSonos::onPluginTimer() -{ - foreach (Device *device, myDevices().filterByDeviceClassId(sonosConnectionDeviceClassId)) { - - //get groups for each household in order to add or remove groups - Sonos *sonos = m_sonosConnections.value(device); - if (!sonos) - continue; - sonos->getHouseholds(); - - foreach (Device *groupDevice, myDevices().filterByParentDeviceId(device->id())) { - if (device->deviceClassId() == sonosGroupDeviceClassId) { - //get playback status of each group - QByteArray groupId = groupDevice->paramValue(sonosGroupDeviceGroupIdParamTypeId).toByteArray(); - sonos->getGroupPlaybackStatus(groupId); - sonos->getGroupMetadataStatus(groupId); - sonos->getGroupVolume(groupId); - } - } - } -} - void DevicePluginSonos::onConnectionChanged(bool connected) { Sonos *sonos = static_cast(sender()); @@ -354,6 +374,10 @@ void DevicePluginSonos::onRefreshTimeout() if (jsonDoc.toVariant().toMap().contains("expires_in")) { int expireTime = jsonDoc.toVariant().toMap().value("expires_in").toInt(); qCDebug(dcSonos()) << "expires at" << QDateTime::currentDateTime().addSecs(expireTime).toString(); + if (!m_tokenRefreshTimer) { + qWarning(dcSonos()) << "Token refresh timer not initialized"; + return; + } m_tokenRefreshTimer->start((expireTime - 20) * 1000); } }); @@ -364,6 +388,34 @@ 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; + } +} + +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); + } +} + +void DevicePluginSonos::onPlaylistSummaryReceived(const QString &householdId, Sonos::PlaylistSummaryObject playlistSummary) +{ + Q_UNUSED(householdId); + qDebug(dcSonos()) << "Playlist summary received: " << playlistSummary.name; + foreach(Sonos::PlaylistTrackObject track, playlistSummary.tracks) { + qDebug(dcSonos()) << "---- Track: " << track.name << track.album << track.artist; } } @@ -376,27 +428,54 @@ void DevicePluginSonos::onGroupsReceived(QList groupObjects) QList deviceDescriptors; foreach(Sonos::GroupObject groupObject, groupObjects) { - if (!myDevices().filterByParam(sonosGroupDeviceGroupIdParamTypeId, groupObject.groupId).isEmpty()) { - continue; + Device *groupDevice = myDevices().findByParams(ParamList() << Param(sonosGroupDeviceGroupIdParamTypeId, groupObject.groupId)); + if (groupDevice) { + 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)); + deviceDescriptor.setParams(params); + deviceDescriptors.append(deviceDescriptor); } - DeviceDescriptor deviceDescriptor(sonosGroupDeviceClassId, groupObject.displayName, "Sonos Group", parentDevice->id()); - ParamList params; - params.append(Param(sonosGroupDeviceGroupIdParamTypeId, groupObject.groupId)); - deviceDescriptor.setParams(params); - deviceDescriptors.append(deviceDescriptor); } if (!deviceDescriptors.isEmpty()) emit autoDevicesAppeared(sonosGroupDeviceClassId, deviceDescriptors); + + //delete auto devices + foreach(Device *groupDevice, myDevices().filterByParentDeviceId(parentDevice->id())) { + QString groupId = groupDevice->paramValue(sonosGroupDeviceGroupIdParamTypeId).toString(); + bool deviceRemoved = true; + foreach (Sonos::GroupObject groupObject, groupObjects) { + if(groupObject.groupId == groupId) { + deviceRemoved = false; + } + } + if (deviceRemoved) { + emit autoDeviceDisappeared(groupDevice->id()); + } + } + } void DevicePluginSonos::onPlayBackStatusReceived(const QString &groupId, Sonos::PlayBackObject playBack) { - qDebug(dcSonos()) << "Playback status received" << playBack.playbackState; Device *device = myDevices().findByParams(ParamList() << Param(sonosGroupDeviceGroupIdParamTypeId, groupId)); if (!device) return; + device->setStateValue(sonosGroupShuffleStateTypeId, playBack.playMode.shuffle); + + if (playBack.playMode.repeatOne) { + device->setStateValue(sonosGroupRepeatStateTypeId, "One"); + } else if (playBack.playMode.repeat) { + device->setStateValue(sonosGroupRepeatStateTypeId, "All"); + } else { + device->setStateValue(sonosGroupRepeatStateTypeId, "None"); + } + switch (playBack.playbackState) { case Sonos::PlayBackStateIdle: device->setStateValue(sonosGroupPlaybackStatusStateTypeId, "Stopped"); @@ -424,7 +503,7 @@ void DevicePluginSonos::onMetadataStatusReceived(const QString &groupId, Sonos:: device->setStateValue(sonosGroupArtworkStateTypeId, metaDataStatus.container.imageUrl); } -void DevicePluginSonos::onVolumeReceived(const QString &groupId, Sonos::GroupVolumeObject groupVolume) +void DevicePluginSonos::onVolumeReceived(const QString &groupId, Sonos::VolumeObject groupVolume) { Device *device = myDevices().findByParams(ParamList() << Param(sonosGroupDeviceGroupIdParamTypeId, groupId)); if (!device) diff --git a/sonos/devicepluginsonos.h b/sonos/devicepluginsonos.h index 5820bd73..7d0cd69e 100644 --- a/sonos/devicepluginsonos.h +++ b/sonos/devicepluginsonos.h @@ -52,7 +52,8 @@ public: Device::DeviceError executeAction(Device *device, const Action &action) override; private: - PluginTimer *m_pluginTimer = nullptr; + PluginTimer *m_pluginTimer5sec = nullptr; + PluginTimer *m_pluginTimer60sec = nullptr; QTimer *m_tokenRefreshTimer = nullptr; QHash m_sonosConnections; @@ -63,15 +64,18 @@ private: QHash m_pendingActions; private slots: - void onPluginTimer(); void onConnectionChanged(bool connected); void onRefreshTimeout(); void onHouseholdIdsReceived(QList householdIds); + void onFavouritesReceived(const QString &householdId, QList favourites); + void onPlaylistsReceived(const QString &householdId, QList playlists); + void onPlaylistSummaryReceived(const QString &householdId, Sonos::PlaylistSummaryObject playlistSummary); + void onGroupsReceived(QList groupIds); void onPlayBackStatusReceived(const QString &groupId, Sonos::PlayBackObject playBack); void onMetadataStatusReceived(const QString &groupId, Sonos::MetadataStatus metaDataStatus); - void onVolumeReceived(const QString &groupId, Sonos::GroupVolumeObject groupVolume); + void onVolumeReceived(const QString &groupId, Sonos::VolumeObject groupVolume); void onActionExecuted(QUuid actionId, bool success); }; diff --git a/sonos/sonos.cpp b/sonos/sonos.cpp index 7aa13fe6..9544e078 100644 --- a/sonos/sonos.cpp +++ b/sonos/sonos.cpp @@ -76,28 +76,80 @@ void Sonos::getHouseholds() }); } -QUuid Sonos::cancelAudioClip() + +QUuid Sonos::loadFavorite(const QString &groupId, const QString &favouriteId) { + QNetworkRequest request; + request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); + request.setRawHeader("Authorization", "Bearer " + m_accessToken); + request.setRawHeader("X-Sonos-Api-Key", m_apiKey); + request.setUrl(QUrl(m_baseControlUrl + "/groups/" + groupId + "/favourites")); QUuid actionId = QUuid::createUuid(); + + QJsonObject object; + object.insert("favoriteId", QJsonValue::fromVariant(favouriteId)); + QJsonDocument doc(object); + + QNetworkReply *reply = m_networkManager->post(request, doc.toBinaryData()); + connect(reply, &QNetworkReply::finished, this, [reply, actionId, this] { + reply->deleteLater(); + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + // Check HTTP status code + if (status != 200 || reply->error() != QNetworkReply::NoError) { + qCWarning(dcSonos()) << "Request error:" << status << reply->errorString(); + emit actionExecuted(actionId, false); + return; + } + + //TODO parse response + emit actionExecuted(actionId, true); + }); return actionId; } -QUuid Sonos::loadAudioClip() +void Sonos::getFavorites(const QString &householdId) { - QUuid actionId = QUuid::createUuid(); - return actionId; + QNetworkRequest request; + request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); + request.setRawHeader("Authorization", "Bearer " + m_accessToken); + request.setRawHeader("X-Sonos-Api-Key", m_apiKey); + request.setUrl(QUrl(m_baseControlUrl + "/households/" + householdId + "/favorites")); + QNetworkReply *reply = m_networkManager->get(request); + connect(reply, &QNetworkReply::finished, this, [reply, householdId, this] { + reply->deleteLater(); + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + // Check HTTP status code + if (status != 200 || reply->error() != QNetworkReply::NoError) { + qCWarning(dcSonos()) << "Request error:" << status << reply->errorString(); + return; + } + + //qDebug(dcSonos()) << "Received response from Sonos" << reply->readAll(); + QJsonDocument data = QJsonDocument::fromJson(reply->readAll()); + if (!data.isObject()) + return; + + if (!data["items"].isArray()) + return; + + QJsonArray array = data["items"].toArray(); + QList favourites + ; + foreach (const QJsonValue & value, array) { + QJsonObject itemObject = value.toObject(); + 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(); + favourites.append(favourite); + } + emit favouritesReceived(householdId, favourites); + }); } -void Sonos::getFavorites() -{ - -} - -QUuid Sonos::loadFavorite() -{ - QUuid actionId = QUuid::createUuid(); - return actionId; -} void Sonos::getGroups(const QString &householdId) { @@ -139,27 +191,6 @@ void Sonos::getGroups(const QString &householdId) }); } -QUuid Sonos::createGroup(const QString &householdId, QList playerIds) -{ - Q_UNUSED(householdId) - Q_UNUSED(playerIds) - QUuid actionId = QUuid::createUuid(); - return actionId; -} - -QUuid Sonos::modifyGroupMembers() -{ - QUuid actionId = QUuid::createUuid(); - return actionId; -} - -QUuid Sonos::setGroupMembers(const QString &groupId) -{ - Q_UNUSED(groupId) - QUuid actionId = QUuid::createUuid(); - return actionId; -} - void Sonos::getGroupVolume(const QString &groupId) { QNetworkRequest request; @@ -183,13 +214,13 @@ void Sonos::getGroupVolume(const QString &groupId) if (!data.isObject()) return; - GroupVolumeObject groupVolume; + VolumeObject volume; - groupVolume.volume = data["volume"].toInt(); - groupVolume.muted = data["muted"].toBool(); - groupVolume.fixed = data["fixed"].toBool(); + volume.volume = data["volume"].toInt(); + volume.muted = data["muted"].toBool(); + volume.fixed = data["fixed"].toBool(); - emit volumeReceived(groupId, groupVolume); + emit volumeReceived(groupId, volume); }); } @@ -203,11 +234,12 @@ QUuid Sonos::setGroupVolume(const QString &groupId, int volume) QUuid actionId = QUuid::createUuid(); QJsonObject object; - object.insert("volume", QJsonValue::fromVariant(volume)); + object.insert("volume", volume); QJsonDocument doc(object); + qDebug(dcSonos()) << "Set volume:" << groupId << doc.toJson(QJsonDocument::Compact); - QNetworkReply *reply = m_networkManager->post(request, doc.toBinaryData()); - connect(reply, &QNetworkReply::finished, this, [reply, actionId, this] { + QNetworkReply *reply = m_networkManager->post(request, doc.toJson(QJsonDocument::Compact)); + connect(reply, &QNetworkReply::finished, this, [reply, actionId, groupId, this] { reply->deleteLater(); int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); @@ -217,6 +249,7 @@ QUuid Sonos::setGroupVolume(const QString &groupId, int volume) emit actionExecuted(actionId, false); return; } + getGroupVolume(groupId); emit actionExecuted(actionId, true); }); return actionId; @@ -224,9 +257,6 @@ QUuid Sonos::setGroupVolume(const QString &groupId, int volume) QUuid Sonos::setGroupMute(const QString &groupId, bool mute) { - Q_UNUSED(groupId) - Q_UNUSED(mute) - QNetworkRequest request; request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); request.setRawHeader("Authorization", "Bearer " + m_accessToken); @@ -235,11 +265,13 @@ QUuid Sonos::setGroupMute(const QString &groupId, bool mute) QUuid actionId = QUuid::createUuid(); QJsonObject object; - object.insert("muted", QJsonValue::fromVariant(mute)); + object.insert("muted", mute); QJsonDocument doc(object); - QNetworkReply *reply = m_networkManager->post(request, doc.toBinaryData()); - connect(reply, &QNetworkReply::finished, this, [reply, actionId, this] { + qDebug(dcSonos()) << "Set mute:" << groupId << doc.toJson(QJsonDocument::Compact); + + QNetworkReply *reply = m_networkManager->post(request, doc.toJson(QJsonDocument::Compact)); + connect(reply, &QNetworkReply::finished, this, [reply, actionId, groupId, this] { reply->deleteLater(); int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); @@ -249,6 +281,7 @@ QUuid Sonos::setGroupMute(const QString &groupId, bool mute) emit actionExecuted(actionId, false); return; } + getGroupVolume(groupId); emit actionExecuted(actionId, true); }); return actionId; @@ -267,8 +300,10 @@ QUuid Sonos::setGroupRelativeVolume(const QString &groupId, int volumeDelta) object.insert("volumeDelta", QJsonValue::fromVariant(volumeDelta)); QJsonDocument doc(object); - QNetworkReply *reply = m_networkManager->post(request, doc.toBinaryData()); - connect(reply, &QNetworkReply::finished, this, [reply, actionId, this] { + qDebug(dcSonos()) << "Relative volume:" << groupId << volumeDelta; + + QNetworkReply *reply = m_networkManager->post(request, doc.toJson(QJsonDocument::Compact)); + connect(reply, &QNetworkReply::finished, this, [reply, actionId, groupId, this] { reply->deleteLater(); int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); @@ -278,6 +313,7 @@ QUuid Sonos::setGroupRelativeVolume(const QString &groupId, int volumeDelta) emit actionExecuted(actionId, false); return; } + getGroupVolume(groupId); emit actionExecuted(actionId, true); }); return actionId; @@ -305,32 +341,61 @@ void Sonos::getGroupPlaybackStatus(const QString &groupId) if (!data.isObject()) return; - PlayBackObject playBackObject; + PlayBackObject playBack; QJsonObject object = data.object(); - playBackObject.itemId = object["itemId"].toString(); - playBackObject.positionMillis = object["positionMillis"].toInt(); - playBackObject.previousItemId = object["previousItemId"].toInt(); - playBackObject.previousPositionMillis = object["previousPositionMillis"].toInt(); + playBack.itemId = object["itemId"].toString(); + playBack.positionMillis = object["positionMillis"].toInt(); + playBack.previousItemId = object["previousItemId"].toInt(); + playBack.previousPositionMillis = object["previousPositionMillis"].toInt(); QString playBackState = object["playbackState"].toString(); if (playBackState.contains("BUFFERING")) { - playBackObject.playbackState = PlayBackStateBuffering; + playBack.playbackState = PlayBackStateBuffering; } else if (playBackState.contains("IDLE")) { - playBackObject.playbackState = PlayBackStateIdle; + playBack.playbackState = PlayBackStateIdle; } else if (playBackState.contains("PAUSE")) { - playBackObject.playbackState = PlayBackStatePause; + playBack.playbackState = PlayBackStatePause; } else if (playBackState.contains("PLAYING")) { - playBackObject.playbackState = PlayBackStatePlaying; + playBack.playbackState = PlayBackStatePlaying; } - playBackObject.isDucking = object["isDucking"].toBool(); - playBackObject.queueVersion = object["queueVersion"].toString(); - emit playBackStatusReceived(groupId, playBackObject); + playBack.isDucking = object["isDucking"].toBool(); + playBack.queueVersion = object["queueVersion"].toString(); + if (object.contains("playModes")) { + PlayMode playMode; + QJsonObject playModeObject = object["playModes"].toObject(); + playMode.repeat = playModeObject["repeat"].toBool(); + playMode.repeatOne = playModeObject["repeatOne"].toBool(); + playMode.crossfade = playModeObject["crossfade"].toBool(); + playMode.shuffle = playModeObject["shuffle"].toBool(); + playBack.playMode = playMode; + } + emit playBackStatusReceived(groupId, playBack); }); } QUuid Sonos::groupLoadLineIn(const QString &groupId) { - Q_UNUSED(groupId) + qDebug(dcSonos()) << "Load line in:" << groupId; + QNetworkRequest request; + request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); + request.setRawHeader("Authorization", "Bearer " + m_accessToken); + request.setRawHeader("X-Sonos-Api-Key", m_apiKey); + request.setUrl(QUrl(m_baseControlUrl + "/groups/" + groupId + "/playback/lineIn")); QUuid actionId = QUuid::createUuid(); + + QNetworkReply *reply = m_networkManager->post(request, ""); + connect(reply, &QNetworkReply::finished, this, [reply, actionId, groupId, this] { + reply->deleteLater(); + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + // Check HTTP status code + if (status != 200 || reply->error() != QNetworkReply::NoError) { + qCWarning(dcSonos()) << "Request error:" << status << reply->errorString(); + emit actionExecuted(actionId, false); + return; + } + getGroupVolume(groupId); + emit actionExecuted(actionId, true); + }); return actionId; } @@ -343,8 +408,10 @@ QUuid Sonos::groupPlay(const QString &groupId) request.setUrl(QUrl(m_baseControlUrl + "/groups/" + groupId + "/playback/play")); QUuid actionId = QUuid::createUuid(); + qDebug(dcSonos()) << "Play:" << groupId; + QNetworkReply *reply = m_networkManager->post(request, ""); - connect(reply, &QNetworkReply::finished, this, [reply, actionId, this] { + connect(reply, &QNetworkReply::finished, this, [reply, actionId, groupId, this] { reply->deleteLater(); int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); @@ -354,6 +421,7 @@ QUuid Sonos::groupPlay(const QString &groupId) emit actionExecuted(actionId, false); return; } + getGroupPlaybackStatus(groupId); emit actionExecuted(actionId, true); }); return actionId; @@ -368,8 +436,10 @@ QUuid Sonos::groupPause(const QString &groupId) request.setUrl(QUrl(m_baseControlUrl + "/groups/" + groupId + "/playback/pause")); QUuid actionId = QUuid::createUuid(); + qDebug(dcSonos()) << "Pause:" << groupId; + QNetworkReply *reply = m_networkManager->post(request, ""); - connect(reply, &QNetworkReply::finished, this, [reply, actionId, this] { + connect(reply, &QNetworkReply::finished, this, [reply, actionId, groupId, this] { reply->deleteLater(); int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); @@ -379,6 +449,7 @@ QUuid Sonos::groupPause(const QString &groupId) emit actionExecuted(actionId, false); return; } + getGroupPlaybackStatus(groupId); emit actionExecuted(actionId, true); }); return actionId; @@ -398,7 +469,7 @@ QUuid Sonos::groupSeek(const QString &groupId, int possitionMillis) object.insert("positionMillis", QJsonValue::fromVariant(possitionMillis)); QJsonDocument doc(object); - QNetworkReply *reply = m_networkManager->post(request, doc.toBinaryData()); + QNetworkReply *reply = m_networkManager->post(request, doc.toJson(QJsonDocument::Compact)); connect(reply, &QNetworkReply::finished, this, [reply, actionId, this] { reply->deleteLater(); int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); @@ -416,7 +487,6 @@ QUuid Sonos::groupSeek(const QString &groupId, int possitionMillis) QUuid Sonos::groupSeekRelative(const QString &groupId, int deltaMillis) { - Q_UNUSED(groupId) QNetworkRequest request; request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); request.setRawHeader("Authorization", "Bearer " + m_accessToken); @@ -428,7 +498,7 @@ QUuid Sonos::groupSeekRelative(const QString &groupId, int deltaMillis) object.insert("deltaMillis", QJsonValue::fromVariant(deltaMillis)); QJsonDocument doc(object); - QNetworkReply *reply = m_networkManager->post(request, doc.toBinaryData()); + QNetworkReply *reply = m_networkManager->post(request, doc.toJson(QJsonDocument::Compact)); connect(reply, &QNetworkReply::finished, this, [reply, actionId, this] { reply->deleteLater(); int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); @@ -462,8 +532,8 @@ QUuid Sonos::groupSetPlayModes(const QString &groupId, PlayMode playMode) object.insert("playModes", playModesObject); QJsonDocument doc(object); - QNetworkReply *reply = m_networkManager->post(request, doc.toBinaryData()); - connect(reply, &QNetworkReply::finished, this, [reply, actionId, this] { + QNetworkReply *reply = m_networkManager->post(request, doc.toJson(QJsonDocument::Compact)); + connect(reply, &QNetworkReply::finished, this, [reply, actionId, groupId, this] { reply->deleteLater(); int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); @@ -473,6 +543,7 @@ QUuid Sonos::groupSetPlayModes(const QString &groupId, PlayMode playMode) emit actionExecuted(actionId, false); return; } + getGroupPlaybackStatus(groupId); emit actionExecuted(actionId, true); }); return actionId; @@ -493,8 +564,8 @@ QUuid Sonos::groupSetShuffle(const QString &groupId, bool shuffle) object.insert("playModes", playModesObject); QJsonDocument doc(object); - QNetworkReply *reply = m_networkManager->post(request, doc.toBinaryData()); - connect(reply, &QNetworkReply::finished, this, [reply, actionId, this] { + QNetworkReply *reply = m_networkManager->post(request, doc.toJson(QJsonDocument::Compact)); + connect(reply, &QNetworkReply::finished, this, [reply, actionId, groupId, this] { reply->deleteLater(); int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); @@ -504,6 +575,7 @@ QUuid Sonos::groupSetShuffle(const QString &groupId, bool shuffle) emit actionExecuted(actionId, false); return; } + getGroupPlaybackStatus(groupId); emit actionExecuted(actionId, true); }); return actionId; @@ -521,20 +593,23 @@ QUuid Sonos::groupSetRepeat(const QString &groupId, RepeatMode repeatMode) QJsonObject object; QJsonObject playModesObject; if (repeatMode == RepeatModeAll) { + qDebug(dcSonos()) << "Setting repeat mode all"; playModesObject["repeat"] = true; playModesObject["repeatOne"] = false; } else if (repeatMode == RepeatModeOne) { + qDebug(dcSonos()) << "Setting repeat mode one"; playModesObject["repeat"] = false; playModesObject["repeatOne"] = true; - } else if (repeatMode == RepeatModeAll) { + } else if (repeatMode == RepeatModeNone) { + qDebug(dcSonos()) << "Setting repeat mode none"; playModesObject["repeat"] = false; playModesObject["repeatOne"] = false; } object.insert("playModes", playModesObject); QJsonDocument doc(object); - QNetworkReply *reply = m_networkManager->post(request, doc.toBinaryData()); - connect(reply, &QNetworkReply::finished, this, [reply, actionId, this] { + QNetworkReply *reply = m_networkManager->post(request, doc.toJson(QJsonDocument::Compact)); + connect(reply, &QNetworkReply::finished, this, [reply, actionId, groupId, this] { reply->deleteLater(); int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); @@ -544,6 +619,7 @@ QUuid Sonos::groupSetRepeat(const QString &groupId, RepeatMode repeatMode) emit actionExecuted(actionId, false); return; } + getGroupPlaybackStatus(groupId); emit actionExecuted(actionId, true); }); return actionId; @@ -564,8 +640,8 @@ QUuid Sonos::groupSetCrossfade(const QString &groupId, bool crossfade) object.insert("playModes", playModesObject); QJsonDocument doc(object); - QNetworkReply *reply = m_networkManager->post(request, doc.toBinaryData()); - connect(reply, &QNetworkReply::finished, this, [reply, actionId, this] { + QNetworkReply *reply = m_networkManager->post(request, doc.toJson(QJsonDocument::Compact)); + connect(reply, &QNetworkReply::finished, this, [reply, actionId, groupId, this] { reply->deleteLater(); int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); @@ -575,6 +651,7 @@ QUuid Sonos::groupSetCrossfade(const QString &groupId, bool crossfade) emit actionExecuted(actionId, false); return; } + getGroupPlaybackStatus(groupId); emit actionExecuted(actionId, true); }); return actionId; @@ -590,7 +667,7 @@ QUuid Sonos::groupSkipToNextTrack(const QString &groupId) QUuid actionId = QUuid::createUuid(); QNetworkReply *reply = m_networkManager->post(request, ""); - connect(reply, &QNetworkReply::finished, this, [reply, actionId, this] { + connect(reply, &QNetworkReply::finished, this, [reply, actionId, groupId, this] { reply->deleteLater(); int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); @@ -600,6 +677,7 @@ QUuid Sonos::groupSkipToNextTrack(const QString &groupId) actionExecuted(actionId, false); return; } + getGroupMetadataStatus(groupId); actionExecuted(actionId, true); }); return actionId; @@ -615,7 +693,7 @@ QUuid Sonos::groupSkipToPreviousTrack(const QString &groupId) QUuid actionId = QUuid::createUuid(); QNetworkReply *reply = m_networkManager->post(request, ""); - connect(reply, &QNetworkReply::finished, this, [reply, actionId, this] { + connect(reply, &QNetworkReply::finished, this, [reply, actionId, groupId, this] { reply->deleteLater(); int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); @@ -625,6 +703,7 @@ QUuid Sonos::groupSkipToPreviousTrack(const QString &groupId) actionExecuted(actionId, false); return; } + getGroupMetadataStatus(groupId); actionExecuted(actionId, true); }); return actionId; @@ -640,7 +719,7 @@ QUuid Sonos::groupTogglePlayPause(const QString &groupId) QUuid actionId = QUuid::createUuid(); QNetworkReply *reply = m_networkManager->post(request, ""); - connect(reply, &QNetworkReply::finished, this, [reply, actionId, this] { + connect(reply, &QNetworkReply::finished, this, [reply, actionId, groupId, this] { reply->deleteLater(); int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); @@ -650,6 +729,7 @@ QUuid Sonos::groupTogglePlayPause(const QString &groupId) actionExecuted(actionId, false); return; } + getGroupPlaybackStatus(groupId); actionExecuted(actionId, true); }); return actionId; @@ -693,7 +773,7 @@ void Sonos::getGroupMetadataStatus(const QString &groupId) container.service = service; } if (containerObject.contains("id")) { - qDebug(dcSonos()) << "Item ID" << containerObject.value("id").toString(); + //TODO parse ID } metaDataStatus.container = container; } @@ -727,7 +807,7 @@ void Sonos::getGroupMetadataStatus(const QString &groupId) track.service = service; } if (trackObject.contains("id")) { - qDebug(dcSonos()) << "Item ID" << trackObject.value("id").toString(); + //TODO parse id } track.type = trackObject["type"].toString(); @@ -770,7 +850,7 @@ void Sonos::getGroupMetadataStatus(const QString &groupId) track.service = service; } if (trackObject.contains("id")) { - qDebug(dcSonos()) << "Item ID" << trackObject.value("id").toString(); + //TODO parse id } track.type = trackObject["type"].toString(); track.name = trackObject["name"].toString(); @@ -788,57 +868,304 @@ void Sonos::getGroupMetadataStatus(const QString &groupId) void Sonos::getPlayerVolume(const QByteArray &playerId) { - Q_UNUSED(playerId) + QNetworkRequest request; + request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); + request.setRawHeader("Authorization", "Bearer " + m_accessToken); + request.setRawHeader("X-Sonos-Api-Key", m_apiKey); + request.setUrl(QUrl(m_baseControlUrl + "/players/" + playerId + "/playerVolume")); + QNetworkReply *reply = m_networkManager->get(request); + connect(reply, &QNetworkReply::finished, this, [reply, playerId, this] { + reply->deleteLater(); + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + // Check HTTP status code + if (status != 200 || reply->error() != QNetworkReply::NoError) { + qCWarning(dcSonos()) << "Request error:" << status << reply->errorString(); + return; + } + + //qDebug(dcSonos()) << "Received response from Sonos" << reply->readAll(); + QJsonDocument data = QJsonDocument::fromJson(reply->readAll()); + if (!data.isObject()) + return; + + VolumeObject volume; + volume.volume = data["volume"].toInt(); + volume.muted = data["muted"].toBool(); + volume.fixed = data["fixed"].toBool(); + emit playerVolumeReceived(playerId, volume); + }); } QUuid Sonos::setPlayerVolume(const QByteArray &playerId, int volume) { - Q_UNUSED(playerId) - Q_UNUSED(volume) + QNetworkRequest request; + request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); + request.setRawHeader("Authorization", "Bearer " + m_accessToken); + request.setRawHeader("X-Sonos-Api-Key", m_apiKey); + request.setUrl(QUrl(m_baseControlUrl + "/players/" + playerId + "/playerVolume")); QUuid actionId = QUuid::createUuid(); + + qDebug(dcSonos()) << "Setting volume:" << playerId << volume; + + QJsonObject object; + object.insert("volume", QJsonValue::fromVariant(volume)); + QJsonDocument doc(object); + + QNetworkReply *reply = m_networkManager->post(request, doc.toJson(QJsonDocument::Compact)); + connect(reply, &QNetworkReply::finished, this, [reply, actionId, playerId, this] { + reply->deleteLater(); + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + // Check HTTP status code + if (status != 200 || reply->error() != QNetworkReply::NoError) { + qCWarning(dcSonos()) << "Request error:" << status << reply->errorString(); + emit actionExecuted(actionId, false); + return; + } + getPlayerVolume(playerId); + emit actionExecuted(actionId, true); + }); return actionId; } QUuid Sonos::setPlayerRelativeVolume(const QByteArray &playerId, int volumeDelta) { - Q_UNUSED(playerId) - Q_UNUSED(volumeDelta) + QNetworkRequest request; + request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); + request.setRawHeader("Authorization", "Bearer " + m_accessToken); + request.setRawHeader("X-Sonos-Api-Key", m_apiKey); + request.setUrl(QUrl(m_baseControlUrl + "/players/" + playerId + "/playerVolume/relative")); QUuid actionId = QUuid::createUuid(); + + QJsonObject object; + object.insert("volumeDelta", QJsonValue::fromVariant(volumeDelta)); + QJsonDocument doc(object); + + QNetworkReply *reply = m_networkManager->post(request, doc.toJson(QJsonDocument::Compact)); + connect(reply, &QNetworkReply::finished, this, [reply, actionId, playerId, this] { + reply->deleteLater(); + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + // Check HTTP status code + if (status != 200 || reply->error() != QNetworkReply::NoError) { + qCWarning(dcSonos()) << "Request error:" << status << reply->errorString(); + emit actionExecuted(actionId, false); + return; + } + getPlayerVolume(playerId); + emit actionExecuted(actionId, true); + }); return actionId; } QUuid Sonos::setPlayerMute(const QByteArray &playerId, bool mute) { - Q_UNUSED(playerId) - Q_UNUSED(mute) + QNetworkRequest request; + request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); + request.setRawHeader("Authorization", "Bearer " + m_accessToken); + request.setRawHeader("X-Sonos-Api-Key", m_apiKey); + request.setUrl(QUrl(m_baseControlUrl + "/players/" + playerId + "/playerVolume")); QUuid actionId = QUuid::createUuid(); + + QJsonObject object; + object.insert("muted", QJsonValue::fromVariant(mute)); + QJsonDocument doc(object); + + QNetworkReply *reply = m_networkManager->post(request, doc.toJson(QJsonDocument::Compact)); + connect(reply, &QNetworkReply::finished, this, [reply, actionId, playerId, this] { + reply->deleteLater(); + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + // Check HTTP status code + if (status != 200 || reply->error() != QNetworkReply::NoError) { + qCWarning(dcSonos()) << "Request error:" << status << reply->errorString(); + emit actionExecuted(actionId, false); + return; + } + getPlayerVolume(playerId); + emit actionExecuted(actionId, true); + }); return actionId; } -void Sonos::getPlaylist() +void Sonos::getPlaylist(const QString &householdId, const QString &playlistId) { + QNetworkRequest request; + request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); + request.setRawHeader("Authorization", "Bearer " + m_accessToken); + request.setRawHeader("X-Sonos-Api-Key", m_apiKey); + request.setUrl(QUrl(m_baseControlUrl + "/households/" + householdId + "/playlists/getPlaylist")); + + QJsonObject object; + object["playlistId"] = playlistId; + QJsonDocument doc(object); + + QNetworkReply *reply = m_networkManager->post(request, doc.toJson(QJsonDocument::Compact)); + connect(reply, &QNetworkReply::finished, this, [reply, householdId, this] { + reply->deleteLater(); + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + // Check HTTP status code + if (status != 200 || reply->error() != QNetworkReply::NoError) { + qCWarning(dcSonos()) << "Request error:" << status << reply->errorString(); + return; + } + + //qDebug(dcSonos()) << "Received response from Sonos" << reply->readAll(); + QJsonDocument data = QJsonDocument::fromJson(reply->readAll()); + if (!data.isObject()) + return; + + if (!data["tracks"].isArray()) + return; + + PlaylistSummaryObject playlist; + QJsonArray array = data["tracks"].toArray(); + foreach (const QJsonValue & value, array) { + QJsonObject itemObject = value.toObject(); + qDebug(dcSonos()) << "Item ID received:" << itemObject["id"].toString(); + PlaylistTrackObject track; + track.name = itemObject["name"].toString(); + track.album = itemObject["album"].toString(); + track.artist= itemObject["artist"].toString(); + playlist.tracks.append(track); + } + emit playlistSummaryReceived(householdId, playlist); + }); } -void Sonos::getPlaylists() +void Sonos::getPlaylists(const QString &householdId) { + QNetworkRequest request; + request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); + request.setRawHeader("Authorization", "Bearer " + m_accessToken); + request.setRawHeader("X-Sonos-Api-Key", m_apiKey); + request.setUrl(QUrl(m_baseControlUrl + "/households/" + householdId + "/playlists")); + QNetworkReply *reply = m_networkManager->get(request); + connect(reply, &QNetworkReply::finished, this, [reply, householdId, this] { + reply->deleteLater(); + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + // Check HTTP status code + if (status != 200 || reply->error() != QNetworkReply::NoError) { + qCWarning(dcSonos()) << "Request error:" << status << reply->errorString(); + return; + } + + //qDebug(dcSonos()) << "Received response from Sonos" << reply->readAll(); + QJsonDocument data = QJsonDocument::fromJson(reply->readAll()); + if (!data.isObject()) + return; + + if (!data["items"].isArray()) + return; + + QJsonArray array = data["playlists"].toArray(); + QList playlists; + foreach (const QJsonValue & value, array) { + QJsonObject itemObject = value.toObject(); + qDebug(dcSonos()) << "Item ID received:" << itemObject["id"].toString(); + PlaylistObject playlist; + playlist.id = itemObject["id"].toString(); + playlist.name = itemObject["name"].toString(); + playlist.type = itemObject["type"].toString(); + playlist.trackCount = itemObject["trackCount"].toString(); + playlists.append(playlist); + } + emit playlistsReceived(householdId, playlists); + }); } -QUuid Sonos::loadPlaylist() +QUuid Sonos::loadPlaylist(const QString &groupId, const QString &playlistId) { + QNetworkRequest request; + request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); + request.setRawHeader("Authorization", "Bearer " + m_accessToken); + request.setRawHeader("X-Sonos-Api-Key", m_apiKey); + request.setUrl(QUrl(m_baseControlUrl + "/groups/" + groupId + "/playlists")); QUuid actionId = QUuid::createUuid(); + + QJsonObject object; + object["playlistId"] = playlistId; + QJsonDocument doc(object); + + QNetworkReply *reply = m_networkManager->post(request, doc.toJson(QJsonDocument::Compact)); + connect(reply, &QNetworkReply::finished, this, [reply, actionId, this] { + reply->deleteLater(); + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + // Check HTTP status code + if (status != 200 || reply->error() != QNetworkReply::NoError) { + qCWarning(dcSonos()) << "Request error:" << status << reply->errorString(); + emit actionExecuted(actionId, false); + return; + } + emit actionExecuted(actionId, true); + }); return actionId; } -void Sonos::getPlayerSettings() +void Sonos::getPlayerSettings(const QString &playerId) { + QNetworkRequest request; + request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); + request.setRawHeader("Authorization", "Bearer " + m_accessToken); + request.setRawHeader("X-Sonos-Api-Key", m_apiKey); + request.setUrl(QUrl(m_baseControlUrl + "/players/" + playerId + "/settings/player")); + QNetworkReply *reply = m_networkManager->get(request); + connect(reply, &QNetworkReply::finished, this, [reply, playerId, this] { + reply->deleteLater(); + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + // Check HTTP status code + if (status != 200 || reply->error() != QNetworkReply::NoError) { + qCWarning(dcSonos()) << "Request error:" << status << reply->errorString(); + return; + } + QJsonDocument data = QJsonDocument::fromJson(reply->readAll()); + if (!data.isObject()) + return; + + PlayerSettingsObject playerSettings; + playerSettings.monoMode = data["monoMode"].toBool(); + playerSettings.volumeMode = data["volumeMode"].toString(); + playerSettings.wifiDisabled = data["wifiDisable"].toBool(); + playerSettings.volumeScalingFactor = data["wifiDisable"].toDouble(); + emit playerSettingsRecieved(playerId, playerSettings); + }); } -QUuid Sonos::setPlayerSettings() +QUuid Sonos::setPlayerSettings(const QString &playerId, PlayerSettingsObject settings) { + QNetworkRequest request; + request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); + request.setRawHeader("Authorization", "Bearer " + m_accessToken); + request.setRawHeader("X-Sonos-Api-Key", m_apiKey); + request.setUrl(QUrl(m_baseControlUrl + "/players/" + playerId + "/settings/player")); QUuid actionId = QUuid::createUuid(); + + QJsonObject object; + object["volumeMode"] = settings.volumeMode; + object["volumeScalingFactor"] = settings.volumeScalingFactor; + object["monoMode"] = settings.monoMode; + object["wifiDisable"] = settings.wifiDisabled; + QJsonDocument doc(object); + + QNetworkReply *reply = m_networkManager->post(request, doc.toJson(QJsonDocument::Compact)); + connect(reply, &QNetworkReply::finished, this, [reply, actionId, playerId, this] { + reply->deleteLater(); + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + // Check HTTP status code + if (status != 200 || reply->error() != QNetworkReply::NoError) { + qCWarning(dcSonos()) << "Request error:" << status << reply->errorString(); + emit actionExecuted(actionId, false); + return; + } + getPlayerSettings(playerId); + emit actionExecuted(actionId, true); + }); return actionId; } - diff --git a/sonos/sonos.h b/sonos/sonos.h index 541e345e..4ce327a5 100644 --- a/sonos/sonos.h +++ b/sonos/sonos.h @@ -53,10 +53,6 @@ public: bool crossfade; }; - struct PlayerObject { - - }; - /* Represents a Sonos household.*/ struct GroupObject { QString CoordinatorId; //Player acting as the group coordinator for the group @@ -66,17 +62,11 @@ public: QString displayName; //The display name for the group, such as “Living Room” or “Kitchen + 2”. }; - struct GroupVolumeObject { + struct VolumeObject { int volume; //Group volume as an integer between 0 and 100, inclusive. bool muted; //A value indicating whether or not the group is muted bool fixed; //A value indicating whether or not the group volume is fixed or changeable. }; - /* - * Represents a Sonos household.*/ - /*struct HouseholdObject { - QString id; //Identifies a Sonos household. - QString name; //A user-displayable name of the Sonos household - };*/ /* * The music service identifier or a pseudo-service identifier in the case of local library. */ @@ -95,6 +85,7 @@ public: QString name; QString description; QString imageUrl; + ServiceObject service; }; struct PlaylistObject @@ -107,8 +98,8 @@ public: struct PlayerSettingsObject { - int volumeMode; - float volumeScalingFactor; + QString volumeMode; + double volumeScalingFactor; bool monoMode; bool wifiDisabled; }; @@ -173,8 +164,7 @@ public: * A single music track or audio file. Tracks are identified by type, * which determines the key values for the object types included. * The following fields are shared by all types of tracks. */ - struct TrackObject - { + struct TrackObject { QString type; QString name; QString imageUrl; @@ -190,8 +180,7 @@ public: /* An item in a queue. Used for cloud queue tracks and radio stations that have track-like data * for the currently playing content. For example, the currentItem and nextItem parameters in the * metadataStatus event are item object types.*/ - struct ItemObject - { + struct ItemObject { QString itemId; TrackObject track; bool deleted; @@ -204,21 +193,28 @@ public: ItemObject nextItem; }; + struct PlaylistTrackObject { + QString name; + QString artist; + QString album; + }; + + struct PlaylistSummaryObject { + QString id; + QString name; + QString type; + QList tracks; + }; + explicit Sonos(NetworkAccessManager *networkManager, const QByteArray &accessToken, QObject *parent = nullptr); void setAccessToken(const QByteArray &accessToken); void getHouseholds(); - void getFavorites(); + void getFavorites(const QString &householdId); void getGroups(const QString &householdId); - QUuid cancelAudioClip(); - QUuid loadAudioClip(); - QUuid loadFavorite(); - - QUuid createGroup(const QString &householdId, QList playerIds); - QUuid modifyGroupMembers(); - QUuid setGroupMembers(const QString &groupId); + QUuid loadFavorite(const QString &groupId, const QString &favouriteId); //Group volume void getGroupVolume(const QString &groupId); //Get the volume and mute state of a group. @@ -254,13 +250,13 @@ public: QUuid setPlayerMute(const QByteArray &playerId, bool mute); //Playlists API namespace - void getPlaylist(); - void getPlaylists(); - QUuid loadPlaylist(); + void getPlaylists(const QString &householdId); + void getPlaylist(const QString &householdId, const QString &playlistId); + QUuid loadPlaylist(const QString &groupId, const QString &playlistId); //Settings - void getPlayerSettings(); - QUuid setPlayerSettings(); + void getPlayerSettings(const QString &playerId); + QUuid setPlayerSettings(const QString &playerId, PlayerSettingsObject settings); private: QByteArray m_baseAuthorizationUrl = "https://api.sonos.com/login/v3/oauth"; @@ -275,12 +271,17 @@ private slots: signals: void connectionChanged(bool connected); void householdIdsReceived(QList householdIds); + void favouritesReceived(const QString &householdId, QList favourites); + void playlistsReceived(const QString &householdId, QList playlists); void groupsReceived(QList groups); + void playlistSummaryReceived(const QString &householdId, PlaylistSummaryObject playlistSummary); void playBackStatusReceived(const QString &groupId, PlayBackObject playBack); void metadataStatusReceived(const QString &groupId, MetadataStatus metaDataStatus); - void volumeReceived(const QString &groupId, GroupVolumeObject groupVolume); + void volumeReceived(const QString &groupId, VolumeObject groupVolume); + void playerVolumeReceived(const QString &playerId, VolumeObject playerVolume); + void playerSettingsRecieved(const QString &playerId, PlayerSettingsObject playerSettings); void actionExecuted(QUuid actionId,bool success); };