vdr  2.0.4
dvbplayer.c
Go to the documentation of this file.
1 /*
2  * dvbplayer.c: The DVB player
3  *
4  * See the main source file 'vdr.c' for copyright information and
5  * how to reach the author.
6  *
7  * $Id: dvbplayer.c 2.35 2013/03/08 13:44:19 kls Exp $
8  */
9 
10 #include "dvbplayer.h"
11 #include <math.h>
12 #include <stdlib.h>
13 #include "recording.h"
14 #include "remux.h"
15 #include "ringbuffer.h"
16 #include "thread.h"
17 #include "tools.h"
18 
19 // --- cPtsIndex -------------------------------------------------------------
20 
21 #define PTSINDEX_ENTRIES 500
22 
23 class cPtsIndex {
24 private:
25  struct tPtsIndex {
26  uint32_t pts; // no need for 33 bit - some devices don't even supply the msb
27  int index;
28  };
30  int w, r;
31  int lastFound;
33 public:
34  cPtsIndex(void);
35  void Clear(void);
36  bool IsEmpty(void);
37  void Put(uint32_t Pts, int Index);
38  int FindIndex(uint32_t Pts);
39  };
40 
42 {
43  lastFound = 0;
44  Clear();
45 }
46 
47 void cPtsIndex::Clear(void)
48 {
49  cMutexLock MutexLock(&mutex);
50  w = r = 0;
51 }
52 
54 {
55  cMutexLock MutexLock(&mutex);
56  return w == r;
57 }
58 
59 void cPtsIndex::Put(uint32_t Pts, int Index)
60 {
61  cMutexLock MutexLock(&mutex);
62  pi[w].pts = Pts;
63  pi[w].index = Index;
64  w = (w + 1) % PTSINDEX_ENTRIES;
65  if (w == r)
66  r = (r + 1) % PTSINDEX_ENTRIES;
67 }
68 
69 int cPtsIndex::FindIndex(uint32_t Pts)
70 {
71  cMutexLock MutexLock(&mutex);
72  if (w == r)
73  return lastFound; // list is empty, let's not jump way off the last known position
74  uint32_t Delta = 0xFFFFFFFF;
75  int Index = -1;
76  for (int i = w; i != r; ) {
77  if (--i < 0)
78  i = PTSINDEX_ENTRIES - 1;
79  uint32_t d = pi[i].pts < Pts ? Pts - pi[i].pts : pi[i].pts - Pts;
80  if (d > 0x7FFFFFFF)
81  d = 0xFFFFFFFF - d; // handle rollover
82  if (d < Delta) {
83  Delta = d;
84  Index = pi[i].index;
85  }
86  }
87  lastFound = Index;
88  return Index;
89 }
90 
91 // --- cNonBlockingFileReader ------------------------------------------------
92 
94 private:
97  int wanted;
98  int length;
102 protected:
103  void Action(void);
104 public:
107  void Clear(void);
108  void Request(cUnbufferedFile *File, int Length);
109  int Result(uchar **Buffer);
110  bool Reading(void) { return buffer; }
111  bool WaitForDataMs(int msToWait);
112  };
113 
115 :cThread("non blocking file reader")
116 {
117  f = NULL;
118  buffer = NULL;
119  wanted = length = 0;
120  Start();
121 }
122 
124 {
125  newSet.Signal();
126  Cancel(3);
127  free(buffer);
128 }
129 
131 {
132  Lock();
133  f = NULL;
134  free(buffer);
135  buffer = NULL;
136  wanted = length = 0;
137  Unlock();
138 }
139 
141 {
142  Lock();
143  Clear();
144  wanted = Length;
146  f = File;
147  Unlock();
148  newSet.Signal();
149 }
150 
152 {
153  LOCK_THREAD;
154  if (buffer && length == wanted) {
155  *Buffer = buffer;
156  buffer = NULL;
157  return wanted;
158  }
159  errno = EAGAIN;
160  return -1;
161 }
162 
164 {
165  while (Running()) {
166  Lock();
167  if (f && buffer && length < wanted) {
168  int r = f->Read(buffer + length, wanted - length);
169  if (r > 0)
170  length += r;
171  else if (r == 0) { // r == 0 means EOF
172  if (length > 0)
173  wanted = length; // already read something, so return the rest
174  else
175  length = wanted = 0; // report EOF
176  }
177  else if (FATALERRNO) {
178  LOG_ERROR;
179  length = wanted = r; // this will forward the error status to the caller
180  }
181  if (length == wanted) {
182  cMutexLock NewDataLock(&newDataMutex);
184  }
185  }
186  Unlock();
187  newSet.Wait(1000);
188  }
189 }
190 
192 {
193  cMutexLock NewDataLock(&newDataMutex);
194  if (buffer && length == wanted)
195  return true;
196  return newDataCond.TimedWait(newDataMutex, msToWait);
197 }
198 
199 // --- cDvbPlayer ------------------------------------------------------------
200 
201 #define PLAYERBUFSIZE MEGABYTE(1)
202 
203 #define RESUMEBACKUP 10 // number of seconds to back up when resuming an interrupted replay session
204 #define MAXSTUCKATEOF 3 // max. number of seconds to wait in case the device doesn't play the last frame
205 
206 class cDvbPlayer : public cPlayer, cThread {
207 private:
210  static int Speeds[];
219  bool pauseLive;
220  bool eof;
231  void TrickSpeed(int Increment);
232  void Empty(void);
233  bool NextFile(uint16_t FileNumber = 0, off_t FileOffset = -1);
234  int Resume(void);
235  bool Save(void);
236 protected:
237  virtual void Activate(bool On);
238  virtual void Action(void);
239 public:
240  cDvbPlayer(const char *FileName, bool PauseLive);
241  virtual ~cDvbPlayer();
242  bool Active(void) { return cThread::Running(); }
243  void Pause(void);
244  void Play(void);
245  void Forward(void);
246  void Backward(void);
247  int SkipFrames(int Frames);
248  void SkipSeconds(int Seconds);
249  void Goto(int Position, bool Still = false);
250  virtual double FramesPerSecond(void) { return framesPerSecond; }
251  virtual void SetAudioTrack(eTrackType Type, const tTrackId *TrackId);
252  virtual bool GetIndex(int &Current, int &Total, bool SnapToIFrame = false);
253  virtual bool GetReplayMode(bool &Play, bool &Forward, int &Speed);
254  };
255 
256 #define MAX_VIDEO_SLOWMOTION 63 // max. arg to pass to VIDEO_SLOWMOTION // TODO is this value correct?
257 #define NORMAL_SPEED 4 // the index of the '1' entry in the following array
258 #define MAX_SPEEDS 3 // the offset of the maximum speed from normal speed in either direction
259 #define SPEED_MULT 12 // the speed multiplier
260 int cDvbPlayer::Speeds[] = { 0, -2, -4, -8, 1, 2, 4, 12, 0 };
261 
262 cDvbPlayer::cDvbPlayer(const char *FileName, bool PauseLive)
263 :cThread("dvbplayer")
264 {
265  nonBlockingFileReader = NULL;
266  ringBuffer = NULL;
267  index = NULL;
268  cRecording Recording(FileName);
269  framesPerSecond = Recording.FramesPerSecond();
270  isPesRecording = Recording.IsPesRecording();
271  pauseLive = PauseLive;
272  eof = false;
273  firstPacket = true;
274  playMode = pmPlay;
275  playDir = pdForward;
277  readIndex = -1;
278  readIndependent = false;
279  readFrame = NULL;
280  playFrame = NULL;
281  dropFrame = NULL;
282  resyncAfterPause = false;
283  isyslog("replay %s", FileName);
284  fileName = new cFileName(FileName, false, false, isPesRecording);
285  replayFile = fileName->Open();
286  if (!replayFile)
287  return;
289  // Create the index file:
290  index = new cIndexFile(FileName, false, isPesRecording, pauseLive);
291  if (!index)
292  esyslog("ERROR: can't allocate index");
293  else if (!index->Ok()) {
294  delete index;
295  index = NULL;
296  }
297  else if (PauseLive)
298  framesPerSecond = cRecording(FileName).FramesPerSecond(); // the fps rate might have changed from the default
299 }
300 
302 {
303  Save();
304  Detach();
305  delete readFrame; // might not have been stored in the buffer in Action()
306  delete index;
307  delete fileName;
308  delete ringBuffer;
309 }
310 
311 void cDvbPlayer::TrickSpeed(int Increment)
312 {
313  int nts = trickSpeed + Increment;
314  if (Speeds[nts] == 1) {
315  trickSpeed = nts;
316  if (playMode == pmFast)
317  Play();
318  else
319  Pause();
320  }
321  else if (Speeds[nts]) {
322  trickSpeed = nts;
323  int Mult = (playMode == pmSlow && playDir == pdForward) ? 1 : SPEED_MULT;
324  int sp = (Speeds[nts] > 0) ? Mult / Speeds[nts] : -Speeds[nts] * Mult;
325  if (sp > MAX_VIDEO_SLOWMOTION)
327  DeviceTrickSpeed(sp);
328  }
329 }
330 
332 {
333  LOCK_THREAD;
336  if (!firstPacket) // don't set the readIndex twice if Empty() is called more than once
337  readIndex = ptsIndex.FindIndex(DeviceGetSTC()) - 1; // Action() will first increment it!
338  delete readFrame; // might not have been stored in the buffer in Action()
339  readFrame = NULL;
340  playFrame = NULL;
341  dropFrame = NULL;
342  ringBuffer->Clear();
343  ptsIndex.Clear();
344  DeviceClear();
345  firstPacket = true;
346 }
347 
348 bool cDvbPlayer::NextFile(uint16_t FileNumber, off_t FileOffset)
349 {
350  if (FileNumber > 0)
351  replayFile = fileName->SetOffset(FileNumber, FileOffset);
352  else if (replayFile && eof)
354  eof = false;
355  return replayFile != NULL;
356 }
357 
359 {
360  if (index) {
361  int Index = index->GetResume();
362  if (Index >= 0) {
363  uint16_t FileNumber;
364  off_t FileOffset;
365  if (index->Get(Index, &FileNumber, &FileOffset) && NextFile(FileNumber, FileOffset))
366  return Index;
367  }
368  }
369  return -1;
370 }
371 
373 {
374  if (index) {
375  int Index = ptsIndex.FindIndex(DeviceGetSTC());
376  if (Index >= 0) {
377  int backup = int(round(RESUMEBACKUP * framesPerSecond));
378  if (Index >= index->Last() - backup)
379  Index = 0;
380  else {
381  Index -= backup;
382  if (Index > 0)
383  Index = index->GetNextIFrame(Index, false);
384  else
385  Index = 0;
386  }
387  if (Index >= 0)
388  return index->StoreResume(Index);
389  }
390  }
391  return false;
392 }
393 
394 void cDvbPlayer::Activate(bool On)
395 {
396  if (On) {
397  if (replayFile)
398  Start();
399  }
400  else
401  Cancel(9);
402 }
403 
405 {
406  uchar *p = NULL;
407  int pc = 0;
408 
409  readIndex = Resume();
410  if (readIndex >= 0)
411  isyslog("resuming replay at index %d (%s)", readIndex, *IndexToHMSF(readIndex, true, framesPerSecond));
412 
414  int Length = 0;
415  bool Sleep = false;
416  bool WaitingForData = false;
417  time_t StuckAtEof = 0;
418  uint32_t LastStc = 0;
419  int LastReadIFrame = -1;
420  int SwitchToPlayFrame = 0;
421 
422  if (pauseLive)
423  Goto(0, true);
424  while (Running()) {
425  if (WaitingForData)
426  WaitingForData = !nonBlockingFileReader->WaitForDataMs(3); // this keeps the CPU load low, but reacts immediately on new data
427  else if (Sleep) {
428  cPoller Poller;
429  DevicePoll(Poller, 10);
430  Sleep = false;
431  if (playMode == pmStill || playMode == pmPause)
433  }
434  {
435  LOCK_THREAD;
436 
437  // Read the next frame from the file:
438 
439  if (playMode != pmStill && playMode != pmPause) {
440  if (!readFrame && (replayFile || readIndex >= 0)) {
441  if (!nonBlockingFileReader->Reading()) {
442  if (!SwitchToPlayFrame && (playMode == pmFast || (playMode == pmSlow && playDir == pdBackward))) {
443  uint16_t FileNumber;
444  off_t FileOffset;
445  bool TimeShiftMode = index->IsStillRecording();
446  int Index = -1;
447  readIndependent = false;
449  if (index->Get(readIndex + 1, &FileNumber, &FileOffset, &readIndependent, &Length))
450  Index = readIndex + 1;
451  }
452  else {
453  int d = int(round(0.4 * framesPerSecond));
454  if (playDir != pdForward)
455  d = -d;
456  int NewIndex = readIndex + d;
457  if (NewIndex <= 0 && readIndex > 0)
458  NewIndex = 1; // make sure the very first frame is delivered
459  NewIndex = index->GetNextIFrame(NewIndex, playDir == pdForward, &FileNumber, &FileOffset, &Length);
460  if (NewIndex < 0 && TimeShiftMode && playDir == pdForward)
461  SwitchToPlayFrame = readIndex;
462  Index = NewIndex;
463  readIndependent = true;
464  }
465  if (Index >= 0) {
466  readIndex = Index;
467  if (!NextFile(FileNumber, FileOffset))
468  continue;
469  }
470  else if (!(TimeShiftMode && playDir == pdForward))
471  eof = true;
472  }
473  else if (index) {
474  uint16_t FileNumber;
475  off_t FileOffset;
476  if (index->Get(readIndex + 1, &FileNumber, &FileOffset, &readIndependent, &Length) && NextFile(FileNumber, FileOffset))
477  readIndex++;
478  else
479  eof = true;
480  }
481  else // allows replay even if the index file is missing
482  Length = MAXFRAMESIZE;
483  if (Length == -1)
484  Length = MAXFRAMESIZE; // this means we read up to EOF (see cIndex)
485  else if (Length > MAXFRAMESIZE) {
486  esyslog("ERROR: frame larger than buffer (%d > %d)", Length, MAXFRAMESIZE);
487  Length = MAXFRAMESIZE;
488  }
489  if (!eof)
491  }
492  if (!eof) {
493  uchar *b = NULL;
494  int r = nonBlockingFileReader->Result(&b);
495  if (r > 0) {
496  WaitingForData = false;
497  uint32_t Pts = 0;
498  if (readIndependent) {
499  Pts = isPesRecording ? PesGetPts(b) : TsGetPts(b, r);
500  LastReadIFrame = readIndex;
501  }
502  readFrame = new cFrame(b, -r, ftUnknown, readIndex, Pts); // hands over b to the ringBuffer
503  }
504  else if (r < 0) {
505  if (errno == EAGAIN)
506  WaitingForData = true;
507  else if (FATALERRNO) {
508  LOG_ERROR;
509  break;
510  }
511  }
512  else
513  eof = true;
514  }
515  }
516 
517  // Store the frame in the buffer:
518 
519  if (readFrame) {
520  if (ringBuffer->Put(readFrame))
521  readFrame = NULL;
522  else
523  Sleep = true;
524  }
525  }
526  else
527  Sleep = true;
528 
529  if (dropFrame) {
530  if (!eof || (playDir != pdForward && dropFrame->Index() > 0) || (playDir == pdForward && dropFrame->Index() < readIndex)) {
531  ringBuffer->Drop(dropFrame); // the very first and last frame are continuously repeated to flush data through the device
532  dropFrame = NULL;
533  }
534  }
535 
536  // Get the next frame from the buffer:
537 
538  if (!playFrame) {
539  playFrame = ringBuffer->Get();
540  p = NULL;
541  pc = 0;
542  }
543 
544  // Play the frame:
545 
546  if (playFrame) {
547  if (!p) {
548  p = playFrame->Data();
549  pc = playFrame->Count();
550  if (p) {
551  if (playFrame->Index() >= 0 && playFrame->Pts() != 0)
553  if (firstPacket) {
554  if (isPesRecording) {
555  PlayPes(NULL, 0);
556  cRemux::SetBrokenLink(p, pc);
557  }
558  else
559  PlayTs(NULL, 0);
560  firstPacket = false;
561  }
562  }
563  }
564  if (p) {
565  int w;
566  bool VideoOnly = (dropFrame || playMode != pmPlay && !(playMode == pmSlow && playDir == pdForward)) && DeviceIsPlayingVideo();
567  if (isPesRecording)
568  w = PlayPes(p, pc, VideoOnly);
569  else
570  w = PlayTs(p, pc, VideoOnly);
571  if (w > 0) {
572  p += w;
573  pc -= w;
574  }
575  else if (w < 0 && FATALERRNO)
576  LOG_ERROR;
577  else
578  Sleep = true;
579  }
580  if (pc <= 0) {
582  playFrame = NULL;
583  p = NULL;
584  }
585  }
586  else
587  Sleep = true;
588 
589  // Handle hitting begin/end of recording:
590 
591  if (eof || SwitchToPlayFrame) {
592  bool SwitchToPlay = false;
593  uint32_t Stc = DeviceGetSTC();
594  if (Stc != LastStc || playMode == pmPause)
595  StuckAtEof = 0;
596  else if (!StuckAtEof)
597  StuckAtEof = time(NULL);
598  else if (time(NULL) - StuckAtEof > MAXSTUCKATEOF) {
599  if (playDir == pdForward)
600  break; // automatically stop at end of recording
601  SwitchToPlay = true;
602  }
603  LastStc = Stc;
604  int Index = ptsIndex.FindIndex(Stc);
605  if (playDir == pdForward && !SwitchToPlayFrame) {
606  if (Index >= LastReadIFrame)
607  break; // automatically stop at end of recording
608  }
609  else if (Index <= 0 || SwitchToPlayFrame && Index >= SwitchToPlayFrame)
610  SwitchToPlay = true;
611  if (SwitchToPlay) {
612  if (!SwitchToPlayFrame)
613  Empty();
614  DevicePlay();
615  playMode = pmPlay;
616  playDir = pdForward;
617  SwitchToPlayFrame = 0;
618  }
619  }
620  }
621  }
622 
624  nonBlockingFileReader = NULL;
625  delete nbfr;
626 }
627 
629 {
630  if (playMode == pmPause || playMode == pmStill)
631  Play();
632  else {
633  LOCK_THREAD;
634  if (playMode == pmFast || (playMode == pmSlow && playDir == pdBackward)) {
636  Empty();
637  }
638  DeviceFreeze();
639  playMode = pmPause;
640  }
641 }
642 
644 {
645  if (playMode != pmPlay) {
646  LOCK_THREAD;
647  if (playMode == pmStill || playMode == pmFast || (playMode == pmSlow && playDir == pdBackward)) {
649  Empty();
650  }
651  DevicePlay();
652  playMode = pmPlay;
653  playDir = pdForward;
654  if (resyncAfterPause) {
655  int Current, Total;
656  if (GetIndex(Current, Total, true))
657  Goto(Current);
658  resyncAfterPause = false;
659  }
660  }
661 }
662 
664 {
665  if (index) {
666  switch (playMode) {
667  case pmFast:
668  if (Setup.MultiSpeedMode) {
669  TrickSpeed(playDir == pdForward ? 1 : -1);
670  break;
671  }
672  else if (playDir == pdForward) {
673  Play();
674  break;
675  }
676  // run into pmPlay
677  case pmPlay: {
678  LOCK_THREAD;
680  Empty();
681  if (DeviceIsPlayingVideo())
682  DeviceMute();
683  playMode = pmFast;
684  playDir = pdForward;
687  }
688  break;
689  case pmSlow:
690  if (Setup.MultiSpeedMode) {
691  TrickSpeed(playDir == pdForward ? -1 : 1);
692  break;
693  }
694  else if (playDir == pdForward) {
695  Pause();
696  break;
697  }
698  Empty();
699  // run into pmPause
700  case pmStill:
701  case pmPause:
702  DeviceMute();
703  playMode = pmSlow;
704  playDir = pdForward;
707  break;
708  default: esyslog("ERROR: unknown playMode %d (%s)", playMode, __FUNCTION__);
709  }
710  }
711 }
712 
714 {
715  if (index) {
716  switch (playMode) {
717  case pmFast:
718  if (Setup.MultiSpeedMode) {
719  TrickSpeed(playDir == pdBackward ? 1 : -1);
720  break;
721  }
722  else if (playDir == pdBackward) {
723  Play();
724  break;
725  }
726  // run into pmPlay
727  case pmPlay: {
728  LOCK_THREAD;
729  Empty();
730  if (DeviceIsPlayingVideo())
731  DeviceMute();
732  playMode = pmFast;
736  }
737  break;
738  case pmSlow:
739  if (Setup.MultiSpeedMode) {
740  TrickSpeed(playDir == pdBackward ? -1 : 1);
741  break;
742  }
743  else if (playDir == pdBackward) {
744  Pause();
745  break;
746  }
747  Empty();
748  // run into pmPause
749  case pmStill:
750  case pmPause: {
751  LOCK_THREAD;
752  Empty();
753  DeviceMute();
754  playMode = pmSlow;
758  }
759  break;
760  default: esyslog("ERROR: unknown playMode %d (%s)", playMode, __FUNCTION__);
761  }
762  }
763 }
764 
765 int cDvbPlayer::SkipFrames(int Frames)
766 {
767  if (index && Frames) {
768  int Current, Total;
769  GetIndex(Current, Total, true);
770  int OldCurrent = Current;
771  // As GetNextIFrame() increments/decrements at least once, the
772  // destination frame (= Current + Frames) must be adjusted by
773  // -1/+1 respectively.
774  Current = index->GetNextIFrame(Current + Frames + (Frames > 0 ? -1 : 1), Frames > 0);
775  return Current >= 0 ? Current : OldCurrent;
776  }
777  return -1;
778 }
779 
780 void cDvbPlayer::SkipSeconds(int Seconds)
781 {
782  if (index && Seconds) {
783  LOCK_THREAD;
784  int Index = ptsIndex.FindIndex(DeviceGetSTC());
785  Empty();
786  if (Index >= 0) {
787  Index = max(Index + SecondsToFrames(Seconds, framesPerSecond), 0);
788  if (Index > 0)
789  Index = index->GetNextIFrame(Index, false, NULL, NULL, NULL);
790  if (Index >= 0)
791  readIndex = Index - 1; // Action() will first increment it!
792  }
793  Play();
794  }
795 }
796 
797 void cDvbPlayer::Goto(int Index, bool Still)
798 {
799  if (index) {
800  LOCK_THREAD;
801  Empty();
802  if (++Index <= 0)
803  Index = 1; // not '0', to allow GetNextIFrame() below to work!
804  uint16_t FileNumber;
805  off_t FileOffset;
806  int Length;
807  Index = index->GetNextIFrame(Index, false, &FileNumber, &FileOffset, &Length);
808  if (Index >= 0 && NextFile(FileNumber, FileOffset) && Still) {
809  uchar b[MAXFRAMESIZE];
810  int r = ReadFrame(replayFile, b, Length, sizeof(b));
811  if (r > 0) {
812  if (playMode == pmPause)
813  DevicePlay();
814  DeviceStillPicture(b, r);
815  ptsIndex.Put(isPesRecording ? PesGetPts(b) : TsGetPts(b, r), Index);
816  }
817  playMode = pmStill;
818  }
819  readIndex = Index;
820  }
821 }
822 
824 {
825  if (!cThread::IsMainThread())
826  return; // only do this upon user interaction
827  if (playMode == pmPlay) {
828  if (!ptsIndex.IsEmpty()) {
829  int Current, Total;
830  if (GetIndex(Current, Total, true))
831  Goto(Current);
832  }
833  }
834  else if (playMode == pmPause)
835  resyncAfterPause = true;
836 }
837 
838 bool cDvbPlayer::GetIndex(int &Current, int &Total, bool SnapToIFrame)
839 {
840  if (index) {
841  Current = ptsIndex.FindIndex(DeviceGetSTC());
842  if (SnapToIFrame) {
843  int i1 = index->GetNextIFrame(Current + 1, false);
844  int i2 = index->GetNextIFrame(Current, true);
845  Current = (abs(Current - i1) <= abs(Current - i2)) ? i1 : i2;
846  }
847  Total = index->Last();
848  return true;
849  }
850  Current = Total = -1;
851  return false;
852 }
853 
854 bool cDvbPlayer::GetReplayMode(bool &Play, bool &Forward, int &Speed)
855 {
856  Play = (playMode == pmPlay || playMode == pmFast);
857  Forward = (playDir == pdForward);
858  if (playMode == pmFast || playMode == pmSlow)
859  Speed = Setup.MultiSpeedMode ? abs(trickSpeed - NORMAL_SPEED) : 0;
860  else
861  Speed = -1;
862  return true;
863 }
864 
865 // --- cDvbPlayerControl -----------------------------------------------------
866 
867 cDvbPlayerControl::cDvbPlayerControl(const char *FileName, bool PauseLive)
868 :cControl(player = new cDvbPlayer(FileName, PauseLive))
869 {
870 }
871 
873 {
874  Stop();
875 }
876 
878 {
879  return player && player->Active();
880 }
881 
883 {
884  delete player;
885  player = NULL;
886 }
887 
889 {
890  if (player)
891  player->Pause();
892 }
893 
895 {
896  if (player)
897  player->Play();
898 }
899 
901 {
902  if (player)
903  player->Forward();
904 }
905 
907 {
908  if (player)
909  player->Backward();
910 }
911 
913 {
914  if (player)
915  player->SkipSeconds(Seconds);
916 }
917 
919 {
920  if (player)
921  return player->SkipFrames(Frames);
922  return -1;
923 }
924 
925 bool cDvbPlayerControl::GetIndex(int &Current, int &Total, bool SnapToIFrame)
926 {
927  if (player) {
928  player->GetIndex(Current, Total, SnapToIFrame);
929  return true;
930  }
931  return false;
932 }
933 
934 bool cDvbPlayerControl::GetReplayMode(bool &Play, bool &Forward, int &Speed)
935 {
936  return player && player->GetReplayMode(Play, Forward, Speed);
937 }
938 
939 void cDvbPlayerControl::Goto(int Position, bool Still)
940 {
941  if (player)
942  player->Goto(Position, Still);
943 }
944