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
26
27// zoneminder
28#include "zmliveplayer.h"
29#include "zmclient.h"
30
31static constexpr std::chrono::milliseconds FRAME_UPDATE_TIME { 100ms }; // try to update the frame 10 times a second
32
34 :MythScreenType(parent, "zmliveview"),
35 m_frameTimer(new QTimer(this)),
36 m_isMiniPlayer(isMiniPlayer)
37{
39
42
43 connect(m_frameTimer, &QTimer::timeout, this,
45}
46
48{
49 // Load the theme for this screen
50 QString winName = m_isMiniPlayer ? "miniplayer" : "zmliveplayer";
51
52 if (!LoadWindowFromXML("zoneminder-ui.xml", winName, this))
53 {
54 LOG(VB_GENERAL, LOG_ERR, QString("Cannot load screen '%1'").arg(winName));
55 return false;
56 }
57
58 if (!hideAll())
59 return false;
60
62 {
63 // we only support the single camera layout in the mini player
64 if (!initMonitorLayout(1))
65 return false;
66 }
67 else
68 {
69 if (!initMonitorLayout(gCoreContext->GetNumSetting("ZoneMinderLiveLayout", 1)))
70 return false;
71 }
72
73 return true;
74}
75
76MythUIType* ZMLivePlayer::GetMythUIType(const QString &name, bool optional)
77{
78 MythUIType *type = GetChild(name);
79
80 if (!optional && !type)
81 throw name;
82
83 return type;
84}
85
87{
88 try
89 {
90 // one player layout
91 GetMythUIType("name1-1")->SetVisible(false);
92 GetMythUIType("status1-1")->SetVisible(false);
93 GetMythUIType("frame1-1")->SetVisible(false);
94
95 if (!m_isMiniPlayer)
96 {
97 // two player layout
98 for (int x = 1; x < 3; x++)
99 {
100 GetMythUIType(QString("name2-%1").arg(x))->SetVisible(false);
101 GetMythUIType(QString("status2-%1").arg(x))->SetVisible(false);
102 GetMythUIType(QString("frame2-%1").arg(x))->SetVisible(false);
103 }
104
105 // four player layout
106 for (int x = 1; x < 5; x++)
107 {
108 GetMythUIType(QString("name3-%1").arg(x))->SetVisible(false);
109 GetMythUIType(QString("status3-%1").arg(x))->SetVisible(false);
110 GetMythUIType(QString("frame3-%1").arg(x))->SetVisible(false);
111 }
112
113 // six player layout
114 for (int x = 1; x < 7; x++)
115 {
116 GetMythUIType(QString("name4-%1").arg(x))->SetVisible(false);
117 GetMythUIType(QString("status4-%1").arg(x))->SetVisible(false);
118 GetMythUIType(QString("frame4-%1").arg(x))->SetVisible(false);
119 }
120
121 // eight player layout
122 for (int x = 1; x < 9; x++)
123 {
124 GetMythUIType(QString("name5-%1").arg(x))->SetVisible(false);
125 GetMythUIType(QString("status5-%1").arg(x))->SetVisible(false);
126 GetMythUIType(QString("frame5-%1").arg(x))->SetVisible(false);
127 }
128 }
129 }
130 catch (const QString &name)
131 {
132 LOG(VB_GENERAL, LOG_ERR,
133 QString("Theme is missing a critical theme element ('%1')")
134 .arg(name));
135 return false;
136 }
137
138 return true;
139}
140
142{
143 // if we haven't got any monitors there's not much we can do so bail out!
144 if (ZMClient::get()->getMonitorCount() == 0)
145 {
146 LOG(VB_GENERAL, LOG_ERR, "Cannot find any monitors. Bailing out!");
147 ShowOkPopup(tr("Can't show live view.") + "\n" +
148 tr("You don't have any monitors defined!"));
149 return false;
150 }
151
152 setMonitorLayout(layout, true);
154
155 return true;
156}
157
159{
160 gCoreContext->SaveSetting("ZoneMinderLiveLayout", m_monitorLayout);
161
164
165 if (m_players)
166 {
167 QString s = "";
168 for (auto *p : *m_players)
169 {
170 if (s != "")
171 s += ",";
172 s += QString("%1").arg(p->getMonitor()->id);
173 }
174
175 gCoreContext->SaveSetting("ZoneMinderLiveCameras", s);
176
177 delete m_players;
178 }
179 else
180 {
181 gCoreContext->SaveSetting("ZoneMinderLiveCameras", "");
182 }
183
184 delete m_frameTimer;
185
187}
188
189bool ZMLivePlayer::keyPressEvent(QKeyEvent *event)
190{
192 return true;
193
194 QStringList actions;
195 bool handled = GetMythMainWindow()->TranslateKeyPress("TV Playback", event, actions);
196
197 for (int i = 0; i < actions.size() && !handled; i++)
198 {
199 const QString& action = actions[i];
200 handled = true;
201
202 if (action == "PAUSE")
203 {
204 if (m_paused)
205 {
207 m_paused = false;
208 }
209 else
210 {
211 m_frameTimer->stop();
212 m_paused = true;
213 }
214 }
215 else if (action == "INFO" && !m_isMiniPlayer)
216 {
217 changeView();
218 }
219 else if (action == "1" || action == "2" || action == "3" ||
220 action == "4" || action == "5" || action == "6" ||
221 action == "7" || action == "8" || action == "9")
222 {
224 }
225 else if (action == "MENU")
226 {
227 ShowMenu();
228 }
229 else
230 {
231 handled = false;
232 }
233 }
234
235 if (!handled && MythScreenType::keyPressEvent(event))
236 handled = true;
237
238 return handled;
239}
240
242{
243 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
244
245 auto *menuPopup = new MythDialogBox("Menu", popupStack, "mainmenu");
246
247 if (menuPopup->Create())
248 popupStack->AddScreen(menuPopup);
249
250 menuPopup->SetReturnEvent(this, "mainmenu");
251
252 menuPopup->AddButtonV(tr("Change View"), QVariant::fromValue(QString("VIEW")));
253 menuPopup->AddButtonV(tr("Change Camera 1"), QVariant::fromValue(QString("CAMERA1")));
254
255 if (m_monitorLayout > 1)
256 menuPopup->AddButtonV(tr("Change Camera 2"), QVariant::fromValue(QString("CAMERA2")));
257
258 if (m_monitorLayout > 2)
259 {
260 menuPopup->AddButtonV(tr("Change Camera 3"), QVariant::fromValue(QString("CAMERA3")));
261 menuPopup->AddButtonV(tr("Change Camera 4"), QVariant::fromValue(QString("CAMERA4")));
262 }
263
264 if (m_monitorLayout > 3)
265 {
266 menuPopup->AddButtonV(tr("Change Camera 5"), QVariant::fromValue(QString("CAMERA5")));
267 menuPopup->AddButtonV(tr("Change Camera 6"), QVariant::fromValue(QString("CAMERA6")));
268 }
269
270 if (m_monitorLayout > 4)
271 {
272 menuPopup->AddButtonV(tr("Change Camera 7"), QVariant::fromValue(QString("CAMERA7")));
273 menuPopup->AddButtonV(tr("Change Camera 8"), QVariant::fromValue(QString("CAMERA8")));
274 }
275}
276
277void ZMLivePlayer::customEvent(QEvent *event)
278{
279 if (event->type() == DialogCompletionEvent::kEventType)
280 {
281 auto *dce = dynamic_cast<DialogCompletionEvent*>(event);
282
283 // make sure the user didn't ESCAPE out of the menu
284 if ((dce == nullptr) || (dce->GetResult() < 0))
285 return;
286
287 QString resultid = dce->GetId();
288 QString data = dce->GetData().toString();
289
290 if (resultid == "mainmenu")
291 {
292 if (data == "VIEW")
293 changeView();
294 else if (data.startsWith("CAMERA"))
295 {
296 data = data.remove("CAMERA");
297 int monitor = data.toInt();
298 changePlayerMonitor(monitor);
299 }
300 }
301 }
303}
304
306{
308 if (m_monitorLayout > 5)
309 m_monitorLayout = 1;
311}
312
314{
315 if (playerNo > (int)m_players->size())
316 return;
317
318 m_frameTimer->stop();
319
320 int oldMonID = m_players->at(playerNo - 1)->getMonitor()->id;
321
322 // find the old monitor ID in the list of available monitors
323 int pos = 0;
324 for (pos = 0; pos < ZMClient::get()->getMonitorCount(); pos++)
325 {
326 Monitor *omon = ZMClient::get()->getMonitorAt(pos);
327 if (oldMonID == omon->id)
328 {
329 break;
330 }
331 }
332
333 // get the next monitor in the list
334 if (pos != ZMClient::get()->getMonitorCount())
335 pos++;
336
337 // wrap around to the start if we've reached the end
338 if (pos >= ZMClient::get()->getMonitorCount())
339 pos = 0;
340
341 Monitor *mon = ZMClient::get()->getMonitorAt(pos);
342
343 m_players->at(playerNo - 1)->setMonitor(mon);
344 m_players->at(playerNo - 1)->updateCamera();
345
347}
348
350{
351 static std::array<uint8_t,MAX_IMAGE_SIZE> s_buffer {};
352 m_frameTimer->stop();
353
354 // get a list of monitor id's that need updating
355 QList<int> monList;
356 for (auto *p : *m_players)
357 {
358 if (!monList.contains(p->getMonitor()->id))
359 monList.append(p->getMonitor()->id);
360 }
361
362 for (int x = 0; x < monList.count(); x++)
363 {
364 QString status;
365 int frameSize = ZMClient::get()->getLiveFrame(monList[x], status, s_buffer);
366
367 if (frameSize > 0 && !status.startsWith("ERROR"))
368 {
369 // update each player that is displaying this monitor
370 for (auto *p : *m_players)
371 {
372 if (p->getMonitor()->id == monList[x])
373 {
374 if (p->getMonitor()->status != status)
375 {
376 p->getMonitor()->status = status;
377 p->updateStatus();
378 }
379 p->updateFrame(s_buffer.data());
380 }
381 }
382 }
383 }
384
386}
387
389{
390 m_frameTimer->stop();
391}
392
393void ZMLivePlayer::setMonitorLayout(int layout, bool restore)
394{
395 QStringList monList;
396
397 if (m_alarmMonitor != -1)
398 monList.append(QString::number(m_alarmMonitor));
399 else
400 monList = gCoreContext->GetSetting("ZoneMinderLiveCameras", "").split(",");
401
402 m_monitorLayout = layout;
403
404 if (m_players)
405 {
406 stopPlayers();
407 delete m_players;
408 }
409
410 m_players = new std::vector<Player *>;
411 m_monitorCount = 1;
412
413 if (layout == 1)
414 m_monitorCount = 1;
415 else if (layout == 2)
416 m_monitorCount = 2;
417 else if (layout == 3)
418 m_monitorCount = 4;
419 else if (layout == 4)
420 m_monitorCount = 6;
421 else if (layout == 5)
422 m_monitorCount = 8;
423
424 hideAll();
425
426 int monitorNo = 1;
427
428 for (int x = 1; x <= m_monitorCount; x++)
429 {
430 Monitor *monitor = nullptr;
431
432 if (restore)
433 {
434 if (x <= monList.size())
435 {
436 const QString& s = monList.at(x - 1);
437 int monID = s.toInt();
438
439 // try to find a monitor that matches the monID
440 monitor = ZMClient::get()->getMonitorByID(monID);
441 }
442 }
443
444 if (!monitor)
445 monitor = ZMClient::get()->getMonitorAt(monitorNo - 1);
446
447 MythUIImage *frameImage = dynamic_cast<MythUIImage *> (GetChild(QString("frame%1-%2").arg(layout).arg(x)));
448 MythUIText *cameraText = dynamic_cast<MythUIText *> (GetChild(QString("name%1-%2").arg(layout).arg(x)));
449 MythUIText *statusText = dynamic_cast<MythUIText *> (GetChild(QString("status%1-%2").arg(layout).arg(x)));
450
451 auto *p = new Player();
452 p->setMonitor(monitor);
453 p->setWidgets(frameImage, statusText, cameraText);
454 p->updateCamera();
455 m_players->push_back(p);
456
457 monitorNo++;
458 if (monitorNo > ZMClient::get()->getMonitorCount())
459 monitorNo = 1;
460 }
461
462 updateFrame();
463}
464
466
468{
469 m_monitor = *mon;
470}
471
473{
474 m_frameImage = image;
475 m_statusText = status;
476 m_cameraText = camera;
477
478 if (m_frameImage)
480
481 if (m_statusText)
483
484 if (m_cameraText)
486}
487
488void Player::updateFrame(const unsigned char* buffer)
489{
490 QImage image(buffer, m_monitor.width, m_monitor.height, QImage::Format_RGB888);
491
493 img->Assign(image);
495 img->DecrRef();
496}
497
499{
500 if (m_statusText)
501 {
502 if (m_monitor.status == "Alarm" || m_monitor.status == "Error")
503 m_statusText->SetFontState("alarm");
504 else if (m_monitor.status == "Alert")
505 m_statusText->SetFontState("alert");
506 else
507 m_statusText->SetFontState("idle");
508
510 }
511}
512
514{
515 if (m_cameraText)
517}
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: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:68
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