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 <QTimer>
18 #include <QKeyEvent>
19 
20 // myth
21 #include <mythcontext.h>
22 #include <mythuihelper.h>
23 #include <mythmainwindow.h>
24 #include <mythdialogbox.h>
25 
26 // zoneminder
27 #include "zmliveplayer.h"
28 #include "zmclient.h"
29 
30 // the maximum image size we are ever likely to get from ZM
31 #define MAX_IMAGE_SIZE (2048*1536*3)
32 
33 const int FRAME_UPDATE_TIME = 1000 / 10; // try to update the frame 10 times a second
34 
35 ZMLivePlayer::ZMLivePlayer(MythScreenStack *parent, bool isMiniPlayer)
36  :MythScreenType(parent, "zmliveview"),
37  m_frameTimer(new QTimer(this)),
38  m_isMiniPlayer(isMiniPlayer)
39 {
41 
44 
45  connect(m_frameTimer, SIGNAL(timeout()), this,
46  SLOT(updateFrame()));
47 }
48 
50 {
51  // Load the theme for this screen
52  QString winName = m_isMiniPlayer ? "miniplayer" : "zmliveplayer";
53 
54  if (!LoadWindowFromXML("zoneminder-ui.xml", winName, this))
55  {
56  LOG(VB_GENERAL, LOG_ERR, QString("Cannot load screen '%1'").arg(winName));
57  return false;
58  }
59 
60  if (!hideAll())
61  return false;
62 
63  if (m_isMiniPlayer)
64  {
65  // we only support the single camera layout in the mini player
66  if (!initMonitorLayout(1))
67  return false;
68  }
69  else
70  {
71  if (!initMonitorLayout(gCoreContext->GetNumSetting("ZoneMinderLiveLayout", 1)))
72  return false;
73  }
74 
75  return true;
76 }
77 
78 MythUIType* ZMLivePlayer::GetMythUIType(const QString &name, bool optional)
79 {
81 
82  if (!optional && !type)
83  throw name;
84 
85  return type;
86 }
87 
89 {
90  try
91  {
92  // one player layout
93  GetMythUIType("name1-1")->SetVisible(false);
94  GetMythUIType("status1-1")->SetVisible(false);
95  GetMythUIType("frame1-1")->SetVisible(false);
96 
97  if (!m_isMiniPlayer)
98  {
99  // two player layout
100  for (int x = 1; x < 3; x++)
101  {
102  GetMythUIType(QString("name2-%1").arg(x))->SetVisible(false);
103  GetMythUIType(QString("status2-%1").arg(x))->SetVisible(false);
104  GetMythUIType(QString("frame2-%1").arg(x))->SetVisible(false);
105  }
106 
107  // four player layout
108  for (int x = 1; x < 5; x++)
109  {
110  GetMythUIType(QString("name3-%1").arg(x))->SetVisible(false);
111  GetMythUIType(QString("status3-%1").arg(x))->SetVisible(false);
112  GetMythUIType(QString("frame3-%1").arg(x))->SetVisible(false);
113  }
114 
115  // six player layout
116  for (int x = 1; x < 7; x++)
117  {
118  GetMythUIType(QString("name4-%1").arg(x))->SetVisible(false);
119  GetMythUIType(QString("status4-%1").arg(x))->SetVisible(false);
120  GetMythUIType(QString("frame4-%1").arg(x))->SetVisible(false);
121  }
122 
123  // eight player layout
124  for (int x = 1; x < 9; x++)
125  {
126  GetMythUIType(QString("name5-%1").arg(x))->SetVisible(false);
127  GetMythUIType(QString("status5-%1").arg(x))->SetVisible(false);
128  GetMythUIType(QString("frame5-%1").arg(x))->SetVisible(false);
129  }
130  }
131  }
132  catch (const QString &name)
133  {
134  LOG(VB_GENERAL, LOG_ERR,
135  QString("Theme is missing a critical theme element ('%1')")
136  .arg(name));
137  return false;
138  }
139 
140  return true;
141 }
142 
144 {
145  // if we haven't got any monitors there's not much we can do so bail out!
146  if (ZMClient::get()->getMonitorCount() == 0)
147  {
148  LOG(VB_GENERAL, LOG_ERR, "Cannot find any monitors. Bailing out!");
149  ShowOkPopup(tr("Can't show live view.") + "\n" +
150  tr("You don't have any monitors defined!"));
151  return false;
152  }
153 
154  setMonitorLayout(layout, true);
156 
157  return true;
158 }
159 
161 {
162  gCoreContext->SaveSetting("ZoneMinderLiveLayout", m_monitorLayout);
163 
166 
167  if (m_players)
168  {
169  QString s = "";
170  vector<Player*>::iterator i = m_players->begin();
171  for (; i != m_players->end(); ++i)
172  {
173  Player *p = *i;
174  if (s != "")
175  s += ",";
176  s += QString("%1").arg(p->getMonitor()->id);
177  }
178 
179  gCoreContext->SaveSetting("ZoneMinderLiveCameras", s);
180 
181  delete m_players;
182  }
183  else
184  gCoreContext->SaveSetting("ZoneMinderLiveCameras", "");
185 
186  delete m_frameTimer;
187 
189 }
190 
191 bool ZMLivePlayer::keyPressEvent(QKeyEvent *event)
192 {
193  if (GetFocusWidget() && GetFocusWidget()->keyPressEvent(event))
194  return true;
195 
196  QStringList actions;
197  bool handled = GetMythMainWindow()->TranslateKeyPress("TV Playback", event, actions);
198 
199  for (int i = 0; i < actions.size() && !handled; i++)
200  {
201  QString action = actions[i];
202  handled = true;
203 
204  if (action == "PAUSE")
205  {
206  if (m_paused)
207  {
209  m_paused = false;
210  }
211  else
212  {
213  m_frameTimer->stop();
214  m_paused = true;
215  }
216  }
217  else if (action == "INFO" && !m_isMiniPlayer)
218  {
219  changeView();
220  }
221  else if (action == "1" || action == "2" || action == "3" ||
222  action == "4" || action == "5" || action == "6" ||
223  action == "7" || action == "8" || action == "9")
224  changePlayerMonitor(action.toInt());
225  else if (action == "MENU")
226  {
227  ShowMenu();
228  }
229  else
230  handled = false;
231  }
232 
233  if (!handled && MythScreenType::keyPressEvent(event))
234  handled = true;
235 
236  return handled;
237 }
238 
240 {
241  MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
242 
243  MythDialogBox *menuPopup = new MythDialogBox("Menu", popupStack, "mainmenu");
244 
245  if (menuPopup->Create())
246  popupStack->AddScreen(menuPopup);
247 
248  menuPopup->SetReturnEvent(this, "mainmenu");
249 
250  menuPopup->AddButton(tr("Change View"), qVariantFromValue(QString("VIEW")));
251  menuPopup->AddButton(tr("Change Camera 1"), qVariantFromValue(QString("CAMERA1")));
252 
253  if (m_monitorLayout > 1)
254  menuPopup->AddButton(tr("Change Camera 2"), qVariantFromValue(QString("CAMERA2")));
255 
256  if (m_monitorLayout > 2)
257  {
258  menuPopup->AddButton(tr("Change Camera 3"), qVariantFromValue(QString("CAMERA3")));
259  menuPopup->AddButton(tr("Change Camera 4"), qVariantFromValue(QString("CAMERA4")));
260  }
261 
262  if (m_monitorLayout > 3)
263  {
264  menuPopup->AddButton(tr("Change Camera 5"), qVariantFromValue(QString("CAMERA5")));
265  menuPopup->AddButton(tr("Change Camera 6"), qVariantFromValue(QString("CAMERA6")));
266  }
267 
268  if (m_monitorLayout > 4)
269  {
270  menuPopup->AddButton(tr("Change Camera 7"), qVariantFromValue(QString("CAMERA7")));
271  menuPopup->AddButton(tr("Change Camera 8"), qVariantFromValue(QString("CAMERA8")));
272  }
273 }
274 
275 void ZMLivePlayer::customEvent(QEvent *event)
276 {
277  if (event->type() == DialogCompletionEvent::kEventType)
278  {
279  DialogCompletionEvent *dce = static_cast<DialogCompletionEvent*>(event);
280 
281  // make sure the user didn't ESCAPE out of the menu
282  if (dce->GetResult() < 0)
283  return;
284 
285  QString resultid = dce->GetId();
286  QString data = dce->GetData().toString();
287 
288  if (resultid == "mainmenu")
289  {
290  if (data == "VIEW")
291  changeView();
292  else if (data.startsWith("CAMERA"))
293  {
294  data = data.remove("CAMERA");
295  int monitor = data.toInt();
296  changePlayerMonitor(monitor);
297  }
298  }
299  }
300 }
301 
303 {
304  m_monitorLayout++;
305  if (m_monitorLayout > 5)
306  m_monitorLayout = 1;
308 }
309 
311 {
312  if (playerNo > (int)m_players->size())
313  return;
314 
315  m_frameTimer->stop();
316 
317  int oldMonID = m_players->at(playerNo - 1)->getMonitor()->id;
318  Monitor *mon;
319 
320  // find the old monitor ID in the list of available monitors
321  int pos;
322  for (pos = 0; pos < ZMClient::get()->getMonitorCount(); pos++)
323  {
324  mon = ZMClient::get()->getMonitorAt(pos);
325  if (oldMonID == mon->id)
326  {
327  break;
328  }
329  }
330 
331  // get the next monitor in the list
332  if (pos != ZMClient::get()->getMonitorCount())
333  pos++;
334 
335  // wrap around to the start if we've reached the end
336  if (pos >= ZMClient::get()->getMonitorCount())
337  pos = 0;
338 
339  mon = ZMClient::get()->getMonitorAt(pos);
340 
341  m_players->at(playerNo - 1)->setMonitor(mon);
342  m_players->at(playerNo - 1)->updateCamera();
343 
345 }
346 
348 {
349  static unsigned char buffer[MAX_IMAGE_SIZE];
350  m_frameTimer->stop();
351 
352  // get a list of monitor id's that need updating
353  QList<int> monList;
354  for (auto i = m_players->begin(); i != m_players->end(); ++i)
355  {
356  Player *p = *i;
357  if (!monList.contains(p->getMonitor()->id))
358  monList.append(p->getMonitor()->id);
359  }
360 
361  for (int x = 0; x < monList.count(); x++)
362  {
363  QString status;
364  int frameSize = ZMClient::get()->getLiveFrame(monList[x], status, buffer, sizeof(buffer));
365 
366  if (frameSize > 0 && !status.startsWith("ERROR"))
367  {
368  // update each player that is displaying this monitor
369  for (auto it = m_players->begin(); it != m_players->end(); ++it)
370  {
371  Player *p = *it;
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(buffer);
380  }
381  }
382  }
383  }
384 
386 }
387 
389 {
390  m_frameTimer->stop();
391 }
392 
393 void 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 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  Player *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  if (m_rgba)
470  free(m_rgba);
471 }
472 
474 {
475  m_monitor = *mon;
476 
477  if (m_rgba)
478  free(m_rgba);
479 
480  m_rgba = (uchar *) malloc(m_monitor.width * m_monitor.height * 4);
481 }
482 
483 void Player::setWidgets(MythUIImage *image, MythUIText *status, MythUIText *camera)
484 {
485  m_frameImage = image;
486  m_statusText = status;
487  m_cameraText = camera;
488 
489  if (m_frameImage)
490  m_frameImage->SetVisible(true);
491 
492  if (m_statusText)
493  m_statusText->SetVisible(true);
494 
495  if (m_cameraText)
496  m_cameraText->SetVisible(true);
497 }
498 
499 void Player::updateFrame(const unsigned char* buffer)
500 {
501  QImage image(buffer, m_monitor.width, m_monitor.height, QImage::Format_RGB888);
502 
504  img->Assign(image);
505  m_frameImage->SetImage(img);
506  img->DecrRef();
507 }
508 
510 {
511  if (m_statusText)
512  {
513  if (m_monitor.status == "Alarm" || m_monitor.status == "Error")
514  m_statusText->SetFontState("alarm");
515  else if (m_monitor.status == "Alert")
516  m_statusText->SetFontState("alert");
517  else
518  m_statusText->SetFontState("idle");
519 
521  }
522 }
523 
525 {
526  if (m_cameraText)
528 }
int height
Definition: zmdefines.h:123
void stopPlayers(void)
const int FRAME_UPDATE_TIME
void SetImage(MythImage *img)
Should not be used unless absolutely necessary since it bypasses the image caching and threaded loade...
bool TranslateKeyPress(const QString &context, QKeyEvent *e, QStringList &actions, bool allowJumps=true)
Get a list of actions for a keypress in the given context.
int m_monitorCount
Definition: zmliveplayer.h:91
MythConfirmationDialog * ShowOkPopup(const QString &message, QObject *parent, const char *slot, bool showCancel)
Non-blocking version of MythPopupBox::showOkPopup()
All purpose text widget, displays a text string.
Definition: mythuitext.h:28
Monitor * getMonitorAt(int pos)
Definition: zmclient.cpp:847
void SaveSetting(const QString &key, int newValue)
int DecrRef(void) override
Decrements reference count and deletes on 0.
Definition: mythimage.cpp:55
Image widget, displays a single image or multiple images in sequence.
Definition: mythuiimage.h:97
void customEvent(QEvent *event) override
Basic menu dialog, message and a list of options.
MythUIText * m_statusText
Definition: zmliveplayer.h:53
virtual void SetText(const QString &text)
Definition: mythuitext.cpp:136
MythUIImage * m_frameImage
Definition: zmliveplayer.h:52
MythScreenStack * GetStack(const QString &stackname)
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
void setWidgets(MythUIImage *image, MythUIText *status, MythUIText *camera)
Monitor * getMonitorByID(int monID)
Definition: zmclient.cpp:857
MythUIType * GetMythUIType(const QString &name, bool optional=false)
The base class on which all widgets and screens are based.
Definition: mythuitype.h:63
static Type kEventType
Definition: mythdialogbox.h:50
bool m_isMiniPlayer
Definition: zmliveplayer.h:95
void updateStatus(void)
void AddButton(const QString &title, QVariant data=0, bool newMenu=false, bool setCurrent=false)
bool Create(void) override
virtual void SetVisible(bool visible)
virtual void AddScreen(MythScreenType *screen, bool allowFade=true)
void setMonitorLayout(int layout, bool restore=false)
int getMonitorCount(void)
Definition: zmclient.cpp:841
void setIsMiniPlayerEnabled(bool enabled)
Definition: zmclient.h:65
int id
Definition: zmdefines.h:111
Monitor m_monitor
Definition: zmliveplayer.h:58
void updateCamera()
QString status
Definition: zmdefines.h:121
void DoDisableScreensaver(void)
QString GetSetting(const QString &key, const QString &defaultval="")
~Player(void)
int width
Definition: zmdefines.h:122
void DoRestoreScreensaver(void)
static ZMClient * get(void)
Definition: zmclient.cpp:38
void updateFrame(const uchar *buffer)
int m_alarmMonitor
Definition: zmliveplayer.h:96
MythPainter * GetCurrentPainter()
ZMLivePlayer(MythScreenStack *parent, bool isMiniPlayer=false)
const char * name
Definition: ParseText.cpp:328
Monitor * getMonitor(void)
Definition: zmliveplayer.h:49
vector< Player * > * m_players
Definition: zmliveplayer.h:93
MythUIHelper * GetMythUI()
static bool LoadWindowFromXML(const QString &xmlfile, const QString &windowname, MythUIType *parent)
MythUIType * GetFocusWidget(void) const
MythMainWindow * GetMythMainWindow(void)
void PauseIdleTimer(bool pause)
int GetNumSetting(const QString &key, int defaultval=0)
bool keyPressEvent(QKeyEvent *) override
Key event handler.
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
void SetReturnEvent(QObject *retobject, const QString &resultid)
void changeView(void)
void setMonitor(Monitor *mon)
#define MAX_IMAGE_SIZE
void changePlayerMonitor(int playerNo)
MythUIText * m_cameraText
Definition: zmliveplayer.h:54
void updateFrame(void)
bool keyPressEvent(QKeyEvent *) override
Key event handler.
Screen in which all other widgets are contained and rendered.
void Assign(const QImage &img)
Definition: mythimage.cpp:80
void SetFontState(const QString &)
Definition: mythuitext.cpp:225
uchar * m_rgba
Definition: zmliveplayer.h:56
void ShowMenu() override
MythImage * GetFormatImage()
Returns a blank reference counted image in the format required for the Draw functions for this painte...
Event dispatched from MythUI modal dialogs to a listening class containing a result of some form.
Definition: mythdialogbox.h:37
QTimer * m_frameTimer
Definition: zmliveplayer.h:88
MythUIType * GetChild(const QString &name) const
Get a named child of this UIType.
Definition: mythuitype.cpp:132
int m_monitorLayout
Definition: zmliveplayer.h:90
QString name
Definition: zmdefines.h:112
bool initMonitorLayout(int layout)
bool Create(void) override
int getLiveFrame(int monitorID, QString &status, unsigned char *buffer, int bufferSize)
Definition: zmclient.cpp:741