From ac58d41ea6382b8c1b392b57acfb03aec6bd91e2 Mon Sep 17 00:00:00 2001 From: nymea Date: Tue, 3 Sep 2019 23:42:49 +0200 Subject: [PATCH] first working version --- sonos/devicepluginsonos.cpp | 179 +++++++-- sonos/devicepluginsonos.h | 11 +- sonos/sonos.cpp | 701 ++++++++++++++++++++++++++++++++---- sonos/sonos.h | 174 +++++---- 4 files changed, 907 insertions(+), 158 deletions(-) diff --git a/sonos/devicepluginsonos.cpp b/sonos/devicepluginsonos.cpp index bba6e3a3..f071275d 100644 --- a/sonos/devicepluginsonos.cpp +++ b/sonos/devicepluginsonos.cpp @@ -45,7 +45,8 @@ DevicePluginSonos::~DevicePluginSonos() Device::DeviceSetupStatus DevicePluginSonos::setupDevice(Device *device) { if (!m_pluginTimer) { - hardwareManager()->pluginTimerManager()->registerTimer(1); + hardwareManager()->pluginTimerManager()->registerTimer(10); + connect(m_pluginTimer, &PluginTimer::timeout, this, &DevicePluginSonos::onPluginTimer); } if(!m_tokenRefreshTimer) { @@ -62,19 +63,25 @@ Device::DeviceSetupStatus DevicePluginSonos::setupDevice(Device *device) pluginStorage()->endGroup(); Sonos *sonos = new Sonos(hardwareManager()->networkManager(), accessToken, this); + connect(sonos, &Sonos::connectionChanged, this, &DevicePluginSonos::onConnectionChanged); + connect(sonos, &Sonos::householdIdsReceived, this, &DevicePluginSonos::onHouseholdIdsReceived); + connect(sonos, &Sonos::groupsReceived, this, &DevicePluginSonos::onGroupsReceived); + connect(sonos, &Sonos::playBackStatusReceived, this, &DevicePluginSonos::onPlayBackStatusReceived); + connect(sonos, &Sonos::metadataStatusReceived, this, &DevicePluginSonos::onMetadataStatusReceived); + connect(sonos, &Sonos::volumeReceived, this, &DevicePluginSonos::onVolumeReceived); + connect(sonos, &Sonos::actionExecuted, this, &DevicePluginSonos::onActionExecuted); m_sonosConnections.insert(device, sonos); } if (device->deviceClassId() == sonosGroupDeviceClassId) { - //set parent ID + } return Device::DeviceSetupStatusSuccess; } DevicePairingInfo DevicePluginSonos::pairDevice(DevicePairingInfo &devicePairingInfo) { - if (devicePairingInfo.deviceClassId() == sonosConnectionDeviceClassId) { QString clientId = "b15cbf8c-a39c-47aa-bd93-635a96e9696c"; QString clientSecret = "c086ba71-e562-430b-a52f-867c6482fd11"; @@ -153,11 +160,12 @@ DevicePairingInfo DevicePluginSonos::confirmPairing(DevicePairingInfo &devicePai pluginStorage()->setValue("refresh_token", refreshToken); pluginStorage()->endGroup(); - /*if (jsonDoc.toVariant().toMap().contains("expires_in")) { + 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(); - m_tokenRefreshTimer->start((expireTime - 20) * 1000); - }*/ + //m_tokenRefreshTimer->start((expireTime - 20) * 1000); + //TODO + } info.setStatus(Device::DeviceErrorNoError); emit pairingFinished(info); @@ -179,7 +187,15 @@ void DevicePluginSonos::postSetupDevice(Device *device) } if (device->deviceClassId() == sonosGroupDeviceClassId) { - + Device *parentDevice = myDevices().findById(device->parentId()); + Sonos *sonos = m_sonosConnections.value(parentDevice); + if (!sonos) { + return; + } + QString groupId = device->paramValue(sonosGroupDeviceGroupIdParamTypeId).toString(); + sonos->getGroupPlaybackStatus(groupId); + sonos->getGroupMetadataStatus(groupId); + sonos->getGroupVolume(groupId); } } @@ -188,7 +204,7 @@ void DevicePluginSonos::startMonitoringAutoDevices() { foreach (Device *device, myDevices()) { if (device->deviceClassId() == sonosGroupDeviceClassId) { - return; // We already have a Auto Mock device... do nothing. + return; } } } @@ -197,13 +213,14 @@ void DevicePluginSonos::deviceRemoved(Device *device) { qCDebug(dcSonos) << "Delete " << device->name(); if (myDevices().empty()) { + hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer); + m_pluginTimer = nullptr; } } Device::DeviceError DevicePluginSonos::executeAction(Device *device, const Action &action) { - Q_UNUSED(action) if (device->deviceClassId() == sonosGroupDeviceClassId) { Sonos *sonos = m_sonosConnections.value(device); QByteArray groupId = device->paramValue(sonosGroupDeviceGroupIdParamTypeId).toByteArray(); @@ -237,29 +254,41 @@ Device::DeviceError DevicePluginSonos::executeAction(Device *device, const Actio } if (action.actionTypeId() == sonosGroupPauseActionTypeId) { - sonos->groupPause(groupId); - return Device::DeviceErrorNoError; + m_pendingActions.insert(sonos->groupPause(groupId), action.id()); + return Device::DeviceErrorAsync; } if (action.actionTypeId() == sonosGroupStopActionTypeId) { - sonos->groupPause(groupId); - return Device::DeviceErrorNoError; + m_pendingActions.insert(sonos->groupPause(groupId), action.id()); + return Device::DeviceErrorAsync; } if (action.actionTypeId() == sonosGroupMuteActionTypeId) { bool mute = action.param(sonosGroupMuteActionMuteParamTypeId).value().toBool(); - sonos->setGroupMute(groupId, mute); - return Device::DeviceErrorNoError; + m_pendingActions.insert(sonos->setGroupMute(groupId, mute), action.id()); + return Device::DeviceErrorAsync; } if (action.actionTypeId() == sonosGroupSkipNextActionTypeId) { - sonos->groupSkipToNextTrack(groupId); - return Device::DeviceErrorNoError; + m_pendingActions.insert(sonos->groupSkipToNextTrack(groupId), action.id()); + return Device::DeviceErrorAsync; } if (action.actionTypeId() == sonosGroupSkipBackActionTypeId) { - sonos->groupSkipToPreviousTrack(groupId); - return Device::DeviceErrorNoError; + m_pendingActions.insert(sonos->groupSkipToPreviousTrack(groupId), action.id()); + return Device::DeviceErrorAsync; + } + + if (action.actionTypeId() == sonosGroupPlaybackStatusActionTypeId) { + QString playbackStatus = action.param(sonosGroupPlaybackStatusActionPlaybackStatusParamTypeId).value().toString(); + if (playbackStatus == "Playing") { + sonos->groupPlay(groupId); + } else if(playbackStatus == "Stopped") { + sonos->groupPause(groupId); + } else if(playbackStatus == "Paused") { + sonos->groupPause(groupId); + } + return Device::DeviceErrorAsync; } return Device::DeviceErrorActionTypeNotFound; } @@ -267,16 +296,36 @@ Device::DeviceError DevicePluginSonos::executeAction(Device *device, const Actio } 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() +void DevicePluginSonos::onConnectionChanged(bool connected) { Sonos *sonos = static_cast(sender()); Device *device = m_sonosConnections.key(sonos); - device->setStateValue(sonosConnectionConnectedStateTypeId, false); //TODO + device->setStateValue(sonosConnectionConnectedStateTypeId, connected); - //TODO set all groups + foreach (Device *groupDevice, myDevices().filterByParentDeviceId(device->id())) { + groupDevice->setStateValue(sonosGroupConnectedStateTypeId, connected); + } } void DevicePluginSonos::onRefreshTimeout() @@ -310,11 +359,89 @@ void DevicePluginSonos::onRefreshTimeout() }); } -void DevicePluginSonos::onHouseholdIdsReceived(QList householdIds) +void DevicePluginSonos::onHouseholdIdsReceived(QList householdIds) { - qDebug(dcSonos()) << "Household Id received, start to discover groups"; Sonos *sonos = static_cast(sender()); - foreach(QByteArray householdId, householdIds) { + foreach(QString householdId, householdIds) { sonos->getGroups(householdId); } } + +void DevicePluginSonos::onGroupsReceived(QList groupObjects) +{ + Sonos *sonos = static_cast(sender()); + Device *parentDevice = m_sonosConnections.key(sonos); + if (!parentDevice) + return; + + QList deviceDescriptors; + foreach(Sonos::GroupObject groupObject, groupObjects) { + if (!myDevices().filterByParam(sonosGroupDeviceGroupIdParamTypeId, groupObject.groupId).isEmpty()) { + continue; + } + 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); +} + +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; + + switch (playBack.playbackState) { + case Sonos::PlayBackStateIdle: + device->setStateValue(sonosGroupPlaybackStatusStateTypeId, "Stopped"); + break; + case Sonos::PlayBackStatePause: + device->setStateValue(sonosGroupPlaybackStatusStateTypeId, "Paused"); + break; + case Sonos::PlayBackStateBuffering: + case Sonos::PlayBackStatePlaying: + device->setStateValue(sonosGroupPlaybackStatusStateTypeId, "Playing"); + break; + } +} + +void DevicePluginSonos::onMetadataStatusReceived(const QString &groupId, Sonos::MetadataStatus metaDataStatus) +{ + Device *device = myDevices().findByParams(ParamList() << Param(sonosGroupDeviceGroupIdParamTypeId, groupId)); + if (!device) + return; + + 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); +} + +void DevicePluginSonos::onVolumeReceived(const QString &groupId, Sonos::GroupVolumeObject groupVolume) +{ + Device *device = myDevices().findByParams(ParamList() << Param(sonosGroupDeviceGroupIdParamTypeId, groupId)); + if (!device) + return; + + device->setStateValue(sonosGroupVolumeStateTypeId, groupVolume.volume); + device->setStateValue(sonosGroupMuteStateTypeId, groupVolume.muted); +} + +void DevicePluginSonos::onActionExecuted(QUuid sonosActionId, bool success) +{ + if (m_pendingActions.contains(sonosActionId)) { + ActionId nymeaActionId = m_pendingActions.value(sonosActionId); + if (success) { + emit actionExecutionFinished(nymeaActionId, Device::DeviceErrorNoError); + } else { + emit actionExecutionFinished(nymeaActionId, Device::DeviceErrorHardwareFailure); + } + } +} diff --git a/sonos/devicepluginsonos.h b/sonos/devicepluginsonos.h index 075375b0..5820bd73 100644 --- a/sonos/devicepluginsonos.h +++ b/sonos/devicepluginsonos.h @@ -61,13 +61,18 @@ private: QByteArray m_sonosConnectionAccessToken; QByteArray m_sonosConnectionRefreshToken; + QHash m_pendingActions; private slots: void onPluginTimer(); - void onConnectionChanged(); + void onConnectionChanged(bool connected); void onRefreshTimeout(); - void onHouseholdIdsReceived(QList householdIds); - + void onHouseholdIdsReceived(QList householdIds); + 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 onActionExecuted(QUuid actionId, bool success); }; #endif // DEVICEPLUGINSONOS_H diff --git a/sonos/sonos.cpp b/sonos/sonos.cpp index 3d606860..7aa13fe6 100644 --- a/sonos/sonos.cpp +++ b/sonos/sonos.cpp @@ -24,6 +24,8 @@ #include "extern-plugininfo.h" #include +#include +#include Sonos::Sonos(NetworkAccessManager *networkmanager, const QByteArray &accessToken, QObject *parent) : QObject(parent), @@ -50,29 +52,40 @@ void Sonos::getHouseholds() reply->deleteLater(); int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + connectionChanged(true); // 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 = reply->readAll(); - if (!data.isObject()) + //qDebug(dcSonos()) << "Received response from Sonos" << reply->readAll(); + QJsonDocument data = QJsonDocument::fromJson(reply->readAll()); + if (!data.isObject()) { + qDebug(dcSonos()) << "Household ID: Recieved invalide JSON object"; return; - QList households; - emit householdObjectsReceived(households);*/ + } + QList households; + QJsonArray jsonArray = data["households"].toArray(); + foreach (const QJsonValue & value, jsonArray) { + QJsonObject obj = value.toObject(); + qDebug(dcSonos()) << "Household ID received:" << obj["id"].toString(); + households.append(obj["id"].toString()); + } + emit householdIdsReceived(households); }); } -void Sonos::cancelAudioClip() +QUuid Sonos::cancelAudioClip() { - + QUuid actionId = QUuid::createUuid(); + return actionId; } -void Sonos::loadAudioClip() +QUuid Sonos::loadAudioClip() { - + QUuid actionId = QUuid::createUuid(); + return actionId; } void Sonos::getFavorites() @@ -80,12 +93,13 @@ void Sonos::getFavorites() } -void Sonos::loadFavorite() +QUuid Sonos::loadFavorite() { - + QUuid actionId = QUuid::createUuid(); + return actionId; } -void Sonos::getGroups(const QByteArray &householdId) +void Sonos::getGroups(const QString &householdId) { QNetworkRequest request; request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); @@ -103,124 +117,673 @@ void Sonos::getGroups(const QByteArray &householdId) return; } - qDebug(dcSonos()) << "Received response from Sonos" << reply->readAll(); - /*QJsonDocument data = reply->readAll(); + //qDebug(dcSonos()) << "Received response from Sonos" << reply->readAll(); + QJsonDocument data = QJsonDocument::fromJson(reply->readAll()); if (!data.isObject()) return; - QList households; - emit householdObjectsReceived(households);*/ + if (!data["groups"].isArray()) + return; + + QJsonArray array = data["groups"].toArray(); + QList groupObjects; + foreach (const QJsonValue & value, array) { + QJsonObject obj = value.toObject(); + qDebug(dcSonos()) << "Group ID received:" << obj["id"].toString(); + GroupObject group; + group.groupId = obj["id"].toString(); + group.displayName = obj["name"].toString(); + groupObjects.append(group); + } + emit groupsReceived(groupObjects); }); } -void Sonos::createGroup(const QByteArray &householdId, QList playerIds) +QUuid Sonos::createGroup(const QString &householdId, QList playerIds) { Q_UNUSED(householdId) Q_UNUSED(playerIds) + QUuid actionId = QUuid::createUuid(); + return actionId; } -void Sonos::modifyGroupMembers() +QUuid Sonos::modifyGroupMembers() { - + QUuid actionId = QUuid::createUuid(); + return actionId; } -void Sonos::setGroupMembers(const QByteArray &groupId) +QUuid Sonos::setGroupMembers(const QString &groupId) { Q_UNUSED(groupId) + QUuid actionId = QUuid::createUuid(); + return actionId; } -void Sonos::getGroupVolume(const QByteArray &groupId) +void Sonos::getGroupVolume(const QString &groupId) { - Q_UNUSED(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 + "/groupVolume")); + QNetworkReply *reply = m_networkManager->get(request); + connect(reply, &QNetworkReply::finished, this, [reply, 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(); + return; + } + + //qDebug(dcSonos()) << "Received response from Sonos" << reply->readAll(); + QJsonDocument data = QJsonDocument::fromJson(reply->readAll()); + if (!data.isObject()) + return; + + GroupVolumeObject groupVolume; + + groupVolume.volume = data["volume"].toInt(); + groupVolume.muted = data["muted"].toBool(); + groupVolume.fixed = data["fixed"].toBool(); + + emit volumeReceived(groupId, groupVolume); + }); } -void Sonos::setGroupVolume(const QByteArray &groupId, int volume) +QUuid Sonos::setGroupVolume(const QString &groupId, int volume) { - Q_UNUSED(groupId) - 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 + "/groups/" + groupId + "/groupVolume")); + QUuid actionId = QUuid::createUuid(); + + QJsonObject object; + object.insert("volume", QJsonValue::fromVariant(volume)); + 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; + } + emit actionExecuted(actionId, true); + }); + return actionId; } -void Sonos::setGroupMute(const QByteArray &groupId, bool mute) +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); + request.setRawHeader("X-Sonos-Api-Key", m_apiKey); + request.setUrl(QUrl(m_baseControlUrl + "/groups/" + groupId + "/groupVolume/mute")); + QUuid actionId = QUuid::createUuid(); + + QJsonObject object; + object.insert("muted", QJsonValue::fromVariant(mute)); + 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; + } + emit actionExecuted(actionId, true); + }); + return actionId; } -void Sonos::setGroupRelativeVolume(const QByteArray &groupId, int volumeDelta) +QUuid Sonos::setGroupRelativeVolume(const QString &groupId, int volumeDelta) { - Q_UNUSED(groupId) - 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 + "/groups/" + groupId + "/groupVolume/relative")); + QUuid actionId = QUuid::createUuid(); + + QJsonObject object; + 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] { + 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::getGroupPlaybackStatus(const QByteArray &groupId) +void Sonos::getGroupPlaybackStatus(const QString &groupId) { - Q_UNUSED(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")); + QNetworkReply *reply = m_networkManager->get(request); + connect(reply, &QNetworkReply::finished, this, [reply, this, groupId] { + 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; + + PlayBackObject playBackObject; + QJsonObject object = data.object(); + playBackObject.itemId = object["itemId"].toString(); + playBackObject.positionMillis = object["positionMillis"].toInt(); + playBackObject.previousItemId = object["previousItemId"].toInt(); + playBackObject.previousPositionMillis = object["previousPositionMillis"].toInt(); + QString playBackState = object["playbackState"].toString(); + if (playBackState.contains("BUFFERING")) { + playBackObject.playbackState = PlayBackStateBuffering; + } else if (playBackState.contains("IDLE")) { + playBackObject.playbackState = PlayBackStateIdle; + } else if (playBackState.contains("PAUSE")) { + playBackObject.playbackState = PlayBackStatePause; + } else if (playBackState.contains("PLAYING")) { + playBackObject.playbackState = PlayBackStatePlaying; + } + playBackObject.isDucking = object["isDucking"].toBool(); + playBackObject.queueVersion = object["queueVersion"].toString(); + emit playBackStatusReceived(groupId, playBackObject); + }); } -void Sonos::groupLoadLineIn(const QByteArray &groupId) +QUuid Sonos::groupLoadLineIn(const QString &groupId) { Q_UNUSED(groupId) - + QUuid actionId = QUuid::createUuid(); + return actionId; } -void Sonos::groupPlay(const QByteArray &groupId) +QUuid Sonos::groupPlay(const QString &groupId) { - Q_UNUSED(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/play")); + QUuid actionId = QUuid::createUuid(); + + QNetworkReply *reply = m_networkManager->post(request, ""); + 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::groupPause(const QByteArray &groupId) +QUuid Sonos::groupPause(const QString &groupId) { - Q_UNUSED(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/pause")); + QUuid actionId = QUuid::createUuid(); + + QNetworkReply *reply = m_networkManager->post(request, ""); + 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::groupSeek(const QByteArray &groupId) +QUuid Sonos::groupSeek(const QString &groupId, int possitionMillis) { Q_UNUSED(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/seek")); + QUuid actionId = QUuid::createUuid(); + + QJsonObject object; + object.insert("positionMillis", QJsonValue::fromVariant(possitionMillis)); + 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; + } + emit actionExecuted(actionId, true); + }); + return actionId; } -void Sonos::groupSeekRelative(const QByteArray &groupId, int millis) +QUuid Sonos::groupSeekRelative(const QString &groupId, int deltaMillis) { Q_UNUSED(groupId) - Q_UNUSED(millis) + 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/seekRelative")); + QUuid actionId = QUuid::createUuid(); + + QJsonObject object; + object.insert("deltaMillis", QJsonValue::fromVariant(deltaMillis)); + 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; + } + emit actionExecuted(actionId, true); + }); + return actionId; } -void Sonos::groupSetPlayModes(const QByteArray &groupId, PlayMode playMode) +QUuid Sonos::groupSetPlayModes(const QString &groupId, PlayMode playMode) { - Q_UNUSED(groupId) - Q_UNUSED(playMode) + 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/playMode")); + QUuid actionId = QUuid::createUuid(); + + QJsonObject object; + QJsonObject playModesObject; + playModesObject["repeat"] = playMode.repeat; + playModesObject["repeatOne"] = playMode.repeatOne; + playModesObject["crossfade"] = playMode.crossfade; + playModesObject["shuffle"] = playMode.shuffle; + object.insert("playModes", playModesObject); + 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; + } + emit actionExecuted(actionId, true); + }); + return actionId; } -void Sonos::groupSetShuffle(const QByteArray &groupId, bool shuffle) +QUuid Sonos::groupSetShuffle(const QString &groupId, bool shuffle) { - Q_UNUSED(groupId) - Q_UNUSED(shuffle) + 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/playMode")); + QUuid actionId = QUuid::createUuid(); + + QJsonObject object; + QJsonObject playModesObject; + playModesObject["shuffle"] = shuffle; + object.insert("playModes", playModesObject); + 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; + } + emit actionExecuted(actionId, true); + }); + return actionId; } -void Sonos::groupSetRepeat(const QByteArray &groupId, RepeatMode repeatMode) +QUuid Sonos::groupSetRepeat(const QString &groupId, RepeatMode repeatMode) { - Q_UNUSED(groupId) - Q_UNUSED(repeatMode) + 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/playMode")); + QUuid actionId = QUuid::createUuid(); + + QJsonObject object; + QJsonObject playModesObject; + if (repeatMode == RepeatModeAll) { + playModesObject["repeat"] = true; + playModesObject["repeatOne"] = false; + } else if (repeatMode == RepeatModeOne) { + playModesObject["repeat"] = false; + playModesObject["repeatOne"] = true; + } else if (repeatMode == RepeatModeAll) { + 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] { + 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::groupSetCrossfade(const QByteArray &groupId, bool crossfade) +QUuid Sonos::groupSetCrossfade(const QString &groupId, bool crossfade) { - Q_UNUSED(groupId) - Q_UNUSED(crossfade) + 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/playMode")); + QUuid actionId = QUuid::createUuid(); + + QJsonObject object; + QJsonObject playModesObject; + playModesObject["crossfade"] = crossfade; + object.insert("playModes", playModesObject); + 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; + } + emit actionExecuted(actionId, true); + }); + return actionId; } -void Sonos::groupSkipToNextTrack(const QByteArray &groupId) +QUuid Sonos::groupSkipToNextTrack(const QString &groupId) { - Q_UNUSED(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/skipToNextTrack")); + QUuid actionId = QUuid::createUuid(); + + QNetworkReply *reply = m_networkManager->post(request, ""); + 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(); + actionExecuted(actionId, false); + return; + } + actionExecuted(actionId, true); + }); + return actionId; } -void Sonos::groupSkipToPreviousTrack(const QByteArray &groupId) +QUuid Sonos::groupSkipToPreviousTrack(const QString &groupId) { - Q_UNUSED(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/skipToPreviousTrack")); + QUuid actionId = QUuid::createUuid(); + + QNetworkReply *reply = m_networkManager->post(request, ""); + 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(); + actionExecuted(actionId, false); + return; + } + actionExecuted(actionId, true); + }); + return actionId; } -void Sonos::groupTogglePlayPause(const QByteArray &groupId) +QUuid Sonos::groupTogglePlayPause(const QString &groupId) { - Q_UNUSED(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/togglePlayPause")); + QUuid actionId = QUuid::createUuid(); + + QNetworkReply *reply = m_networkManager->post(request, ""); + 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(); + actionExecuted(actionId, false); + return; + } + actionExecuted(actionId, true); + }); + return actionId; +} + +void Sonos::getGroupMetadataStatus(const QString &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 + "/playbackMetadata")); + QNetworkReply *reply = m_networkManager->get(request); + connect(reply, &QNetworkReply::finished, this, [reply, 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(); + return; + } + + QJsonDocument data = QJsonDocument::fromJson(reply->readAll()); + if (!data.isObject()) + return; + + MetadataStatus metaDataStatus; + QJsonObject object = data.object(); + + if (object.contains("container")) { + ContainerObject container; + QJsonObject containerObject = object["container"].toObject(); + container.name = containerObject["name"].toString(); + container.type = containerObject["type"].toString(); + container.imageUrl = containerObject["imageUrl"].toString(); + if (containerObject.contains("service")) { + ServiceObject service; + QJsonObject serviceObject = containerObject.value("artist").toObject(); + service.name = serviceObject["name"].toString(); + container.service = service; + } + if (containerObject.contains("id")) { + qDebug(dcSonos()) << "Item ID" << containerObject.value("id").toString(); + } + metaDataStatus.container = container; + } + + if (object.contains("currentItem")) { + QJsonObject currentItemObject = object["currentItem"].toObject(); + ItemObject currentItem; + if (currentItemObject.contains("track")) { + TrackObject track; + QJsonObject trackObject = currentItemObject["track"].toObject(); + + if (trackObject.contains("artist")) { + ArtistObject artist; + QJsonObject artistObject = trackObject["artist"].toObject(); + artist.name = artistObject["name"].toString(); + //qDebug(dcSonos()) << "Track object contains artist" << artist.name; + track.artist = artist; + } + if (trackObject.contains("album")) { + AlbumObject album; + QJsonObject albumObject = trackObject["album"].toObject(); + album.name = albumObject["name"].toString(); + //qDebug(dcSonos()) << "Track object contains album" << album.name; + track.album = album; + } + if (trackObject.contains("service")) { + ServiceObject service; + QJsonObject serviceObject = trackObject["service"].toObject(); + service.name = serviceObject["name"].toString(); + //qDebug(dcSonos()) << "Track object contains service" << service.name; + track.service = service; + } + if (trackObject.contains("id")) { + qDebug(dcSonos()) << "Item ID" << trackObject.value("id").toString(); + } + + track.type = trackObject["type"].toString(); + track.name = trackObject["name"].toString(); + track.imageUrl = trackObject["imageUrl"].toString(); + track.trackNumber = trackObject["trackNumber"].toInt(); + track.durationMillis = trackObject["durationMillis"].toInt(); + + currentItem.track = track; + } + metaDataStatus.currentItem = currentItem; + } + + if (object.contains("nextItem")) { + ItemObject nextItem; + QJsonObject nextItemObject = object["nextItem"].toObject(); + if (nextItemObject.contains("track")) { + TrackObject track; + QJsonObject trackObject = nextItemObject.value("track").toObject(); + + if (trackObject.contains("artist")) { + ArtistObject artist; + QJsonObject artistObject = trackObject.value("artist").toObject(); + artist.name = artistObject["name"].toString(); + //qDebug(dcSonos()) << "Track object contains artist" << artist.name; + track.artist = artist; + } + if (trackObject.contains("album")) { + AlbumObject album; + QJsonObject albumObject = trackObject.value("album").toObject(); + album.name = albumObject["name"].toString(); + //qDebug(dcSonos()) << "Track object contains album" << album.name; + track.album = album; + } + if (trackObject.contains("service")) { + ServiceObject service; + QJsonObject serviceObject = trackObject.value("service").toObject(); + service.name = serviceObject["name"].toString(); + //qDebug(dcSonos()) << "Track object contains service" << service.name; + track.service = service; + } + if (trackObject.contains("id")) { + qDebug(dcSonos()) << "Item ID" << trackObject.value("id").toString(); + } + track.type = trackObject["type"].toString(); + track.name = trackObject["name"].toString(); + track.imageUrl = trackObject["imageUrl"].toString(); + track.trackNumber = trackObject["trackNumber"].toInt(); + track.durationMillis = trackObject["durationMillis"].toInt(); + + nextItem.track = track; + } + metaDataStatus.nextItem = nextItem; + } + emit metadataStatusReceived(groupId, metaDataStatus); + }); } void Sonos::getPlayerVolume(const QByteArray &playerId) @@ -228,22 +791,28 @@ void Sonos::getPlayerVolume(const QByteArray &playerId) Q_UNUSED(playerId) } -void Sonos::setPlayerVolume(const QByteArray &playerId, int volume) +QUuid Sonos::setPlayerVolume(const QByteArray &playerId, int volume) { Q_UNUSED(playerId) Q_UNUSED(volume) + QUuid actionId = QUuid::createUuid(); + return actionId; } -void Sonos::setPlayerRelativeVolume(const QByteArray &playerId, int volumeDelta) +QUuid Sonos::setPlayerRelativeVolume(const QByteArray &playerId, int volumeDelta) { Q_UNUSED(playerId) Q_UNUSED(volumeDelta) + QUuid actionId = QUuid::createUuid(); + return actionId; } -void Sonos::setPlayerMute(const QByteArray &playerId, bool mute) +QUuid Sonos::setPlayerMute(const QByteArray &playerId, bool mute) { Q_UNUSED(playerId) Q_UNUSED(mute) + QUuid actionId = QUuid::createUuid(); + return actionId; } void Sonos::getPlaylist() @@ -256,9 +825,10 @@ void Sonos::getPlaylists() } -void Sonos::loadPlaylist() +QUuid Sonos::loadPlaylist() { - + QUuid actionId = QUuid::createUuid(); + return actionId; } void Sonos::getPlayerSettings() @@ -266,8 +836,9 @@ void Sonos::getPlayerSettings() } -void Sonos::setPlayerSettings() +QUuid Sonos::setPlayerSettings() { - + QUuid actionId = QUuid::createUuid(); + return actionId; } diff --git a/sonos/sonos.h b/sonos/sonos.h index d91c1191..541e345e 100644 --- a/sonos/sonos.h +++ b/sonos/sonos.h @@ -39,6 +39,13 @@ public: RepeatModeNone }; + enum PlayBackState { + PlayBackStateBuffering, + PlayBackStateIdle, + PlayBackStatePause, + PlayBackStatePlaying + }; + struct PlayMode { bool repeat; bool repeatOne; @@ -50,15 +57,20 @@ public: }; - /* Represents a Sonos household.*/ - struct GroupObject { - QByteArray CoordinatorId; //Player acting as the group coordinator for the group - QByteArray groupId; //The ID of the group. - QString playbackState; //The playback state corresponding to the group. - QList playerIds; //The IDs of the primary players in the group. - QString displayName; //The display name for the group, such as “Living Room” or “Kitchen + 2”. - }; + /* Represents a Sonos household.*/ + struct GroupObject { + QString CoordinatorId; //Player acting as the group coordinator for the group + QString groupId; //The ID of the group. + QString playbackState; //The playback state corresponding to the group. + QList playerIds; //The IDs of the primary players in the group. + QString displayName; //The display name for the group, such as “Living Room” or “Kitchen + 2”. + }; + struct GroupVolumeObject { + 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 { @@ -100,16 +112,7 @@ public: bool monoMode; bool wifiDisabled; }; - /* - * 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 - { - bool canCrossfade; - bool canSkip; - int durationMillis; - }; + /* The music object identifier for the item in a music service. * This identifies the content within a music service, the music service, @@ -130,17 +133,6 @@ public: bool isVisible; }; - /* 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 - { - QString itemId; - TrackObject track; - bool deleted; - TrackPoliciesObject policies; - }; - /* The artist of the track. */ struct ArtistObject { @@ -154,7 +146,6 @@ public: { QString name; ArtistObject artist; - //TODO }; struct ContainerObject @@ -167,76 +158,131 @@ public: //tags enum }; + struct PlayBackObject { + QString itemId; + bool isDucking; + PlayBackState playbackState; + PlayMode playMode; + uint positionMillis; + QString previousItemId; + uint previousPositionMillis; + QString queueVersion; + }; + + /* + * 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 + { + QString type; + QString name; + QString imageUrl; + int trackNumber; + bool canCrossfade; + bool canSkip; + int durationMillis; + ArtistObject artist; + AlbumObject album; + ServiceObject service; + }; + + /* 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 + { + QString itemId; + TrackObject track; + bool deleted; + TrackPoliciesObject policies; + }; + + struct MetadataStatus { + ContainerObject container; + ItemObject currentItem; + ItemObject nextItem; + }; + explicit Sonos(NetworkAccessManager *networkManager, const QByteArray &accessToken, QObject *parent = nullptr); void setAccessToken(const QByteArray &accessToken); void getHouseholds(); - - void cancelAudioClip(); - void loadAudioClip(); - void getFavorites(); - void loadFavorite(); + void getGroups(const QString &householdId); - void getGroups(const QByteArray &householdId); - void createGroup(const QByteArray &householdId, QList playerIds); - void modifyGroupMembers(); - void setGroupMembers(const QByteArray &groupId); + QUuid cancelAudioClip(); + QUuid loadAudioClip(); + QUuid loadFavorite(); - //group volume - void getGroupVolume(const QByteArray &groupId); //Get the volume and mute state of a group. - void setGroupVolume(const QByteArray &groupId, int volume); //Set group volume to a specific level and unmute the group if muted. - void setGroupMute(const QByteArray &groupId, bool mute); //Mute and unmute the group. - void setGroupRelativeVolume(const QByteArray &groupId, int volumeDelta); //Increase or decrease group volume. + QUuid createGroup(const QString &householdId, QList playerIds); + QUuid modifyGroupMembers(); + QUuid setGroupMembers(const QString &groupId); + + //Group volume + void getGroupVolume(const QString &groupId); //Get the volume and mute state of a group. + //Group volume actions + QUuid setGroupVolume(const QString &groupId, int volume); //Set group volume to a specific level and unmute the group if muted. + QUuid setGroupMute(const QString &groupId, bool mute); //Mute and unmute the group. + QUuid setGroupRelativeVolume(const QString &groupId, int volumeDelta); //Increase or decrease group volume. //group playback - void getGroupPlaybackStatus(const QByteArray &groupId); - void groupLoadLineIn(const QByteArray &groupId); - void groupPlay(const QByteArray &groupId); - void groupPause(const QByteArray &groupId); - void groupSeek(const QByteArray &groupId); - void groupSeekRelative(const QByteArray &groupId, int deltaMillis); - void groupSetPlayModes(const QByteArray &groupId, PlayMode playMode); - void groupSetShuffle(const QByteArray &groupId, bool shuffle); - void groupSetRepeat(const QByteArray &groupId, RepeatMode repeatMode); - void groupSetCrossfade(const QByteArray &groupId, bool crossfade); + void getGroupPlaybackStatus(const QString &groupId); - void groupSkipToNextTrack(const QByteArray &groupId); - void groupSkipToPreviousTrack(const QByteArray &groupId); - void groupTogglePlayPause(const QByteArray &groupId); + //Group playback actions + QUuid groupLoadLineIn(const QString &groupId); + QUuid groupPlay(const QString &groupId); + QUuid groupPause(const QString &groupId); + QUuid groupSeek(const QString &groupId, int possitionMillis); + QUuid groupSeekRelative(const QString &groupId, int deltaMillis); + QUuid groupSetPlayModes(const QString &groupId, PlayMode playMode); + QUuid groupSetShuffle(const QString &groupId, bool shuffle); + QUuid groupSetRepeat(const QString &groupId, RepeatMode repeatMode); + QUuid groupSetCrossfade(const QString &groupId, bool crossfade); + QUuid groupSkipToNextTrack(const QString &groupId); + QUuid groupSkipToPreviousTrack(const QString &groupId); + QUuid groupTogglePlayPause(const QString &groupId); //playbackMetadata + void getGroupMetadataStatus(const QString &groupId); // playerVolume void getPlayerVolume(const QByteArray &playerId); - void setPlayerVolume(const QByteArray &playerId, int volume); - void setPlayerRelativeVolume(const QByteArray &playerId, int volumeDelta); - void setPlayerMute(const QByteArray &playerId, bool mute); + QUuid setPlayerVolume(const QByteArray &playerId, int volume); + QUuid setPlayerRelativeVolume(const QByteArray &playerId, int volumeDelta); + QUuid setPlayerMute(const QByteArray &playerId, bool mute); //Playlists API namespace void getPlaylist(); void getPlaylists(); - void loadPlaylist(); + QUuid loadPlaylist(); //Settings void getPlayerSettings(); - void setPlayerSettings(); + QUuid setPlayerSettings(); private: QByteArray m_baseAuthorizationUrl = "https://api.sonos.com/login/v3/oauth"; QByteArray m_baseControlUrl = "https://api.ws.sonos.com/control/api/v1"; - QByteArray m_accessToken; QByteArray m_apiKey = "b15cbf8c-a39c-47aa-bd93-635a96e9696c"; + QByteArray m_accessToken; NetworkAccessManager *m_networkManager = nullptr; private slots: signals: - void householdIdsReceived(QList householdIds); + void connectionChanged(bool connected); + void householdIdsReceived(QList householdIds); void groupsReceived(QList groups); + void playBackStatusReceived(const QString &groupId, PlayBackObject playBack); + void metadataStatusReceived(const QString &groupId, MetadataStatus metaDataStatus); + void volumeReceived(const QString &groupId, GroupVolumeObject groupVolume); + + void actionExecuted(QUuid actionId,bool success); + }; #endif // SONOS_H