diff --unified vdr-2.4.6-original/device.h vdr-2.4.6/device.h --- vdr-2.4.6-original/device.h 2020-06-27 12:24:46.000000000 +0200 +++ vdr-2.4.6/device.h 2021-01-26 19:50:27.512032149 +0100 @@ -85,6 +85,7 @@ class cPlayer; class cReceiver; +class cRecorder; class cLiveSubtitle; class cDeviceHook : public cListObject { @@ -852,6 +853,8 @@ ///< Returns true if we are currently receiving. The parameter has no meaning (for backwards compatibility only). bool AttachReceiver(cReceiver *Receiver); ///< Attaches the given receiver to this device. + cRecorder* GetPreRecording(const cChannel *Channel); + ///< Get precocious recording for the channel if there is one. void Detach(cReceiver *Receiver); ///< Detaches the given receiver from this device. void DetachAll(int Pid); diff --unified vdr-2.4.6-original/device.c vdr-2.4.6/device.c --- vdr-2.4.6-original/device.c 2020-09-16 15:48:33.000000000 +0200 +++ vdr-2.4.6/device.c 2021-01-26 19:50:27.512032149 +0100 @@ -1862,6 +1862,17 @@ Detach(receiver[i]); } +cRecorder* cDevice::GetPreRecording(const cChannel *Channel) +{ + cMutexLock MutexLock(&mutexReceiver); + for (int i = 0; i < MAXRECEIVERS; i++) { + if (receiver[i]) + if (receiver[i]->IsPreRecording(Channel)) + return (cRecorder*)receiver[i]; + } + return NULL; +} + // --- cTSBuffer ------------------------------------------------------------- cTSBuffer::cTSBuffer(int File, int Size, int DeviceNumber) diff --unified vdr-2.4.6-original/dvbplayer.h vdr-2.4.6/dvbplayer.h --- vdr-2.4.6-original/dvbplayer.h 2016-12-22 11:36:50.000000000 +0100 +++ vdr-2.4.6/dvbplayer.h 2021-01-26 19:50:27.512032149 +0100 @@ -16,6 +16,14 @@ class cDvbPlayer; +enum ReplayState +{ + restNormal, + restPauseLive, + restReusePause, + restReuseRewind +}; + class cDvbPlayerControl : public cControl { private: cDvbPlayer *player; @@ -25,6 +33,8 @@ // If PauseLive is true, special care is taken to make sure the index // file of the recording is long enough to allow the player to display // the first frame in still picture mode. + cDvbPlayerControl(const char *FileName, ReplayState replayState); + // Sets up a player for the given file. replayState represents the initial state. virtual ~cDvbPlayerControl(); void SetMarks(const cMarks *Marks); bool Active(void); diff --unified vdr-2.4.6-original/dvbplayer.c vdr-2.4.6/dvbplayer.c --- vdr-2.4.6-original/dvbplayer.c 2019-05-27 15:54:19.000000000 +0200 +++ vdr-2.4.6/dvbplayer.c 2021-01-26 19:50:27.512032149 +0100 @@ -249,13 +249,14 @@ cUnbufferedFile *replayFile; double framesPerSecond; bool isPesRecording; - bool pauseLive; + ReplayState replayState; bool eof; bool firstPacket; ePlayModes playMode; ePlayDirs playDir; int trickSpeed; int readIndex; + int startIndex; bool readIndependent; cFrame *readFrame; cFrame *playFrame; @@ -271,6 +272,8 @@ virtual void Action(void); public: cDvbPlayer(const char *FileName, bool PauseLive); + cDvbPlayer(const char *FileName, ReplayState newReplayState); + void Construct(const char *FileName, ReplayState newReplayState); virtual ~cDvbPlayer(); void SetMarks(const cMarks *Marks); bool Active(void) { return cThread::Running(); } @@ -297,6 +300,17 @@ cDvbPlayer::cDvbPlayer(const char *FileName, bool PauseLive) :cThread("dvbplayer") { + Construct(FileName, PauseLive? restPauseLive : restNormal); +} + +cDvbPlayer::cDvbPlayer(const char *FileName, ReplayState newReplayState) +:cThread("dvbplayer") +{ + Construct(FileName, newReplayState); +} + +void cDvbPlayer::Construct(const char *FileName, ReplayState newReplayState) +{ nonBlockingFileReader = NULL; ringBuffer = NULL; marks = NULL; @@ -304,7 +318,8 @@ cRecording Recording(FileName); framesPerSecond = Recording.FramesPerSecond(); isPesRecording = Recording.IsPesRecording(); - pauseLive = PauseLive; + replayState = newReplayState; + bool reuse = (replayState == restReusePause || replayState == restReuseRewind); eof = false; firstPacket = true; playMode = pmPlay; @@ -323,15 +338,21 @@ return; ringBuffer = new cRingBufferFrame(PLAYERBUFSIZE); // Create the index file: - index = new cIndexFile(FileName, false, isPesRecording, pauseLive); + index = new cIndexFile(FileName, false, isPesRecording, replayState == restPauseLive); if (!index) esyslog("ERROR: can't allocate index"); else if (!index->Ok()) { delete index; index = NULL; } - else if (PauseLive) + else if (reuse) framesPerSecond = cRecording(FileName).FramesPerSecond(); // the fps rate might have changed from the default + startIndex = 0; + if (replayState == restReuseRewind || replayState == restReusePause) { + int Current, Total; + GetIndex(Current, Total, false); + startIndex = max(Total - 1, 0); + } } cDvbPlayer::~cDvbPlayer() @@ -481,8 +502,21 @@ bool CutIn = false; bool AtLastMark = false; - if (pauseLive) - Goto(0, true); + if (replayState == restPauseLive) { + Goto(0, true); + } + else if (replayState == restReuseRewind || replayState == restReusePause) { + readIndex = startIndex; + Goto(readIndex, true); + playMode = pmPlay; + if (replayState == restReuseRewind) { + Backward(); + } + else if (replayState == restReusePause) { + Pause(); + } + } + while (Running()) { if (WaitingForData) WaitingForData = !nonBlockingFileReader->WaitForDataMs(3); // this keeps the CPU load low, but reacts immediately on new data @@ -985,6 +1019,11 @@ { } +cDvbPlayerControl::cDvbPlayerControl(const char *FileName, ReplayState replayState) +:cControl(player = new cDvbPlayer(FileName, replayState)) +{ +} + cDvbPlayerControl::~cDvbPlayerControl() { Stop(); diff --unified vdr-2.4.6-original/menu.h vdr-2.4.6/menu.h --- vdr-2.4.6-original/menu.h 2018-04-14 12:24:41.000000000 +0200 +++ vdr-2.4.6/menu.h 2021-01-26 19:50:27.512032149 +0100 @@ -244,6 +244,8 @@ bool GetEvent(void); public: cRecordControl(cDevice *Device, cTimers *Timers, cTimer *Timer = NULL, bool Pause = false); + cRecordControl(cDevice *Device, cTimers *Timers, cTimer *Timer, bool Pause, bool* reused); + void Construct(cDevice *Device, cTimers *Timers, cTimer *Timer, bool Pause, bool* reused); virtual ~cRecordControl(); bool Process(time_t t); cDevice *Device(void) { return device; } @@ -259,10 +261,12 @@ static int state; public: static bool Start(cTimers *Timers, cTimer *Timer, bool Pause = false); + static bool Start(cTimers *Timers, cTimer *Timer, bool Pause, bool* reused); static bool Start(bool Pause = false); static void Stop(const char *InstantId); static void Stop(cTimer *Timer); static bool PauseLiveVideo(void); + static bool PauseLiveVideo(bool rewind); static const char *GetInstantId(const char *LastInstantId); static cRecordControl *GetRecordControl(const char *FileName); static cRecordControl *GetRecordControl(const cTimer *Timer); @@ -318,6 +322,8 @@ void EditTest(void); public: cReplayControl(bool PauseLive = false); + cReplayControl(ReplayState replayState); + void Construct(ReplayState replayState); virtual ~cReplayControl(); void Stop(void); virtual cOsdObject *GetInfo(void); diff --unified vdr-2.4.6-original/menu.c vdr-2.4.6/menu.c --- vdr-2.4.6-original/menu.c 2020-12-12 23:01:01.000000000 +0100 +++ vdr-2.4.6/menu.c 2021-01-26 19:50:27.512032149 +0100 @@ -5226,6 +5226,16 @@ cRecordControl::cRecordControl(cDevice *Device, cTimers *Timers, cTimer *Timer, bool Pause) { + Construct(Device, Timers, Timer, Pause, NULL); +} + +cRecordControl::cRecordControl(cDevice *Device, cTimers *Timers, cTimer *Timer, bool Pause, bool* reused) +{ + Construct(Device, Timers, Timer, Pause, reused); +} + +void cRecordControl::Construct(cDevice *Device, cTimers *Timers, cTimer *Timer, bool Pause, bool* reused) +{ const char *LastReplayed = cReplayControl::LastReplayed(); // must do this before locking schedules! // Whatever happens here, the timers will be modified in some way... Timers->SetModified(); @@ -5254,6 +5264,7 @@ timer->SetPending(true); timer->SetRecording(true); event = timer->Event(); + if (reused != NULL) *reused = false; if (event || GetEvent()) dsyslog("Title: '%s' Subtitle: '%s'", event->Title(), event->ShortText()); @@ -5283,8 +5294,21 @@ if (MakeDirs(fileName, true)) { Recording.WriteInfo(); // we write this *before* attaching the recorder to the device, to make sure the info file is present when the recorder needs to update the fps value! const cChannel *ch = timer->Channel(); - recorder = new cRecorder(fileName, ch, timer->Priority()); - if (device->AttachReceiver(recorder)) { + + if (!Timer) { + recorder = device->GetPreRecording(ch); + if (recorder != NULL) { + recorder->ActivatePreRecording(fileName, timer->Priority()); + if (reused != NULL) *reused = true; + } + } + + if (recorder == NULL) { + recorder = new cRecorder(fileName, ch, timer->Priority()); + if (!device->AttachReceiver(recorder)) DELETENULL(recorder); + } + + if (recorder != NULL) { cStatus::MsgRecording(device, Recording.Name(), Recording.FileName(), true); if (!Timer && !LastReplayed) // an instant recording, maybe from cRecordControls::PauseLiveVideo() cReplayControl::SetRecording(fileName); @@ -5294,8 +5318,6 @@ Recordings->AddByName(fileName); return; } - else - DELETENULL(recorder); } else timer->SetDeferred(DEFERTIMER); @@ -5366,7 +5388,7 @@ cRecordControl *cRecordControls::RecordControls[MAXRECORDCONTROLS] = { NULL }; int cRecordControls::state = 0; -bool cRecordControls::Start(cTimers *Timers, cTimer *Timer, bool Pause) +bool cRecordControls::Start(cTimers *Timers, cTimer *Timer, bool Pause, bool* reused) { static time_t LastNoDiskSpaceMessage = 0; int FreeMB = 0; @@ -5404,7 +5426,7 @@ if (!Timer || Timer->Matches()) { for (int i = 0; i < MAXRECORDCONTROLS; i++) { if (!RecordControls[i]) { - RecordControls[i] = new cRecordControl(device, Timers, Timer, Pause); + RecordControls[i] = new cRecordControl(device, Timers, Timer, Pause, reused); return RecordControls[i]->Process(time(NULL)); } } @@ -5428,6 +5450,11 @@ return Start(Timers, NULL, Pause); } +bool cRecordControls::Start(cTimers *Timers, cTimer *Timer, bool Pause) +{ + return Start(Timers, Timer, Pause, NULL); +} + void cRecordControls::Stop(const char *InstantId) { LOCK_TIMERS_WRITE; @@ -5463,10 +5490,17 @@ bool cRecordControls::PauseLiveVideo(void) { + return PauseLiveVideo(false); +} + +bool cRecordControls::PauseLiveVideo(bool rewind) +{ Skins.Message(mtStatus, tr("Pausing live video...")); + bool reused = false; cReplayControl::SetRecording(NULL); // make sure the new cRecordControl will set cReplayControl::LastReplayed() - if (Start(true)) { - cReplayControl *rc = new cReplayControl(true); + LOCK_TIMERS_WRITE; + if (Start(Timers, NULL, true, &reused)) { + cReplayControl *rc = new cReplayControl(rewind? restReuseRewind : reused? restReusePause : restPauseLive); cControl::Launch(rc); cControl::Attach(); Skins.Message(mtStatus, NULL); @@ -5609,7 +5643,18 @@ cReplayControl::cReplayControl(bool PauseLive) :cDvbPlayerControl(fileName, PauseLive) { - cDevice::PrimaryDevice()->SetKeepTracks(PauseLive); + Construct(PauseLive? restPauseLive : restNormal); +} + +cReplayControl::cReplayControl(ReplayState replayState) +:cDvbPlayerControl(fileName, replayState) +{ + Construct(replayState); +} + +void cReplayControl::Construct(ReplayState replayState) +{ + cDevice::PrimaryDevice()->SetKeepTracks(replayState == restPauseLive); currentReplayControl = this; displayReplay = NULL; marksModified = false; diff --unified vdr-2.4.6-original/receiver.h vdr-2.4.6/receiver.h --- vdr-2.4.6-original/receiver.h 2017-05-01 10:48:34.000000000 +0200 +++ vdr-2.4.6/receiver.h 2021-01-26 19:50:27.512032149 +0100 @@ -85,6 +85,10 @@ ///< case the device is needed otherwise, so code that uses a cReceiver ///< should repeatedly check whether it is still attached, and if ///< it isn't, delete it (or take any other appropriate measures). + virtual bool IsPreRecording(const cChannel *Channel) { return false; } + ///< prerecords given channel; may be turned into a disc recording. + virtual bool ActivatePreRecording(const char* fileName, int Priority) { return false; } + ///< turn prerecording into a disc recording }; #endif //__RECEIVER_H diff --unified vdr-2.4.6-original/recorder.h vdr-2.4.6/recorder.h --- vdr-2.4.6-original/recorder.h 2015-09-05 13:46:23.000000000 +0200 +++ vdr-2.4.6/recorder.h 2021-01-26 19:50:27.512032149 +0100 @@ -16,8 +16,8 @@ #include "ringbuffer.h" #include "thread.h" -class cRecorder : public cReceiver, cThread { -private: +class cRecorder : public cReceiver, protected cThread { +protected: cRingBufferLinear *ringBuffer; cFrameDetector *frameDetector; cPatPmtGenerator patPmtGenerator; @@ -37,6 +37,9 @@ ///< destroyed. virtual void Receive(const uchar *Data, int Length); virtual void Action(void); + void InitializeFile(const char *FileName, const cChannel *Channel); + ///< Starts recording to file. + ///< Called in constructor if file name has been given. public: cRecorder(const char *FileName, const cChannel *Channel, int Priority); ///< Creates a new recorder for the given Channel and diff --unified vdr-2.4.6-original/recorder.c vdr-2.4.6/recorder.c --- vdr-2.4.6-original/recorder.c 2015-09-12 16:56:15.000000000 +0200 +++ vdr-2.4.6/recorder.c 2021-01-26 19:50:27.512032149 +0100 @@ -24,44 +24,55 @@ cRecorder::cRecorder(const char *FileName, const cChannel *Channel, int Priority) :cReceiver(Channel, Priority) ,cThread("recording") +,ringBuffer(NULL), frameDetector(NULL), fileName(NULL), index(NULL), recordFile(NULL), recordingName(NULL) { - recordingName = strdup(FileName); + if (FileName != NULL) + { + InitializeFile(FileName, Channel); + } +} + +void cRecorder::InitializeFile(const char *FileName, const cChannel *Channel) +{ + recordingName = strdup(FileName); + + // Make sure the disk is up and running: - // Make sure the disk is up and running: + SpinUpDisk(FileName); - SpinUpDisk(FileName); + ringBuffer = new cRingBufferLinear(RECORDERBUFSIZE, MIN_TS_PACKETS_FOR_FRAME_DETECTOR * TS_SIZE, true, "Recorder"); + ringBuffer->SetTimeouts(0, 100); + ringBuffer->SetIoThrottle(); - ringBuffer = new cRingBufferLinear(RECORDERBUFSIZE, MIN_TS_PACKETS_FOR_FRAME_DETECTOR * TS_SIZE, true, "Recorder"); - ringBuffer->SetTimeouts(0, 100); - ringBuffer->SetIoThrottle(); - - int Pid = Channel->Vpid(); - int Type = Channel->Vtype(); - if (!Pid && Channel->Apid(0)) { - Pid = Channel->Apid(0); - Type = 0x04; - } - if (!Pid && Channel->Dpid(0)) { - Pid = Channel->Dpid(0); - Type = 0x06; - } - frameDetector = new cFrameDetector(Pid, Type); - index = NULL; - fileSize = 0; - lastDiskSpaceCheck = time(NULL); - fileName = new cFileName(FileName, true); - int PatVersion, PmtVersion; - if (fileName->GetLastPatPmtVersions(PatVersion, PmtVersion)) - patPmtGenerator.SetVersions(PatVersion + 1, PmtVersion + 1); - patPmtGenerator.SetChannel(Channel); - recordFile = fileName->Open(); - if (!recordFile) - return; - // Create the index file: - index = new cIndexFile(FileName, true); - if (!index) - esyslog("ERROR: can't allocate index"); - // let's continue without index, so we'll at least have the recording + int Pid = Channel->Vpid(); + int Type = Channel->Vtype(); + if (!Pid && Channel->Apid(0)) { + Pid = Channel->Apid(0); + Type = 0x04; + } + if (!Pid && Channel->Dpid(0)) { + Pid = Channel->Dpid(0); + Type = 0x06; + } + if (frameDetector == NULL) { + frameDetector = new cFrameDetector(Pid, Type); + } + index = NULL; + fileSize = 0; + lastDiskSpaceCheck = time(NULL); + fileName = new cFileName(FileName, true); + int PatVersion, PmtVersion; + if (fileName->GetLastPatPmtVersions(PatVersion, PmtVersion)) + patPmtGenerator.SetVersions(PatVersion + 1, PmtVersion + 1); + patPmtGenerator.SetChannel(Channel); + recordFile = fileName->Open(); + if (!recordFile) + return; + // Create the index file: + index = new cIndexFile(FileName, true); + if (!index) + esyslog("ERROR: can't allocate index"); + // let's continue without index, so we'll at least have the recording } cRecorder::~cRecorder() diff --unified vdr-2.4.6-original/ringbuffer.h vdr-2.4.6/ringbuffer.h --- vdr-2.4.6-original/ringbuffer.h 2017-03-19 14:11:39.000000000 +0100 +++ vdr-2.4.6/ringbuffer.h 2021-01-26 19:50:27.512032149 +0100 @@ -98,6 +98,12 @@ ///< The data will remain in the buffer until a call to Del() deletes it. ///< Returns a pointer to the data, and stores the number of bytes ///< actually available in Count. If the returned pointer is NULL, Count has no meaning. + uchar *GetRest(int &Count); + ///< Gets data from the ring buffer disregarding the margin. + ///< Might have to be called several times to get all data. + ///< The data will remain in the buffer until a call to Del() deletes it. + ///< Returns a pointer to the data, and stores the number of bytes + ///< actually available in Count. If the returned pointer is NULL, Count has no meaning. void Del(int Count); ///< Deletes at most Count bytes from the ring buffer. ///< Count must be less or equal to the number that was returned by a previous diff --unified vdr-2.4.6-original/ringbuffer.c vdr-2.4.6/ringbuffer.c --- vdr-2.4.6-original/ringbuffer.c 2017-03-19 13:43:36.000000000 +0100 +++ vdr-2.4.6/ringbuffer.c 2021-01-26 19:50:27.512032149 +0100 @@ -368,6 +368,25 @@ return NULL; } +uchar *cRingBufferLinear::GetRest(int &Count) +{ + int Head = head; + if (getThreadTid <= 0) + getThreadTid = cThread::ThreadId(); + int rest = Size() - tail; + int diff = Head - tail; + int cont = (diff >= 0) ? diff : Size() + diff - margin; + if (cont > rest) + cont = rest; + uchar *p = buffer + tail; + if (cont > 0) { + Count = gotten = cont; + return p; + } + WaitForGet(); + return NULL; +} + void cRingBufferLinear::Del(int Count) { if (Count > gotten) { diff --unified vdr-2.4.6-original/vdr.c vdr-2.4.6/vdr.c --- vdr-2.4.6-original/vdr.c 2020-11-20 14:49:58.000000000 +0100 +++ vdr-2.4.6/vdr.c 2021-01-26 20:18:51.112869755 +0100 @@ -1353,13 +1353,21 @@ key = kNone; break; // Pausing live video: + case kFastRew: { + // test if there's a live buffer to rewind into... + LOCK_CHANNELS_READ; + if (cDevice::ActualDevice()->GetPreRecording(Channels->GetByNumber(cDevice::CurrentChannel())) == NULL) { + break; + } + } + // fall through to pause case kPlayPause: case kPause: if (!Control) { DELETE_MENU; if (Setup.PauseKeyHandling) { if (Setup.PauseKeyHandling > 1 || Interface->Confirm(tr("Pause live video?"))) { - if (!cRecordControls::PauseLiveVideo()) + if (!cRecordControls::PauseLiveVideo(int(key) == kFastRew)) Skins.QueueMessage(mtError, tr("No free DVB device to record!")); } }