diff --git a/denon/heos.cpp b/denon/heos.cpp index c7cdfb0f..fd3a6447 100644 --- a/denon/heos.cpp +++ b/denon/heos.cpp @@ -53,7 +53,7 @@ Heos::~Heos() m_socket->close(); } -void Heos::connectHeos() +void Heos::connectDevice() { if (m_socket->state() == QAbstractSocket::ConnectingState) { return; @@ -61,6 +61,18 @@ void Heos::connectHeos() m_socket->connectToHost(m_hostAddress, 1255); } +bool Heos::connected() +{ + return m_socket->isOpen(); +} + + +void Heos::disconnectDevice() +{ + m_socket->close(); +} + + /******************************** * PLAYER COMMANDS ********************************/ @@ -182,17 +194,19 @@ void Heos::getNowPlayingMedia(int playerId) m_socket->write(cmd); } -HeosPlayer *Heos::getPlayer(int playerId) -{ - return m_heosPlayers.value(playerId); -} - void Heos::getPlayers() { QByteArray cmd = "heos://player/get_players\r\n"; m_socket->write(cmd); } +void Heos::getPlayerInfo(int playerId) +{ + QByteArray cmd = "heos://player/get_player_info?pid=" + QVariant(playerId).toByteArray() + "\r\n"; + qCDebug(dcDenon) << "Get player info:" << cmd; + m_socket->write(cmd); +} + void Heos::getVolume(int playerId) { QByteArray cmd = "heos://player/get_volume?pid=" + QVariant(playerId).toByteArray() + "\r\n"; @@ -331,6 +345,19 @@ void Heos::setGroupMute(int groupId, bool mute) m_socket->write(cmd); } +void Heos::setGroup(QList playerIds) +{ + QByteArray cmd = "heos://group/set_group?pid="; + foreach(int playerId, playerIds) { + cmd.append(QVariant(playerId).toByteArray()); + cmd.append(','); + } + cmd.resize(cmd.size()-1); //remove last ',' + cmd.append("\r\n"); + qCDebug(dcDenon) << "Set group:" << cmd; + m_socket->write(cmd); +} + void Heos::toggleGroupMute(int groupId) { QByteArray cmd = "heos://group/toggle_mute?gid=" + QVariant(groupId).toByteArray() + "\r\n"; @@ -483,7 +510,7 @@ void Heos::onDisconnected() { qCDebug(dcDenon()) << "Disconnected from" << m_hostAddress.toString() << "try reconnecting in 5 seconds"; QTimer::singleShot(5000, this, [this](){ - connectHeos(); + connectDevice(); }); emit connectionStatusChanged(false); } @@ -495,7 +522,6 @@ void Heos::onError(QAbstractSocket::SocketError socketError) void Heos::readData() { - QByteArray data; QJsonParseError error; @@ -534,9 +560,11 @@ void Heos::readData() if (enabled.contains("off")) { qDebug(dcDenon) << "Events are disabled"; m_eventRegistered = false; + emit systemEventsEnabled(false); } else { qDebug(dcDenon) << "Events are enabled"; m_eventRegistered = true; + emit systemEventsEnabled(true); } } else if (command.contains("check_account")) { @@ -551,8 +579,9 @@ void Heos::readData() } else if (command.contains("prettify_json_response")) { + } else { + qDebug(dcDenon) << "Unhandled Heos system command" << command; } - } /* 4.2 Player Commands * 4.2.1 Get Players * 4.2.2 Get Player Info @@ -578,7 +607,7 @@ void Heos::readData() * 4.2.25 Get QuickSelects [LS AVR Only] * 4.2.26 Check for Firmware Update */ - if (command.startsWith("player")) { + } else if (command.startsWith("player")) { int playerId = 0; if (message.hasQueryItem("pid")) { playerId = message.queryItemValue("pid").toInt(); @@ -586,28 +615,45 @@ void Heos::readData() if (command.contains("get_players")) { QVariantList payloadVariantList = jsonDoc.toVariant().toMap().value("payload").toList(); - + QList players; foreach (const QVariant &payloadEntryVariant, payloadVariantList) { - playerId = payloadEntryVariant.toMap().value("pid").toInt(); - if(!m_heosPlayers.contains(playerId)){ - QString serialNumber = payloadEntryVariant.toMap().value("serial").toString(); - QString name = payloadEntryVariant.toMap().value("name").toString(); - HeosPlayer *heosPlayer = new HeosPlayer(playerId, name, serialNumber, this); - m_heosPlayers.insert(playerId, heosPlayer); - emit playerDiscovered(heosPlayer); - } + HeosPlayer *player = new HeosPlayer(payloadEntryVariant.toMap().value("pid").toInt()); + player->setSerialNumber(payloadEntryVariant.toMap().value("serial").toString()); + player->setName(payloadEntryVariant.toMap().value("name").toString()); + getPlayerInfo(player->playerId()); + players.append(player); } - }else if (command.contains("get_player_info")) { + emit playersRecieved(players); + + } else if (command.contains("get_player_info")) { //update heos player info + int pid = dataMap.value("payload").toMap().value("pid").toInt(); + HeosPlayer *player = new HeosPlayer(pid); + player->setName(dataMap.value("payload").toMap().value("name").toString()); + if (dataMap.value("payload").toMap().contains("gid")) { + player->setGroupId(dataMap.value("payload").toMap().value("gid").toInt()); + } else { + player->setGroupId(-1); //no group assigned + } + player->setPlayerModel(dataMap.value("payload").toMap().value("model").toString()); + player->setPlayerVersion(dataMap.value("payload").toMap().value("version").toString()); + player->setLineOut(dataMap.value("payload").toMap().value("lineout").toString()); + player->setControl(dataMap.value("payload").toMap().value("control").toString()); + player->setSerialNumber(dataMap.value("payload").toMap().value("serial").toString()); + player->setNetwork(dataMap.value("payload").toMap().value("network").toString()); + emit playerInfoRecieved(player); + } else if (command.contains("get_now_playing_media")) { QString artist = dataMap.value("payload").toMap().value("artist").toString(); QString song = dataMap.value("payload").toMap().value("song").toString(); QString artwork = dataMap.value("payload").toMap().value("image_url").toString(); QString album = dataMap.value("payload").toMap().value("album").toString(); - SOURCE_ID sourceId = SOURCE_ID(dataMap.value("payload").toMap().value("sid").toInt()); + QString sourceId = dataMap.value("payload").toMap().value("sid").toString(); + qDebug(dcDenon) << "Now playing" << playerId << sourceId << artist << album << song; emit nowPlayingMediaStatusReceived(playerId, sourceId, artist, album, song, artwork); - }else if (command.contains("get_play_state") || command.contains("set_play_state")) { + + } else if (command.contains("get_play_state") || command.contains("set_play_state")) { if (message.hasQueryItem("state")) { PLAYER_STATE playState = PLAYER_STATE_STOP; if (message.queryItemValue("state").contains("play")) { @@ -657,22 +703,23 @@ void Heos::readData() QVariantMap payloadVariantMap = jsonDoc.toVariant().toMap().value("payload").toMap(); bool updateExist = payloadVariantMap.value("update").toString().contains("exist"); emit playerUpdateAvailable(playerId, updateExist); + } else { + qDebug(dcDenon) << "Unhandled Heos group command" << command; } - } /* - * 4.3 Group Commands - * 4.3.1 Get Groups - * 4.3.2 Get Group Info - * 4.3.3 Set Group - * 4.3.4 Get Group Volume - * 4.3.5 Set Group Volume - * 4.2.6 Group Volume Up - * 4.2.7 Group Volume Down - * 4.3.8 Get Group Mute - * 4.3.9 Set Group Mute - * 4.3.10 Toggle Group Mute - */ - if (command.startsWith("group")) { + * 4.3 Group Commands + * 4.3.1 Get Groups + * 4.3.2 Get Group Info + * 4.3.3 Set Group + * 4.3.4 Get Group Volume + * 4.3.5 Set Group Volume + * 4.2.6 Group Volume Up + * 4.2.7 Group Volume Down + * 4.3.8 Get Group Mute + * 4.3.9 Set Group Mute + * 4.3.10 Toggle Group Mute + */ + } else if (command.startsWith("group")) { int groupId = 0; if (message.hasQueryItem("gid")) { qDebug(dcDenon) << "Group id" << message.queryItemValue("gid"); @@ -698,8 +745,32 @@ void Heos::readData() } emit groupsReceived(groups); } else if (command.contains("get_group_info")) { + QVariantMap payloadVariantMap = jsonDoc.toVariant().toMap().value("payload").toMap(); + GroupObject group; + group.groupId = payloadVariantMap.value("gid").toInt(); + group.name = payloadVariantMap.value("name").toString(); + if (!payloadVariantMap.value("players").toList().isEmpty()) { + QVariantList playerlist = payloadVariantMap.value("players").toList(); + foreach (const QVariant &playerVariant, playerlist) { + PlayerObject player; + player.name = playerVariant.toMap().value("name").toString(); + player.playerId = playerVariant.toMap().value("pid").toInt(); + group.players.append(player); + } + } + emit groupInfoReceived(group); } else if (command.contains("set_group")) { + if (message.hasQueryItem("gid")) { + + int groupId = message.queryItemValue("gid").toInt(); + QString groupName = message.queryItemValue("name"); + emit setGroupReceived(groupId, groupName); + } else { + //No group Id so it must have been an ungoup request + int playerId = message.queryItemValue("pid").toInt(); + emit deleteGroupReceived(playerId); + } } else if (command.contains("get_volume") || command.contains("set_volume")) { @@ -722,8 +793,10 @@ void Heos::readData() } } else if (command.contains("toggle_mute")) { + } else { + qDebug(dcDenon) << "Unhandled Heos group command" << command; } - } + /* 4.4 Browse Commands * 4.4.1 Get Music Sources - "command": "browse/get_music_sources" @@ -742,7 +815,7 @@ void Heos::readData() * 4.4.15 Delete HEOS Playlist - "command": "browse/delete_playlist " * 4.4.17 Retrieve Album Metadata - "command": "browse/retrieve_metadata", */ - if (command.startsWith("browse") || command.startsWith(" browse")) { + } else if (command.startsWith("browse") || command.startsWith(" browse")) { if (command.contains("get_music_sources") || command.contains("get_source_info")) { qDebug(dcDenon()) << "Get music source request response received" << command; @@ -835,8 +908,10 @@ void Heos::readData() } else if (command.contains("retrieve_metadata")) { + } else { + qDebug(dcDenon) << "Unhandled Heos browse command" << command; } - } + /* * 5. Change Events (Unsolicited Responses) 5.1 Sources Changed @@ -853,7 +928,7 @@ void Heos::readData() * 5.12 Group Volume Changed * 5.13 User Changed */ - if (command.startsWith("event")) { + } else if (command.startsWith("event")) { if (command.contains("sources_changed")) { emit sourcesChanged(); @@ -864,7 +939,7 @@ void Heos::readData() emit groupsChanged(); } else if (command.contains("player_state_changed")) { - qDebug() << "Player state changed"; + qDebug(dcDenon()) << "Player state changed"; if (message.hasQueryItem("pid")) { int playerId = message.queryItemValue("pid").toInt(); if (message.hasQueryItem("state")) { @@ -880,13 +955,13 @@ void Heos::readData() } } } else if (command.contains("player_now_playing_changed")) { - qDebug(dcDenon()) << "Player now playing changed"; + qDebug(dcDenon()) << "Player now playing changed, player id:" << message.queryItemValue("pid").toInt(); if (message.hasQueryItem("pid")) { int playerId = message.queryItemValue("pid").toInt(); emit playerNowPlayingChanged(playerId); } } else if (command.contains("player_now_playing_progress")) { - qDebug(dcDenon()) << "Player now playing progress"; + //qDebug(dcDenon()) << "Player now playing progress"; if (message.hasQueryItem("pid")) { int playerId = message.queryItemValue("pid").toInt(); int currentPossition = message.queryItemValue("cur_pos").toInt(); @@ -994,7 +1069,11 @@ void Heos::readData() username = message.queryItemValue("un"); } emit userChanged(signedIn, username); + } else { + qDebug(dcDenon) << "Unhandled Heos event"; } + } else { + qDebug(dcDenon) << "Unhandled Heos category" << command; } } } diff --git a/denon/heos.h b/denon/heos.h index 85dc27a5..617ea9bf 100644 --- a/denon/heos.h +++ b/denon/heos.h @@ -38,6 +38,11 @@ #include "heosplayer.h" #include "heostypes.h" +#include "devices/device.h" +#include "types/mediabrowseritem.h" +#include "devices/browseresult.h" +#include "devices/browseritemresult.h" + class Heos : public QObject { Q_OBJECT @@ -46,10 +51,12 @@ public: explicit Heos(const QHostAddress &hostAddress, QObject *parent = nullptr); ~Heos(); - void connectHeos(); + void connectDevice(); + void disconnectDevice(); + bool connected(); + void setAddress(QHostAddress address); QHostAddress getAddress(); - HeosPlayer *getPlayer(int playerId); // Heos system commands void registerForChangeEvents(bool state); //By default HEOS speaker does not send Change events. Controller needs to send this command with enable=on when it is ready to receive unsolicit responses from CLI. Please refer to "Driver Initialization" section regarding when to register for change events. @@ -62,6 +69,7 @@ public: //Player Get Calls void getPlayers(); //get a list of players associated with this heos master + void getPlayerInfo(int playerId); void getPlayerState(int playerId); void getVolume(int playerId); void getNowPlayingMedia(int playerId); @@ -87,9 +95,12 @@ public: void getGroupInfo(int groupId); void getGroupVolume(int groupId); void getGroupMute(int groupId); + //Group Set Calls void setGroupVolume(int groupId, bool volume); void setGroupMute(int groupId, bool mute); + void setGroup(QList playerIds); + void deleteGroup(int leadPlayerId); void toggleGroupMute(int groupId); void groupVolumeUp(int groupId, int step = 5); void groupVolumeDown(int groupId, int step = 5); @@ -101,8 +112,6 @@ public: void browseSource(const QString &sourceId); void browseSourceContainers(const QString &sourceId, const QString &containerId); - //void search(); - //Play commands void playStation(int playerId, const QString &sourceId, const QString &containerId, const QString &mediaId, const QString &stationName); void playPresetStation(int playerId, int presetNumber); @@ -114,14 +123,15 @@ private: bool m_eventRegistered = false; QHostAddress m_hostAddress; QTcpSocket *m_socket = nullptr; - QHash m_heosPlayers; void setConnected(const bool &connected); signals: - void playerDiscovered(HeosPlayer *heosPlayer); void connectionStatusChanged(bool status); + void systemEventsEnabled(bool status); void playersChanged(); + void playersRecieved(QList heosPlayers); + void playerInfoRecieved(HeosPlayer *heosPlayers); void playerQueueChanged(int playerId); void playerPlayStateReceived(int playerId, PLAYER_STATE state); void playerShuffleModeReceived(int playerId, bool shuffle); @@ -134,12 +144,16 @@ signals: void playerNowPlayingChanged(int playerId); void groupsReceived(QList groups); // Callback of getGroups() + void groupInfoReceived(GroupObject group); void groupVolumeReceived(int groupId, int volume); void groupMuteStatusReceived(int groupId, bool mute); void groupsChanged(); + void setGroupReceived(int groupId, const QString &groupName); // Callback of setGroup() + void deleteGroupReceived(int leadPlayerId); // Callback of deleteGroup(); + void sourcesChanged(); - void nowPlayingMediaStatusReceived(int playerId, SOURCE_ID source, QString artist, QString album, QString Song, QString artwork); + void nowPlayingMediaStatusReceived(int playerId, const QString &sourceId, const QString &artist, const QString &album, const QString &song, const QString &artwork); void musicSourcesReceived(QList musicSources); //callback of getMusicSource, not associated to a playerId void browseRequestReceived(const QString &sourceId, const QString &containerId, QList musicSources, QList mediaItems); //callback of browseSource diff --git a/denon/heosplayer.cpp b/denon/heosplayer.cpp index 0bf536f3..09b70c79 100644 --- a/denon/heosplayer.cpp +++ b/denon/heosplayer.cpp @@ -30,15 +30,13 @@ #include "heosplayer.h" -HeosPlayer::HeosPlayer(int playerId, QObject *parent) : - QObject(parent), +HeosPlayer::HeosPlayer(int playerId) : m_playerId(playerId) { } -HeosPlayer::HeosPlayer(int playerId, QString name, QString serialNumber, QObject *parent) : - QObject(parent), +HeosPlayer::HeosPlayer(int playerId, const QString &name, const QString &serialNumber) : m_playerId(playerId), m_serialNumber(serialNumber), m_name(name) @@ -50,7 +48,7 @@ QString HeosPlayer::name() return m_name; } -void HeosPlayer::setName(QString name) +void HeosPlayer::setName(const QString &name) { m_name = name; } @@ -75,29 +73,59 @@ QString HeosPlayer::playerModel() return m_playerModel; } +void HeosPlayer::setPlayerModel(const QString &playerModel) +{ + m_playerModel = playerModel; +} + QString HeosPlayer::playerVersion() { return m_playerVersion; } +void HeosPlayer::setPlayerVersion(const QString &playerVersion) +{ + m_playerVersion = playerVersion; +} + QString HeosPlayer::network() { return m_network; } +void HeosPlayer::setNetwork(const QString &network) +{ + m_network = network; +} + QString HeosPlayer::serialNumber() { return m_serialNumber; } +void HeosPlayer::setSerialNumber(const QString &serialNumber) +{ + m_serialNumber = serialNumber; +} + QString HeosPlayer::lineOut() { return m_lineOut; } +void HeosPlayer::setLineOut(const QString &lineout) +{ + m_lineOut = lineout; +} + QString HeosPlayer::control() { return m_control; } +void HeosPlayer::setControl(const QString &control) +{ + m_control = control; +} + diff --git a/denon/heosplayer.h b/denon/heosplayer.h index f6d0a9a6..7976e8cf 100644 --- a/denon/heosplayer.h +++ b/denon/heosplayer.h @@ -31,26 +31,31 @@ #ifndef HEOSPLAYER_H #define HEOSPLAYER_H -#include +#include -class HeosPlayer : public QObject +class HeosPlayer { - Q_OBJECT public: - explicit HeosPlayer(int playerId, QObject *parent = nullptr); - explicit HeosPlayer(int playerId, QString name, QString serialNumber, QObject *parent = nullptr); + explicit HeosPlayer(int playerId); + explicit HeosPlayer(int playerId, const QString &name, const QString &serialNumber); QString name(); - void setName(QString name); + void setName(const QString &name); int playerId(); int groupId(); void setGroupId(int groupId); QString playerModel(); + void setPlayerModel(const QString &playerModel); QString playerVersion(); + void setPlayerVersion(const QString &playerVersion); QString network(); + void setNetwork(const QString &network); QString serialNumber(); + void setSerialNumber(const QString &serialNumber); QString lineOut(); + void setLineOut(const QString &lineout); QString control(); + void setControl(const QString &control); private: int m_playerId; @@ -62,7 +67,6 @@ private: QString m_playerModel; QString m_playerVersion; QString m_control; - }; #endif // HEOSPLAYER_H diff --git a/denon/integrationplugindenon.cpp b/denon/integrationplugindenon.cpp index 8d05f1de..d49f55ae 100644 --- a/denon/integrationplugindenon.cpp +++ b/denon/integrationplugindenon.cpp @@ -37,12 +37,14 @@ #include "network/upnp/upnpdiscoveryreply.h" #include "platform/platformzeroconfcontroller.h" #include "network/zeroconf/zeroconfservicebrowser.h" +#include "types/mediabrowseritem.h" #include #include #include #include #include +#include IntegrationPluginDenon::IntegrationPluginDenon() { @@ -184,36 +186,43 @@ void IntegrationPluginDenon::setupThing(ThingSetupInfo *info) denonConnection->connectDevice(); return; - } - if (thing->thingClassId() == heosThingClassId) { - qCDebug(dcDenon) << "Setup Denon device" << thing->paramValue(heosThingIpParamTypeId).toString(); + } else if (device->deviceClassId() == heosDeviceClassId) { + qCDebug(dcDenon) << "Setup Denon device" << device->paramValue(heosDeviceIpParamTypeId).toString(); + + QHostAddress address(device->paramValue(heosDeviceIpParamTypeId).toString()); + if (address.isNull()) { + qCWarning(dcDenon) << "Could not parse ip address" << device->paramValue(heosDeviceIpParamTypeId).toString(); + info->finish(Device::DeviceErrorInvalidParameter, QT_TR_NOOP("The given IP address is not valid.")); + return; + } - QHostAddress address(thing->paramValue(heosThingIpParamTypeId).toString()); Heos *heos = new Heos(address, this); - - connect(heos, &Heos::connectionStatusChanged, this, &IntegrationPluginDenon::onHeosConnectionChanged); - connect(heos, &Heos::playerDiscovered, this, &IntegrationPluginDenon::onHeosPlayerDiscovered); - connect(heos, &Heos::playStateReceived, this, &IntegrationPluginDenon::onHeosPlayStateReceived); - connect(heos, &Heos::repeatModeReceived, this, &IntegrationPluginDenon::onHeosRepeatModeReceived); - connect(heos, &Heos::shuffleModeReceived, this, &IntegrationPluginDenon::onHeosShuffleModeReceived); - connect(heos, &Heos::muteStatusReceived, this, &IntegrationPluginDenon::onHeosMuteStatusReceived); - connect(heos, &Heos::volumeStatusReceived, this, &IntegrationPluginDenon::onHeosVolumeStatusReceived); + connect(heos, &Heos::playersChanged, this, &IntegrationPluginDenon::onHeosPlayersChanged); + connect(heos, &Heos::playersRecieved, this, &IntegrationPluginDenon::onHeosPlayersReceived); + connect(heos, &Heos::playerInfoRecieved, this, &IntegrationPluginDenon::onHeosPlayerInfoRecieved); + connect(heos, &Heos::playerPlayStateReceived, this, &IntegrationPluginDenon::onHeosPlayStateReceived); + connect(heos, &Heos::playerRepeatModeReceived, this, &IntegrationPluginDenon::onHeosRepeatModeReceived); + connect(heos, &Heos::playerShuffleModeReceived, this, &IntegrationPluginDenon::onHeosShuffleModeReceived); + connect(heos, &Heos::playerMuteStatusReceived, this, &IntegrationPluginDenon::onHeosMuteStatusReceived); + connect(heos, &Heos::playerVolumeReceived, this, &IntegrationPluginDenon::onHeosVolumeStatusReceived); connect(heos, &Heos::nowPlayingMediaStatusReceived, this, &IntegrationPluginDenon::onHeosNowPlayingMediaStatusReceived); + connect(heos, &Heos::playerNowPlayingChanged, this, &IntegrationPluginDenon::onHeosPlayerNowPlayingChanged); connect(heos, &Heos::musicSourcesReceived, this, &IntegrationPluginDenon::onHeosMusicSourcesReceived); - connect(heos, &Heos::mediaItemsReceived, this, &IntegrationPluginDenon::onHeosMediaItemsReceived); connect(heos, &Heos::browseRequestReceived, this, &IntegrationPluginDenon::onHeosBrowseRequestReceived); connect(heos, &Heos::browseErrorReceived, this, &IntegrationPluginDenon::onHeosBrowseErrorReceived); connect(heos, &Heos::playerQueueChanged, this, &IntegrationPluginDenon::onHeosPlayerQueueChanged); + connect(heos, &Heos::groupsReceived, this, &IntegrationPluginDenon::onHeosGroupsReceived); + connect(heos, &Heos::groupsChanged, this, &IntegrationPluginDenon::onHeosGroupsChanged); - m_heos.insert(thing->id(), heos); + m_heos.insert(device->id(), heos); m_asyncHeosSetups.insert(heos, info); // In case the setup is cancelled before we finish it... connect(info, &QObject::destroyed, this, [this, info, heos]() { m_asyncHeosSetups.remove(heos); }); - heos->connectHeos(); + heos->connectDevice(); return; } @@ -228,24 +237,24 @@ void IntegrationPluginDenon::thingRemoved(Thing *thing) { qCDebug(dcDenon) << "Delete " << thing->name(); + AvrConnection *denonConnection = m_avrConnections.take(thing->id()); + if (thing->thingClassId() == AVRX1000ThingClassId) { - AvrConnection *denonConnection = m_avrConnections.value(thing->id()); - m_avrConnections.remove(thing->id()); - - denonConnection->disconnectDevice(); - denonConnection->deleteLater(); - } - - if (thing->thingClassId() == heosThingClassId) { if (m_avrConnections.contains(thing->id())) { AvrConnection *denonConnection = m_avrConnections.take(thing->id()); denonConnection->disconnectDevice(); denonConnection->deleteLater(); } + } else if (thing->thingClassId() == heosThingClassId) { + if (m_heos.contains(thing->id())) { + Heos *heos = m_heos.take(thing->id()); + heos->deleteLater(); + } } if (myThings().empty()) { hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer); + m_pluginTimer = nullptr; } } @@ -376,7 +385,9 @@ void IntegrationPluginDenon::postSetupThing(Thing *thing) { if (thing->thingClassId() == heosThingClassId) { Heos *heos = m_heos.value(thing->id()); + device->setStateValue(heosConnectedStateTypeId, heos->connected()); heos->getPlayers(); + heos->getGroups(); } if (thing->thingClassId() == heosPlayerThingClassId) { @@ -547,6 +558,7 @@ void IntegrationPluginDenon::onHeosConnectionChanged(bool status) { Heos *heos = static_cast(sender()); heos->registerForChangeEvents(true); +<<<<<<< HEAD:denon/integrationplugindenon.cpp Thing *thing = myThings().findById(m_heos.key(heos)); if (!thing) @@ -563,6 +575,24 @@ void IntegrationPluginDenon::onHeosConnectionChanged(bool status) } } thing->setStateValue(heosConnectedStateTypeId, status); +======= + if (status) { + // and from the first setup + if (m_asyncHeosSetups.contains(heos)) { + DeviceSetupInfo *info = m_asyncHeosSetups.take(heos); + info->finish(Device::DeviceErrorNoError); + } + } + + Device *device = myDevices().findById(m_heos.key(heos)); + if (!device) + return; + + if (device->deviceClassId() == heosDeviceClassId) { + // if the device is connected + + device->setStateValue(heosConnectedStateTypeId, status); +>>>>>>> added join/unjoin group:denon/IntegrationPlugindenon.cpp // update connection status for all child devices foreach (Thing *playerDevice, myThings()) { if (playerDevice->thingClassId() == heosPlayerThingClassId) { @@ -574,23 +604,45 @@ void IntegrationPluginDenon::onHeosConnectionChanged(bool status) } } -void DevicePluginDenon::onHeosPlayersChanged() +void IntegrationPluginDenon::onHeosPlayersChanged() { Heos *heos = static_cast(sender()); heos->getPlayers(); } +<<<<<<< HEAD:denon/integrationplugindenon.cpp void IntegrationPluginDenon::onHeosPlayerDiscovered(HeosPlayer *heosPlayer) { +======= +void IntegrationPluginDenon::onHeosPlayersReceived(QList heosPlayers) { + +>>>>>>> added join/unjoin group:denon/IntegrationPlugindenon.cpp Heos *heos = static_cast(sender()); +<<<<<<< HEAD:denon/integrationplugindenon.cpp Thing *thing = myThings().findById(m_heos.key(heos)); foreach (Thing *heosPlayerThing, myThings()) { if(heosPlayerThing->thingClassId() == heosPlayerThingClassId) { if (heosPlayerThing->paramValue(heosPlayerThingPlayerIdParamTypeId).toInt() == heosPlayer->playerId()) return; +======= + QList heosPlayerDescriptors; + foreach (HeosPlayer *player, heosPlayers) { + DeviceDescriptor descriptor(heosPlayerDeviceClassId, player->name(), player->playerModel(), device->id()); + ParamList params; + if (!myDevices().filterByParam(heosPlayerDevicePlayerIdParamTypeId, player->playerId()).isEmpty()) { + continue; +>>>>>>> added join/unjoin group:denon/IntegrationPlugindenon.cpp } + params.append(Param(heosPlayerDeviceModelParamTypeId, player->playerModel())); + params.append(Param(heosPlayerDevicePlayerIdParamTypeId, player->playerId())); + params.append(Param(heosPlayerDeviceSerialNumberParamTypeId, player->serialNumber())); + params.append(Param(heosPlayerDeviceVersionParamTypeId, player->playerVersion())); + descriptor.setParams(params); + qCDebug(dcDenon) << "Found new heos player" << player->name(); + heosPlayerDescriptors.append(descriptor); } +<<<<<<< HEAD:denon/integrationplugindenon.cpp QList heosPlayerDescriptors; ThingDescriptor descriptor(heosPlayerThingClassId, heosPlayer->name(), heosPlayer->playerModel(), thing->id()); ParamList params; @@ -605,6 +657,21 @@ void IntegrationPluginDenon::onHeosPlayerDiscovered(HeosPlayer *heosPlayer) { } void IntegrationPluginDenon::onHeosPlayStateReceived(int playerId, PLAYER_STATE state) +======= + + //TODO remove devices + //TODO remove player from player Buffer + autoDevicesAppeared(heosPlayerDescriptors); +} + +void IntegrationPluginDenon::onHeosPlayerInfoRecieved(HeosPlayer *heosPlayer) +{ + qDebug(dcDenon()) << "Heos player info received" << heosPlayer->name() << heosPlayer->playerId() << heosPlayer->groupId(); + m_playerBuffer.insert(heosPlayer->playerId(), heosPlayer); +} + +void IntegrationPluginDenon::onHeosPlayStateReceived(int playerId, PLAYER_STATE state) +>>>>>>> added join/unjoin group:denon/IntegrationPlugindenon.cpp { foreach(Thing *thing, myThings().filterByParam(heosPlayerThingPlayerIdParamTypeId, playerId)) { if (state == PLAYER_STATE_PAUSE) { @@ -657,6 +724,7 @@ void IntegrationPluginDenon::onHeosVolumeStatusReceived(int playerId, int volume } } +<<<<<<< HEAD:denon/integrationplugindenon.cpp void IntegrationPluginDenon::onHeosNowPlayingMediaStatusReceived(int playerId, SOURCE_ID sourceId, QString artist, QString album, QString song, QString artwork) { foreach(Thing *thing, myThings().filterByParam(heosPlayerThingPlayerIdParamTypeId, playerId)) { @@ -739,6 +807,19 @@ void IntegrationPluginDenon::onHeosNowPlayingMediaStatusReceived(int playerId, S thing->setStateValue(heosPlayerSourceStateTypeId, source); break; } +======= +void IntegrationPluginDenon::onHeosNowPlayingMediaStatusReceived(int playerId, const QString &sourceId, const QString &artist, const QString &album, const QString &song, const QString &artwork) +{ + Device *device = myDevices().filterByParam(heosPlayerDevicePlayerIdParamTypeId, playerId).first(); + if (!device) + return; + + device->setStateValue(heosPlayerArtistStateTypeId, artist); + device->setStateValue(heosPlayerTitleStateTypeId, song); + device->setStateValue(heosPlayerArtworkStateTypeId, artwork); + device->setStateValue(heosPlayerCollectionStateTypeId, album); + device->setStateValue(heosPlayerSourceStateTypeId, sourceId); +>>>>>>> added join/unjoin group:denon/IntegrationPlugindenon.cpp } @@ -748,12 +829,37 @@ void IntegrationPluginDenon::onHeosMusicSourcesReceived(QList if (m_pendingGetSourcesRequest.contains(heos)) { BrowseResult *result = m_pendingGetSourcesRequest.take(heos); foreach(MusicSourceObject source, musicSources) { - BrowserItem item; + MediaBrowserItem item; item.setDisplayName(source.name); item.setId("source=" + QString::number(source.sourceId)); - item.setThumbnail(source.image_url); item.setExecutable(false); item.setBrowsable(true); + item.setIcon(BrowserItem::BrowserIconMusic); + if (source.name == "Amazon") { + item.setMediaIcon(MediaBrowserItem::MediaBrowserIconAmazon); + } else if (source.name == "Deezer") { + item.setMediaIcon(MediaBrowserItem::MediaBrowserIconDeezer); + } else if (source.name == "Napster") { + item.setMediaIcon(MediaBrowserItem::MediaBrowserIconNapster); + } else if (source.name == "SoundCloud") { + item.setMediaIcon(MediaBrowserItem::MediaBrowserIconSoundCloud); + } else if (source.name == "Tidal") { + item.setMediaIcon(MediaBrowserItem::MediaBrowserIconTidal); + } else if (source.name == "TuneIn") { + item.setMediaIcon(MediaBrowserItem::MediaBrowserIconTuneIn); + } else if (source.name == "Local Music") { + item.setMediaIcon(MediaBrowserItem::MediaBrowserIconDisk); + } else if (source.name == "Playlists") { + item.setMediaIcon(MediaBrowserItem::MediaBrowserIconPlaylist); + } else if (source.name == "History") { + item.setMediaIcon(MediaBrowserItem::MediaBrowserIconRecentlyPlayed); + } else if (source.name == "AUX Input") { + item.setMediaIcon(MediaBrowserItem::MediaBrowserIconAux); + } else if (source.name == "Favorites") { + item.setIcon(BrowserItem::BrowserIconFavorites); + } else { + item.setThumbnail(source.image_url); + } result->addItem(item); qDebug(dcDenon()) << "Music source received:" << source.name << source.type << source.sourceId << source.image_url; } @@ -763,7 +869,7 @@ void IntegrationPluginDenon::onHeosMusicSourcesReceived(QList void IntegrationPluginDenon::onHeosMediaItemsReceived(QList mediaItems) -void DevicePluginDenon::onHeosBrowseRequestReceived(const QString &sourceId, const QString &containerId, QList musicSources, QList mediaItems) +void IntegrationPluginDenon::onHeosBrowseRequestReceived(const QString &sourceId, const QString &containerId, QList musicSources, QList mediaItems) { QString identifier; if (containerId.isEmpty()) { @@ -784,7 +890,7 @@ void IntegrationPluginDenon::onHeosBrowseRequestReceived(QListaddItem(item); } foreach(MusicSourceObject source, musicSources) { - BrowserItem item; + MediaBrowserItem item; item.setDisplayName(source.name); qDebug(dcDenon()) << "Adding Item" << source.name << source.sourceId; - //item.setDescription("test"); item.setId("source=" + QString::number(source.sourceId)); - item.setThumbnail(source.image_url); + item.setIcon(BrowserItem::BrowserIconMusic); + if (source.name.contains("Amazon")) { + item.setMediaIcon(MediaBrowserItem::MediaBrowserIconAmazon); + } else if (source.name == "Deezer") { + item.setMediaIcon(MediaBrowserItem::MediaBrowserIconDeezer); + } else if (source.name == "Napster") { + item.setMediaIcon(MediaBrowserItem::MediaBrowserIconNapster); + } else if (source.name == "SoundCloud") { + item.setMediaIcon(MediaBrowserItem::MediaBrowserIconSoundCloud); + } else if (source.name == "Tidal") { + item.setMediaIcon(MediaBrowserItem::MediaBrowserIconTidal); + } else if (source.name == "TuneIn") { + item.setMediaIcon(MediaBrowserItem::MediaBrowserIconTuneIn); + } else if (source.name == "Local Music") { + item.setMediaIcon(MediaBrowserItem::MediaBrowserIconDisk); + } else if (source.name == "Playlists") { + item.setMediaIcon(MediaBrowserItem::MediaBrowserIconPlaylist); + } else if (source.name == "History") { + item.setMediaIcon(MediaBrowserItem::MediaBrowserIconRecentlyPlayed); + } else if (source.name == "AUX Input") { + item.setMediaIcon(MediaBrowserItem::MediaBrowserIconAux); + } else if (source.name == "Favorites") { + item.setIcon(BrowserItem::BrowserIconFavorites); + } else { + item.setThumbnail(source.image_url); + } item.setExecutable(false); item.setBrowsable(true); result->addItem(item); @@ -839,13 +969,31 @@ void IntegrationPluginDenon::onHeosPlayerNowPlayingChanged(int playerId) } -void DevicePluginDenon::onHeosPlayerQueueChanged(int playerId) +void IntegrationPluginDenon::onHeosPlayerQueueChanged(int playerId) { Heos *heos = static_cast(sender()); heos->getNowPlayingMedia(playerId); } +<<<<<<< HEAD:denon/integrationplugindenon.cpp void IntegrationPluginDenon::onAvahiServiceEntryAdded(const ZeroConfServiceEntry &serviceEntry) +======= +void IntegrationPluginDenon::onHeosGroupsReceived(QList groups) +{ + m_groupBuffer.clear(); + foreach(GroupObject group, groups) { + m_groupBuffer.insert(group.groupId, group); + } +} + +void IntegrationPluginDenon::onHeosGroupsChanged() +{ + Heos *heos = static_cast(sender()); + heos->getGroups(); +} + +void IntegrationPluginDenon::onAvahiServiceEntryAdded(const ZeroConfServiceEntry &serviceEntry) +>>>>>>> added join/unjoin group:denon/IntegrationPlugindenon.cpp { qCDebug(dcDenon()) << "Avahi service entry added:" << serviceEntry; } @@ -877,9 +1025,55 @@ void IntegrationPluginDenon::browseDevice(BrowseResult *result) if (result->itemId().isEmpty()) { qDebug(dcDenon()) << "Browse source"; + MediaBrowserItem item; + item.setId("type=group"); + item.setIcon(BrowserItem::BrowserIcon::BrowserIconPackage); + item.setBrowsable(true); + item.setExecutable(false); + item.setDisplayName("Groups"); + result->addItem(item); heos->getMusicSources(); m_pendingGetSourcesRequest.insert(heos, result); connect(result, &QObject::destroyed, this, [this, heos](){m_pendingGetSourcesRequest.remove(heos);}); + } else if (result->itemId().startsWith("type=group")){ + qDebug(dcDenon()) << "Browse source" << result->itemId(); + int pid = result->device()->paramValue(heosPlayerDevicePlayerIdParamTypeId).toInt(); + HeosPlayer *browsingPlayer = m_playerBuffer.value(pid); + foreach (GroupObject group, m_groupBuffer) { + MediaBrowserItem item; + item.setBrowsable(true); + item.setExecutable(true); + item.setIcon(BrowserItem::BrowserIconFolder); + item.setDisplayName(group.name); + item.setId(result->itemId() + "&" + "group=" + QString::number(group.groupId)); + // if player is already part of the group set action type id to unjoin + if (browsingPlayer->groupId() == group.groupId) { + item.setActionTypeIds(QList() << heosPlayerUnjoinBrowserItemActionTypeId); + } else { + item.setActionTypeIds(QList() << heosPlayerJoinBrowserItemActionTypeId); + } + result->addItem(item); + } + + foreach (HeosPlayer *player, m_playerBuffer.values()) { + qDebug(dcDenon) << "Adding group item" << player->name(); + if (browsingPlayer->playerId() == player->playerId()) { //player is the current browsing device + continue; + } + if (player->groupId() != -1) {// Dont display players that are already assigned to a group + continue; + } + MediaBrowserItem item; + item.setBrowsable(true); + item.setExecutable(true); + item.setIcon(BrowserItem::BrowserIconFile); + item.setDisplayName(player->name()); + item.setId(result->itemId() + "&player=" + QString::number(player->playerId())); + item.setActionTypeIds(QList() << heosPlayerJoinBrowserItemActionTypeId); + result->addItem(item); + + } + result->finish(Device::DeviceErrorNoError); } else if (result->itemId().startsWith("source=")){ qDebug(dcDenon()) << "Browse source" << result->itemId(); @@ -943,11 +1137,41 @@ void IntegrationPluginDenon::executeBrowserItem(BrowserActionInfo *info) void IntegrationPluginDenon::executeBrowserItemAction(BrowserItemActionInfo *info) { - Heos *kodi = m_heos.value(info->device()->parentId()); - if (!kodi) { + Heos *heos = m_heos.value(info->device()->parentId()); + if (!heos) { info->finish(Device::DeviceErrorHardwareNotAvailable); return; } - qDebug(dcDenon()) << "Execute browse item action called"; + + QUrlQuery query(info->browserItemAction().itemId()); + if (info->browserItemAction().actionTypeId() == heosPlayerJoinBrowserItemActionTypeId) { + if (query.hasQueryItem("player")) { + QList playerIds; + playerIds.append(query.queryItemValue("player").toInt()); + playerIds.append(info->device()->paramValue(heosPlayerDevicePlayerIdParamTypeId).toInt()); + heos->setGroup(playerIds); + } else if(query.hasQueryItem("group")) { + + GroupObject group = m_groupBuffer.value(query.queryItemValue("group").toInt()); + qDebug(dcDenon()) << "Execute browse item action called, Group:" << query.queryItemValue("group").toInt() << group.name; + QList playerIds; + foreach(PlayerObject player, group.players) { + playerIds.append(player.playerId); + } + playerIds.append(info->device()->paramValue(heosPlayerDevicePlayerIdParamTypeId).toInt()); + heos->setGroup(playerIds); + } + } else if (info->browserItemAction().actionTypeId() == heosPlayerUnjoinBrowserItemActionTypeId) { + if(query.hasQueryItem("group")) { + GroupObject group = m_groupBuffer.value(query.queryItemValue("group").toInt()); + QList playerIds; + foreach(PlayerObject player, group.players) { + if (player.playerId != info->device()->paramValue(heosPlayerDevicePlayerIdParamTypeId).toInt()) + playerIds.append(player.playerId); + } + heos->setGroup(playerIds); + } + } + info->finish(Device::DeviceErrorNoError); return; } diff --git a/denon/integrationplugindenon.h b/denon/integrationplugindenon.h index 4b13b0f2..a2205ae9 100644 --- a/denon/integrationplugindenon.h +++ b/denon/integrationplugindenon.h @@ -91,24 +91,31 @@ private: QHash m_pendingBrowserItemActions; QHash m_mediaObjects; + QHash m_groupBuffer; + QHash m_playerBuffer; + private slots: void onPluginTimer(); void onHeosConnectionChanged(bool status); void onHeosPlayersChanged(); - void onHeosPlayerDiscovered(HeosPlayer *heosPlayer); + void onHeosPlayersReceived(QList players); + void onHeosPlayerInfoRecieved(HeosPlayer *player); void onHeosPlayStateReceived(int playerId, PLAYER_STATE state); void onHeosShuffleModeReceived(int playerId, bool shuffle); void onHeosRepeatModeReceived(int playerId, REPEAT_MODE repeatMode); void onHeosMuteStatusReceived(int playerId, bool mute); void onHeosVolumeStatusReceived(int playerId, int volume); - void onHeosNowPlayingMediaStatusReceived(int playerId, SOURCE_ID source, QString artist, QString album, QString Song, QString artwork); + void onHeosNowPlayingMediaStatusReceived(int playerId, const QString &sourceId, const QString &artist, const QString &album, const QString &song, const QString &artwork); void onHeosMusicSourcesReceived(QList musicSources); - //void onHeosMediaItemsReceived(QList mediaItems); + void onHeosBrowseRequestReceived(const QString &sourceId, const QString &containerId, QList musicSources, QList mediaItems); void onHeosBrowseErrorReceived(const QString &sourceId, const QString &containerId, int errorId, const QString &errorMessage); void onHeosPlayerNowPlayingChanged(int playerId); void onHeosPlayerQueueChanged(int playerId); + void onHeosGroupsReceived(QList groups); + void onHeosGroupsChanged(); + void onAvahiServiceEntryAdded(const ZeroConfServiceEntry &serviceEntry); void onAvahiServiceEntryRemoved(const ZeroConfServiceEntry &serviceEntry); diff --git a/denon/integrationplugindenon.json b/denon/integrationplugindenon.json index 7d10edd5..fd5d3e31 100644 --- a/denon/integrationplugindenon.json +++ b/denon/integrationplugindenon.json @@ -392,14 +392,14 @@ ], "browserItemActionTypes": [ { - "id": "34167b7d-c9d0-486c-b82b-0778d69599ea", - "name": "updateLibrary", - "displayName": "Update library" + "id": "73112a01-84c7-4b1d-8b86-71672c110d06", + "name": "join", + "displayName": "Join group" }, { - "id": "02bcfbbe-d929-439e-a04f-68ace25e93a7", - "name": "cleanLibrary", - "displayName": "Clean library" + "id": "1b866b95-1fc7-4b45-ad71-c85e43fcc367", + "name": "unjoin", + "displayName": "Unjoin group" } ] }