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 <QDateTime>
17#include <QKeyEvent>
18#include <QTimer>
19
20// 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 changeView();
295 else if (data.startsWith("CAMERA"))
296 {
297 data = data.remove("CAMERA");
298 int monitor = data.toInt();
299 changePlayerMonitor(monitor);
300 }
301 }
302 }
304}
305
307{
309 if (m_monitorLayout > 5)
310 m_monitorLayout = 1;
312}
313
315{
316 if (playerNo > (int)m_players->size())
317 return;
318
319 m_frameTimer->stop();
320
321 int oldMonID = m_players->at(playerNo - 1)->getMonitor()->id;
322
323 // find the old monitor ID in the list of available monitors
324 int pos = 0;
325 for (pos = 0; pos < ZMClient::get()->getMonitorCount(); pos++)
326 {
327 Monitor *omon = ZMClient::get()->getMonitorAt(pos);
328 if (oldMonID == omon->id)
329 {
330 break;
331 }
332 }
333
334 // get the next monitor in the list
335 if (pos != ZMClient::get()->getMonitorCount())
336 pos++;
337
338 // wrap around to the start if we've reached the end
339 if (pos >= ZMClient::get()->getMonitorCount())
340 pos = 0;
341
342 Monitor *mon = ZMClient::get()->getMonitorAt(pos);
343
344 m_players->at(playerNo - 1)->setMonitor(mon);
345 m_players->at(playerNo - 1)->updateCamera();
346
348}
349
351{
352 static std::array<uint8_t,MAX_IMAGE_SIZE> s_buffer {};
353 m_frameTimer->stop();
354
355 // get a list of monitor id's that need updating
356 QList<int> monList;
357 for (auto *p : *m_players)
358 {
359 if (!monList.contains(p->getMonitor()->id))
360 monList.append(p->getMonitor()->id);
361 }
362
363 for (int x = 0; x < monList.count(); x++)
364 {
365 QString status;
366 int frameSize = ZMClient::get()->getLiveFrame(monList[x], status, s_buffer);
367
368 if (frameSize > 0 && !status.startsWith("ERROR"))
369 {
370 // update each player that is displaying this monitor
371 for (auto *p : *m_players)
372 {
373 if (p->getMonitor()->id == monList[x])
374 {
375 if (p->getMonitor()->status != status)
376 {
377 p->getMonitor()->status = status;
378 p->updateStatus();
379 }
380 p->updateFrame(s_buffer.data());
381 }
382 }
383 }
384 }
385
387}
388
390{
391 m_frameTimer->stop();
392}
393
394void ZMLivePlayer::setMonitorLayout(int layout, bool restore)
395{
396 QStringList monList;
397
398 if (m_alarmMonitor != -1)
399 monList.append(QString::number(m_alarmMonitor));
400 else
401 monList = gCoreContext->GetSetting("ZoneMinderLiveCameras", "").split(",");
402
403 m_monitorLayout = layout;
404
405 if (m_players)
406 {
407 stopPlayers();
408 delete m_players;
409 }
410
411 m_players = new std::vector<Player *>;
412 m_monitorCount = 1;
413
414 if (layout == 1)
415 m_monitorCount = 1;
416 else if (layout == 2)
417 m_monitorCount = 2;
418 else if (layout == 3)
419 m_monitorCount = 4;
420 else if (layout == 4)
421 m_monitorCount = 6;
422 else if (layout == 5)
423 m_monitorCount = 8;
424
425 hideAll();
426
427 int monitorNo = 1;
428
429 for (int x = 1; x <= m_monitorCount; x++)
430 {
431 Monitor *monitor = nullptr;
432
433 if (restore)
434 {
435 if (x <= monList.size())
436 {
437 const QString& s = monList.at(x - 1);
438 int monID = s.toInt();
439
440 // try to find a monitor that matches the monID
441 monitor = ZMClient::get()->getMonitorByID(monID);
442 }
443 }
444
445 if (!monitor)
446 monitor = ZMClient::get()->getMonitorAt(monitorNo - 1);
447
448 MythUIImage *frameImage = dynamic_cast<MythUIImage *> (GetChild(QString("frame%1-%2").arg(layout).arg(x)));
449 MythUIText *cameraText = dynamic_cast<MythUIText *> (GetChild(QString("name%1-%2").arg(layout).arg(x)));
450 MythUIText *statusText = dynamic_cast<MythUIText *> (GetChild(QString("status%1-%2").arg(layout).arg(x)));
451
452 auto *p = new Player();
453 p->setMonitor(monitor);
454 p->setWidgets(frameImage, statusText, cameraText);
455 p->updateCamera();
456 m_players->push_back(p);
457
458 monitorNo++;
459 if (monitorNo > ZMClient::get()->getMonitorCount())
460 monitorNo = 1;
461 }
462
463 updateFrame();
464}
465
467
469{
470 if (m_rgba)
471 free(m_rgba);
472}
473
475{
476 m_monitor = *mon;
477
478 if (m_rgba)
479 free(m_rgba);
480
481 m_rgba = (uchar *) malloc(4_UZ * m_monitor.width * m_monitor.height);
482}
483
485{
486 m_frameImage = image;
487 m_statusText = status;
488 m_cameraText = camera;
489
490 if (m_frameImage)
492
493 if (m_statusText)
495
496 if (m_cameraText)
498}
499
500void Player::updateFrame(const unsigned char* buffer)
501{
502 QImage image(buffer, m_monitor.width, m_monitor.height, QImage::Format_RGB888);
503
505 img->Assign(image);
507 img->DecrRef();
508}
509
511{
512 if (m_statusText)
513 {
514 if (m_monitor.status == "Alarm" || m_monitor.status == "Error")
515 m_statusText->SetFontState("alarm");
516 else if (m_monitor.status == "Alert")
517 m_statusText->SetFontState("alert");
518 else
519 m_statusText->SetFontState("idle");
520
522 }
523}
524
526{
527 if (m_cameraText)
529}
Event dispatched from MythUI modal dialogs to a listening class containing a result of some form.
Definition: mythdialogbox.h:41
static const Type kEventType
Definition: mythdialogbox.h:56
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:98
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:86
void customEvent(QEvent *event) override
virtual void SetVisible(bool visible)
MythUIType * GetChild(const QString &name) const
Get a named child of this UIType.
Definition: mythuitype.cpp:138
MythUIImage * m_frameImage
Definition: zmliveplayer.h:51
MythUIText * m_statusText
Definition: zmliveplayer.h:52
void updateFrame(const uchar *buffer)
~Player(void)
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:57
uchar * m_rgba
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:68
int getLiveFrame(int monitorID, QString &status, FrameData &buffer)
Definition: zmclient.cpp:746
bool m_isMiniPlayer
Definition: zmliveplayer.h:94
QTimer * m_frameTimer
Definition: zmliveplayer.h:87
MythUIType * GetMythUIType(const QString &name, bool optional=false)
int m_monitorCount
Definition: zmliveplayer.h:90
~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:89
bool Create(void) override
std::vector< Player * > * m_players
Definition: zmliveplayer.h:92
void changeView(void)
void setMonitorLayout(int layout, bool restore=false)
int m_alarmMonitor
Definition: zmliveplayer.h:95
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