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