]> git.vomp.tv Git - vompclient-marten.git/blob - vvideolivetv.cc
DVB Subtitles: Live TV
[vompclient-marten.git] / vvideolivetv.cc
1 /*
2     Copyright 2007-2008 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19 */
20
21 #include "vvideolivetv.h"
22
23 #include "vchannellist.h"
24 #include "video.h"
25 #include "playerlive.h"
26 #include "playerlivetv.h"
27 #include "playerliveradio.h"
28 #include "channel.h"
29 #include "boxstack.h"
30 #include "colour.h"
31 #include "osd.h"
32 #include "command.h"
33 #include "i18n.h"
34 #include "wtextbox.h"
35 #include "remote.h"
36 #include "vaudioselector.h"
37 #include "colour.h"
38 #include "event.h"
39 #include "timers.h"
40 #include "vepg.h"
41 #include "bitmap.h"
42
43 VVideoLiveTV::VVideoLiveTV(ChannelList* tchanList, ULONG initialChannelNumber, VChannelList* tvchannelList)
44 {
45   vdr = VDR::getInstance();
46   boxstack = BoxStack::getInstance();
47   video = Video::getInstance();
48   
49   vas = NULL;
50
51   chanList = tchanList;
52   vchannelList = tvchannelList;
53   numberWidth = (int)VDR::getInstance()->getChannelNumberWidth();
54
55   currentChannelIndex = 0;
56   previousChannelIndex = 0;
57   osdChannelIndex = 0;
58   keying = 0;
59   preBuffering = 0;
60
61   playing = false;
62
63   // Convert channel number to index
64   UINT i;
65   for(i = 0; i < chanList->size(); i++)
66   {
67     if ((*chanList)[i]->number == (UINT)initialChannelNumber)
68     {
69       currentChannelIndex = i;
70       osdChannelIndex = i;
71       break;
72     }
73   }
74
75   eventList = NULL;
76
77   videoMode = video->getMode();
78   
79   if ((*chanList)[currentChannelIndex]->type == VDR::VIDEO)
80   {
81     streamType = VDR::VIDEO;
82     player = new PlayerLiveTV(Command::getInstance(), this, this, chanList);
83   }
84   else
85   {
86     streamType = VDR::RADIO;
87     player = new PlayerLiveRadio(Command::getInstance(), this, chanList);
88   }
89   player->init();
90
91   setSize(video->getScreenWidth(), video->getScreenHeight());
92   createBuffer();
93   Colour transparent(0, 0, 0, 0);
94   setBackgroundColour(transparent);
95
96   dowss = false;
97   char* optionWSS = vdr->configLoad("General", "WSS");
98   if (optionWSS)
99   {
100     if (strstr(optionWSS, "Yes")) dowss = true;
101     delete[] optionWSS;
102   }
103   Log::getInstance()->log("VVideoLiveTV", Log::DEBUG, "Do WSS: %u", dowss);
104
105   if (dowss)
106   {
107     wss.setFormat(video->getFormat());
108     wss.setWide(true);
109     add(&wss);
110     
111     wssRegion.x = 0;
112     wssRegion.y = 6;
113     wssRegion.w = video->getScreenWidth();
114     wssRegion.h = 2;
115   }
116   
117   // This variable is set to true if the user pressed OK to bring the OSD on screen
118   // This is only used on old remotes to stop up/down buttons being used for osd-epg scrolling
119   okTriggeredOSD = false;
120   
121   Colour osdBack = Colour(0, 0, 0, 128);
122   
123   osd.setBackgroundColour(osdBack);
124   osd.setPosition(0, video->getScreenHeight() - 150);
125   osd.setSize(video->getScreenWidth(), 150);
126   osd.setVisible(false);
127   add(&osd);
128   
129   clock.setBackgroundColour(osdBack);
130   clock.setPosition(osd.getWidth() - 100, 4);
131   clock.setSize(90, 30);
132   osd.add(&clock);
133
134   osdChanNum.setBackgroundColour(osdBack);
135   osdChanNum.setPosition(50, 4);
136   osdChanNum.setSize((numberWidth*10) + 22, 30); // 10 px = width of number chars in font
137   osd.add(&osdChanNum);  
138
139   osdChanName.setBackgroundColour(osdBack);
140   osdChanName.setPosition(osdChanNum.getX2() + 10, 4);
141   osdChanName.setSize(300, 30);
142   osd.add(&osdChanName);
143   
144   boxRed.setBackgroundColour(Colour::RED);
145   boxRed.setPosition(54, 104);
146   boxRed.setSize(18, 16);
147   osd.add(&boxRed);
148
149   boxGreen.setBackgroundColour(Colour::GREEN);
150   boxGreen.setPosition(220, 104);
151   boxGreen.setSize(18, 16);
152   osd.add(&boxGreen);
153
154   boxYellow.setBackgroundColour(Colour::YELLOW);
155   boxYellow.setPosition(390, 104);
156   boxYellow.setSize(18, 16);
157   osd.add(&boxYellow);
158
159   boxBlue.setBackgroundColour(Colour::BLUE);
160   boxBlue.setPosition(560, 104);
161   boxBlue.setSize(18, 16);
162   osd.add(&boxBlue);  
163   
164   textRed.setBackgroundColour(osdBack);
165   textRed.setPosition(boxRed.getX2(), 98);
166   textRed.setSize(boxGreen.getX() - boxRed.getX2(), 30);
167   textRed.setText(tr("Summary"));
168   osd.add(&textRed);  
169     
170   if (streamType == VDR::VIDEO)
171   {
172     textGreen.setBackgroundColour(osdBack);
173     textGreen.setPosition(boxGreen.getX2(), 98);
174     textGreen.setSize(boxYellow.getX() - boxGreen.getX2(), 30);
175     textGreen.setText(tr("Audio"));
176     osd.add(&textGreen);  
177   }
178     
179   textYellow.setBackgroundColour(osdBack);
180   textYellow.setPosition(boxYellow.getX2(), 98);
181   textYellow.setSize(boxBlue.getX() - boxYellow.getX2(), 30);
182   textYellow.setText("");
183   osd.add(&textYellow);  
184     
185   textBlue.setBackgroundColour(osdBack);
186   textBlue.setPosition(boxBlue.getX2(), 98);
187   textBlue.setSize(osd.getX2() - boxBlue.getX2(), 30);
188   textBlue.setText(tr("EPG"));
189   osd.add(&textBlue);  
190     
191   sl.setBackgroundColour(osdBack);
192   sl.setPosition(70, 36);
193   sl.setSize(500, 58);
194   sl.setNoLoop();
195   osd.add(&sl);
196   
197   // Summary Box
198   summary.setBackgroundColour(osdBack);
199   summary.setPosition(0, video->getScreenHeight() - 300);
200   summary.setSize(video->getScreenWidth(), 150);
201   summary.setVisible(false);
202   add(&summary);  
203   
204   textSummary.setBackgroundColour(osdBack);
205   textSummary.setPosition(40, 10);
206   textSummary.setSize(video->getScreenWidth() - 80, 130);
207   textSummary.setParaMode(true);
208   summary.add(&textSummary);
209   
210   summaryBlackLine.setBackgroundColour(Colour::BLACK);
211   summaryBlackLine.setPosition(0, summary.getHeight() - 4);
212   summaryBlackLine.setSize(summary.getWidth(), 4);
213   summary.add(&summaryBlackLine);
214   
215   sAspectRatio.setPosition(osd.getWidth() - 90, 40);
216   sAspectRatio.nextColour = Colour::SELECTHIGHLIGHT;
217   sAspectRatio.setVisible(false);
218   osd.add(&sAspectRatio);
219   
220   bufferBar.setPosition(osd.getWidth() - 90, 70);
221   bufferBar.setSize(40, 20);
222   bufferBar.setVisible(true);
223   osd.add(&bufferBar);
224   
225   sAudioChannels.setPosition(osd.getWidth() - 130, 40);
226   sAudioChannels.nextColour = Colour::SELECTHIGHLIGHT;
227   sAudioChannels.setVisible(false);
228   osd.add(&sAudioChannels);
229   
230   textUnavailable.setBackgroundColour(osdBack);
231   textUnavailable.setPosition(60, 30);
232   textUnavailable.setSize(osd.getWidth() - 120, 30);
233   textUnavailable.setText(tr("Channel Unavailable"));
234   textUnavailable.setVisible(false);
235   add(&textUnavailable);
236   
237   // FIXME painful
238   Region r1 = summary.getRegionR();
239   Region r2 = osd.getRegionR();
240   osdSummaryRegion = r1 + r2;
241 }
242
243 void VVideoLiveTV::preDelete()
244 {
245   if (playing) stop();
246 }
247
248 VVideoLiveTV::~VVideoLiveTV()
249 {
250   delete player;
251   video->setDefaultAspect();
252   delData();
253 }
254
255 void VVideoLiveTV::delData()
256 {
257   if (eventList)
258   {
259     int eventListSize = eventList->size();
260     for(int i = 0; i < eventListSize; i++)
261     {
262       delete (*eventList)[i];
263     }
264     eventList->clear();
265     delete eventList;
266
267   }
268   sl.clear();
269 }
270
271 int VVideoLiveTV::handleCommand(int command)
272 {
273   switch(command)
274   {
275     case Remote::BACK:
276     {
277       if (osd.getVisible() && !textUnavailable.getVisible())
278       {
279         clearScreen();
280         return 2;
281       }
282       // else drop through to stop
283     }
284     case Remote::STOP:
285     {
286       stop();
287       vchannelList->highlightChannel((*chanList)[currentChannelIndex]);
288       return 4;
289     }
290     
291     // NEW REMOTE ONLY - navigate EPG, bring it onscreen if it's not there
292     case Remote::UP:
293     {
294       doUpDown(false);
295       return 2;
296     }
297     case Remote::DOWN:
298     {
299       doUpDown(true);
300       return 2;
301     }
302     case Remote::LEFT:
303     {
304       doLeftRight(false);
305       return 2;
306     }
307     case Remote::RIGHT:
308     {
309       doLeftRight(true);
310       return 2;
311     }
312     // Continue new remote only...
313     case Remote::CHANNELUP:
314     {
315       doChanUpDown(UP);
316       return 2;
317     }
318     case Remote::CHANNELDOWN:
319     {
320       doChanUpDown(DOWN);
321       return 2;
322     }
323
324     // END NEW REMOTE ONLY, START OLD REMOTE ONLY
325     
326     // DF_LEFT and DF_RIGHT never get here because they are stolen
327     // by command as vol- and vol+
328     
329     // Old remote. Decide what to do based on whether
330     // OK was pressed - osd shown manually, use up/down for epg nav
331     // UP/DOWN was pressed to change channel, osd was shown auto, use up/down for ch+/ch-
332     
333     case Remote::DF_UP:
334     {
335       // Old remote, decide what to do based on okTriggeredOSD
336       if (okTriggeredOSD) doUpDown(false);
337       else doChanUpDown(UP);
338       return 2;
339     }
340     case Remote::DF_DOWN:
341     {
342       // Old remote, decide what to do based on okTriggeredOSD
343       if (okTriggeredOSD) doUpDown(true);
344       else doChanUpDown(DOWN);
345       return 2;
346     }
347
348     // END NEW/OLD REMOTE STUFF
349
350     case Remote::PREVCHANNEL:
351     {
352       channelChange(PREVIOUS, 0);
353       osdChannelIndex = currentChannelIndex;
354       displayOSD(true);
355       return 2;
356     }
357     case Remote::OK:
358     {
359       doOK();
360       return 2;
361     }
362     case Remote::RED:
363     case Remote::MENU:
364     {
365       doSummary();
366       return 2;
367     }
368     case Remote::FULL:
369     case Remote::TV:
370     {
371       toggleChopSides();
372       return 2;
373     }
374
375     case Remote::ZERO:
376     case Remote::ONE:
377     case Remote::TWO:
378     case Remote::THREE:
379     case Remote::FOUR:
380     case Remote::FIVE:
381     case Remote::SIX:
382     case Remote::SEVEN:
383     case Remote::EIGHT:
384     case Remote::NINE:
385     {
386       // key in channel number
387       doKey(command);
388       return 2;
389     }
390
391     case Remote::GREEN:
392     {
393       if (streamType == VDR::VIDEO) doAudioSelector();
394       return 2;   
395     }
396     case Remote::YELLOW:
397     {
398       return 2;
399     }
400     case Remote::GUIDE:
401     case Remote::BLUE:
402     {
403       doEPG();
404       return 2;
405     }
406     case Remote::RECORD:
407       if (streamType = VDR::VIDEO)
408         (static_cast<PlayerLiveTV*>(player))->toggleSubtitles();
409       return 2;
410   }
411
412   return 1;
413 }
414
415 void VVideoLiveTV::go()
416 {
417   playing = true;
418   draw();
419   boxstack->update(this);
420
421   setClock();
422   displayOSD(true);
423   
424   player->go(currentChannelIndex);
425 }
426
427 void VVideoLiveTV::stop()
428 {
429   Timers::getInstance()->cancelTimer(this, 1);
430   Timers::getInstance()->cancelTimer(this, 2);
431   player->stop();
432   playing = false;
433 }
434
435 void VVideoLiveTV::doLeftRight(bool right)
436 {
437   if (osd.getVisible())
438   {
439     if (right) osdChannelIndex = upChannel(osdChannelIndex);
440     else       osdChannelIndex = downChannel(osdChannelIndex);
441   }
442   else
443   {
444     osdChannelIndex = currentChannelIndex;
445   }
446   displayOSD(true);
447 }
448
449 void VVideoLiveTV::doUpDown(bool down)
450 {
451   if (osd.getVisible())
452   {
453     if (down) sl.down();
454     else      sl.up();
455     sl.draw();
456     
457     displayOSD(false);
458   }
459   else
460   {
461     displayOSD(true);
462   }
463 }
464
465 void VVideoLiveTV::doChanUpDown(int which)
466 {
467   channelChange(OFFSET, which);
468   osdChannelIndex = currentChannelIndex;
469   displayOSD(true);
470 }
471
472 void VVideoLiveTV::doOK()
473 {
474   if (osd.getVisible())
475   {
476     if (keying)
477     {
478       UINT newChannel = 0;
479       for(int i = keying - 1; i >= 0; i--) newChannel += keyingInput[i] * (ULONG)pow(10., i);
480       
481       channelChange(NUMBER, newChannel);
482       osdChannelIndex = currentChannelIndex;
483       displayOSD(true);
484     }
485     else if (osdChannelIndex == currentChannelIndex)
486     {
487       clearScreen();
488     }
489     else
490     {
491       channelChange(INDEX, osdChannelIndex);
492       displayOSD(true);
493     }
494   }
495   else
496   {
497     osdChannelIndex = currentChannelIndex;
498     displayOSD(true);
499     okTriggeredOSD = true;
500   }
501 }
502
503 void VVideoLiveTV::doSummary()
504 {
505   if (summary.getVisible())
506   {
507     summary.setVisible(false);
508     draw();
509     boxstack->update(this, summary.getRegion());
510     Timers::getInstance()->setTimerD(this, 1, 8); // Restart a timer to get rid of osd
511     return;
512   }
513
514   summary.setVisible(true);
515
516   if (osd.getVisible())
517   {
518     Timers::getInstance()->cancelTimer(this, 1);
519     displayOSD(false);
520   }
521   else
522   {
523     displayOSD(true);
524   }
525 }
526
527 void VVideoLiveTV::doKey(int command)
528 {
529   if (!osd.getVisible()) // First key. prep the data
530   {
531     setNowNextData();
532     keying = 0;    
533   }
534
535   int i;
536   for (i = keying - 1; i >= 0; i--) keyingInput[i+1] = keyingInput[i];
537   keyingInput[0] = command;
538   keying++;
539
540   char* keyingString = new char[numberWidth + 1];
541   for (i = 0; i < numberWidth; i++) keyingString[i] = '_';
542   keyingString[numberWidth] = '\0';
543
544   for (i = 0; i < keying; i++) keyingString[i] = keyingInput[keying - 1 - i] + 48;
545   
546   if (keying == numberWidth)
547   {
548     UINT newChannel = 0;
549     for(i = keying - 1; i >= 0; i--) newChannel += keyingInput[i] * (ULONG)pow(10., i);
550     
551     channelChange(NUMBER, newChannel);
552     osdChannelIndex = currentChannelIndex;
553     Timers::getInstance()->cancelTimer(this, 1); // cancel the timer to react to keying input,
554     displayOSD(true); // this will put one back if required
555   }
556   else
557   {
558     osdChanNum.setText(keyingString);
559
560     if (!osd.getVisible())
561     {
562       osd.setVisible(true);
563       draw();
564     }
565     else
566     {
567       osdChanNum.draw();
568     }
569     boxstack->update(this, osd.getRegion());
570     Timers::getInstance()->setTimerD(this, 1, 3);  // 3s for keying input
571   }
572   delete[] keyingString;
573 }
574
575 void VVideoLiveTV::doAudioSelector()
576 {
577   // If the osd is already visisble there might be a timer for it
578   Timers::getInstance()->cancelTimer(this, 1);
579
580   // Cancel keying
581   if (keying)
582   {
583     keying = 0;
584     // and reset the display - this is a copy from setNowNextData
585     char formatChanNum[20];
586     SNPRINTF(formatChanNum, 19, "%0*lu", numberWidth, (*chanList)[osdChannelIndex]->number);
587     osdChanNum.setText(formatChanNum);
588     osdChanName.setText((*chanList)[osdChannelIndex]->name);
589   }
590
591   // Draw the selector
592   vas = new VAudioSelector(this, (*chanList)[currentChannelIndex], ((PlayerLiveTV*)player)->getCurrentAudioChannel());
593   Colour osdBack = Colour(0, 0, 0, 128);
594   vas->setBackgroundColour(osdBack);
595   vas->setPosition(0, osd.getScreenY() - vas->getHeight());
596   vas->draw();
597
598   // make vas != null and displayOSD will not set a timer or do any boxstack update
599   summary.setVisible(false);
600   if (osd.getVisible()) displayOSD(false);
601   else displayOSD(true);
602   draw();
603
604   BoxStack::getInstance()->add(vas);
605   BoxStack::getInstance()->update(this);        
606   BoxStack::getInstance()->update(vas);     
607 }      
608       
609 void VVideoLiveTV::doEPG()
610 {
611   if (osd.getVisible()) clearScreen();
612
613   video->setMode(Video::QUARTER);
614   video->setPosition(170, 5); //TODO need to deal with 4:3 switching
615
616   VEpg* vepg = new VEpg(this, currentChannelIndex, streamType);
617   vepg->draw();
618   boxstack->add(vepg);
619   boxstack->update(vepg);
620 }
621
622 void VVideoLiveTV::setNowNextData()
623 {
624   delData();
625   
626   Channel* currentChannel = (*chanList)[osdChannelIndex];
627
628   char formatChanNum[20];
629   SNPRINTF(formatChanNum, 19, "%0*lu", numberWidth, currentChannel->number);
630   osdChanNum.setText(formatChanNum);
631   osdChanName.setText(currentChannel->name);
632
633   eventList = VDR::getInstance()->getChannelSchedule(currentChannel->number);
634
635   if (!eventList)
636   {
637     sl.addOption(tr("No channel data available"), 0, 1);
638   }
639   else
640   {
641     sort(eventList->begin(), eventList->end(), EventSorter());
642
643     char tempString[300];
644     char tempString2[300];
645     struct tm* btime;
646     Event* event;
647     int eventListSize = eventList->size();
648     for(int i = 0; i < eventListSize; i++)
649     {
650       event = (*eventList)[i];
651
652       //btime = localtime((time_t*)&event->time);
653       time_t etime = event->time;
654       btime = localtime(&etime);
655 #ifndef _MSC_VER
656       strftime(tempString2, 299, "%0H:%0M ", btime);
657 #else
658       strftime(tempString2, 299, "%H:%M ", btime);
659 #endif
660       SNPRINTF(tempString, 299, "%s %s", tempString2, event->title);
661       
662       sl.addOption(tempString, (ULONG)event, (i==0));
663     }
664   }
665 }
666
667 void VVideoLiveTV::setSummaryData()
668 {
669   // If osd is not being displayed, sl will be filled with now, current channel
670   // If the display was already on, sl will have programme to show summary for, not necessarily current channel and now
671   Event* selectedEvent = (Event*)sl.getCurrentOptionData();
672   
673   if (!selectedEvent)
674   {
675     Log::getInstance()->log("VVideoLiveTV", Log::DEBUG, "No summary");  
676     textSummary.setText(tr("No summary available"));
677   }
678   else
679   {
680     Log::getInstance()->log("VVideoLiveTV", Log::DEBUG, "Summary: %s", selectedEvent->description);  
681     textSummary.setText(selectedEvent->description);
682   }
683 }
684
685 void VVideoLiveTV::displayOSD(bool newNowNextData)
686 {
687   osd.setVisible(true);
688   if (newNowNextData)
689   {
690     setNowNextData();
691     keying = 0;
692   }
693   osd.draw();
694   
695   if (summary.getVisible())
696   {
697     setSummaryData();
698     summary.draw();
699     boxstack->update(this, &osdSummaryRegion);
700   }
701   else
702   {
703     boxstack->update(this, osd.getRegion());
704   }
705   
706   bool setTimer = true;
707   if (vas) setTimer = false;
708   if (summary.getVisible()) setTimer = false;
709   if (textUnavailable.getVisible()) setTimer = false;
710
711   if (setTimer) Timers::getInstance()->setTimerD(this, 1, 4);
712 }
713
714 void VVideoLiveTV::clearScreen()
715 {  
716   if (!summary.getVisible()) Timers::getInstance()->cancelTimer(this, 1);
717
718   textUnavailable.setVisible(false);
719   osd.setVisible(false);
720   summary.setVisible(false);
721
722   okTriggeredOSD = false;
723
724   draw();
725   boxstack->update(this);
726 }
727
728 void VVideoLiveTV::showUnavailable()
729 {
730   Log::getInstance()->log("VVideoLiveTV", Log::DEBUG, "Show unavailable called");  
731   textUnavailable.setVisible(true);
732   textUnavailable.draw();
733   
734   if (!osd.getVisible()) displayOSD(true);
735
736   boxstack->update(this, textUnavailable.getRegion());  
737 }
738
739 void VVideoLiveTV::setClock()
740 {
741   char timeString[20];
742   time_t t;
743   time(&t);
744   struct tm* tms = localtime(&t);
745   strftime(timeString, 19, "%H:%M", tms);
746   clock.setText(timeString);
747
748   time_t dt = 60 - (t % 60);  // seconds to the next minute
749   if (dt == 0) dt = 60; // advance a whole minute if necessary
750   dt += t;  // get a time_t value for it rather than using duration
751   // (so it will occur at the actual second and not second and a half)
752
753   Timers::getInstance()->setTimerT(this, 2, dt);
754 }
755
756 void VVideoLiveTV::timercall(int ref)
757 {
758   if (ref == 1)
759   {
760     if (keying)
761     {
762       UINT newChannel = 0;
763       for(int i = keying - 1; i >= 0; i--) newChannel += keyingInput[i] * (ULONG)pow(10., i);
764       
765       Message* m = new Message();
766       m->message = Message::CHANNEL_CHANGE;
767       m->to = this;
768       m->parameter = newChannel;
769       m->tag = 1; // signal to call displayOSD();
770       Command::getInstance()->postMessageFromOuterSpace(m);
771     }
772     else
773     {
774       // We have received a timer, we are not keying. If still prebuffering, don't remove the bar
775       if (preBuffering < 100)
776       {
777         Log::getInstance()->log("VVideoLiveTV", Log::DEBUG, "Still prebuffering, not removing osd...");  
778         Timers::getInstance()->setTimerD(this, 1, 2); // reset timer for another 2s
779         return;
780       }
781       
782       osd.setVisible(false);
783       okTriggeredOSD = false;
784       draw();
785       boxstack->update(this, osd.getRegion());
786     }
787   }
788   else if (ref == 2)
789   {
790     setClock();
791     if (osd.getVisible())
792     {
793       clock.draw();
794       boxstack->update(this, osd.getRegion());
795     }
796   }
797 }
798
799 bool VVideoLiveTV::channelChange(UCHAR changeType, UINT newData)
800 {
801   UINT newChannel = 0;
802
803   if (changeType == INDEX)
804   {
805     newChannel = newData;
806   }
807   else if (changeType == NUMBER)
808   {
809     UINT i;
810     for(i = 0; i < chanList->size(); i++)
811     {
812       if ((*chanList)[i]->number == (UINT)newData)
813       {
814         newChannel = i;
815         break;
816       }
817     }
818
819     if (i == chanList->size())
820     {
821       // no such channel
822       return false;
823     }
824   }
825   else if (changeType == OFFSET)
826   {
827     if (newData == UP) newChannel = upChannel(currentChannelIndex);
828     else newChannel = downChannel(currentChannelIndex);
829   }
830   else if (changeType == PREVIOUS)
831   {
832     newChannel = previousChannelIndex;
833   }
834   else
835   {
836     return false; // bad input
837   }
838
839   if (newChannel == currentChannelIndex) return true;
840
841   previousChannelIndex = currentChannelIndex;
842   currentChannelIndex = newChannel;
843   
844   preBuffering = 0;
845   
846   Log::getInstance()->log("VVideoLiveTV", Log::DEBUG, "Set player to channel %u", currentChannelIndex);
847   player->setChannel(currentChannelIndex);
848   Log::getInstance()->log("VVideoLiveTV", Log::DEBUG, "Done Set player to channel %u", currentChannelIndex);
849
850   // Blank out the symbols
851   sAspectRatio.setVisible(false);
852   bufferBar.setPercent(0);
853   sAudioChannels.setVisible(false);
854   
855   // Remove other stuff
856   if (textUnavailable.getVisible())
857   {
858     textUnavailable.setVisible(false);
859     draw();
860     BoxStack::getInstance()->update(this);
861   }
862   
863   return true;
864 }
865
866 void VVideoLiveTV::processMessage(Message* m)
867 {
868   if (m->message == Message::MOUSE_LBDOWN)
869   {
870     //check if press is outside this view! then simulate cancel
871     int x=(m->parameter>>16)-osd.getScreenX();
872     int y=(m->parameter&0xFFFF)-osd.getScreenY();
873     if (osd.getVisible()) {
874     
875         if ((boxRed.getX()<=x) && (boxRed.getX()+(int)boxRed.getWidth()>=x ) &&
876             (boxRed.getY()<=y) && (boxRed.getY()+(int)boxRed.getHeight()>=y )) {
877             BoxStack::getInstance()->handleCommand(Remote::RED);
878         } else if ((boxGreen.getX()<=x) && (boxGreen.getX()+(int)boxGreen.getWidth()>=x ) &&
879             (boxGreen.getY()<=y) && (boxGreen.getY()+(int)boxGreen.getHeight()>=y)){
880             BoxStack::getInstance()->handleCommand(Remote::GREEN);
881         } else if ((boxYellow.getX()<=x) && (boxYellow.getX()+(int)boxYellow.getWidth()>=x ) &&
882             (boxYellow.getY()<=y) && (boxYellow.getY()+(int)boxYellow.getHeight()>=y )){
883             BoxStack::getInstance()->handleCommand(Remote::YELLOW);
884         } else if ((boxBlue.getX()<=x) && (boxBlue.getX()+(int)boxBlue.getWidth()>=x ) &&
885             (boxBlue.getY()<=y) && (boxBlue.getY()+(int)boxBlue.getHeight()>=y )){
886             BoxStack::getInstance()->handleCommand(Remote::BLUE);
887         } else {
888             BoxStack::getInstance()->handleCommand(Remote::OK); //simulate rok press
889         }
890
891     } else {
892         BoxStack::getInstance()->handleCommand(Remote::OK); //simulate rok press
893     }
894   }
895   else if (m->message == Message::CHANNEL_CHANGE)
896   {
897     channelChange(NUMBER, m->parameter);
898     osdChannelIndex = currentChannelIndex;
899     if (m->tag == 1) displayOSD(true);
900   }
901   else if (m->message == Message::EPG_CLOSE)
902   {
903     video->setMode(videoMode);
904   }
905   else if (m->message == Message::CHILD_CLOSE)
906   {
907     if (m->from == vas)
908     {
909       vas = NULL;
910       displayOSD(false);
911     }
912   }
913   else if (m->message == Message::AUDIO_CHANGE_CHANNEL)
914   {
915     Log::getInstance()->log("VVideoLiveTV", Log::DEBUG, "Received change audio channel to %i", m->parameter);
916     player->setAudioChannel((m->parameter & 0xFFFF),(m->parameter & 0xFF0000)>>16);
917   }
918   else if (m->message == Message::PLAYER_EVENT)
919   {
920     switch(m->parameter)
921     {
922       case PlayerLiveTV::CONNECTION_LOST: // connection lost detected
923       {
924         Log::getInstance()->log("VVideoLiveTV", Log::DEBUG, "Received connection lost from player");
925         Command::getInstance()->connectionLost();
926         break;
927       }
928       
929       case PlayerLiveTV::STREAM_END:
930       {
931         // Message comes from playerlivetv through master mutex, so can do anything here
932         showUnavailable();        
933         break;
934       }
935       
936       case PlayerLiveTV::ASPECT43:
937       {
938         if ((video->getTVsize() == Video::ASPECT16X9) && dowss)
939         {
940           Log::getInstance()->log("VVideoRec", Log::DEBUG, "Received do WSS 43");
941           wss.setWide(false);
942           wss.draw();
943           BoxStack::getInstance()->update(this, &wssRegion);
944         }
945         
946         sAspectRatio.nextSymbol = WSymbol::VIDEOASPECT43;
947         sAspectRatio.setVisible(true);
948         
949         if (osd.getVisible()) // don't wake up the whole osd just for a aspect change
950         {
951           osd.draw();
952           BoxStack::getInstance()->update(this, osd.getRegion());
953         }
954         
955         break;
956       }
957       case PlayerLiveTV::ASPECT169:
958       {
959         if ((video->getTVsize() == Video::ASPECT16X9) && dowss)
960         {
961           Log::getInstance()->log("VVideoRec", Log::DEBUG, "Received do WSS 169");
962           wss.setWide(true);
963           wss.draw();
964           BoxStack::getInstance()->update(this, &wssRegion);
965         }
966         
967         sAspectRatio.nextSymbol = WSymbol::VIDEOASPECT169;
968         sAspectRatio.setVisible(true);
969
970         if (osd.getVisible()) // don't wake up the whole osd just for a aspect change
971         {
972           osd.draw();
973           BoxStack::getInstance()->update(this, osd.getRegion());
974         }
975                 
976         break;
977       }
978       case PlayerLiveTV::PREBUFFERING:
979       {
980         preBuffering = m->tag;
981         Log::getInstance()->log("VVideoRec", Log::DEBUG, "Prebuffering - %u", preBuffering);
982         bufferBar.setPercent(preBuffering);
983
984         if (osd.getVisible())
985         {
986           bufferBar.setVisible(true);
987           bufferBar.draw();
988           Region r;
989           bufferBar.getRootBoxRegion(&r);                       ///////// FIXME !!!
990           BoxStack::getInstance()->update(this, &r);
991           
992           if (preBuffering == 100)
993           {
994             doAudioChannelSymbol();
995           }
996         }
997       }
998     }
999   }
1000 }
1001
1002 void VVideoLiveTV::doAudioChannelSymbol()
1003 {
1004   // get the doobery
1005   Channel* currentChannel = (*chanList)[osdChannelIndex];
1006     
1007   bool multiAudio = false;
1008   #if WIN32
1009   if (currentChannel->numDPids > 1) multiAudio = true;
1010   #endif
1011   if (currentChannel->numAPids > 1) multiAudio = true;
1012   
1013   // draw the doobery
1014   
1015   if (multiAudio) sAudioChannels.nextSymbol = WSymbol::MULTIAUDIO;
1016   else            sAudioChannels.nextSymbol = WSymbol::SINGLEAUDIO;
1017   sAudioChannels.setVisible(true);
1018   
1019   if (osd.getVisible())
1020   {
1021     sAudioChannels.draw();
1022     Region r;
1023     sAudioChannels.getRootBoxRegion(&r);                       ///////// FIXME !!!
1024     // Fix this n'all.
1025     r.w = 32;
1026     r.h = 16;
1027     BoxStack::getInstance()->update(this, &r);
1028   }
1029 }
1030
1031 UINT VVideoLiveTV::upChannel(UINT index)
1032 {
1033   if (index == (chanList->size() - 1)) // at the end
1034     return 0; // so go to start
1035   else
1036     return index + 1;
1037 }
1038
1039 UINT VVideoLiveTV::downChannel(UINT index)
1040 {
1041   if (index == 0) // at the start
1042     return chanList->size() - 1; // so go to end
1043   else
1044     return index - 1;
1045 }
1046
1047 void VVideoLiveTV::toggleChopSides()
1048 {
1049   if (video->getTVsize() == Video::ASPECT16X9) return; // Means nothing for 16:9 TVs
1050
1051   if (videoMode == Video::NORMAL)
1052   {
1053     videoMode = Video::LETTERBOX;
1054     video->setMode(Video::LETTERBOX);
1055   }
1056   else
1057   {
1058     videoMode = Video::NORMAL;
1059     video->setMode(Video::NORMAL);
1060   }
1061 }
1062
1063 void VVideoLiveTV::drawOSDBitmap(UINT posX, UINT posY, const Bitmap& bm)
1064 {
1065   drawBitmap(posX, posY, bm);
1066   Region r;
1067   r.x = posX; r.y = posY; r.w = bm.getWidth(); r.h = bm.getHeight();
1068   boxstack->update(this, &r);
1069 }
1070
1071 void VVideoLiveTV::clearOSD()
1072 {
1073   rectangle(area, Colour(0,0,0,0));
1074   boxstack->update(this, &area);
1075 }
1076
1077 void VVideoLiveTV::clearOSDArea(UINT posX, UINT posY, UINT width, UINT height)
1078 {
1079   Region r;
1080   r.x = posX; r.y = posY; r.w = width; r.h = height;
1081   rectangle(r, Colour(0,0,0,0));
1082   boxstack->update(this, &r);
1083 }