]> git.vomp.tv Git - vompclient.git/blob - vvideomedia.cc
More compiler warning fixes
[vompclient.git] / vvideomedia.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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19 */
20
21 #include "vvideomedia.h"
22 #include "vmedialist.h"
23 #include "media.h"
24 #include "mediaplayer.h"
25
26 #include "command.h"
27 #include "osd.h"
28 #include "wsymbol.h"
29 #include "audio.h"
30 #include "video.h"
31 #include "timers.h"
32 #include "playermedia.h"
33 #include "recording.h"
34 #include "vaudioselector.h"
35 #include "message.h"
36 #include "remote.h"
37 #include "boxstack.h"
38 #include "vinfo.h"
39 #include "i18n.h"
40 #include "log.h"
41 #include "recinfo.h"
42
43 //use the picture channel
44 #define MEDIACHANNEL 1
45
46 //we misuse PLAYER_EVENTS for timer messages
47 //this should be larger then any player message
48 #define PLAYER_TIMER_BASE 100
49
50 VVideoMedia::VVideoMedia(Media* media, VMediaList *p)
51 {
52   lparent=p;
53   boxstack = BoxStack::getInstance();
54   video = Video::getInstance();
55   timers = Timers::getInstance();
56   vas = NULL;
57   vsummary = NULL;
58   lengthBytes=0;
59   myMedia = new Media(media);
60
61   player = new PlayerMedia(this);
62   player->run();
63
64   videoMode = video->getMode();
65
66   playing = false;
67
68
69   setSize(video->getScreenWidth(), video->getScreenHeight());
70   createBuffer();
71   transparent.set(0, 0, 0, 0);
72   setBackgroundColour(transparent);
73
74   barRegion.x = 0;
75   barRegion.y = video->getScreenHeight() - 58;   // FIXME, need to be - 1? and below?
76   barRegion.w = video->getScreenWidth();
77   barRegion.h = 58;
78
79   clocksRegion.x = barRegion.x + 140;
80   clocksRegion.y = barRegion.y + 12;
81   clocksRegion.w = 170;
82   clocksRegion.h = getFontHeight();
83
84
85   barBlue.set(0, 0, 150, 150);
86
87   barShowing = false;
88   barGenHold = false;
89   barScanHold = false;
90   barVasHold = false;
91
92   dowss = false;
93   char* optionWSS = VDR::getInstance()->configLoad("General", "WSS");
94   if (optionWSS)
95   {
96     if (strstr(optionWSS, "Yes")) dowss = true;
97     delete[] optionWSS;
98   }
99   Log::getInstance()->log("VVideoMedia", Log::DEBUG, "Do WSS: %u", dowss);
100
101   if (dowss)
102   {
103     wss.setFormat(video->getFormat());
104     wss.setWide(true);
105     add(&wss);
106
107     wssRegion.x = 0;
108     wssRegion.y = 0;
109     wssRegion.w = video->getScreenWidth();
110     wssRegion.h = 300;
111   }
112 }
113
114 VVideoMedia::~VVideoMedia()
115 {
116   Log::getInstance()->log("VVideoMedia", Log::DEBUG, "Entering  destructor");
117
118   if (vas)
119   {
120     boxstack->remove(vas);
121     vas = NULL;
122   }
123
124   if (vsummary) {
125     remove(vsummary);
126     delete vsummary;
127   }
128
129   if (playing) stopPlay();
130   video->setDefaultAspect();
131
132   timers->cancelTimer(this, 1);
133   timers->cancelTimer(this, 2);
134
135   delete myMedia;
136   Log::getInstance()->log("VVideoMedia", Log::DEBUG, "shutting down player");
137   player->shutdown();
138   delete player;
139   Log::getInstance()->log("VVideoMedia", Log::DEBUG, "deleted");
140 }
141
142 void VVideoMedia::go(bool resume)
143 {
144   ULONG startFrameNum=0;
145
146   Log::getInstance()->log("VVideoMedia", Log::DEBUG, "Starting stream: %s at frame: %lu", myMedia->getFileName(), startFrameNum);
147
148   lengthBytes = 0;
149
150   int rt=0;
151   const MediaURI *u=myMedia->getURI();
152   if (!u) {
153     Log::getInstance()->log("VVideoMedia", Log::ERR, "stream: %s has no URI", myMedia->getFileName());
154     rt=-1;
155   }
156   else {
157     rt=MediaPlayer::getInstance()->openMedium(MEDIACHANNEL,u,&lengthBytes,area.w,area.h);
158   }
159   if (rt==0)
160   {
161     //TODO: figure out len in frames
162     int seq=player->playNew(MEDIACHANNEL,lengthBytes,0);
163     int ok=player->waitForSequence(2,seq);
164     if (ok < 0) rt=-1; 
165     else {
166       playing = true;
167       doBar(0);
168     }
169   }
170   if (rt != 0)
171   {
172     stopPlay(); // clean up
173
174     Message* m = new Message();
175     m->message = Message::CLOSE_ME;
176     m->from = this;
177     m->to = boxstack;
178     Command::getInstance()->postMessageNoLock(m);
179
180     VInfo* vi = new VInfo();
181     vi->setSize(400, 150);
182     vi->createBuffer();
183     if (video->getFormat() == Video::PAL)
184       vi->setPosition(170, 200);
185     else
186       vi->setPosition(160, 150);
187     vi->setExitable();
188     vi->setBorderOn(1);
189     vi->setTitleBarOn(0);
190     vi->setOneLiner(tr("Error playing media"));
191     vi->draw();
192
193     m = new Message();
194     m->message = Message::ADD_VIEW;
195     m->to = boxstack;
196     m->parameter.num = (ULONG)vi;
197     Command::getInstance()->postMessageNoLock(m);
198   }
199 }
200
201 int VVideoMedia::handleCommand(int command)
202 {
203   switch(command)
204   {
205     case Remote::PLAY:
206     {
207       player->play();
208       doBar(0);
209       return 2;
210     }
211
212     case Remote::BACK:
213     {
214       if (vsummary)
215       {
216         removeSummary();
217         return 2;
218       }
219     } // DROP THROUGH
220     case Remote::STOP:
221     case Remote::MENU:
222     {
223       if (playing) stopPlay();
224
225       return 4;
226     }
227     case Remote::PAUSE:
228     {
229       player->pause();
230       doBar(0);
231       return 2;
232     }
233     case Remote::SKIPFORWARD:
234     {
235       doBar(3);
236       player->skipForward(60);
237       return 2;
238     }
239     case Remote::SKIPBACK:
240     {
241       doBar(4);
242       player->skipBackward(60);
243       return 2;
244     }
245     case Remote::FORWARD:
246     {
247       player->fastForward();
248       doBar(0);
249       return 2;
250     }
251     case Remote::REVERSE:
252     {
253       player->fastBackward();
254       doBar(0);
255       return 2;
256     }
257     case Remote::RED:
258     {
259       if (vsummary) removeSummary();
260       else doSummary();
261       return 2;
262     }
263     case Remote::GREEN:
264     {
265       doAudioSelector();
266       return 2;
267     }
268     case Remote::YELLOW:
269     {
270       doBar(2);
271       player->skipBackward(10);
272       return 2;
273     }
274     case Remote::BLUE:
275     {
276       doBar(1);
277       player->skipForward(10);
278       return 2;
279     }
280     case Remote::STAR:
281     {
282       doBar(2);
283       player->skipBackward(10);
284       return 2;
285     }
286     case Remote::HASH:
287     {
288       doBar(1);
289       player->skipForward(10);
290       return 2;
291     }
292     case Remote::FULL:
293     case Remote::TV:
294     {
295       toggleChopSides();
296       return 2;
297     }
298
299     case Remote::OK:
300     {
301       if (vsummary)
302       {
303         removeSummary();
304         return 2;
305       }
306       
307       if (barShowing) removeBar();
308       else {
309         doBar(0);
310         barGenHold=true;
311       }
312       return 2;
313     }
314
315     case Remote::ZERO:  player->jumpToPercent(0);  doBar(0);  return 2;
316     case Remote::ONE:   player->jumpToPercent(10); doBar(0);  return 2;
317     case Remote::TWO:   player->jumpToPercent(20); doBar(0);  return 2;
318     case Remote::THREE: player->jumpToPercent(30); doBar(0);  return 2;
319     case Remote::FOUR:  player->jumpToPercent(40); doBar(0);  return 2;
320     case Remote::FIVE:  player->jumpToPercent(50); doBar(0);  return 2;
321     case Remote::SIX:   player->jumpToPercent(60); doBar(0);  return 2;
322     case Remote::SEVEN: player->jumpToPercent(70); doBar(0);  return 2;
323     case Remote::EIGHT: player->jumpToPercent(80); doBar(0);  return 2;
324     case Remote::NINE:  player->jumpToPercent(90); doBar(0);  return 2;
325
326
327   }
328
329   return 1;
330 }
331
332 void VVideoMedia::processMessage(Message* m)
333 {
334   Log::getInstance()->log("VVideoMedia", Log::DEBUG, "Message received");
335
336   if (m->message == Message::MOUSE_LBDOWN)
337   {
338     UINT x = (m->parameter.num>>16) - getScreenX();
339     UINT y = (m->parameter.num&0xFFFF) - getScreenY();
340
341     if (!barShowing)
342     {
343       BoxStack::getInstance()->handleCommand(Remote::OK); //simulate rok press
344     }
345     else if (barRegion.x<=x && barRegion.y<=y && (barRegion.x+barRegion.w)>=x && (barRegion.y+barRegion.h)>=y)
346     {
347       int progBarXbase = barRegion.x + 300;
348       if (x>=barRegion.x + progBarXbase + 24
349           && x<=barRegion.x + progBarXbase + 4 + 302
350           && y>=barRegion.y + 12 - 2
351           && y<=barRegion.y + 12 - 2+28)
352       {
353         int cx=x-(barRegion.x + progBarXbase + 4);
354         double percent=((double)cx)/302.*100.;
355         player->jumpToPercent(percent);
356         doBar(3);
357         return;
358         //  int progressWidth = 302 * currentFrameNum / lengthFrames;
359         //  rectangle(barRegion.x + progBarXbase + 4, barRegion.y + 16, progressWidth, 16, DrawStyle::SELECTHIGHLIGHT);
360       }
361     }
362     else
363     {
364       BoxStack::getInstance()->handleCommand(Remote::OK); //simulate rok press
365     }
366   }
367   else if (m->message == Message::PLAYER_EVENT)
368   {
369     switch(m->parameter.num)
370     {
371       case PlayerMedia::CONNECTION_LOST: // connection lost detected
372       {
373         // I can't handle this, send it to command
374         Message* m2 = new Message();
375         m2->to = Command::getInstance();
376         m2->message = Message::CONNECTION_LOST;
377         Command::getInstance()->postMessageNoLock(m2);
378         break;
379       }
380       case PlayerMedia::STREAM_END:
381       {
382         Message* m2 = new Message(); // Must be done after this thread finishes, and must break into master mutex
383         m2->to = BoxStack::getInstance();
384         m2->message = Message::CLOSE_ME;
385         Command::getInstance()->postMessageNoLock(m2);
386         break;
387       }
388       case PlayerMedia::STATUS_CHANGE:
389         doBar(0);
390         break;
391       case PlayerMedia::ASPECT43:
392       {
393         if (dowss)
394         {
395           Log::getInstance()->log("VVideoMedia", Log::DEBUG, "Received do WSS 43");
396           wss.setWide(false);
397           wss.draw();
398           boxstack->update(this, &wssRegion);
399         }
400         break;
401       }
402       case PlayerMedia::ASPECT169:
403       {
404         if (dowss)
405         {
406           Log::getInstance()->log("VVideoMedia", Log::DEBUG, "Received do WSS 169");
407           wss.setWide(true);
408           wss.draw();
409           boxstack->update(this, &wssRegion);
410         }
411         break;
412       }
413       case (PLAYER_TIMER_BASE+1) :
414         //timer1:
415         // Remove bar
416         removeBar();
417         break;
418       case (PLAYER_TIMER_BASE+2) :
419         //timer2:
420         // Update clock
421         if (!barShowing) break;
422         drawBarClocks();
423         BoxStack::getInstance()->update(this,&barRegion);
424         if (player->getLengthFrames() != 0)   timers->setTimerD(this, 2, 0, 200000000);
425         else   timers->setTimerD(this, 2, 1);
426         break;
427     }
428   }
429   else if (m->message == Message::AUDIO_CHANGE_CHANNEL)
430   {
431     Log::getInstance()->log("VVideoMedia", Log::DEBUG, "Received change audio channel to %i", m->parameter);
432     player->setAudioChannel(m->parameter.num);
433   }
434   else if (m->message == Message::CHILD_CLOSE)
435   {
436     if (m->from == vas)
437     {
438       vas = NULL;
439       barVasHold = false;
440       if (!barGenHold && !barScanHold && !barVasHold) removeBar();
441     }
442   }
443 }
444
445 void VVideoMedia::stopPlay()
446 {
447   Log::getInstance()->log("VVideoMedia", Log::DEBUG, "Pre stopPlay");
448
449   removeBar();
450
451   player->stop();
452
453   playing = false;
454   MediaPlayer::getInstance()->closeMediaChannel(MEDIACHANNEL);
455
456   Log::getInstance()->log("VVideoMedia", Log::DEBUG, "Post stopPlay");
457 }
458
459 void VVideoMedia::toggleChopSides()
460 {
461   if (video->getTVsize() == Video::ASPECT16X9) return; // Means nothing for 16:9 TVs
462
463   if (videoMode == Video::NORMAL)
464   {
465     videoMode = Video::LETTERBOX;
466     video->setMode(Video::LETTERBOX);
467   }
468   else
469   {
470     videoMode = Video::NORMAL;
471     video->setMode(Video::NORMAL);
472   }
473 }
474
475 void VVideoMedia::doAudioSelector()
476 {
477   bool* availableMpegAudioChannels = player->getDemuxerMpegAudioChannels();
478   bool* availableAc3AudioChannels = 0;
479   int currentAudioChannel = player->getCurrentAudioChannel();
480   if (Audio::getInstance()->supportsAc3())
481   {
482       availableAc3AudioChannels = player->getDemuxerAc3AudioChannels();
483   }
484
485
486   RecInfo ri;
487   ri.summary=new char[strlen(myMedia->getDisplayName())+1];
488   strcpy(ri.summary,myMedia->getDisplayName());
489   vas = new VAudioSelector(this, availableMpegAudioChannels, availableAc3AudioChannels, currentAudioChannel, NULL,NULL,0,0, &ri);
490  
491   vas->setBackgroundColour(barBlue);
492   vas->setPosition(0, barRegion.y - 120);
493
494 // pal 62, ntsc 57
495
496   barVasHold = true;
497   doBar(0);
498
499   vas->draw();
500   boxstack->add(vas);
501   boxstack->update(vas);
502 }
503
504 void VVideoMedia::doBar(int action)
505 {
506   Log::getInstance()->log("VVideoMedia",Log::DEBUG,"doBar %d",action);
507   barShowing = true;
508
509   rectangle(barRegion, barBlue);
510
511   /* Work out what to display - choices:
512
513   Playing  >
514   Paused   ||
515   FFwd     >>
516   FBwd     <<
517
518   Specials, informed by parameter
519
520   Skip forward 10s    >|
521   Skip backward 10s   |<
522   Skip forward 1m     >>|
523   Skip backward 1m    |<<
524
525   */
526
527   WSymbol w;
528   TEMPADD(&w);
529   w.nextSymbol = 0;
530   w.setPosition(barRegion.x + 66, barRegion.y + 16);
531
532   UCHAR playerState = 0;
533
534   if (action)
535   {
536     if (action == 1)       w.nextSymbol = WSymbol::SKIPFORWARD;
537     else if (action == 2)  w.nextSymbol = WSymbol::SKIPBACK;
538     else if (action == 3)  w.nextSymbol = WSymbol::SKIPFORWARD2;
539     else if (action == 4)  w.nextSymbol = WSymbol::SKIPBACK2;
540   }
541   else
542   {
543     playerState = player->getState();
544     if (playerState == PlayerMedia::S_PLAY)      w.nextSymbol = WSymbol::PLAY;
545     else if (playerState == PlayerMedia::S_FF)    w.nextSymbol = WSymbol::FFWD;
546     else if (playerState == PlayerMedia::S_BACK)    w.nextSymbol = WSymbol::FBWD;
547     else if (playerState == PlayerMedia::S_SEEK)    w.nextSymbol = WSymbol::RIGHTARROW;
548     else if (playerState == PlayerMedia::S_STOP)    w.nextSymbol = WSymbol::PAUSE;
549     else                                       w.nextSymbol = WSymbol::PAUSE;
550   }
551
552   w.draw();
553
554   if ((playerState == PlayerMedia::S_FF) || (playerState == PlayerMedia::S_BACK))
555   {
556     // draw blips to show how fast the scan is
557     UCHAR scanrate = 2;//player->getIScanRate();
558     if (scanrate >= 2)
559     {
560       char text[5];
561       SNPRINTF(text, 5, "%ux", scanrate);
562       drawText(text, barRegion.x + 102, barRegion.y + 12, DrawStyle::LIGHTTEXT);
563     }
564   }
565
566   drawBarClocks();
567   boxstack->update(this, &barRegion);
568
569   timers->cancelTimer(this, 1);
570
571
572   if ((playerState == PlayerMedia::S_FF) || (playerState == PlayerMedia::S_BACK)) barScanHold = true;
573   else barScanHold = false;
574
575   if (!barGenHold && !barScanHold && !barVasHold) timers->setTimerD(this, 1, 4);
576
577   if (player->getLengthFrames() != 0) timers->setTimerD(this, 2, 0, 200000000);
578   else timers->setTimerD(this, 2, 1);
579 }
580
581 void VVideoMedia::timercall(int clientReference)
582 {
583   Message *m=new Message();
584   m->message=Message::PLAYER_EVENT;
585   m->to=this;
586   m->from=this;
587   m->parameter.num=PLAYER_TIMER_BASE+clientReference;
588   Command::getInstance()->postMessageFromOuterSpace(m);
589 }
590
591 void VVideoMedia::drawBarClocks()
592 {
593   if (barScanHold)
594   {
595     UCHAR playerState = player->getState();
596     // sticky bar is set if we are in ffwd/fbwd mode
597     // if player has gone to S_PLAY then kill stickyBar, and run doBar(0) which
598     // will repaint all the bar (it will call this function again, but
599     // this section won't run because stickyBarF will then == false)
600
601     if ((playerState != PlayerMedia::S_FF) && (playerState != PlayerMedia::S_BACK))
602     {
603       barScanHold = false;
604       doBar(0);
605       return; 
606     }
607   }
608
609   Log* logger = Log::getInstance();
610   logger->log("VVideoMedia", Log::DEBUG, "Draw bar clocks");
611
612   // Draw RTC
613   // Blank the area first
614   rectangle(barRegion.x + 624, barRegion.y + 12, 60, 30, barBlue);
615   char timeString[20];
616   time_t t;
617   time(&t);
618   struct tm tms;
619   localtime_r(&t, &tms);
620   strftime(timeString, 19, "%H:%M", &tms);
621   drawText(timeString, barRegion.x + 624, barRegion.y + 12, DrawStyle::LIGHTTEXT);
622
623   ULLONG lenPTS=player->getLenPTS();
624   // Draw clocks
625
626   rectangle(clocksRegion, barBlue);
627
628   ULLONG currentPTS = player->getCurrentPTS();
629
630   hmsf currentFrameHMSF = ptsToHMS(currentPTS);
631   hmsf lengthHMSF = ptsToHMS(lenPTS);
632
633   char buffer[100];
634   if (currentPTS > lenPTS && lenPTS != 0)
635   {
636     strcpy(buffer, "-:--:-- / -:--:--");
637   }
638   else
639   {
640     SNPRINTF(buffer, 99, "%01i:%02i:%02i / %01i:%02i:%02i", currentFrameHMSF.hours, currentFrameHMSF.minutes, currentFrameHMSF.seconds, lengthHMSF.hours, lengthHMSF.minutes, lengthHMSF.seconds);
641   }
642     logger->log("VVideoMedia", Log::DEBUG, "cur %llu,len %llu, txt %s",currentPTS,lenPTS,buffer);
643
644   drawText(buffer, clocksRegion.x, clocksRegion.y, DrawStyle::LIGHTTEXT);
645
646
647
648
649
650
651
652   // Draw progress bar
653   int progBarXbase = barRegion.x + 300;
654
655   if (lenPTS == 0) return;
656   rectangle(barRegion.x + progBarXbase, barRegion.y + 12, 310, 24, DrawStyle::LIGHTTEXT);
657   rectangle(barRegion.x + progBarXbase + 2, barRegion.y + 14, 306, 20, barBlue);
658
659   if (currentPTS > lenPTS) return;
660
661   // Draw yellow portion
662   int progressWidth = 302 * currentPTS / lenPTS;
663   rectangle(barRegion.x + progBarXbase + 4, barRegion.y + 16, progressWidth, 16, DrawStyle::SELECTHIGHLIGHT);
664
665 }
666
667 void VVideoMedia::removeBar()
668 {
669   if (!barShowing) return;
670   timers->cancelTimer(this, 2);
671   barShowing = false;
672   barGenHold = false;
673   barScanHold = false;
674   barVasHold = false;
675   rectangle(barRegion, transparent);
676   BoxStack::getInstance()->update(this, &barRegion);
677 }
678
679 void VVideoMedia::doSummary()
680 {
681   vsummary = new VInfo();
682   vsummary->setTitleText(myMedia->getDisplayName());
683   vsummary->setBorderOn(1);
684   vsummary->setExitable();
685   const MediaURI *u=myMedia->getURI();
686   int stlen=0;
687   if (u) {
688     stlen+=strlen(myMedia->getFileName());
689     stlen+=strlen(tr("FileName"))+10;
690   }
691   stlen+=strlen(tr("Size"))+50;
692   stlen+=strlen(tr("Directory"))+500;
693   stlen+=strlen(tr("Time"))+50;
694   char *pinfo=player->getInfo();
695   stlen+=strlen(pinfo)+10;
696   char *buf=new char [stlen];
697   char *tsbuf=new char [stlen];
698   char *tsbuf2=new char [stlen];
699   char *tbuf=new char[Media::TIMEBUFLEN];
700   SNPRINTF(buf,stlen,"%s\n" 
701                    "%s: %llu Bytes\n"
702                    "%s\n"
703                    "%s: %s\n"
704                    "%s",
705       shortendedText(tr("FileName"),": ",myMedia->getFileName(),tsbuf,vsummary->getWidth()),
706       tr("Size"),
707       lengthBytes,
708       shortendedText(tr("Directory"),": ",lparent->getDirname(MEDIA_TYPE_VIDEO),tsbuf2,vsummary->getWidth()),
709       tr("Time"),
710       myMedia->getTimeString(tbuf),
711       pinfo
712       );
713   //TODO more info
714   if (u) {
715       Log::getInstance()->log("VVideoMedia",Log::DEBUG,"info %s",buf);
716       vsummary->setMainText(buf);
717   }
718   else vsummary->setMainText(tr("Info unavailable"));
719   delete pinfo;
720   if (Video::getInstance()->getFormat() == Video::PAL)
721   {
722     vsummary->setPosition(70, 100);
723   }
724   else
725   {
726     vsummary->setPosition(40, 70);
727   }
728   vsummary->setSize(580, 350);
729   add(vsummary);
730   vsummary->draw();
731
732   BoxStack::getInstance()->update(this);
733    delete [] buf;
734   delete []  tsbuf;
735   delete [] tsbuf2;
736   delete []  tbuf;
737 }
738
739 void VVideoMedia::removeSummary()
740 {
741   if (vsummary)
742   {
743     remove(vsummary);
744     delete vsummary;
745     vsummary = NULL;
746     draw();
747     BoxStack::getInstance()->update(this);
748   }
749 }
750
751
752 hmsf VVideoMedia::ptsToHMS(ULLONG pts) {
753   ULLONG secs=pts/90000;
754   hmsf rt;
755   rt.frames=0;
756   rt.seconds=secs%60;
757   secs=secs/60;
758   rt.minutes=secs%60;
759   secs=secs/60;
760   rt.hours=secs;
761   return rt;
762 }
763   
764 char * VVideoMedia::shortendedText(const char * title, const char * title2,const char * intext,char *buffer,UINT width) {
765   if (! intext) {
766     intext="";
767   }
768   UINT twidth=0;
769   for (const char *p=title;*p!=0;p++) twidth+=charWidth(*p);
770   for (const char *p=title2;*p!=0;p++) twidth+=charWidth(*p);
771   const char *prfx="...";
772   UINT prfwidth=3*charWidth('.');
773   const char *istart=intext+strlen(intext);
774   UINT iwidth=0;
775   while (twidth+iwidth+prfwidth < width-2*paraMargin && istart> intext) {
776     istart--;
777     iwidth+=charWidth(*istart);
778   }
779   if (twidth+iwidth+prfwidth >= width-2*paraMargin && istart < intext+strlen(intext)) istart++;
780   if (istart == intext) prfx="";
781   sprintf(buffer,"%s%s%s%s",title,title2,prfx,intext);
782   return buffer;
783 }
784
785