hyperion.ng/libsrc/kodivideochecker/KODIVideoChecker.cpp
redPanther 2beccb0912 Webui: extend led hardware config + connection lost page (#226)
* split content and js
tune leds config

* implement connection lost page

* split js/html in huebridge

* add js action for connection lost

* extend led config
make connection loss nicer

* tune led code
add menu entry for grabber

* more tuning of webui

* switch back to botstrap textarea
add v4l to components

* add icon

* extend schema for jsoneditor

* implement ledcolors streaming with 4fps

* implement component state
2016-09-07 20:10:37 +02:00

508 lines
15 KiB
C++

// Qt includes
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <kodivideochecker/KODIVideoChecker.h>
#include <hyperion/Hyperion.h>
using namespace hyperion;
KODIVideoChecker* KODIVideoChecker::_kodichecker = nullptr;
KODIVideoChecker* KODIVideoChecker::initInstance(const QString & address, uint16_t port, bool grabVideo, bool grabPhoto, bool grabAudio, bool grabMenu, bool grabPause, bool grabScreensaver, bool enable3DDetection)
{
if ( KODIVideoChecker::_kodichecker != nullptr )
throw std::runtime_error("KODIVideoChecker::initInstance can be called only one time");
KODIVideoChecker::_kodichecker = new KODIVideoChecker(address, port, grabVideo, grabPhoto, grabAudio, grabMenu, grabPause, grabScreensaver, enable3DDetection);
return KODIVideoChecker::_kodichecker;
}
KODIVideoChecker* KODIVideoChecker::getInstance()
{
return KODIVideoChecker::_kodichecker;
}
// Request player example:
// {"jsonrpc":"2.0","method":"Player.GetActivePlayers", "id":666}
// {"id":666,"jsonrpc":"2.0","result":[{"playerid":1,"type":"video"}]}
// Request playing item example:
// {"id":667,"jsonrpc":"2.0","method":"Player.GetItem","params":{"playerid":1,"properties":["file"]}}
// {"id":667,"jsonrpc":"2.0","result":{"item":{"file":"smb://xbmc:xbmc@192.168.53.12/video/Movies/Avatar (2009)/Avatar.mkv","label":"Avatar","type":"unknown"}}}
// Request if screensaver is on
// {"id":668,"jsonrpc":"2.0","method":"XBMC.GetInfoBooleans","params":{"booleans":["System.ScreenSaverActive"]}}
// {"id":668,"jsonrpc":"2.0","result":{"System.ScreenSaverActive":false}}
// Request stereoscopicmode example:
// {"jsonrpc":"2.0","method":"GUI.GetProperties","params":{"properties":["stereoscopicmode"]},"id":669}
// {"id":669,"jsonrpc":"2.0","result":{"stereoscopicmode":{"label":"Nebeneinander","mode":"split_vertical"}}}
KODIVideoChecker::KODIVideoChecker(const QString & address, uint16_t port, bool grabVideo, bool grabPhoto, bool grabAudio, bool grabMenu, bool grabPause, bool grabScreensaver, bool enable3DDetection)
: QObject()
, _address(address)
, _port(port)
, _activePlayerRequest(R"({"jsonrpc":"2.0","method":"Player.GetActivePlayers", "id":666})")
, _currentPlayingItemRequest(R"({"id":667,"jsonrpc":"2.0","method":"Player.GetItem","params":{"playerid":%1,"properties":["file"]}})")
, _checkScreensaverRequest(R"({"id":668,"jsonrpc":"2.0","method":"XBMC.GetInfoBooleans","params":{"booleans":["System.ScreenSaverActive"]}})")
, _getStereoscopicMode(R"({"jsonrpc":"2.0","method":"GUI.GetProperties","params":{"properties":["stereoscopicmode"]},"id":669})")
, _getKodiVersion(R"({"jsonrpc":"2.0","method":"Application.GetProperties","params":{"properties":["version"]},"id":670})")
, _getCurrentPlaybackState(R"({"id":671,"jsonrpc":"2.0","method":"Player.GetProperties","params":{"playerid":%1,"properties":["speed"]}})")
, _socket()
, _grabVideo(grabVideo)
, _grabPhoto(grabPhoto)
, _grabAudio(grabAudio)
, _grabMenu(grabMenu)
, _grabPause(grabPause)
, _grabScreensaver(grabScreensaver)
, _enable3DDetection(enable3DDetection)
, _previousGrabbingMode(GRABBINGMODE_INVALID)
, _previousVideoMode(VIDEO_2D)
, _currentPlaybackState(false)
, _currentPlayerID(0)
, _kodiVersion(0)
, _log(Logger::getInstance("KODI"))
, _active(false)
, _getCurrentPlaybackStateInitialized(false)
{
// setup socket
connect(&_socket, SIGNAL(readyRead()), this, SLOT(receiveReply()));
connect(&_socket, SIGNAL(disconnected()), this, SLOT(disconnected()));
connect(&_socket, SIGNAL(connected()), this, SLOT(connected()));
connect(&_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(connectionError(QAbstractSocket::SocketError)));
}
KODIVideoChecker::~KODIVideoChecker()
{
stop();
}
void KODIVideoChecker::setConfig(const QString & address, uint16_t port, bool grabVideo, bool grabPhoto, bool grabAudio, bool grabMenu, bool grabPause, bool grabScreensaver, bool enable3DDetection)
{
_address = address;
_port = port;
_grabVideo = grabVideo;
_grabPhoto = grabPhoto;
_grabAudio = grabAudio;
_grabMenu = grabMenu;
_grabPause = grabPause;
_grabScreensaver = grabScreensaver;
_enable3DDetection = enable3DDetection;
_previousGrabbingMode = GRABBINGMODE_INVALID;
_previousVideoMode = VIDEO_2D;
_kodiVersion = 0;
// restart if active
if (_active)
{
stop();
QTimer::singleShot(2000, this, SLOT(()));
}
}
void KODIVideoChecker::start()
{
Info(_log, "started");
_active = true;
reconnect();
}
void KODIVideoChecker::stop()
{
Info(_log, "stopped");
_active = false;
_socket.close();
}
void KODIVideoChecker::componentStateChanged(const hyperion::Components component, bool enable)
{
if (component == COMP_KODICHECKER)
{
if (_active != enable)
{
if (enable) start();
else stop();
Info(_log, "change state to %s", (_active ? "enabled" : "disabled") );
}
Hyperion::getInstance()->getComponentRegister().componentStateChanged(component, _active);
}
}
void KODIVideoChecker::receiveReply()
{
QJsonParseError error;
QByteArray qtreply = _socket.readAll();
QJsonDocument doc = QJsonDocument::fromJson(qtreply, &error);
if (error.error == QJsonParseError::NoError)
{
if (doc.isObject())
{
Debug(_log, "message: %s", doc.toJson(QJsonDocument::Compact).constData());
// Reply
if (doc.object().contains("id"))
{
int id = doc.object()["id"].toInt();
switch (id)
{
case 666:
{
if (doc.object()["result"].isArray())
{
QJsonArray resultArray = doc.object()["result"].toArray();
if (!resultArray.isEmpty())
{
// always start a new video in 2D mode
emit videoMode(VIDEO_2D);
QString type = resultArray[0].toObject()["type"].toString();
int prevCurrentPlayerID = _currentPlayerID;
_currentPlayerID = resultArray[0].toObject()["playerid"].toInt();
// set initial player state
if (! _getCurrentPlaybackStateInitialized && prevCurrentPlayerID == 0 && _currentPlayerID != 0)
{
_socket.write(_getCurrentPlaybackState.arg(_currentPlayerID).toUtf8());
_getCurrentPlaybackStateInitialized = true;
return;
}
if (type == "video")
{
if (_currentPlaybackState)
{
// video is playing
setGrabbingMode(_grabVideo ? GRABBINGMODE_VIDEO : GRABBINGMODE_OFF);
}
else
{
setGrabbingMode(_grabPause ? GRABBINGMODE_PAUSE : GRABBINGMODE_OFF);
}
// request info of the playing item
_socket.write(_currentPlayingItemRequest.arg(_currentPlayerID).toUtf8());
}
else if (type == "picture")
{
if (_currentPlaybackState)
{
// picture is playing
setGrabbingMode(_grabPhoto ? GRABBINGMODE_PHOTO : GRABBINGMODE_OFF);
}
else
{
setGrabbingMode(_grabPause ? GRABBINGMODE_PAUSE : GRABBINGMODE_OFF);
}
}
else if (type == "audio")
{
// audio is playing
if (_currentPlaybackState)
{
// audio is playing
setGrabbingMode(_grabAudio ? GRABBINGMODE_AUDIO : GRABBINGMODE_OFF);
}
else
{
setGrabbingMode(_grabPause ? GRABBINGMODE_PAUSE : GRABBINGMODE_OFF);
}
}
}
else
{
// Nothing is playing.
setGrabbingMode(_grabMenu ? GRABBINGMODE_MENU : GRABBINGMODE_OFF);
}
}
else
{
setGrabbingMode(_grabMenu ? GRABBINGMODE_MENU : GRABBINGMODE_OFF);
}
break;
}
case 667:
{
if (_kodiVersion >= 13)
{
// check of active stereoscopicmode
_socket.write(_getStereoscopicMode.toUtf8());
}
else
{
if (doc.object()["result"].toObject()["item"].toObject().contains("file"))
{
QString filename = doc.object()["result"].toObject()["item"].toObject()["file"].toString();
if (filename.contains("3DSBS", Qt::CaseInsensitive) || filename.contains("HSBS", Qt::CaseInsensitive))
setVideoMode(VIDEO_3DSBS);
else if (filename.contains("3DTAB", Qt::CaseInsensitive) || filename.contains("HTAB", Qt::CaseInsensitive))
setVideoMode(VIDEO_3DTAB);
else
setVideoMode(VIDEO_2D);
}
else
{
setGrabbingMode(_grabMenu ? GRABBINGMODE_MENU : GRABBINGMODE_OFF);
}
}
break;
}
case 668:
{
if (doc.object()["result"].toObject().contains("System.ScreenSaverActive"))
{
// result of System.ScreenSaverActive
if (doc.object()["result"].toObject()["System.ScreenSaverActive"].toBool())
setGrabbingMode(_grabScreensaver ? GRABBINGMODE_SCREENSAVER : GRABBINGMODE_OFF);
else
_socket.write(_activePlayerRequest.toUtf8());
// check here kodi version
if (_socket.state() == QTcpSocket::ConnectedState)
{
if (_kodiVersion == 0)
{
_socket.write(_getKodiVersion.toUtf8());
}
}
}
else
{
setGrabbingMode(_grabMenu ? GRABBINGMODE_MENU : GRABBINGMODE_OFF);
}
break;
}
case 669:
{
if (doc.object()["result"].toObject()["stereoscopicmode"].toObject().contains("mode"))
{
// result of stereoscopicmode
QString mode = doc.object()["result"].toObject()["stereoscopicmode"].toObject()["mode"].toString();
if (mode == "split_vertical")
setVideoMode(VIDEO_3DSBS);
else if (mode == "split_horizontal")
setVideoMode(VIDEO_3DTAB);
}
else
{
setGrabbingMode(_grabMenu ? GRABBINGMODE_MENU : GRABBINGMODE_OFF);
}
break;
}
case 670:
{
if (doc.object()["result"].toObject()["version"].toObject().contains("major"))
{
// kodi major version
_kodiVersion = doc.object()["result"].toObject()["version"].toObject()["major"].toInt();
}
else
{
setGrabbingMode(_grabMenu ? GRABBINGMODE_MENU : GRABBINGMODE_OFF);
}
break;
}
case 671:
{
if (doc.object()["result"].toObject().contains("speed"))
{
// result of Player.PlayPause
_currentPlaybackState = static_cast<bool>(doc.object()["result"].toObject()["speed"].toInt());
}
else
{
setGrabbingMode(_grabMenu ? GRABBINGMODE_MENU : GRABBINGMODE_OFF);
}
break;
}
}
}
// Notification
else if (doc.object().contains("method"))
{
QString method = doc.object()["method"].toString();
if (method == "Player.OnPlay")
{
if (doc.object()["params"].toObject()["data"].toObject()["player"].toObject().contains("speed"))
{
_getCurrentPlaybackStateInitialized = true;
_currentPlaybackState = static_cast<bool>(doc.object()["params"].toObject()["data"].toObject()["player"].toObject()["speed"].toInt());
}
// send a request for the current player state
_socket.write(_activePlayerRequest.toUtf8());
return;
}
else if (method == "Player.OnStop")
{
// the player has stopped
setGrabbingMode(_grabMenu ? GRABBINGMODE_MENU : GRABBINGMODE_OFF);
setVideoMode(VIDEO_2D);
}
else if (method == "Player.OnPause")
{
if (doc.object()["params"].toObject()["data"].toObject()["player"].toObject().contains("speed"))
{
_getCurrentPlaybackStateInitialized = true;
_currentPlaybackState = static_cast<bool>(doc.object()["params"].toObject()["data"].toObject()["player"].toObject()["speed"].toInt());
}
// player at pause
setGrabbingMode(_grabPause ? GRABBINGMODE_PAUSE : GRABBINGMODE_OFF);
}
else if (method == "GUI.OnScreensaverActivated")
setGrabbingMode(_grabScreensaver ? GRABBINGMODE_SCREENSAVER : GRABBINGMODE_OFF);
else if (method == "GUI.OnScreensaverDeactivated")
_socket.write(_activePlayerRequest.toUtf8());
else if (method == "Playlist.OnAdd" &&
(doc.object()["params"]
.toObject()["data"]
.toObject()["item"]
.toObject()["type"]) == QString("picture"))
{
// picture viewer is playing
setGrabbingMode(_grabPhoto ? GRABBINGMODE_PHOTO : GRABBINGMODE_OFF);
}
else if (method == "Input.OnInputFinished")
{
// This Event is fired when Kodi Login
_socket.write(_activePlayerRequest.toUtf8());
_socket.write(_checkScreensaverRequest.toUtf8());
if (_currentPlayerID != 0)
_socket.write(_getCurrentPlaybackState.arg(_currentPlayerID).toUtf8());
}
}
}
else
{
Debug(_log, "Incomplete data");
}
}
}
void KODIVideoChecker::connected()
{
Info(_log, "Connected");
_getCurrentPlaybackStateInitialized = false;
// send a request for the current player state
_socket.write(_activePlayerRequest.toUtf8());
_socket.write(_checkScreensaverRequest.toUtf8());
}
void KODIVideoChecker::disconnected()
{
Info(_log, "Disconnected");
_previousGrabbingMode = GRABBINGMODE_INVALID;
_previousVideoMode = VIDEO_2D;
_kodiVersion = 0;
reconnect();
}
void KODIVideoChecker::reconnect()
{
if (_socket.state() == QTcpSocket::ConnectedState || ! _active )
{
return;
}
Debug(_log, "try reconnect");
// try to connect
switch (_socket.state())
{
case QTcpSocket::ConnectingState:
// Somehow when KODI restarts we get stuck in connecting state
// If we get here we tried to connect already for 5 seconds. abort and try again in 1 second.
_socket.reset();
_socket.waitForDisconnected(50);
QTimer::singleShot(1000, this, SLOT(reconnect()));
break;
case QTcpSocket::UnconnectedState:
_socket.connectToHost(_address, _port);
QTimer::singleShot(10000, this, SLOT(reconnect()));
break;
default:
QTimer::singleShot(10000, this, SLOT(reconnect()));
break;
}
}
void KODIVideoChecker::connectionError(QAbstractSocket::SocketError error)
{
Error(_log,"Connection error (%s)", _socket.errorString().toStdString().c_str());
// close the socket
_socket.close();
}
void KODIVideoChecker::setGrabbingMode(GrabbingMode newGrabbingMode)
{
if (newGrabbingMode == _previousGrabbingMode)
{
// no change
return;
}
switch (newGrabbingMode)
{
case GRABBINGMODE_VIDEO:
Info(_log, "switching to VIDEO mode");
break;
case GRABBINGMODE_PHOTO:
Info(_log, "switching to PHOTO mode");
break;
case GRABBINGMODE_AUDIO:
Info(_log, "switching to AUDIO mode");
break;
case GRABBINGMODE_MENU:
Info(_log, "switching to MENU mode");
break;
case GRABBINGMODE_PAUSE:
Info(_log, "switching to PAUSE mode");
break;
case GRABBINGMODE_OFF:
Info(_log, "switching to OFF mode");
break;
case GRABBINGMODE_SCREENSAVER:
Info(_log, "switching to SCREENSAVER mode");
break;
default:
Warning(_log, "switching to INVALID mode");
break;
}
emit grabbingMode(newGrabbingMode);
_previousGrabbingMode = newGrabbingMode;
}
void KODIVideoChecker::setVideoMode(VideoMode newVideoMode)
{
if (newVideoMode == _previousVideoMode)
{
// no change
return;
}
switch (newVideoMode)
{
case VIDEO_2D:
Info(_log, "KODICHECK INFO: switching to 2D mode");
break;
case VIDEO_3DSBS:
Info(_log, "KODICHECK INFO: switching to 3D SBS mode");
break;
case VIDEO_3DTAB:
Info(_log, "KODICHECK INFO: switching to 3D TAB mode");
break;
}
emit videoMode(newVideoMode);
_previousVideoMode = newVideoMode;
}