]> git.vomp.tv Git - vompclient.git/blob - vradiorec.cc
Fix text corruption in live TV OSD clock
[vompclient.git] / vradiorec.cc
1 /*
2     Copyright 2004-2020 Chris Tallon
3
4     This file is part of VOMP.
5
6     VOMP is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     VOMP is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with VOMP.  If not, see <https://www.gnu.org/licenses/>.
18 */
19
20 #include "control.h"
21 #include "osd.h"
22 #include "wsymbol.h"
23 #include "recording.h"
24 #include "recinfo.h"
25 #include "message.h"
26 #include "vdr.h"
27 #include "video.h"
28 #include "playerradiorec.h"
29 #include "boxstack.h"
30 #include "input.h"
31 #include "vinfo.h"
32 #include "i18n.h"
33 #include "log.h"
34 #include "messagequeue.h"
35
36 #include "vradiorec.h"
37
38 static const char* TAG = "VRadioRec";
39
40 VRadioRec::VRadioRec(Recording* rec)
41 {
42   boxstack = BoxStack::getInstance();
43   vdr = VDR::getInstance();
44   video = Video::getInstance();
45   timers = Timers::getInstance();
46   myRec = rec;
47   playing = false;
48   startMargin = 0;
49   endMargin = 0;
50
51   player = new PlayerRadioRec(Control::getInstance(), this);
52
53   char* cstartMargin = vdr->configLoad("Timers", "Start margin");
54   char* cendMargin = vdr->configLoad("Timers", "End margin");
55   if (!cstartMargin)
56   {
57     startMargin = 300; // 5 mins default
58   }
59   else
60   {
61     startMargin = atoi(cstartMargin) * 60;
62     delete[] cstartMargin;
63   }
64
65   if (!cendMargin)
66   {
67     endMargin = 300; // 5 mins default
68   }
69   else
70   {
71     endMargin = atoi(cendMargin) * 60;
72     delete[] cendMargin;
73   }
74
75   LogNT::getInstance()->debug(TAG, "SM: {} EM: {}", startMargin, endMargin);
76
77   setSize(video->getScreenWidth(), video->getScreenHeight());
78   createBuffer();
79   setBackgroundColour(DrawStyle::TRANSPARENT);
80   setPosition(0, 0);
81
82   barRegion.x = 0;
83   barRegion.y = video->getScreenHeight() - 58;   // FIXME, need to be - 1? and below?
84   barRegion.w = video->getScreenWidth();
85   barRegion.h = 58;
86
87   clocksRegion.x = barRegion.x + 140;
88   clocksRegion.y = barRegion.y + 12;
89   clocksRegion.w = 170;
90   clocksRegion.h = getFontHeight();
91
92
93   barBlue.set(0, 0, 0, 128);
94
95   barShowing = false;
96
97   MessageQueue::getInstance()->addReceiver(this);
98 }
99
100 void VRadioRec::preDelete()
101 {
102   timers->cancelTimer(this, 1);
103   timers->cancelTimer(this, 2);
104 }
105
106 VRadioRec::~VRadioRec()
107 {
108   MessageQueue::getInstance()->removeReceiver(this);
109
110   if (playing) stopPlay();
111
112   // kill recInfo in case resumePoint has changed (likely)
113   myRec->dropRecInfo();
114   // FIXME - do this properly - save the resume point back to the server manually and update
115   // rec->recInfo->resumePoint - this will fix the ~10s offset problem as well
116 }
117
118 void VRadioRec::draw()
119 {
120   fillColour(DrawStyle::TRANSPARENT);
121 }
122
123 void VRadioRec::go(bool resume)
124 {
125   ULONG startFrameNum;
126   if (resume)
127     startFrameNum = myRec->recInfo->resumePoint;
128   else
129     startFrameNum = 0;
130
131   LogNT::getInstance()->debug(TAG, "Starting stream: {}", myRec->getFileName());
132   ULONG lengthFrames = 0;
133   bool isPesRecording;
134   ULLONG lengthBytes = vdr->streamRecording(myRec->getFileName(), &lengthFrames, &isPesRecording);
135   myRec->IsPesRecording = isPesRecording;
136
137   bool cantStart = false;
138
139   if (!lengthBytes) cantStart = true;
140   else if (!player->init(lengthBytes, lengthFrames, myRec->IsPesRecording)) cantStart = true;
141   else
142   {
143     doBar(0);
144     player->setCurrentFrameNumber(startFrameNum);
145     player->play();
146     playing = true;
147   }
148
149   if (cantStart)
150   {
151     stopPlay(); // clean up
152
153     if (!vdr->isConnected())
154     {
155       Control::getInstance()->connectionLost();
156       return;
157     }
158
159     Message* m = new Message();
160     m->message = Message::CLOSE_ME;
161     m->from = this;
162     m->p_to = Message::BOXSTACK;
163     MessageQueue::getInstance()->postMessage(m);
164
165     VInfo* vi = new VInfo();
166     vi->setSize(400, 150);
167     vi->createBuffer();
168     if (video->getFormat() == Video::PAL)
169       vi->setPosition(170, 200);
170     else
171       vi->setPosition(160, 150);
172     vi->setExitable();
173     vi->setBorderOn(1);
174     vi->setTitleBarOn(0);
175     vi->setOneLiner(tr("Error playing recording"));
176     vi->draw();
177
178     m = new Message();
179     m->message = Message::ADD_VIEW;
180     m->p_to = Message::BOXSTACK;
181     m->data = reinterpret_cast<void*>(vi);
182     MessageQueue::getInstance()->postMessage(m);
183   }
184 }
185
186 int VRadioRec::handleCommand(int command)
187 {
188   switch(command)
189   {
190     case Input::PLAY:
191     {
192       player->play();
193       doBar(0);
194       return BoxStack::COMMAND_HANDLED;
195     }
196     case Input::PLAYPAUSE:
197     {
198         player->playpause();
199         doBar(0);
200         return BoxStack::COMMAND_HANDLED;
201     }
202
203     case Input::STOP:
204     case Input::BACK:
205     case Input::MENU:
206     {
207       if (playing) stopPlay();
208       return BoxStack::DELETE_ME;
209     }
210     case Input::PAUSE:
211     {
212       player->pause();
213       doBar(0);
214       return BoxStack::COMMAND_HANDLED;
215     }
216     case Input::SKIPFORWARD:
217     {
218       doBar(3);
219       player->skipForward(60);
220       return BoxStack::COMMAND_HANDLED;
221     }
222     case Input::SKIPBACK:
223     {
224       doBar(4);
225       player->skipBackward(60);
226       return BoxStack::COMMAND_HANDLED;
227     }
228     case Input::YELLOW:
229     {
230       doBar(2);
231       player->skipBackward(10);
232       return BoxStack::COMMAND_HANDLED;
233     }
234     case Input::BLUE:
235     {
236       doBar(1);
237       player->skipForward(10);
238       return BoxStack::COMMAND_HANDLED;
239     }
240     case Input::OK:
241     {
242       if (barShowing) removeBar();
243       else doBar(0);
244       return BoxStack::COMMAND_HANDLED;
245     }
246
247     case Input::ZERO:  player->jumpToPercent(0);  doBar(0);  return 2;
248     case Input::ONE:   player->jumpToPercent(10); doBar(0);  return 2;
249     case Input::TWO:   player->jumpToPercent(20); doBar(0);  return 2;
250     case Input::THREE: player->jumpToPercent(30); doBar(0);  return 2;
251     case Input::FOUR:  player->jumpToPercent(40); doBar(0);  return 2;
252     case Input::FIVE:  player->jumpToPercent(50); doBar(0);  return 2;
253     case Input::SIX:   player->jumpToPercent(60); doBar(0);  return 2;
254     case Input::SEVEN: player->jumpToPercent(70); doBar(0);  return 2;
255     case Input::EIGHT: player->jumpToPercent(80); doBar(0);  return 2;
256     case Input::NINE:  player->jumpToPercent(90); doBar(0);  return 2;
257
258 #ifdef DEV
259     case Input::RED:
260     {
261       //player->test1();
262
263       return BoxStack::COMMAND_HANDLED;
264     }
265     case Input::GREEN:
266     {
267       //player->test2();
268       return BoxStack::COMMAND_HANDLED;
269     }
270 #endif
271
272   }
273
274   return BoxStack::ABANDON_COMMAND;
275 }
276
277 void VRadioRec::processMessage(Message* m)
278 {
279   if (m->message == Message::MOUSE_LBDOWN)
280   {
281     UINT x = m->parameter - getScreenX();
282     UINT y = m->tag - getScreenY();
283     if (!barShowing)
284     {
285       boxstack->handleCommand(Input::OK); //simulate rok press
286     }
287     else if (    (barRegion.x <= x)                  // If the click happened within the bar region...
288               && (barRegion.y <= y)
289               && ((barRegion.x + barRegion.w) >= x)
290               && ((barRegion.y + barRegion.h) >= y))
291     {
292       UINT progBarXbase = barRegion.x + 300;
293       if ( x >= barRegion.x + progBarXbase + 24
294         && x <= barRegion.x + progBarXbase + 4 + 302
295         && y >= barRegion.y + 12 - 2
296         && y <= barRegion.y + 12 - 2+28)
297       {
298         int cx = x - (barRegion.x + progBarXbase + 4);
299         double percent = 100 * cx / 302.;
300         player->jumpToPercent(percent); // FIXME check this still works
301         doBar(3);
302         return;
303         //  int progressWidth = 302 * currentFrameNum / lengthFrames;
304         //  rectangle(barRegion.x + progBarXbase + 4, barRegion.y + 16, progressWidth, 16, DrawStyle::SELECTHIGHLIGHT);
305       }
306     }
307     else
308     {
309       boxstack->handleCommand(Input::OK); //simulate rok press
310     }
311   }
312   else if (m->message == Message::PLAYER_EVENT)
313   {
314     if (m->from != player) return;
315
316     LogNT::getInstance()->debug(TAG, "Message received");
317
318     switch(m->parameter)
319     {
320       case PlayerRadioRec::CONNECTION_LOST: // connection lost detected
321       {
322         // I can't handle this, send it to control
323         Message* m2 = new Message();
324         m2->p_to = Message::CONTROL;
325         m2->message = Message::CONNECTION_LOST;
326         MessageQueue::getInstance()->postMessage(m2);
327         break;
328       }
329       case PlayerRadioRec::STOP_PLAYBACK:
330       {
331         // FIXME Obselete ish - improve this
332         Message* m2 = new Message(); // Must be done after this thread finishes, and must break into master mutex
333         m2->p_to = Message::CONTROL;
334         m2->message = Message::STOP_PLAYBACK;
335         MessageQueue::getInstance()->postMessage(m2);
336         break;
337       }
338     }
339   }
340 }
341
342 void VRadioRec::stopPlay()
343 {
344   LogNT::getInstance()->debug(TAG, "Pre stopPlay");
345
346   removeBar();
347   player->stop();
348   vdr->stopStreaming();
349   delete player;
350
351   playing = false;
352
353   if (!vdr->isConnected()) { Control::getInstance()->connectionLost(); return; }
354   LogNT::getInstance()->debug(TAG, "Post stopPlay");
355 }
356
357 void VRadioRec::doBar(int action)
358 {
359   barShowing = true;
360
361   rectangle(barRegion, barBlue);
362
363   /* Work out what to display - choices:
364
365   Playing  >
366   Paused   ||
367
368   Specials, informed by parameter
369
370   Skip forward 10s    >|
371   Skip backward 10s   |<
372   Skip forward 1m     >>|
373   Skip backward 1m    |<<
374
375   */
376
377   WSymbol w;
378   TEMPADD(&w);
379   w.nextSymbol = 0;
380   w.setPosition(barRegion.x + 66, barRegion.y + 16);
381
382   UCHAR playerState = 0;
383
384   if (action)
385   {
386     if (action == 1)       w.nextSymbol = WSymbol::SKIPFORWARD;
387     else if (action == 2)  w.nextSymbol = WSymbol::SKIPBACK;
388     else if (action == 3)  w.nextSymbol = WSymbol::SKIPFORWARD2;
389     else if (action == 4)  w.nextSymbol = WSymbol::SKIPBACK2;
390   }
391   else
392   {
393     playerState = player->getState();
394     if (playerState == PlayerRadioRec::S_PAUSE_P)      w.nextSymbol = WSymbol::PAUSE;
395     else if (playerState == PlayerRadioRec::S_PAUSE_I) w.nextSymbol = WSymbol::PAUSE;
396     else                                            w.nextSymbol = WSymbol::PLAY;
397   }
398
399   w.draw();
400
401   drawBarClocks();
402
403   BoxStack::getInstance()->update(this, &barRegion);
404
405   timers->setTimerD(this, 1, 4); // only set the getridofbar timer if not ffwd/fbwd
406   timers->setTimerD(this, 2, 0, 200000000);
407 }
408
409 void VRadioRec::timercall(int clientReference)
410 {
411   switch(clientReference)
412   {
413     case 1:
414     {
415       // Remove bar
416       removeBar();
417       break;
418     }
419     case 2:
420     {
421       // Update clock
422       if (!barShowing) break;
423       drawBarClocks();
424       boxstack->update(this, &barRegion);
425       
426       timers->setTimerD(this, 2, 0, 200000000);
427       break;
428     }
429   }
430 }
431
432 void VRadioRec::drawBarClocks()
433 {
434   LogNT* logger = LogNT::getInstance();
435   logger->debug(TAG, "Draw bar clocks");
436
437   // Draw RTC
438   // Blank the area first
439   rectangle(barRegion.x + 624, barRegion.y + 12, 60, 30, barBlue);
440   char timeString[20];
441   time_t t;
442   time(&t);
443   struct tm tms;
444   LOCALTIME_R(&t, &tms);
445   strftime(timeString, 19, "%H:%M", &tms);
446   drawText(timeString, barRegion.x + 624, barRegion.y + 12, DrawStyle::LIGHTTEXT);
447
448   // Draw clocks
449
450   rectangle(clocksRegion, barBlue);
451
452   ULONG currentSeconds = player->getCurrentSeconds();
453   ULONG lengthSeconds = player->getLengthSeconds();
454   char buffer[100];
455
456   if (lengthSeconds && (currentSeconds < lengthSeconds))
457   {
458     ULONG dcurrentSeconds = currentSeconds;
459     ULONG dlengthSeconds = lengthSeconds;
460
461     ULONG currentHours = dcurrentSeconds / 3600;
462     dcurrentSeconds %= 3600;
463     ULONG currentMinutes = dcurrentSeconds / 60;
464     dcurrentSeconds %= 60;
465
466     ULONG lengthHours = dlengthSeconds / 3600;
467     dlengthSeconds %= 3600;
468     ULONG lengthMinutes = dlengthSeconds / 60;
469     dlengthSeconds %= 60;
470
471     SNPRINTF(buffer, 99, "%01lu:%02lu:%02lu / %01lu:%02lu:%02lu", currentHours, currentMinutes, dcurrentSeconds, lengthHours, lengthMinutes, dlengthSeconds);
472     LogNT::getInstance()->debug(TAG, buffer);
473   }
474   else
475   {
476     strcpy(buffer, "-:--:-- / -:--:--");
477   }
478
479   drawText(buffer, clocksRegion.x, clocksRegion.y, DrawStyle::LIGHTTEXT);
480
481   // Draw progress bar
482   int progBarXbase = barRegion.x + 300;
483
484   rectangle(barRegion.x + progBarXbase, barRegion.y + 12, 310, 24, DrawStyle::LIGHTTEXT);
485   rectangle(barRegion.x + progBarXbase + 2, barRegion.y + 14, 306, 20, barBlue);
486
487   if (currentSeconds > lengthSeconds) return;
488   if (lengthSeconds == 0) return;
489
490   // Draw yellow portion
491   int progressWidth = 302 * currentSeconds / lengthSeconds;
492   rectangle(barRegion.x + progBarXbase + 4, barRegion.y + 16, progressWidth, 16, DrawStyle::SELECTHIGHLIGHT);
493 /*
494
495   if (myRec->recInfo->timerEnd > time(NULL)) // if chasing
496   {
497     int nrWidth = (int)(302 * ((double)(lengthFrames - 0) / lengthFrames)); // 0 inserted instead of getlengthframes
498
499     Log::getInstance()->log("GVASDF", Log::DEBUG, "Length Frames: %lu", lengthFrames);
500 //    Log::getInstance()->log("GVASDF", Log::DEBUG, "Player lf: %lu", player->getLengthFrames());
501     Log::getInstance()->log("GVASDF", Log::DEBUG, "NR WDITH: %i", nrWidth);
502     rectangle(barRegion.x + progBarXbase + 4 + 302 - nrWidth, barRegion.y + 16, nrWidth, 16, DrawStyle::RED);
503   }
504 */
505
506   LogNT::getInstance()->debug(TAG, "blips");
507
508   // Now calc position for start margin blips
509   int posPix;
510
511   posPix = 302 * startMargin / lengthSeconds;
512   LogNT::getInstance()->debug(TAG, "posPix {}", posPix);
513
514   rectangle(barRegion.x + progBarXbase + 2 + posPix, barRegion.y + 12 - 2, 2, 2, DrawStyle::LIGHTTEXT);
515   rectangle(barRegion.x + progBarXbase + 2 + posPix, barRegion.y + 12 + 24, 2, 2, DrawStyle::LIGHTTEXT);
516
517   posPix = 302 * (lengthSeconds - endMargin) / lengthSeconds;
518
519   rectangle(barRegion.x + progBarXbase + 2 + posPix, barRegion.y + 12 - 2, 2, 2, DrawStyle::LIGHTTEXT);
520   rectangle(barRegion.x + progBarXbase + 2 + posPix, barRegion.y + 12 + 24, 2, 2, DrawStyle::LIGHTTEXT);
521 }
522
523 void VRadioRec::removeBar()
524 {
525   if (!barShowing) return;
526   timers->cancelTimer(this, 2);
527   barShowing = false;
528   rectangle(barRegion, DrawStyle::TRANSPARENT);
529   boxstack->update(this, &barRegion);
530 }
531