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
21 #include <libmyth/mythcontext.h>
25 #include <libmythui/mythuihelper.h>
26 
27 // zoneminder
28 #include "zmliveplayer.h"
29 #include "zmclient.h"
30 
31 static constexpr std::chrono::milliseconds FRAME_UPDATE_TIME { 100ms }; // try to update the frame 10 times a second
32 
33 ZMLivePlayer::ZMLivePlayer(MythScreenStack *parent, bool isMiniPlayer)
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 
61  if (m_isMiniPlayer)
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 
76 MythUIType* 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  gCoreContext->SaveSetting("ZoneMinderLiveCameras", "");
181 
182  delete m_frameTimer;
183 
185 }
186 
187 bool ZMLivePlayer::keyPressEvent(QKeyEvent *event)
188 {
189  if (GetFocusWidget() && GetFocusWidget()->keyPressEvent(event))
190  return true;
191 
192  QStringList actions;
193  bool handled = GetMythMainWindow()->TranslateKeyPress("TV Playback", event, actions);
194 
195  for (int i = 0; i < actions.size() && !handled; i++)
196  {
197  QString action = actions[i];
198  handled = true;
199 
200  if (action == "PAUSE")
201  {
202  if (m_paused)
203  {
205  m_paused = false;
206  }
207  else
208  {
209  m_frameTimer->stop();
210  m_paused = true;
211  }
212  }
213  else if (action == "INFO" && !m_isMiniPlayer)
214  {
215  changeView();
216  }
217  else if (action == "1" || action == "2" || action == "3" ||
218  action == "4" || action == "5" || action == "6" ||
219  action == "7" || action == "8" || action == "9")
220  changePlayerMonitor(action.toInt());
221  else if (action == "MENU")
222  {
223  ShowMenu();
224  }
225  else
226  handled = false;
227  }
228 
229  if (!handled && MythScreenType::keyPressEvent(event))
230  handled = true;
231 
232  return handled;
233 }
234 
236 {
237  MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
238 
239  auto *menuPopup = new MythDialogBox("Menu", popupStack, "mainmenu");
240 
241  if (menuPopup->Create())
242  popupStack->AddScreen(menuPopup);
243 
244  menuPopup->SetReturnEvent(this, "mainmenu");
245 
246  menuPopup->AddButtonV(tr("Change View"), QVariant::fromValue(QString("VIEW")));
247  menuPopup->AddButtonV(tr("Change Camera 1"), QVariant::fromValue(QString("CAMERA1")));
248 
249  if (m_monitorLayout > 1)
250  menuPopup->AddButtonV(tr("Change Camera 2"), QVariant::fromValue(QString("CAMERA2")));
251 
252  if (m_monitorLayout > 2)
253  {
254  menuPopup->AddButtonV(tr("Change Camera 3"), QVariant::fromValue(QString("CAMERA3")));
255  menuPopup->AddButtonV(tr("Change Camera 4"), QVariant::fromValue(QString("CAMERA4")));
256  }
257 
258  if (m_monitorLayout > 3)
259  {
260  menuPopup->AddButtonV(tr("Change Camera 5"), QVariant::fromValue(QString("CAMERA5")));
261  menuPopup->AddButtonV(tr("Change Camera 6"), QVariant::fromValue(QString("CAMERA6")));
262  }
263 
264  if (m_monitorLayout > 4)
265  {
266  menuPopup->AddButtonV(tr("Change Camera 7"), QVariant::fromValue(QString("CAMERA7")));
267  menuPopup->AddButtonV(tr("Change Camera 8"), QVariant::fromValue(QString("CAMERA8")));
268  }
269 }
270 
271 void ZMLivePlayer::customEvent(QEvent *event)
272 {
273  if (event->type() == DialogCompletionEvent::kEventType)
274  {
275  auto *dce = dynamic_cast<DialogCompletionEvent*>(event);
276 
277  // make sure the user didn't ESCAPE out of the menu
278  if ((dce == nullptr) || (dce->GetResult() < 0))
279  return;
280 
281  QString resultid = dce->GetId();
282  QString data = dce->GetData().toString();
283 
284  if (resultid == "mainmenu")
285  {
286  if (data == "VIEW")
287  changeView();
288  else if (data.startsWith("CAMERA"))
289  {
290  data = data.remove("CAMERA");
291  int monitor = data.toInt();
292  changePlayerMonitor(monitor);
293  }
294  }
295  }
297 }
298 
300 {
301  m_monitorLayout++;
302  if (m_monitorLayout > 5)
303  m_monitorLayout = 1;
305 }
306 
308 {
309  if (playerNo > (int)m_players->size())
310  return;
311 
312  m_frameTimer->stop();
313 
314  int oldMonID = m_players->at(playerNo - 1)->getMonitor()->id;
315 
316  // find the old monitor ID in the list of available monitors
317  int pos = 0;
318  for (pos = 0; pos < ZMClient::get()->getMonitorCount(); pos++)
319  {
320  Monitor *omon = ZMClient::get()->getMonitorAt(pos);
321  if (oldMonID == omon->id)
322  {
323  break;
324  }
325  }
326 
327  // get the next monitor in the list
328  if (pos != ZMClient::get()->getMonitorCount())
329  pos++;
330 
331  // wrap around to the start if we've reached the end
332  if (pos >= ZMClient::get()->getMonitorCount())
333  pos = 0;
334 
335  Monitor *mon = ZMClient::get()->getMonitorAt(pos);
336 
337  m_players->at(playerNo - 1)->setMonitor(mon);
338  m_players->at(playerNo - 1)->updateCamera();
339 
341 }
342 
344 {
345  static std::array<uint8_t,MAX_IMAGE_SIZE> s_buffer {};
346  m_frameTimer->stop();
347 
348  // get a list of monitor id's that need updating
349  QList<int> monList;
350  for (auto *p : *m_players)
351  {
352  if (!monList.contains(p->getMonitor()->id))
353  monList.append(p->getMonitor()->id);
354  }
355 
356  for (int x = 0; x < monList.count(); x++)
357  {
358  QString status;
359  int frameSize = ZMClient::get()->getLiveFrame(monList[x], status, s_buffer);
360 
361  if (frameSize > 0 && !status.startsWith("ERROR"))
362  {
363  // update each player that is displaying this monitor
364  for (auto *p : *m_players)
365  {
366  if (p->getMonitor()->id == monList[x])
367  {
368  if (p->getMonitor()->status != status)
369  {
370  p->getMonitor()->status = status;
371  p->updateStatus();
372  }
373  p->updateFrame(s_buffer.data());
374  }
375  }
376  }
377  }
378 
380 }
381 
383 {
384  m_frameTimer->stop();
385 }
386 
387 void ZMLivePlayer::setMonitorLayout(int layout, bool restore)
388 {
389  QStringList monList;
390 
391  if (m_alarmMonitor != -1)
392  monList.append(QString::number(m_alarmMonitor));
393  else
394  monList = gCoreContext->GetSetting("ZoneMinderLiveCameras", "").split(",");
395 
396  m_monitorLayout = layout;
397 
398  if (m_players)
399  {
400  stopPlayers();
401  delete m_players;
402  }
403 
404  m_players = new std::vector<Player *>;
405  m_monitorCount = 1;
406 
407  if (layout == 1)
408  m_monitorCount = 1;
409  else if (layout == 2)
410  m_monitorCount = 2;
411  else if (layout == 3)
412  m_monitorCount = 4;
413  else if (layout == 4)
414  m_monitorCount = 6;
415  else if (layout == 5)
416  m_monitorCount = 8;
417 
418  hideAll();
419 
420  int monitorNo = 1;
421 
422  for (int x = 1; x <= m_monitorCount; x++)
423  {
424  Monitor *monitor = nullptr;
425 
426  if (restore)
427  {
428  if (x <= monList.size())
429  {
430  const QString& s = monList.at(x - 1);
431  int monID = s.toInt();
432 
433  // try to find a monitor that matches the monID
434  monitor = ZMClient::get()->getMonitorByID(monID);
435  }
436  }
437 
438  if (!monitor)
439  monitor = ZMClient::get()->getMonitorAt(monitorNo - 1);
440 
441  MythUIImage *frameImage = dynamic_cast<MythUIImage *> (GetChild(QString("frame%1-%2").arg(layout).arg(x)));
442  MythUIText *cameraText = dynamic_cast<MythUIText *> (GetChild(QString("name%1-%2").arg(layout).arg(x)));
443  MythUIText *statusText = dynamic_cast<MythUIText *> (GetChild(QString("status%1-%2").arg(layout).arg(x)));
444 
445  auto *p = new Player();
446  p->setMonitor(monitor);
447  p->setWidgets(frameImage, statusText, cameraText);
448  p->updateCamera();
449  m_players->push_back(p);
450 
451  monitorNo++;
452  if (monitorNo > ZMClient::get()->getMonitorCount())
453  monitorNo = 1;
454  }
455 
456  updateFrame();
457 }
458 
460 
462 {
463  if (m_rgba)
464  free(m_rgba);
465 }
466 
467 void Player::setMonitor(const Monitor *mon)
468 {
469  m_monitor = *mon;
470 
471  if (m_rgba)
472  free(m_rgba);
473 
474  m_rgba = (uchar *) malloc(4_UZ * m_monitor.width * m_monitor.height);
475 }
476 
477 void Player::setWidgets(MythUIImage *image, MythUIText *status, MythUIText *camera)
478 {
479  m_frameImage = image;
480  m_statusText = status;
481  m_cameraText = camera;
482 
483  if (m_frameImage)
484  m_frameImage->SetVisible(true);
485 
486  if (m_statusText)
487  m_statusText->SetVisible(true);
488 
489  if (m_cameraText)
490  m_cameraText->SetVisible(true);
491 }
492 
493 void Player::updateFrame(const unsigned char* buffer)
494 {
495  QImage image(buffer, m_monitor.width, m_monitor.height, QImage::Format_RGB888);
496 
498  img->Assign(image);
499  m_frameImage->SetImage(img);
500  img->DecrRef();
501 }
502 
504 {
505  if (m_statusText)
506  {
507  if (m_monitor.status == "Alarm" || m_monitor.status == "Error")
508  m_statusText->SetFontState("alarm");
509  else if (m_monitor.status == "Alert")
510  m_statusText->SetFontState("alert");
511  else
512  m_statusText->SetFontState("idle");
513 
515  }
516 }
517 
519 {
520  if (m_cameraText)
522 }
MythUIText::SetFontState
void SetFontState(const QString &state)
Definition: mythuitext.cpp:219
ZMLivePlayer::m_isMiniPlayer
bool m_isMiniPlayer
Definition: zmliveplayer.h:94
hardwareprofile.smolt.timeout
float timeout
Definition: smolt.py:102
ZMLivePlayer::Create
bool Create(void) override
Definition: zmliveplayer.cpp:47
MythUIImage
Image widget, displays a single image or multiple images in sequence.
Definition: mythuiimage.h:97
DialogCompletionEvent::GetId
QString GetId()
Definition: mythdialogbox.h:52
ZMLivePlayer::hideAll
bool hideAll()
Definition: zmliveplayer.cpp:86
Monitor::width
int width
Definition: zmdefines.h:117
Monitor::name
QString name
Definition: zmdefines.h:107
Player::m_frameImage
MythUIImage * m_frameImage
Definition: zmliveplayer.h:51
ZMClient::getMonitorCount
int getMonitorCount(void)
Definition: zmclient.cpp:837
ZMLivePlayer::setMonitorLayout
void setMonitorLayout(int layout, bool restore=false)
Definition: zmliveplayer.cpp:387
Monitor::status
QString status
Definition: zmdefines.h:116
MythUIType::GetChild
MythUIType * GetChild(const QString &name) const
Get a named child of this UIType.
Definition: mythuitype.cpp:135
MythPainter::GetFormatImage
MythImage * GetFormatImage()
Returns a blank reference counted image in the format required for the Draw functions for this painte...
Definition: mythpainter.cpp:528
ZMLivePlayer::m_paused
bool m_paused
Definition: zmliveplayer.h:88
mythdialogbox.h
MythScreenStack
Definition: mythscreenstack.h:16
MythMainWindow::RestoreScreensaver
static void RestoreScreensaver()
Definition: mythmainwindow.cpp:577
ZMLivePlayer::~ZMLivePlayer
~ZMLivePlayer() override
Definition: zmliveplayer.cpp:158
MythUIType::customEvent
void customEvent(QEvent *event) override
Definition: mythuitype.cpp:1008
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
MythScreenType
Screen in which all other widgets are contained and rendered.
Definition: mythscreentype.h:45
ZMClient::getMonitorByID
Monitor * getMonitorByID(int monID)
Definition: zmclient.cpp:853
Player::updateStatus
void updateStatus(void)
Definition: zmliveplayer.cpp:503
ZMLivePlayer::keyPressEvent
bool keyPressEvent(QKeyEvent *event) override
Key event handler.
Definition: zmliveplayer.cpp:187
ZMLivePlayer::m_monitorLayout
int m_monitorLayout
Definition: zmliveplayer.h:89
MythMainWindow::GetPainter
MythPainter * GetPainter()
Definition: mythmainwindow.cpp:258
ZMLivePlayer::m_alarmMonitor
int m_alarmMonitor
Definition: zmliveplayer.h:95
ZMLivePlayer::changeView
void changeView(void)
Definition: zmliveplayer.cpp:299
zmliveplayer.h
MythScreenType::GetFocusWidget
MythUIType * GetFocusWidget(void) const
Definition: mythscreentype.cpp:113
ZMLivePlayer::changePlayerMonitor
void changePlayerMonitor(int playerNo)
Definition: zmliveplayer.cpp:307
MythMainWindow::DisableScreensaver
static void DisableScreensaver()
Definition: mythmainwindow.cpp:583
zmclient.h
Player::m_statusText
MythUIText * m_statusText
Definition: zmliveplayer.h:52
MythMainWindow::TranslateKeyPress
bool TranslateKeyPress(const QString &Context, QKeyEvent *Event, QStringList &Actions, bool AllowJumps=true)
Get a list of actions for a keypress in the given context.
Definition: mythmainwindow.cpp:1112
Player::updateCamera
void updateCamera()
Definition: zmliveplayer.cpp:518
hardwareprofile.config.p
p
Definition: config.py:33
MythDialogBox
Basic menu dialog, message and a list of options.
Definition: mythdialogbox.h:166
ZMLivePlayer::ZMLivePlayer
ZMLivePlayer(MythScreenStack *parent, bool isMiniPlayer=false)
Definition: zmliveplayer.cpp:33
ZMLivePlayer::initMonitorLayout
bool initMonitorLayout(int layout)
Definition: zmliveplayer.cpp:141
MythImage::DecrRef
int DecrRef(void) override
Decrements reference count and deletes on 0.
Definition: mythimage.cpp:52
Player::m_cameraText
MythUIText * m_cameraText
Definition: zmliveplayer.h:53
sizetliteral.h
ZMClient::get
static ZMClient * get(void)
Definition: zmclient.cpp:37
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:55
MythCoreContext::GetNumSetting
int GetNumSetting(const QString &key, int defaultval=0)
Definition: mythcorecontext.cpp:912
ZMLivePlayer::ShowMenu
void ShowMenu() override
Definition: zmliveplayer.cpp:235
ZMLivePlayer::m_frameTimer
QTimer * m_frameTimer
Definition: zmliveplayer.h:87
MythUIType
The base class on which all widgets and screens are based.
Definition: mythuitype.h:85
Player::m_monitor
Monitor m_monitor
Definition: zmliveplayer.h:57
mythuihelper.h
Player::updateFrame
void updateFrame(const uchar *buffer)
Definition: zmliveplayer.cpp:493
MythUIText
All purpose text widget, displays a text string.
Definition: mythuitext.h:28
MythScreenType::keyPressEvent
bool keyPressEvent(QKeyEvent *event) override
Key event handler.
Definition: mythscreentype.cpp:404
Monitor::height
int height
Definition: zmdefines.h:118
XMLParseBase::LoadWindowFromXML
static bool LoadWindowFromXML(const QString &xmlfile, const QString &windowname, MythUIType *parent)
Definition: xmlparsebase.cpp:687
Player::setWidgets
void setWidgets(MythUIImage *image, MythUIText *status, MythUIText *camera)
Definition: zmliveplayer.cpp:477
MythImage
Definition: mythimage.h:36
ZMLivePlayer::stopPlayers
void stopPlayers(void)
Definition: zmliveplayer.cpp:382
Player::setMonitor
void setMonitor(const Monitor *mon)
Definition: zmliveplayer.cpp:467
FRAME_UPDATE_TIME
static constexpr std::chrono::milliseconds FRAME_UPDATE_TIME
Definition: zmliveplayer.cpp:31
ZMLivePlayer::m_players
std::vector< Player * > * m_players
Definition: zmliveplayer.h:92
DialogCompletionEvent
Event dispatched from MythUI modal dialogs to a listening class containing a result of some form.
Definition: mythdialogbox.h:41
ZMClient::setIsMiniPlayerEnabled
void setIsMiniPlayerEnabled(bool enabled)
Definition: zmclient.h:68
MythUIText::SetText
virtual void SetText(const QString &text)
Definition: mythuitext.cpp:132
MythUIType::SetVisible
virtual void SetVisible(bool visible)
Definition: mythuitype.cpp:1110
Player::m_rgba
uchar * m_rgba
Definition: zmliveplayer.h:55
mythcontext.h
DialogCompletionEvent::kEventType
static const Type kEventType
Definition: mythdialogbox.h:57
GetMythMainWindow
MythMainWindow * GetMythMainWindow(void)
Definition: mythmainwindow.cpp:104
build_compdb.action
action
Definition: build_compdb.py:9
MythUIImage::SetImage
void SetImage(MythImage *img)
Should not be used unless absolutely necessary since it bypasses the image caching and threaded loade...
Definition: mythuiimage.cpp:746
MythMainWindow::GetStack
MythScreenStack * GetStack(const QString &Stackname)
Definition: mythmainwindow.cpp:323
ZMLivePlayer::m_monitorCount
int m_monitorCount
Definition: zmliveplayer.h:90
Monitor::id
int id
Definition: zmdefines.h:106
ZMLivePlayer::customEvent
void customEvent(QEvent *event) override
Definition: zmliveplayer.cpp:271
Monitor
Definition: zmdefines.h:99
Player
Definition: zmliveplayer.h:34
ZMClient::getLiveFrame
int getLiveFrame(int monitorID, QString &status, FrameData &buffer)
Definition: zmclient.cpp:737
MythCoreContext::SaveSetting
void SaveSetting(const QString &key, int newValue)
Definition: mythcorecontext.cpp:881
MythImage::Assign
void Assign(const QImage &img)
Definition: mythimage.cpp:77
ZMClient::getMonitorAt
Monitor * getMonitorAt(int pos)
Definition: zmclient.cpp:843
mythmainwindow.h
MythScreenStack::AddScreen
virtual void AddScreen(MythScreenType *screen, bool allowFade=true)
Definition: mythscreenstack.cpp:52
ShowOkPopup
MythConfirmationDialog * ShowOkPopup(const QString &message, bool showCancel)
Non-blocking version of MythPopupBox::showOkPopup()
Definition: mythdialogbox.cpp:562
Player::~Player
~Player(void)
Definition: zmliveplayer.cpp:461
ZMLivePlayer::updateFrame
void updateFrame(void)
Definition: zmliveplayer.cpp:343
MythMainWindow::PauseIdleTimer
void PauseIdleTimer(bool Pause)
Pause the idle timeout timer.
Definition: mythmainwindow.cpp:2155
MythCoreContext::GetSetting
QString GetSetting(const QString &key, const QString &defaultval="")
Definition: mythcorecontext.cpp:898
ZMLivePlayer::GetMythUIType
MythUIType * GetMythUIType(const QString &name, bool optional=false)
Definition: zmliveplayer.cpp:76