]> git.vomp.tv Git - vompclient.git/blob - vvideorec.cc
New Windows remote stuff
[vompclient.git] / vvideorec.cc
1 /*
2     Copyright 2004-2005 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, write to the Free Software
18     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19 */
20
21 #include "vvideorec.h"
22
23 VVideoRec::VVideoRec(Recording* rec)
24 {
25   vdr = VDR::getInstance();
26   video = Video::getInstance();
27   timers = Timers::getInstance();
28
29   isRadio = !(rec->recInfo->hasVideo());
30
31   Log::getInstance()->log("VVideoRec", Log::DEBUG, "hasVideo = %i", isRadio);
32
33   player = new Player(Command::getInstance(), this, true, isRadio);
34   player->init();
35
36   videoMode = video->getMode();
37   myRec = rec;
38
39   playing = false;
40
41   startMargin = 0;
42   endMargin = 0;
43   char* cstartMargin = vdr->configLoad("Timers", "Start margin");
44   char* cendMargin = vdr->configLoad("Timers", "End margin");
45   if (!cstartMargin)
46   {
47     startMargin = 300; // 5 mins default
48   }
49   else
50   {
51     startMargin = atoi(cstartMargin) * 60;
52     delete[] cstartMargin;
53   }
54
55   if (!cendMargin)
56   {
57     endMargin = 300; // 5 mins default
58   }
59   else
60   {
61     endMargin = atoi(cendMargin) * 60;
62     delete[] cendMargin;
63   }
64
65   Log::getInstance()->log("VVideoRec", Log::DEBUG, "SM: %u EM: %u", startMargin, endMargin);
66
67   create(video->getScreenWidth(), video->getScreenHeight());
68   transparent.set(0, 0, 0, 0);
69   setBackgroundColour(transparent);
70
71   barRegion.x = 0;
72   barRegion.y = video->getScreenHeight() - 58;   // FIXME, need to be - 1? and below?
73   barRegion.w = video->getScreenWidth();
74   barRegion.h = 58;
75
76   clocksRegion.x = barRegion.x + 140;
77   clocksRegion.y = barRegion.y + 12;
78   clocksRegion.w = 170;
79   clocksRegion.h = surface->getFontHeight();
80
81
82   barBlue.set(0, 0, 150, 150);
83
84   barShowing = false;
85   stickyBar = false;
86
87   dowss = false;
88   char* optionWSS = vdr->configLoad("General", "WSS");
89   if (optionWSS)
90   {
91     if (strstr(optionWSS, "Yes")) dowss = true;
92     delete[] optionWSS;
93   }
94   Log::getInstance()->log("VVideoRec", Log::DEBUG, "Do WSS: %u", dowss);
95
96   wss.setFormat(video->getFormat());
97   wss.setSurface(surface);
98   wss.setWide(true);
99
100 /*
101   wssRegion.x = 0;
102   wssRegion.y = 6;
103   wssRegion.w = video->getScreenWidth();
104   wssRegion.h = 2;
105 */
106   wssRegion.x = 0;
107   wssRegion.y = 0;
108   wssRegion.w = video->getScreenWidth();
109   wssRegion.h = 300;
110 }
111
112 VVideoRec::~VVideoRec()
113 {
114   if (playing) stopPlay();
115   video->setDefaultAspect();
116
117   timers->cancelTimer(this, 1);
118   timers->cancelTimer(this, 2);
119
120   // kill recInfo in case resumePoint has changed (likely)
121   myRec->dropRecInfo();
122   // FIXME - do this properly - save the resume point back to the server manually and update
123   // rec->recInfo->resumePoint - this will fix the ~10s offset problem as well
124 }
125
126 void VVideoRec::draw()
127 {
128   View::draw();
129 }
130
131 void VVideoRec::go(bool resume)
132 {
133   ULONG startFrameNum;
134   if (resume)
135     startFrameNum = myRec->recInfo->resumePoint;
136   else
137     startFrameNum = 0;
138
139   Log::getInstance()->log("VVideoRec", Log::DEBUG, "Starting stream: %s at frame: %lu", myRec->getFileName(), startFrameNum);
140   ULONG lengthFrames = 0;
141   ULLONG lengthBytes = vdr->streamRecording(myRec->getFileName(), &lengthFrames);
142   if (lengthBytes)
143   {
144     doBar(0);
145     player->setLengthBytes(lengthBytes);
146     if (!isRadio) player->setLengthFrames(lengthFrames);
147     player->setStartFrame(startFrameNum); // means bytes if radio (FIXME not done yet!)
148     player->play();
149     playing = true;
150   }
151   else
152   {
153     stopPlay(); // clean up
154
155     if (!vdr->isConnected())
156     {
157       Command::getInstance()->connectionLost();
158       return;
159     }
160
161     ViewMan* viewman = ViewMan::getInstance();
162
163     Message* m = new Message();
164     m->message = Message::CLOSE_ME;
165     m->from = this;
166     m->to = viewman;
167     Command::getInstance()->postMessageNoLock(m);
168
169     VInfo* vi = new VInfo();
170     vi->create(400, 150);
171     if (video->getFormat() == Video::PAL)
172       vi->setScreenPos(170, 200);
173     else
174       vi->setScreenPos(160, 150);
175     vi->setExitable();
176     vi->setBorderOn(1);
177     vi->setTitleBarOn(0);
178     vi->setOneLiner(tr("Error playing recording"));
179     vi->draw();
180
181     m = new Message();
182     m->message = Message::ADD_VIEW;
183     m->to = viewman;
184     m->parameter = (ULONG)vi;
185     Command::getInstance()->postMessageNoLock(m);
186   }
187 }
188
189 int VVideoRec::handleCommand(int command)
190 {
191   switch(command)
192   {
193     case Remote::PLAY:
194     {
195       player->play();
196       doBar(0);
197       return 2;
198     }
199
200     case Remote::STOP:
201     case Remote::BACK:
202     case Remote::MENU:
203     {
204       if (playing) stopPlay();
205       return 4;
206     }
207     case Remote::PAUSE:
208     {
209       player->pause();
210       doBar(0);
211       return 2;
212     }
213     case Remote::SKIPFORWARD:
214     {
215       doBar(3);
216       player->skipForward(60);
217       return 2;
218     }
219     case Remote::SKIPBACK:
220     {
221       doBar(4);
222       player->skipBackward(60);
223       return 2;
224     }
225     case Remote::FORWARD:
226     {
227       if (isRadio) return 2;
228       player->fastForward();
229       doBar(0);
230       return 2;
231     }
232     case Remote::REVERSE:
233     {
234       if (isRadio) return 2;
235       player->fastBackward();
236       doBar(0);
237       return 2;
238     }
239     case Remote::YELLOW:
240     {
241       doBar(2);
242       player->skipBackward(10);
243       return 2;
244     }
245     case Remote::BLUE:
246     {
247       doBar(1);
248       player->skipForward(10);
249       return 2;
250     }
251     case Remote::FULL:
252     case Remote::TV:
253     {
254       if (isRadio) return 2;
255       toggleChopSides();
256       return 2;
257     }
258
259     case Remote::OK:
260     {
261       if (barShowing) removeBar();
262       else doBar(0);
263       return 2;
264     }
265
266     case Remote::ZERO:  player->jumpToPercent(0);  doBar(0);  return 2;
267     case Remote::ONE:   player->jumpToPercent(10); doBar(0);  return 2;
268     case Remote::TWO:   player->jumpToPercent(20); doBar(0);  return 2;
269     case Remote::THREE: player->jumpToPercent(30); doBar(0);  return 2;
270     case Remote::FOUR:  player->jumpToPercent(40); doBar(0);  return 2;
271     case Remote::FIVE:  player->jumpToPercent(50); doBar(0);  return 2;
272     case Remote::SIX:   player->jumpToPercent(60); doBar(0);  return 2;
273     case Remote::SEVEN: player->jumpToPercent(70); doBar(0);  return 2;
274     case Remote::EIGHT: player->jumpToPercent(80); doBar(0);  return 2;
275     case Remote::NINE:  player->jumpToPercent(90); doBar(0);  return 2;
276
277 #ifdef DEV
278     case Remote::RED:
279     {
280       //player->test1();
281
282
283       /*
284       // for testing EPG in NTSC with a NTSC test video
285       Video::getInstance()->setMode(Video::QUARTER);
286       Video::getInstance()->setPosition(170, 5);
287       VEpg* vepg = new VEpg(NULL, 0);
288       vepg->draw();
289       ViewMan::getInstance()->add(vepg);
290       ViewMan::getInstance()->updateView(vepg);
291       */
292
293       return 2;
294     }
295     case Remote::GREEN:
296     {
297       //player->test2();
298       return 2;
299     }
300 #endif
301
302   }
303
304   return 1;
305 }
306
307 void VVideoRec::processMessage(Message* m)
308 {
309   if (m->from != player) return;
310   if (m->message != Message::PLAYER_EVENT) return;
311
312   Log::getInstance()->log("VVideoRec", Log::DEBUG, "Message received");
313
314   switch(m->parameter)
315   {
316     case Player::CONNECTION_LOST: // connection lost detected
317     {
318       // I can't handle this, send it to command
319       Message* m = new Message();
320       m->to = Command::getInstance();
321       m->message = Message::CONNECTION_LOST;
322       Command::getInstance()->postMessageNoLock(m);
323       break;
324     }
325     case Player::STOP_PLAYBACK:
326     {
327       // FIXME Obselete ish - improve this
328       Message* m = new Message(); // Must be done after this thread finishes, and must break into master mutex
329       m->to = Command::getInstance();
330       m->message = Message::STOP_PLAYBACK;
331       Command::getInstance()->postMessageNoLock(m);
332       break;
333     }
334     case Player::ASPECT43:
335     {
336       if (dowss)
337       {
338         Log::getInstance()->log("VVideoRec", Log::DEBUG, "Received do WSS 43");
339         wss.setWide(false);
340         wss.draw();
341         ViewMan::getInstance()->updateView(this, &wssRegion);
342       }
343       break;
344     }
345     case Player::ASPECT169:
346     {
347       if (dowss)
348       {
349         Log::getInstance()->log("VVideoRec", Log::DEBUG, "Received do WSS 169");
350         wss.setWide(true);
351         wss.draw();
352         ViewMan::getInstance()->updateView(this, &wssRegion);
353       }
354       break;
355     }
356   }
357 }
358
359 void VVideoRec::stopPlay()
360 {
361   Log::getInstance()->log("VVideoRec", Log::DEBUG, "Pre stopPlay");
362
363   // FIXME work out a better soln for this
364   // Fix a problem to do with thread sync here
365   // because the bar gets a timer every 0.2s and it seems to take up to 0.1s,
366   // (or maybe just the wrong thread being selected?) for the main loop to lock and process
367   // the video stop message it is possible for a bar message to stack up after a stop message
368   // when the bar message is finally processed the prog crashes because this is deleted by then
369   removeBar();
370   //
371
372   player->stop();
373   vdr->stopStreaming();
374   delete player;
375
376   playing = false;
377
378   if (!vdr->isConnected()) { Command::getInstance()->connectionLost(); return; }
379   Log::getInstance()->log("VVideoRec", Log::DEBUG, "Post stopPlay");
380 }
381
382 void VVideoRec::toggleChopSides()
383 {
384   if (video->getTVsize() == Video::ASPECT16X9) return; // Means nothing for 16:9 TVs
385
386   if (videoMode == Video::NORMAL)
387   {
388     videoMode = Video::LETTERBOX;
389     video->setMode(Video::LETTERBOX);
390   }
391   else
392   {
393     videoMode = Video::NORMAL;
394     video->setMode(Video::NORMAL);
395   }
396 }
397
398 void VVideoRec::doBar(int action)
399 {
400   barShowing = true;
401
402   rectangle(barRegion, barBlue);
403
404   /* Work out what to display - choices:
405
406   Playing  >
407   Paused   ||
408   FFwd     >>
409   FBwd     <<
410
411   Specials, informed by parameter
412
413   Skip forward 10s    >|
414   Skip backward 10s   |<
415   Skip forward 1m     >>|
416   Skip backward 1m    |<<
417
418   */
419
420   WSymbol w;
421   w.setSurface(surface);
422   w.nextSymbol = 0;
423   w.setSurfaceOffset(barRegion.x + 66, barRegion.y + 16);
424
425   UCHAR playerState = 0;
426
427   if (action)
428   {
429     if (action == 1)       w.nextSymbol = WSymbol::SKIPFORWARD;
430     else if (action == 2)  w.nextSymbol = WSymbol::SKIPBACK;
431     else if (action == 3)  w.nextSymbol = WSymbol::SKIPFORWARD2;
432     else if (action == 4)  w.nextSymbol = WSymbol::SKIPBACK2;
433   }
434   else
435   {
436     playerState = player->getState();
437     if (playerState == Player::S_PAUSE_P)      w.nextSymbol = WSymbol::PAUSE;
438     else if (playerState == Player::S_PAUSE_I) w.nextSymbol = WSymbol::PAUSE;
439     else if (playerState == Player::S_FFWD)    w.nextSymbol = WSymbol::FFWD;
440     else if (playerState == Player::S_FBWD)    w.nextSymbol = WSymbol::FBWD;
441     else                                       w.nextSymbol = WSymbol::PLAY;
442   }
443
444   w.draw();
445
446   if ((playerState == Player::S_FFWD) || (playerState == Player::S_FBWD))
447   {
448     // draw blips to show how fast the scan is
449     UCHAR scanrate = player->getIScanRate();
450     if (scanrate >= 2)
451     {
452       char* text = new char[5];
453       SNPRINTF(text, 5, "%ux", scanrate);
454       drawText(text, barRegion.x + 102, barRegion.y + 12, Colour::LIGHTTEXT);
455     }
456   }
457
458   drawBarClocks();
459
460   ViewMan::getInstance()->updateView(this, &barRegion);
461
462   if ((playerState == Player::S_FFWD) || (playerState == Player::S_FBWD))
463   {
464     timers->cancelTimer(this, 1);
465     stickyBar = true;
466   }
467   else
468   {
469     timers->setTimerD(this, 1, 4); // only set the getridofbar timer if not ffwd/fbwd
470     stickyBar = false;
471   }
472   timers->setTimerD(this, 2, 0, 200000000);
473 }
474
475 void VVideoRec::timercall(int clientReference)
476 {
477   switch(clientReference)
478   {
479     case 1:
480     {
481       // Remove bar
482       removeBar();
483       break;
484     }
485     case 2:
486     {
487       // Update clock
488       if (!barShowing) break;
489       drawBarClocks();
490       ViewMan::getInstance()->updateView(this, &barRegion);
491       timers->setTimerD(this, 2, 0, 200000000);
492       break;
493     }
494   }
495 }
496
497 void VVideoRec::drawBarClocks()
498 {
499   if (stickyBar)
500   {
501     UCHAR playerState = player->getState();
502     // sticky bar is set if we are in ffwd/fbwd mode
503     // if player has gone to S_PLAY then kill stickyBar, and run doBar(0) which
504     // will repaint all the bar (it will call this function again, but
505     // this section won't run because stickyBar will then == false)
506
507     if ((playerState != Player::S_FFWD) && (playerState != Player::S_FBWD))
508     {
509       stickyBar = false;
510       doBar(0);
511       return; // doBar will call this function and do the rest
512     }
513   }
514
515   Log* logger = Log::getInstance();
516   logger->log("VVideoRec", Log::DEBUG, "Draw bar clocks");
517
518   // Draw RTC
519   // Blank the area first
520   rectangle(barRegion.x + 624, barRegion.y + 12, 60, 30, barBlue);
521   char timeString[20];
522   time_t t;
523   time(&t);
524   struct tm* tms = localtime(&t);
525   strftime(timeString, 19, "%H:%M", tms);
526   drawText(timeString, barRegion.x + 624, barRegion.y + 12, Colour::LIGHTTEXT);
527
528   // Draw clocks
529
530   rectangle(clocksRegion, barBlue);
531
532   ULONG currentFrameNum = player->getCurrentFrameNum();
533   ULONG lengthFrames;
534   if (myRec->recInfo->timerEnd > time(NULL))
535   {
536     // chasing playback
537     // Work out an approximate length in frames (good to 1s...)
538     lengthFrames = (myRec->recInfo->timerEnd - myRec->recInfo->timerStart) * video->getFPS();
539   }
540   else
541   {
542     lengthFrames = player->getLengthFrames();
543   }
544
545   hmsf currentFrameHMSF = video->framesToHMSF(currentFrameNum);
546   hmsf lengthHMSF = video->framesToHMSF(lengthFrames);
547
548   char buffer[100];
549   if (currentFrameNum >= lengthFrames)
550   {
551     strcpy(buffer, "-:--:-- / -:--:--");
552   }
553   else
554   {
555     SNPRINTF(buffer, 99, "%01i:%02i:%02i / %01i:%02i:%02i", currentFrameHMSF.hours, currentFrameHMSF.minutes, currentFrameHMSF.seconds, lengthHMSF.hours, lengthHMSF.minutes, lengthHMSF.seconds);
556     logger->log("VVideoRec", Log::DEBUG, buffer);
557   }
558
559   drawText(buffer, clocksRegion.x, clocksRegion.y, Colour::LIGHTTEXT);
560
561
562
563
564
565
566
567   // Draw progress bar
568   int progBarXbase = barRegion.x + 300;
569
570   rectangle(barRegion.x + progBarXbase, barRegion.y + 12, 310, 24, Colour::LIGHTTEXT);
571   rectangle(barRegion.x + progBarXbase + 2, barRegion.y + 14, 306, 20, barBlue);
572
573   if (currentFrameNum > lengthFrames) return;
574   if (lengthFrames == 0) return;
575
576   // Draw yellow portion
577   int progressWidth = 302 * currentFrameNum / lengthFrames;
578   rectangle(barRegion.x + progBarXbase + 4, barRegion.y + 16, progressWidth, 16, Colour::SELECTHIGHLIGHT);
579
580   if (myRec->recInfo->timerEnd > time(NULL)) // if chasing
581   {
582     int nrWidth = (int)(302 * ((double)(lengthFrames - player->getLengthFrames()) / lengthFrames));
583
584     Log::getInstance()->log("GVASDF", Log::DEBUG, "Length Frames: %lu", lengthFrames);
585     Log::getInstance()->log("GVASDF", Log::DEBUG, "Player lf: %lu", player->getLengthFrames());
586     Log::getInstance()->log("GVASDF", Log::DEBUG, "NR WDITH: %i", nrWidth);
587     rectangle(barRegion.x + progBarXbase + 4 + 302 - nrWidth, barRegion.y + 16, nrWidth, 16, Colour::RED);
588   }
589
590   // Now calc position for start margin blips
591   int posPix;
592
593   posPix = 302 * startMargin * video->getFPS() / lengthFrames;
594
595   rectangle(barRegion.x + progBarXbase + 2 + posPix, barRegion.y + 12 - 2, 2, 2, Colour::LIGHTTEXT);
596   rectangle(barRegion.x + progBarXbase + 2 + posPix, barRegion.y + 12 + 24, 2, 2, Colour::LIGHTTEXT);
597
598   posPix = 302 * (lengthFrames - endMargin * video->getFPS()) / lengthFrames;
599
600   rectangle(barRegion.x + progBarXbase + 2 + posPix, barRegion.y + 12 - 2, 2, 2, Colour::LIGHTTEXT);
601   rectangle(barRegion.x + progBarXbase + 2 + posPix, barRegion.y + 12 + 24, 2, 2, Colour::LIGHTTEXT);
602 }
603
604 void VVideoRec::removeBar()
605 {
606   if (!barShowing) return;
607   timers->cancelTimer(this, 2);
608   barShowing = false;
609   stickyBar = false;
610   rectangle(barRegion, transparent);
611   ViewMan::getInstance()->updateView(this, &barRegion);
612 }