vdr/dvbplayer.c
Klaus Schmidinger 5076cfb2ed Version 2.1.1
VDR developer version 2.1.1 is now available at

       ftp://ftp.tvdr.de/vdr/Developer/vdr-2.1.1.tar.bz2

A 'diff' against the previous version is available at

       ftp://ftp.tvdr.de/vdr/Developer/vdr-2.0.0-2.1.1.diff

MD5 checksums:

b17f9838bb8ddee9620f838fea7a171d  vdr-2.1.1.tar.bz2
8b8ca593885c380cd370e6d19a5b16a1  vdr-2.0.0-2.1.1.diff

WARNING:
========

This is a *developer* version. Even though *I* use it in my productive
environment, I strongly recommend that you only use it under controlled
conditions and for testing and debugging.

The main focus of this version is on adding basic support for positioners
to control steerable satellite dishes. Manually controlling the dish position
and storing individual positions will follow later.

The fixes contained in this version will be released in a stable version 2.0.3
later, if there are no problems.

From the HISTORY file:
- Fixed initializing cDevice::keepTracks.
- Fixed an endless loop in cTextWrapper::Set() in case the given Width is smaller than
  one character (reported by Stefan Braun).
- Removed all "modified since version 1.6" markers from PLUGINS.html.
- Added definitions for older DVB API versions, back until 5.0 (based on a patch from
  Udo Richter).
- Changed cThread::SetIOPriority() from "best effort class" to "idle class" in order to
  improve overall performance when an editing process is running (thanks to Jochen
  Dolze).
- Fixed handling '/' and '~' in recording file names in case DirectoryEncoding is
  used (thanks to Lars Hanisch).
- Changed the sign of the satellite position value in cSource to reflect the standard
  of western values being negative. The new member function cSource::Position() can be
  used to retrieve the orbital position of a satellite.
- Fixed multiple occurrences of the same directory in the recordings list in case there
  are directories that only differ in non-alphanumeric characters (was broken by
  "Fixed selecting the last replayed recording in the Recordings menu in case there
  are folders and plain recordings with names that differ only in non-alphanumeric
  characters" in version 1.7.36).
- Fixed displaying the frame number when setting an editing mark (thanks to Thomas
  Günther).
- Fixed no longer generating any editing marks if the edited recording results in just
  one single sequence (reported by Halim Sahin).
- Fixed an error message when parsing SCR values in diseqc.conf.
- Fixed an unexpected RCS version tag in the newplugin script.
- Fixed an endless loop in the DrawEllipse() functions for very small ellipses (reported
  by Stefan Braun).
- Fixed a crash in the LCARS skin's main menu in case there is no current channel
  (reported by Dominique Dumont).
- Added basic support for positioners to control steerable satellite dishes (based on
  a patch from Seppo Ingalsuo and Ales Jurik).
  + Supports GotoN (aka "DiSEqC 1.2") and GotoX (aka "USALS").
  + The new DiSEqC command code 'P' can be used to instruct a positioner to move the
    dish to the required satellite position. When a 'P' code is processed, further
    execution of the remaining DiSEqC sequence (if any) is postponed until the positioner
    has reached the new satellite position.
  + The new special source value of "S360E" can be used in diseqc.conf to indicate that
    an entry using a positioner can move the dish to any requested position within its
    range. Think of it as "full circle".
  + The devices a particular cDiseqc or cScr applies to are now stored directly in each
    cDiseqc or cScr, respectively.
  + A plugin can implement a custom positioner control (see PLUGINS.html, section "Positioners").
  + The new function cSkinDisplayChannel::SetPositioner() can be implemented by skins to
    show the user a progress display when the dish is being moved. The default implementation
    calls SetMessage() with a string indicating the new position the dish is being moved to.
    The LCARS skin shows a progress bar indicating the movement of the dish.
  + The new parameters "Site latitude", "Site longitude", "Positioner speed", and
    "Positioner swing" in the "Setup/LNB" menu can be used to configure the necessary
    values for a steerable dish.
  + The cDvbTuner now has a new status tsPositioning, in which it waits until a steerable
    dish has reached its target position. Parsing SI data is paused until the target
    position has been reached.
- The LCARS skin now shows the source value of the current channel in its channel display.
- Fixed asserting free disk space in the cutter.
- No longer trying to delete old recordings in AssertFreeDiskSpace() if the given
  Priority is less than 1.
- Fixed handling LIRC events in case repeated events are lost.
- Fixed a possible crash when shutting down VDR while subtitles are being displayed
  (reported by Ville Skyttä).
- cDevice::IsPrimaryDevice() now also checks whether the primary device actually has
  a decoder and returns false otherwise. This should improve device allocation on
  systems that are only used as a receiver and don't actually display anything.
- Increased the value of MAXRETRIES to 20 to reduce the probability of disturbances
  in transfer mode.
- All bonded devices (except for the master) now turn off their LNB power completely
  to avoid problems when receiving vertically polarized transponders (suggested by
  Manfred Völkel and Oliver Endriss).
- Reverted the change from version 1.5.7 that made all logging go to LOG_ERR (thanks
  to Christopher Reimer).
- Added Begin/EndSegmentTransfer() to the EPG handler interface (thanks to Jörg Wendel).
- The code for distributing recordings over several video directories is now
  deprecated and disabled by default.
  You can re-enable this feature by removing the comment sign ('//') from the beginning
  of the line
  //#define DEPRECATED_DISTRIBUTED_VIDEODIR // Code enclosed with this macro is ...
  in the file videodir.c. Note, though, that this can only be a temporary workaround.
  This feature will be completely removed in one of the next developer versions.
  Distributing the video directory over several disks was a useful feature in times
  when disks were still relatively small, but it also caused serious problems in case
  one of the disks failed. Nowadays hard disks come in sizes measured in terabytes,
  and tools like "mhddfs" can be used to combine several disks to form one large volume.
  A recommended method for a relatively safe disk setup in a VDR system is to use two
  1TB (or larger) disks and use them as a RAID-1 (mirrored). That way, if one disk
  fails, you can replace it without data loss.
2013-08-25 18:40:45 +02:00

939 lines
26 KiB
C

/*
* dvbplayer.c: The DVB player
*
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: dvbplayer.c 3.0 2013/03/08 13:44:19 kls Exp $
*/
#include "dvbplayer.h"
#include <math.h>
#include <stdlib.h>
#include "recording.h"
#include "remux.h"
#include "ringbuffer.h"
#include "thread.h"
#include "tools.h"
// --- cPtsIndex -------------------------------------------------------------
#define PTSINDEX_ENTRIES 500
class cPtsIndex {
private:
struct tPtsIndex {
uint32_t pts; // no need for 33 bit - some devices don't even supply the msb
int index;
};
tPtsIndex pi[PTSINDEX_ENTRIES];
int w, r;
int lastFound;
cMutex mutex;
public:
cPtsIndex(void);
void Clear(void);
bool IsEmpty(void);
void Put(uint32_t Pts, int Index);
int FindIndex(uint32_t Pts);
};
cPtsIndex::cPtsIndex(void)
{
lastFound = 0;
Clear();
}
void cPtsIndex::Clear(void)
{
cMutexLock MutexLock(&mutex);
w = r = 0;
}
bool cPtsIndex::IsEmpty(void)
{
cMutexLock MutexLock(&mutex);
return w == r;
}
void cPtsIndex::Put(uint32_t Pts, int Index)
{
cMutexLock MutexLock(&mutex);
pi[w].pts = Pts;
pi[w].index = Index;
w = (w + 1) % PTSINDEX_ENTRIES;
if (w == r)
r = (r + 1) % PTSINDEX_ENTRIES;
}
int cPtsIndex::FindIndex(uint32_t Pts)
{
cMutexLock MutexLock(&mutex);
if (w == r)
return lastFound; // list is empty, let's not jump way off the last known position
uint32_t Delta = 0xFFFFFFFF;
int Index = -1;
for (int i = w; i != r; ) {
if (--i < 0)
i = PTSINDEX_ENTRIES - 1;
uint32_t d = pi[i].pts < Pts ? Pts - pi[i].pts : pi[i].pts - Pts;
if (d > 0x7FFFFFFF)
d = 0xFFFFFFFF - d; // handle rollover
if (d < Delta) {
Delta = d;
Index = pi[i].index;
}
}
lastFound = Index;
return Index;
}
// --- cNonBlockingFileReader ------------------------------------------------
class cNonBlockingFileReader : public cThread {
private:
cUnbufferedFile *f;
uchar *buffer;
int wanted;
int length;
cCondWait newSet;
cCondVar newDataCond;
cMutex newDataMutex;
protected:
void Action(void);
public:
cNonBlockingFileReader(void);
~cNonBlockingFileReader();
void Clear(void);
void Request(cUnbufferedFile *File, int Length);
int Result(uchar **Buffer);
bool Reading(void) { return buffer; }
bool WaitForDataMs(int msToWait);
};
cNonBlockingFileReader::cNonBlockingFileReader(void)
:cThread("non blocking file reader")
{
f = NULL;
buffer = NULL;
wanted = length = 0;
Start();
}
cNonBlockingFileReader::~cNonBlockingFileReader()
{
newSet.Signal();
Cancel(3);
free(buffer);
}
void cNonBlockingFileReader::Clear(void)
{
Lock();
f = NULL;
free(buffer);
buffer = NULL;
wanted = length = 0;
Unlock();
}
void cNonBlockingFileReader::Request(cUnbufferedFile *File, int Length)
{
Lock();
Clear();
wanted = Length;
buffer = MALLOC(uchar, wanted);
f = File;
Unlock();
newSet.Signal();
}
int cNonBlockingFileReader::Result(uchar **Buffer)
{
LOCK_THREAD;
if (buffer && length == wanted) {
*Buffer = buffer;
buffer = NULL;
return wanted;
}
errno = EAGAIN;
return -1;
}
void cNonBlockingFileReader::Action(void)
{
while (Running()) {
Lock();
if (f && buffer && length < wanted) {
int r = f->Read(buffer + length, wanted - length);
if (r > 0)
length += r;
else if (r == 0) { // r == 0 means EOF
if (length > 0)
wanted = length; // already read something, so return the rest
else
length = wanted = 0; // report EOF
}
else if (FATALERRNO) {
LOG_ERROR;
length = wanted = r; // this will forward the error status to the caller
}
if (length == wanted) {
cMutexLock NewDataLock(&newDataMutex);
newDataCond.Broadcast();
}
}
Unlock();
newSet.Wait(1000);
}
}
bool cNonBlockingFileReader::WaitForDataMs(int msToWait)
{
cMutexLock NewDataLock(&newDataMutex);
if (buffer && length == wanted)
return true;
return newDataCond.TimedWait(newDataMutex, msToWait);
}
// --- cDvbPlayer ------------------------------------------------------------
#define PLAYERBUFSIZE MEGABYTE(1)
#define RESUMEBACKUP 10 // number of seconds to back up when resuming an interrupted replay session
#define MAXSTUCKATEOF 3 // max. number of seconds to wait in case the device doesn't play the last frame
class cDvbPlayer : public cPlayer, cThread {
private:
enum ePlayModes { pmPlay, pmPause, pmSlow, pmFast, pmStill };
enum ePlayDirs { pdForward, pdBackward };
static int Speeds[];
cNonBlockingFileReader *nonBlockingFileReader;
cRingBufferFrame *ringBuffer;
cPtsIndex ptsIndex;
cFileName *fileName;
cIndexFile *index;
cUnbufferedFile *replayFile;
double framesPerSecond;
bool isPesRecording;
bool pauseLive;
bool eof;
bool firstPacket;
ePlayModes playMode;
ePlayDirs playDir;
int trickSpeed;
int readIndex;
bool readIndependent;
cFrame *readFrame;
cFrame *playFrame;
cFrame *dropFrame;
bool resyncAfterPause;
void TrickSpeed(int Increment);
void Empty(void);
bool NextFile(uint16_t FileNumber = 0, off_t FileOffset = -1);
int Resume(void);
bool Save(void);
protected:
virtual void Activate(bool On);
virtual void Action(void);
public:
cDvbPlayer(const char *FileName, bool PauseLive);
virtual ~cDvbPlayer();
bool Active(void) { return cThread::Running(); }
void Pause(void);
void Play(void);
void Forward(void);
void Backward(void);
int SkipFrames(int Frames);
void SkipSeconds(int Seconds);
void Goto(int Position, bool Still = false);
virtual double FramesPerSecond(void) { return framesPerSecond; }
virtual void SetAudioTrack(eTrackType Type, const tTrackId *TrackId);
virtual bool GetIndex(int &Current, int &Total, bool SnapToIFrame = false);
virtual bool GetReplayMode(bool &Play, bool &Forward, int &Speed);
};
#define MAX_VIDEO_SLOWMOTION 63 // max. arg to pass to VIDEO_SLOWMOTION // TODO is this value correct?
#define NORMAL_SPEED 4 // the index of the '1' entry in the following array
#define MAX_SPEEDS 3 // the offset of the maximum speed from normal speed in either direction
#define SPEED_MULT 12 // the speed multiplier
int cDvbPlayer::Speeds[] = { 0, -2, -4, -8, 1, 2, 4, 12, 0 };
cDvbPlayer::cDvbPlayer(const char *FileName, bool PauseLive)
:cThread("dvbplayer")
{
nonBlockingFileReader = NULL;
ringBuffer = NULL;
index = NULL;
cRecording Recording(FileName);
framesPerSecond = Recording.FramesPerSecond();
isPesRecording = Recording.IsPesRecording();
pauseLive = PauseLive;
eof = false;
firstPacket = true;
playMode = pmPlay;
playDir = pdForward;
trickSpeed = NORMAL_SPEED;
readIndex = -1;
readIndependent = false;
readFrame = NULL;
playFrame = NULL;
dropFrame = NULL;
resyncAfterPause = false;
isyslog("replay %s", FileName);
fileName = new cFileName(FileName, false, false, isPesRecording);
replayFile = fileName->Open();
if (!replayFile)
return;
ringBuffer = new cRingBufferFrame(PLAYERBUFSIZE);
// Create the index file:
index = new cIndexFile(FileName, false, isPesRecording, pauseLive);
if (!index)
esyslog("ERROR: can't allocate index");
else if (!index->Ok()) {
delete index;
index = NULL;
}
else if (PauseLive)
framesPerSecond = cRecording(FileName).FramesPerSecond(); // the fps rate might have changed from the default
}
cDvbPlayer::~cDvbPlayer()
{
Save();
Detach();
delete readFrame; // might not have been stored in the buffer in Action()
delete index;
delete fileName;
delete ringBuffer;
}
void cDvbPlayer::TrickSpeed(int Increment)
{
int nts = trickSpeed + Increment;
if (Speeds[nts] == 1) {
trickSpeed = nts;
if (playMode == pmFast)
Play();
else
Pause();
}
else if (Speeds[nts]) {
trickSpeed = nts;
int Mult = (playMode == pmSlow && playDir == pdForward) ? 1 : SPEED_MULT;
int sp = (Speeds[nts] > 0) ? Mult / Speeds[nts] : -Speeds[nts] * Mult;
if (sp > MAX_VIDEO_SLOWMOTION)
sp = MAX_VIDEO_SLOWMOTION;
DeviceTrickSpeed(sp);
}
}
void cDvbPlayer::Empty(void)
{
LOCK_THREAD;
if (nonBlockingFileReader)
nonBlockingFileReader->Clear();
if (!firstPacket) // don't set the readIndex twice if Empty() is called more than once
readIndex = ptsIndex.FindIndex(DeviceGetSTC()) - 1; // Action() will first increment it!
delete readFrame; // might not have been stored in the buffer in Action()
readFrame = NULL;
playFrame = NULL;
dropFrame = NULL;
ringBuffer->Clear();
ptsIndex.Clear();
DeviceClear();
firstPacket = true;
}
bool cDvbPlayer::NextFile(uint16_t FileNumber, off_t FileOffset)
{
if (FileNumber > 0)
replayFile = fileName->SetOffset(FileNumber, FileOffset);
else if (replayFile && eof)
replayFile = fileName->NextFile();
eof = false;
return replayFile != NULL;
}
int cDvbPlayer::Resume(void)
{
if (index) {
int Index = index->GetResume();
if (Index >= 0) {
uint16_t FileNumber;
off_t FileOffset;
if (index->Get(Index, &FileNumber, &FileOffset) && NextFile(FileNumber, FileOffset))
return Index;
}
}
return -1;
}
bool cDvbPlayer::Save(void)
{
if (index) {
int Index = ptsIndex.FindIndex(DeviceGetSTC());
if (Index >= 0) {
Index -= int(round(RESUMEBACKUP * framesPerSecond));
if (Index > 0)
Index = index->GetNextIFrame(Index, false);
else
Index = 0;
if (Index >= 0)
return index->StoreResume(Index);
}
}
return false;
}
void cDvbPlayer::Activate(bool On)
{
if (On) {
if (replayFile)
Start();
}
else
Cancel(9);
}
void cDvbPlayer::Action(void)
{
uchar *p = NULL;
int pc = 0;
readIndex = Resume();
if (readIndex >= 0)
isyslog("resuming replay at index %d (%s)", readIndex, *IndexToHMSF(readIndex, true, framesPerSecond));
nonBlockingFileReader = new cNonBlockingFileReader;
int Length = 0;
bool Sleep = false;
bool WaitingForData = false;
time_t StuckAtEof = 0;
uint32_t LastStc = 0;
int LastReadIFrame = -1;
int SwitchToPlayFrame = 0;
if (pauseLive)
Goto(0, true);
while (Running()) {
if (WaitingForData)
WaitingForData = !nonBlockingFileReader->WaitForDataMs(3); // this keeps the CPU load low, but reacts immediately on new data
else if (Sleep) {
cPoller Poller;
DevicePoll(Poller, 10);
Sleep = false;
if (playMode == pmStill || playMode == pmPause)
cCondWait::SleepMs(3);
}
{
LOCK_THREAD;
// Read the next frame from the file:
if (playMode != pmStill && playMode != pmPause) {
if (!readFrame && (replayFile || readIndex >= 0)) {
if (!nonBlockingFileReader->Reading()) {
if (!SwitchToPlayFrame && (playMode == pmFast || (playMode == pmSlow && playDir == pdBackward))) {
uint16_t FileNumber;
off_t FileOffset;
bool TimeShiftMode = index->IsStillRecording();
int Index = -1;
readIndependent = false;
if (DeviceHasIBPTrickSpeed() && playDir == pdForward) {
if (index->Get(readIndex + 1, &FileNumber, &FileOffset, &readIndependent, &Length))
Index = readIndex + 1;
}
else {
int d = int(round(0.4 * framesPerSecond));
if (playDir != pdForward)
d = -d;
int NewIndex = readIndex + d;
if (NewIndex <= 0 && readIndex > 0)
NewIndex = 1; // make sure the very first frame is delivered
NewIndex = index->GetNextIFrame(NewIndex, playDir == pdForward, &FileNumber, &FileOffset, &Length);
if (NewIndex < 0 && TimeShiftMode && playDir == pdForward)
SwitchToPlayFrame = readIndex;
Index = NewIndex;
readIndependent = true;
}
if (Index >= 0) {
readIndex = Index;
if (!NextFile(FileNumber, FileOffset))
continue;
}
else if (!(TimeShiftMode && playDir == pdForward))
eof = true;
}
else if (index) {
uint16_t FileNumber;
off_t FileOffset;
if (index->Get(readIndex + 1, &FileNumber, &FileOffset, &readIndependent, &Length) && NextFile(FileNumber, FileOffset))
readIndex++;
else
eof = true;
}
else // allows replay even if the index file is missing
Length = MAXFRAMESIZE;
if (Length == -1)
Length = MAXFRAMESIZE; // this means we read up to EOF (see cIndex)
else if (Length > MAXFRAMESIZE) {
esyslog("ERROR: frame larger than buffer (%d > %d)", Length, MAXFRAMESIZE);
Length = MAXFRAMESIZE;
}
if (!eof)
nonBlockingFileReader->Request(replayFile, Length);
}
if (!eof) {
uchar *b = NULL;
int r = nonBlockingFileReader->Result(&b);
if (r > 0) {
WaitingForData = false;
uint32_t Pts = 0;
if (readIndependent) {
Pts = isPesRecording ? PesGetPts(b) : TsGetPts(b, r);
LastReadIFrame = readIndex;
}
readFrame = new cFrame(b, -r, ftUnknown, readIndex, Pts); // hands over b to the ringBuffer
}
else if (r < 0) {
if (errno == EAGAIN)
WaitingForData = true;
else if (FATALERRNO) {
LOG_ERROR;
break;
}
}
else
eof = true;
}
}
// Store the frame in the buffer:
if (readFrame) {
if (ringBuffer->Put(readFrame))
readFrame = NULL;
else
Sleep = true;
}
}
else
Sleep = true;
if (dropFrame) {
if (!eof || (playDir != pdForward && dropFrame->Index() > 0) || (playDir == pdForward && dropFrame->Index() < readIndex)) {
ringBuffer->Drop(dropFrame); // the very first and last frame are continuously repeated to flush data through the device
dropFrame = NULL;
}
}
// Get the next frame from the buffer:
if (!playFrame) {
playFrame = ringBuffer->Get();
p = NULL;
pc = 0;
}
// Play the frame:
if (playFrame) {
if (!p) {
p = playFrame->Data();
pc = playFrame->Count();
if (p) {
if (playFrame->Index() >= 0 && playFrame->Pts() != 0)
ptsIndex.Put(playFrame->Pts(), playFrame->Index());
if (firstPacket) {
if (isPesRecording) {
PlayPes(NULL, 0);
cRemux::SetBrokenLink(p, pc);
}
else
PlayTs(NULL, 0);
firstPacket = false;
}
}
}
if (p) {
int w;
bool VideoOnly = (dropFrame || playMode != pmPlay && !(playMode == pmSlow && playDir == pdForward)) && DeviceIsPlayingVideo();
if (isPesRecording)
w = PlayPes(p, pc, VideoOnly);
else
w = PlayTs(p, pc, VideoOnly);
if (w > 0) {
p += w;
pc -= w;
}
else if (w < 0 && FATALERRNO)
LOG_ERROR;
else
Sleep = true;
}
if (pc <= 0) {
dropFrame = playFrame;
playFrame = NULL;
p = NULL;
}
}
else
Sleep = true;
// Handle hitting begin/end of recording:
if (eof || SwitchToPlayFrame) {
bool SwitchToPlay = false;
uint32_t Stc = DeviceGetSTC();
if (Stc != LastStc || playMode == pmPause)
StuckAtEof = 0;
else if (!StuckAtEof)
StuckAtEof = time(NULL);
else if (time(NULL) - StuckAtEof > MAXSTUCKATEOF) {
if (playDir == pdForward)
break; // automatically stop at end of recording
SwitchToPlay = true;
}
LastStc = Stc;
int Index = ptsIndex.FindIndex(Stc);
if (playDir == pdForward && !SwitchToPlayFrame) {
if (Index >= LastReadIFrame)
break; // automatically stop at end of recording
}
else if (Index <= 0 || SwitchToPlayFrame && Index >= SwitchToPlayFrame)
SwitchToPlay = true;
if (SwitchToPlay) {
if (!SwitchToPlayFrame)
Empty();
DevicePlay();
playMode = pmPlay;
playDir = pdForward;
SwitchToPlayFrame = 0;
}
}
}
}
cNonBlockingFileReader *nbfr = nonBlockingFileReader;
nonBlockingFileReader = NULL;
delete nbfr;
}
void cDvbPlayer::Pause(void)
{
if (playMode == pmPause || playMode == pmStill)
Play();
else {
LOCK_THREAD;
if (playMode == pmFast || (playMode == pmSlow && playDir == pdBackward)) {
if (!(DeviceHasIBPTrickSpeed() && playDir == pdForward))
Empty();
}
DeviceFreeze();
playMode = pmPause;
}
}
void cDvbPlayer::Play(void)
{
if (playMode != pmPlay) {
LOCK_THREAD;
if (playMode == pmStill || playMode == pmFast || (playMode == pmSlow && playDir == pdBackward)) {
if (!(DeviceHasIBPTrickSpeed() && playDir == pdForward))
Empty();
}
DevicePlay();
playMode = pmPlay;
playDir = pdForward;
if (resyncAfterPause) {
int Current, Total;
if (GetIndex(Current, Total, true))
Goto(Current);
resyncAfterPause = false;
}
}
}
void cDvbPlayer::Forward(void)
{
if (index) {
switch (playMode) {
case pmFast:
if (Setup.MultiSpeedMode) {
TrickSpeed(playDir == pdForward ? 1 : -1);
break;
}
else if (playDir == pdForward) {
Play();
break;
}
// run into pmPlay
case pmPlay: {
LOCK_THREAD;
if (!(DeviceHasIBPTrickSpeed() && playDir == pdForward))
Empty();
if (DeviceIsPlayingVideo())
DeviceMute();
playMode = pmFast;
playDir = pdForward;
trickSpeed = NORMAL_SPEED;
TrickSpeed(Setup.MultiSpeedMode ? 1 : MAX_SPEEDS);
}
break;
case pmSlow:
if (Setup.MultiSpeedMode) {
TrickSpeed(playDir == pdForward ? -1 : 1);
break;
}
else if (playDir == pdForward) {
Pause();
break;
}
Empty();
// run into pmPause
case pmStill:
case pmPause:
DeviceMute();
playMode = pmSlow;
playDir = pdForward;
trickSpeed = NORMAL_SPEED;
TrickSpeed(Setup.MultiSpeedMode ? -1 : -MAX_SPEEDS);
break;
default: esyslog("ERROR: unknown playMode %d (%s)", playMode, __FUNCTION__);
}
}
}
void cDvbPlayer::Backward(void)
{
if (index) {
switch (playMode) {
case pmFast:
if (Setup.MultiSpeedMode) {
TrickSpeed(playDir == pdBackward ? 1 : -1);
break;
}
else if (playDir == pdBackward) {
Play();
break;
}
// run into pmPlay
case pmPlay: {
LOCK_THREAD;
Empty();
if (DeviceIsPlayingVideo())
DeviceMute();
playMode = pmFast;
playDir = pdBackward;
trickSpeed = NORMAL_SPEED;
TrickSpeed(Setup.MultiSpeedMode ? 1 : MAX_SPEEDS);
}
break;
case pmSlow:
if (Setup.MultiSpeedMode) {
TrickSpeed(playDir == pdBackward ? -1 : 1);
break;
}
else if (playDir == pdBackward) {
Pause();
break;
}
Empty();
// run into pmPause
case pmStill:
case pmPause: {
LOCK_THREAD;
Empty();
DeviceMute();
playMode = pmSlow;
playDir = pdBackward;
trickSpeed = NORMAL_SPEED;
TrickSpeed(Setup.MultiSpeedMode ? -1 : -MAX_SPEEDS);
}
break;
default: esyslog("ERROR: unknown playMode %d (%s)", playMode, __FUNCTION__);
}
}
}
int cDvbPlayer::SkipFrames(int Frames)
{
if (index && Frames) {
int Current, Total;
GetIndex(Current, Total, true);
int OldCurrent = Current;
// As GetNextIFrame() increments/decrements at least once, the
// destination frame (= Current + Frames) must be adjusted by
// -1/+1 respectively.
Current = index->GetNextIFrame(Current + Frames + (Frames > 0 ? -1 : 1), Frames > 0);
return Current >= 0 ? Current : OldCurrent;
}
return -1;
}
void cDvbPlayer::SkipSeconds(int Seconds)
{
if (index && Seconds) {
LOCK_THREAD;
int Index = ptsIndex.FindIndex(DeviceGetSTC());
Empty();
if (Index >= 0) {
Index = max(Index + SecondsToFrames(Seconds, framesPerSecond), 0);
if (Index > 0)
Index = index->GetNextIFrame(Index, false, NULL, NULL, NULL);
if (Index >= 0)
readIndex = Index - 1; // Action() will first increment it!
}
Play();
}
}
void cDvbPlayer::Goto(int Index, bool Still)
{
if (index) {
LOCK_THREAD;
Empty();
if (++Index <= 0)
Index = 1; // not '0', to allow GetNextIFrame() below to work!
uint16_t FileNumber;
off_t FileOffset;
int Length;
Index = index->GetNextIFrame(Index, false, &FileNumber, &FileOffset, &Length);
if (Index >= 0 && NextFile(FileNumber, FileOffset) && Still) {
uchar b[MAXFRAMESIZE];
int r = ReadFrame(replayFile, b, Length, sizeof(b));
if (r > 0) {
if (playMode == pmPause)
DevicePlay();
DeviceStillPicture(b, r);
ptsIndex.Put(isPesRecording ? PesGetPts(b) : TsGetPts(b, r), Index);
}
playMode = pmStill;
}
readIndex = Index;
}
}
void cDvbPlayer::SetAudioTrack(eTrackType Type, const tTrackId *TrackId)
{
if (!cThread::IsMainThread())
return; // only do this upon user interaction
if (playMode == pmPlay) {
if (!ptsIndex.IsEmpty()) {
int Current, Total;
if (GetIndex(Current, Total, true))
Goto(Current);
}
}
else if (playMode == pmPause)
resyncAfterPause = true;
}
bool cDvbPlayer::GetIndex(int &Current, int &Total, bool SnapToIFrame)
{
if (index) {
Current = ptsIndex.FindIndex(DeviceGetSTC());
if (SnapToIFrame) {
int i1 = index->GetNextIFrame(Current + 1, false);
int i2 = index->GetNextIFrame(Current, true);
Current = (abs(Current - i1) <= abs(Current - i2)) ? i1 : i2;
}
Total = index->Last();
return true;
}
Current = Total = -1;
return false;
}
bool cDvbPlayer::GetReplayMode(bool &Play, bool &Forward, int &Speed)
{
Play = (playMode == pmPlay || playMode == pmFast);
Forward = (playDir == pdForward);
if (playMode == pmFast || playMode == pmSlow)
Speed = Setup.MultiSpeedMode ? abs(trickSpeed - NORMAL_SPEED) : 0;
else
Speed = -1;
return true;
}
// --- cDvbPlayerControl -----------------------------------------------------
cDvbPlayerControl::cDvbPlayerControl(const char *FileName, bool PauseLive)
:cControl(player = new cDvbPlayer(FileName, PauseLive))
{
}
cDvbPlayerControl::~cDvbPlayerControl()
{
Stop();
}
bool cDvbPlayerControl::Active(void)
{
return player && player->Active();
}
void cDvbPlayerControl::Stop(void)
{
delete player;
player = NULL;
}
void cDvbPlayerControl::Pause(void)
{
if (player)
player->Pause();
}
void cDvbPlayerControl::Play(void)
{
if (player)
player->Play();
}
void cDvbPlayerControl::Forward(void)
{
if (player)
player->Forward();
}
void cDvbPlayerControl::Backward(void)
{
if (player)
player->Backward();
}
void cDvbPlayerControl::SkipSeconds(int Seconds)
{
if (player)
player->SkipSeconds(Seconds);
}
int cDvbPlayerControl::SkipFrames(int Frames)
{
if (player)
return player->SkipFrames(Frames);
return -1;
}
bool cDvbPlayerControl::GetIndex(int &Current, int &Total, bool SnapToIFrame)
{
if (player) {
player->GetIndex(Current, Total, SnapToIFrame);
return true;
}
return false;
}
bool cDvbPlayerControl::GetReplayMode(bool &Play, bool &Forward, int &Speed)
{
return player && player->GetReplayMode(Play, Forward, Speed);
}
void cDvbPlayerControl::Goto(int Position, bool Still)
{
if (player)
player->Goto(Position, Still);
}