MythTV master
zmliveplayer.cpp
Go to the documentation of this file.
1/* ============================================================
2 * This program is free software; you can redistribute it
3 * and/or modify it under the terms of the GNU General
4 * Public License as published bythe Free Software Foundation;
5 * either version 2, or (at your option)
6 * any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * ============================================================ */
14
15// qt
16#include <QChar> // Fix Qt6 GCC SFINAE warning
17#include <QDateTime>
18#include <QKeyEvent>
19#include <QTimer>
20
21// MythTV
27
28// zoneminder
29#include "zmliveplayer.h"
30#include "zmclient.h"
31
32static constexpr std::chrono::milliseconds FRAME_UPDATE_TIME { 100ms }; // try to update the frame 10 times a second
33
35 :MythScreenType(parent, "zmliveview"),
36 m_frameTimer(new QTimer(this)),
37 m_isMiniPlayer(isMiniPlayer)
38{
40
43
44 connect(m_frameTimer, &QTimer::timeout, this,
46}
47
49{
50 // Load the theme for this screen
51 QString winName = m_isMiniPlayer ? "miniplayer" : "zmliveplayer";
52
53 if (!LoadWindowFromXML("zoneminder-ui.xml", winName, this))
54 {
55 LOG(VB_GENERAL, LOG_ERR, QString("Cannot load screen '%1'").arg(winName));
56 return false;
57 }
58
59 if (!hideAll())
60 return false;
61
63 {
64 // we only support the single camera layout in the mini player
65 if (!initMonitorLayout(1))
66 return false;
67 }
68 else
69 {
70 if (!initMonitorLayout(gCoreContext->GetNumSetting("ZoneMinderLiveLayout", 1)))
71 return false;
72 }
73
74 return true;
75}
76
77MythUIType* ZMLivePlayer::GetMythUIType(const QString &name, bool optional)
78{
79 MythUIType *type = GetChild(name);
80
81 if (!optional && !type)
82 throw name;
83
84 return type;
85}
86
88{
89 try
90 {
91 // one player layout
92 GetMythUIType("name1-1")->SetVisible(false);
93 GetMythUIType("status1-1")->SetVisible(false);
94 GetMythUIType("frame1-1")->SetVisible(false);
95
96 if (!m_isMiniPlayer)
97 {
98 // two player layout
99 for (int x = 1; x < 3; x++)
100 {
101 GetMythUIType(QString("name2-%1").arg(x))->SetVisible(false);
102 GetMythUIType(QString("status2-%1").arg(x))->SetVisible(false);
103 GetMythUIType(QString("frame2-%1").arg(x))->SetVisible(false);
104 }
105
106 // four player layout
107 for (int x = 1; x < 5; x++)
108 {
109 GetMythUIType(QString("name3-%1").arg(x))->SetVisible(false);
110 GetMythUIType(QString("status3-%1").arg(x))->SetVisible(false);
111 GetMythUIType(QString("frame3-%1").arg(x))->SetVisible(false);
112 }
113
114 // six player layout
115 for (int x = 1; x < 7; x++)
116 {
117 GetMythUIType(QString("name4-%1").arg(x))->SetVisible(false);
118 GetMythUIType(QString("status4-%1").arg(x))->SetVisible(false);
119 GetMythUIType(QString("frame4-%1").arg(x))->SetVisible(false);
120 }
121
122 // eight player layout
123 for (int x = 1; x < 9; x++)
124 {
125 GetMythUIType(QString("name5-%1").arg(x))->SetVisible(false);
126 GetMythUIType(QString("status5-%1").arg(x))->SetVisible(false);
127 GetMythUIType(QString("frame5-%1").arg(x))->SetVisible(false);
128 }
129 }
130 }
131 catch (const QString &name)
132 {
133 LOG(VB_GENERAL, LOG_ERR,
134 QString("Theme is missing a critical theme element ('%1')")
135 .arg(name));
136 return false;
137 }
138
139 return true;
140}
141
143{
144 // if we haven't got any monitors there's not much we can do so bail out!
145 if (ZMClient::get()->getMonitorCount() == 0)
146 {
147 LOG(VB_GENERAL, LOG_ERR, "Cannot find any monitors. Bailing out!");
148 ShowOkPopup(tr("Can't show live view.") + "\n" +
149 tr("You don't have any monitors defined!"));
150 return false;
151 }
152
153 setMonitorLayout(layout, true);
155
156 return true;
157}
158
160{
161 gCoreContext->SaveSetting("ZoneMinderLiveLayout", m_monitorLayout);
162
165
166 if (m_players)
167 {
168 QString s = "";
169 for (auto *p : *m_players)
170 {
171 if (s != "")
172 s += ",";
173 s += QString("%1").arg(p->getMonitor()->id);
174 }
175
176 gCoreContext->SaveSetting("ZoneMinderLiveCameras", s);
177
178 delete m_players;
179 }
180 else
181 {
182 gCoreContext->SaveSetting("ZoneMinderLiveCameras", "");
183 }
184
185 delete m_frameTimer;
186
188}
189
190bool ZMLivePlayer::keyPressEvent(QKeyEvent *event)
191{
193 return true;
194
195 QStringList actions;
196 bool handled = GetMythMainWindow()->TranslateKeyPress("TV Playback", event, actions);
197
198 for (int i = 0; i < actions.size() && !handled; i++)
199 {
200 const QString& action = actions[i];
201 handled = true;
202
203 if (action == "PAUSE")
204 {
205 if (m_paused)
206 {
208 m_paused = false;
209 }
210 else
211 {
212 m_frameTimer->stop();
213 m_paused = true;
214 }
215 }
216 else if (action == "INFO" && !m_isMiniPlayer)
217 {
218 changeView();
219 }
220 else if (action == "1" || action == "2" || action == "3" ||
221 action == "4" || action == "5" || action == "6" ||
222 action == "7" || action == "8" || action == "9")
223 {
225 }
226 else if (action == "MENU")
227 {
228 ShowMenu();
229 }
230 else
231 {
232 handled = false;
233 }
234 }
235
236 if (!handled && MythScreenType::keyPressEvent(event))
237 handled = true;
238
239 return handled;
240}
241
243{
244 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
245
246 auto *menuPopup = new MythDialogBox("Menu", popupStack, "mainmenu");
247
248 if (menuPopup->Create())
249 popupStack->AddScreen(menuPopup);
250
251 menuPopup->SetReturnEvent(this, "mainmenu");
252
253 menuPopup->AddButtonV(tr("Change View"), QVariant::fromValue(QString("VIEW")));
254 menuPopup->AddButtonV(tr("Change Camera 1"), QVariant::fromValue(QString("CAMERA1")));
255
256 if (m_monitorLayout > 1)
257 menuPopup->AddButtonV(tr("Change Camera 2"), QVariant::fromValue(QString("CAMERA2")));
258
259 if (m_monitorLayout > 2)
260 {
261 menuPopup->AddButtonV(tr("Change Camera 3"), QVariant::fromValue(QString("CAMERA3")));
262 menuPopup->AddButtonV(tr("Change Camera 4"), QVariant::fromValue(QString("CAMERA4")));
263 }
264
265 if (m_monitorLayout > 3)
266 {
267 menuPopup->AddButtonV(tr("Change Camera 5"), QVariant::fromValue(QString("CAMERA5")));
268 menuPopup->AddButtonV(tr("Change Camera 6"), QVariant::fromValue(QString("CAMERA6")));
269 }
270
271 if (m_monitorLayout > 4)
272 {
273 menuPopup->AddButtonV(tr("Change Camera 7"), QVariant::fromValue(QString("CAMERA7")));
274 menuPopup->AddButtonV(tr("Change Camera 8"), QVariant::fromValue(QString("CAMERA8")));
275 }
276}
277
278void ZMLivePlayer::customEvent(QEvent *event)
279{
280 if (event->type() == DialogCompletionEvent::kEventType)
281 {
282 auto *dce = dynamic_cast<DialogCompletionEvent*>(event);
283
284 // make sure the user didn't ESCAPE out of the menu
285 if ((dce == nullptr) || (dce->GetResult() < 0))
286 return;
287
288 QString resultid = dce->GetId();
289 QString data = dce->GetData().toString();
290
291 if (resultid == "mainmenu")
292 {
293 if (data == "VIEW")
294 {
295 changeView();
296 }
297 else if (data.startsWith("CAMERA"))
298 {
299 data = data.remove("CAMERA");
300 int monitor = data.toInt();
301 changePlayerMonitor(monitor);
302 }
303 }
304 }
306}
307
309{
311 if (m_monitorLayout > 5)
312 m_monitorLayout = 1;
314}
315
317{
318 if (playerNo > (int)m_players->size())
319 return;
320
321 m_frameTimer->stop();
322
323 int oldMonID = m_players->at(playerNo - 1)->getMonitor()->id;
324
325 // find the old monitor ID in the list of available monitors
326 int pos = 0;
327 for (pos = 0; pos < ZMClient::get()->getMonitorCount(); pos++)
328 {
329 Monitor *omon = ZMClient::get()->getMonitorAt(pos);
330 if (oldMonID == omon->id)
331 {
332 break;
333 }
334 }
335
336 // get the next monitor in the list
337 if (pos != ZMClient::get()->getMonitorCount())
338 pos++;
339
340 // wrap around to the start if we've reached the end
341 if (pos >= ZMClient::get()->getMonitorCount())
342 pos = 0;
343
344 Monitor *mon = ZMClient::get()->getMonitorAt(pos);
345
346 m_players->at(playerNo - 1)->setMonitor(mon);
347 m_players->at(playerNo - 1)->updateCamera();
348
350}
351
353{
354 static std::array<uint8_t,MAX_IMAGE_SIZE> s_buffer {};
355 m_frameTimer->stop();
356
357 // get a list of monitor id's that need updating
358 QList<int> monList;
359 for (auto *p : *m_players)
360 {
361 if (!monList.contains(p->getMonitor()->id))
362 monList.append(p->getMonitor()->id);
363 }
364
365 for (int x = 0; x < monList.count(); x++)
366 {
367 QString status;
368 int frameSize = ZMClient::get()->getLiveFrame(monList[x], status, s_buffer);
369
370 if (frameSize > 0 && !status.startsWith("ERROR"))
371 {
372 // update each player that is displaying this monitor
373 for (auto *p : *m_players)
374 {
375 if (p->getMonitor()->id == monList[x])
376 {
377 if (p->getMonitor()->status != status)
378 {
379 p->getMonitor()->status = status;
380 p->updateStatus();
381 }
382 p->updateFrame(s_buffer.data());
383 }
384 }
385 }
386 }
387
389}
390
392{
393 m_frameTimer->stop();
394}
395
396void ZMLivePlayer::setMonitorLayout(int layout, bool restore)
397{
398 QStringList monList;
399
400 if (m_alarmMonitor != -1)
401 monList.append(QString::number(m_alarmMonitor));
402 else
403 monList = gCoreContext->GetSetting("ZoneMinderLiveCameras", "").split(",");
404
405 m_monitorLayout = layout;
406
407 if (m_players)
408 {
409 stopPlayers();
410 delete m_players;
411 }
412
413 m_players = new std::vector<Player *>;
414 m_monitorCount = 1;
415
416 if (layout == 1)
417 m_monitorCount = 1;
418 else if (layout == 2)
419 m_monitorCount = 2;
420 else if (layout == 3)
421 m_monitorCount = 4;
422 else if (layout == 4)
423 m_monitorCount = 6;
424 else if (layout == 5)
425 m_monitorCount = 8;
426
427 hideAll();
428
429 int monitorNo = 1;
430
431 for (int x = 1; x <= m_monitorCount; x++)
432 {
433 Monitor *monitor = nullptr;
434
435 if (restore)
436 {
437 if (x <= monList.size())
438 {
439 const QString& s = monList.at(x - 1);
440 int monID = s.toInt();
441
442 // try to find a monitor that matches the monID
443 monitor = ZMClient::get()->getMonitorByID(monID);
444 }
445 }
446
447 if (!monitor)
448 monitor = ZMClient::get()->getMonitorAt(monitorNo - 1);
449
450 MythUIImage *frameImage = dynamic_cast<MythUIImage *> (GetChild(QString("frame%1-%2").arg(layout).arg(x)));
451 MythUIText *cameraText = dynamic_cast<MythUIText *> (GetChild(QString("name%1-%2").arg(layout).arg(x)));
452 MythUIText *statusText = dynamic_cast<MythUIText *> (GetChild(QString("status%1-%2").arg(layout).arg(x)));
453
454 auto *p = new Player();
455 p->setMonitor(monitor);
456 p->setWidgets(frameImage, statusText, cameraText);
457 p->updateCamera();
458 m_players->push_back(p);
459
460 monitorNo++;
461 if (monitorNo > ZMClient::get()->getMonitorCount())
462 monitorNo = 1;
463 }
464
465 updateFrame();
466}
467
469
471{
472 m_monitor = *mon;
473}
474
476{
477 m_frameImage = image;
478 m_statusText = status;
479 m_cameraText = camera;
480
481 if (m_frameImage)
483
484 if (m_statusText)
486
487 if (m_cameraText)
489}
490
491void Player::updateFrame(const unsigned char* buffer)
492{
493 QImage image(buffer, m_monitor.width, m_monitor.height, QImage::Format_RGB888);
494
496 img->Assign(image);
498 img->DecrRef();
499}
500
502{
503 if (m_statusText)
504 {
505 if (m_monitor.status == "Alarm" || m_monitor.status == "Error")
506 m_statusText->SetFontState("alarm");
507 else if (m_monitor.status == "Alert")
508 m_statusText->SetFontState("alert");
509 else
510 m_statusText->SetFontState("idle");
511
513 }
514}
515
517{
518 if (m_cameraText)
520}
521
522#include "moc_zmliveplayer.cpp"
Event dispatched from MythUI modal dialogs to a listening class containing a result of some form.
Definition: mythdialogbox.h:40
static const Type kEventType
Definition: mythdialogbox.h:55
int id
Definition: zmdefines.h:115
int width
Definition: zmdefines.h:126
QString status
Definition: zmdefines.h:125
QString name
Definition: zmdefines.h:116
int height
Definition: zmdefines.h:127
void SaveSetting(const QString &key, int newValue)
QString GetSetting(const QString &key, const QString &defaultval="")
int GetNumSetting(const QString &key, int defaultval=0)
Basic menu dialog, message and a list of options.
void Assign(const QImage &img)
Definition: mythimage.cpp:77
int DecrRef(void) override
Decrements reference count and deletes on 0.
Definition: mythimage.cpp:52
static void DisableScreensaver()
void PauseIdleTimer(bool Pause)
Pause the idle timeout timer.
MythPainter * GetPainter()
bool TranslateKeyPress(const QString &Context, QKeyEvent *Event, QStringList &Actions, bool AllowJumps=true)
Get a list of actions for a keypress in the given context.
MythScreenStack * GetStack(const QString &Stackname)
static void RestoreScreensaver()
MythImage * GetFormatImage()
Returns a blank reference counted image in the format required for the Draw functions for this painte...
virtual void AddScreen(MythScreenType *screen, bool allowFade=true)
Screen in which all other widgets are contained and rendered.
MythUIType * GetFocusWidget(void) const
bool keyPressEvent(QKeyEvent *event) override
Key event handler.
Image widget, displays a single image or multiple images in sequence.
Definition: mythuiimage.h:99
void SetImage(MythImage *img)
Should not be used unless absolutely necessary since it bypasses the image caching and threaded loade...
All purpose text widget, displays a text string.
Definition: mythuitext.h:29
void SetFontState(const QString &state)
Definition: mythuitext.cpp:202
virtual void SetText(const QString &text)
Definition: mythuitext.cpp:115
The base class on which all widgets and screens are based.
Definition: mythuitype.h:97
void customEvent(QEvent *event) override
Definition: mythuitype.cpp:989
virtual void SetVisible(bool visible)
MythUIType * GetChild(const QString &name) const
Get a named child of this UIType.
Definition: mythuitype.cpp:130
MythUIImage * m_frameImage
Definition: zmliveplayer.h:51
MythUIText * m_statusText
Definition: zmliveplayer.h:52
void updateFrame(const uchar *buffer)
void updateStatus(void)
void setWidgets(MythUIImage *image, MythUIText *status, MythUIText *camera)
MythUIText * m_cameraText
Definition: zmliveplayer.h:53
void setMonitor(const Monitor *mon)
void updateCamera()
Monitor m_monitor
Definition: zmliveplayer.h:55
static bool LoadWindowFromXML(const QString &xmlfile, const QString &windowname, MythUIType *parent)
int getMonitorCount(void)
Definition: zmclient.cpp:846
Monitor * getMonitorByID(int monID)
Definition: zmclient.cpp:862
Monitor * getMonitorAt(int pos)
Definition: zmclient.cpp:852
static ZMClient * get(void)
Definition: zmclient.cpp:37
void setIsMiniPlayerEnabled(bool enabled)
Definition: zmclient.h:66
int getLiveFrame(int monitorID, QString &status, FrameData &buffer)
Definition: zmclient.cpp:746
bool m_isMiniPlayer
Definition: zmliveplayer.h:92
QTimer * m_frameTimer
Definition: zmliveplayer.h:85
MythUIType * GetMythUIType(const QString &name, bool optional=false)
int m_monitorCount
Definition: zmliveplayer.h:88
~ZMLivePlayer() override
bool initMonitorLayout(int layout)
ZMLivePlayer(MythScreenStack *parent, bool isMiniPlayer=false)
bool keyPressEvent(QKeyEvent *event) override
Key event handler.
void stopPlayers(void)
int m_monitorLayout
Definition: zmliveplayer.h:87
bool Create(void) override
std::vector< Player * > * m_players
Definition: zmliveplayer.h:90
void changeView(void)
void setMonitorLayout(int layout, bool restore=false)
int m_alarmMonitor
Definition: zmliveplayer.h:93
void changePlayerMonitor(int playerNo)
void ShowMenu() override
void customEvent(QEvent *event) override
void updateFrame(void)
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
MythConfirmationDialog * ShowOkPopup(const QString &message, bool showCancel)
Non-blocking version of MythPopupBox::showOkPopup()
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
MythMainWindow * GetMythMainWindow(void)
static constexpr std::chrono::milliseconds FRAME_UPDATE_TIME