diff -a -U 2 -r a/device.c b/device.c --- a/device.c 2025-07-24 13:44:14.000000000 +0200 +++ b/device.c 2025-08-03 22:16:48.112461446 +0200 @@ -1937,4 +1937,15 @@ } +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 ------------------------------------------------------------- diff -a -U 2 -r a/device.h b/device.h --- a/device.h 2025-07-24 13:44:14.000000000 +0200 +++ b/device.h 2025-08-03 22:16:48.112461446 +0200 @@ -86,4 +86,5 @@ class cPlayer; class cReceiver; +class cRecorder; class cLiveSubtitle; @@ -870,4 +871,6 @@ 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, bool ReleaseCam = true); ///< Detaches the given receiver from this device. diff -a -U 2 -r a/dvbplayer.c b/dvbplayer.c --- a/dvbplayer.c 2025-07-24 13:44:14.000000000 +0200 +++ b/dvbplayer.c 2025-08-03 22:37:18.837632671 +0200 @@ -254,5 +254,5 @@ double framesPerSecond; bool isPesRecording; - bool pauseLive; + ReplayState replayState; bool eof; bool firstPacket; @@ -261,4 +261,5 @@ int trickSpeed; int readIndex; + int startIndex; bool readIndependent; cFrame *readFrame; @@ -276,4 +277,6 @@ public: cDvbPlayer(const char *FileName, bool PauseLive); + cDvbPlayer(const char *FileName, ReplayState newReplayState); + void Construct(const char *FileName, ReplayState newReplayState); virtual ~cDvbPlayer() override; void SetMarks(const cMarks *Marks); @@ -303,4 +306,15 @@ :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; @@ -310,5 +324,6 @@ framesPerSecond = Recording.FramesPerSecond(); isPesRecording = Recording.IsPesRecording(); - pauseLive = PauseLive; + replayState = newReplayState; + bool reuse = (replayState == restReusePause || replayState == restReuseRewind); eof = false; firstPacket = true; @@ -329,5 +344,5 @@ 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"); @@ -336,6 +351,12 @@ 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); + } } @@ -487,6 +508,19 @@ 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) @@ -1003,4 +1037,9 @@ } +cDvbPlayerControl::cDvbPlayerControl(const char *FileName, ReplayState replayState) +:cControl(player = new cDvbPlayer(FileName, replayState)) +{ +} + cDvbPlayerControl::~cDvbPlayerControl() { diff -a -U 2 -r a/dvbplayer.h b/dvbplayer.h --- a/dvbplayer.h 2025-07-24 13:44:14.000000000 +0200 +++ b/dvbplayer.h 2025-08-03 22:35:30.189884625 +0200 @@ -17,4 +17,12 @@ class cDvbPlayer; +enum ReplayState +{ + restNormal, + restPauseLive, + restReusePause, + restReuseRewind +}; + class cDvbPlayerControl : public cControl { private: @@ -26,4 +34,6 @@ // 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() override; void SetMarks(const cMarks *Marks); diff -a -U 2 -r a/menu.c b/menu.c --- a/menu.c 2025-07-24 13:44:14.000000000 +0200 +++ b/menu.c 2025-08-03 22:16:48.116461437 +0200 @@ -5340,4 +5340,14 @@ 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... @@ -5368,4 +5378,5 @@ timer->SetRecording(true); event = timer->Event(); + if (reused != NULL) *reused = false; if (event || GetEvent()) @@ -5397,6 +5408,19 @@ 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() @@ -5408,6 +5432,4 @@ return; } - else - DELETENULL(recorder); } else @@ -5489,5 +5511,5 @@ 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; @@ -5527,5 +5549,5 @@ 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)); } @@ -5551,4 +5573,9 @@ } +bool cRecordControls::Start(cTimers *Timers, cTimer *Timer, bool Pause) +{ + return Start(Timers, Timer, Pause, NULL); +} + void cRecordControls::Stop(const char *InstantId) { @@ -5586,8 +5613,15 @@ 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(); @@ -5733,5 +5767,16 @@ :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; diff -a -U 2 -r a/menu.h b/menu.h --- a/menu.h 2025-07-24 13:44:14.000000000 +0200 +++ b/menu.h 2025-08-03 22:27:49.198950714 +0200 @@ -246,4 +246,6 @@ 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); @@ -261,8 +263,10 @@ 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); @@ -323,4 +327,6 @@ public: cReplayControl(bool PauseLive = false); + cReplayControl(ReplayState replayState); + void Construct(ReplayState replayState); virtual ~cReplayControl() override; static void DelTimeshiftTimer(void); diff -a -U 2 -r a/receiver.h b/receiver.h --- a/receiver.h 2025-07-24 13:44:14.000000000 +0200 +++ b/receiver.h 2025-08-03 22:16:48.120461429 +0200 @@ -86,4 +86,8 @@ ///< 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 }; diff -a -U 2 -r a/recorder.c b/recorder.c --- a/recorder.c 2025-07-24 13:44:14.000000000 +0200 +++ b/recorder.c 2025-08-03 22:16:48.120461429 +0200 @@ -25,7 +25,17 @@ :cReceiver(Channel, Priority) ,cThread("recording") +,ringBuffer(NULL), frameDetector(NULL), fileName(NULL), recordingInfo(NULL), index(NULL), recordFile(NULL), recordingName(NULL) +{ + if (FileName != NULL) { + InitializeFile(FileName, Channel); + } +} + +void cRecorder::InitializeFile(const char *FileName, const cChannel *Channel) { recordingName = strdup(FileName); - recordingInfo = new cRecordingInfo(recordingName); + if (recordingInfo == NULL) { + recordingInfo = new cRecordingInfo(recordingName); + } recordingInfo->Read(); oldErrors = max(0, recordingInfo->Errors()); // in case this is a re-started recording @@ -52,5 +62,7 @@ Type = 0x06; } - frameDetector = new cFrameDetector(Pid, Type); + if (frameDetector == NULL) { + frameDetector = new cFrameDetector(Pid, Type); + } index = NULL; fileSize = 0; diff -a -U 2 -r a/recorder.h b/recorder.h --- a/recorder.h 2025-07-24 13:44:14.000000000 +0200 +++ b/recorder.h 2025-08-03 22:31:27.174447284 +0200 @@ -17,6 +17,6 @@ #include "thread.h" -class cRecorder : public cReceiver, cThread { -private: +class cRecorder : public cReceiver, protected cThread { +protected: cRingBufferLinear *ringBuffer; cFrameDetector *frameDetector; @@ -37,5 +37,4 @@ bool NextFile(void); void HandleErrors(bool Force = false); -protected: virtual void Activate(bool On) override; ///< If you override Activate() you need to call Detach() (which is a @@ -45,4 +44,7 @@ virtual void Receive(const uchar *Data, int Length) override; virtual void Action(void) override; + 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); diff -a -U 2 -r a/ringbuffer.c b/ringbuffer.c --- a/ringbuffer.c 2025-07-24 13:44:14.000000000 +0200 +++ b/ringbuffer.c 2025-08-03 22:16:48.120461429 +0200 @@ -369,4 +369,23 @@ } +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) { diff -a -U 2 -r a/ringbuffer.h b/ringbuffer.h --- a/ringbuffer.h 2025-07-24 13:44:14.000000000 +0200 +++ b/ringbuffer.h 2025-08-03 22:16:48.120461429 +0200 @@ -99,4 +99,10 @@ ///< 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. diff -a -U 2 -r a/vdr.c b/vdr.c --- a/vdr.c 2025-07-24 13:44:14.000000000 +0200 +++ b/vdr.c 2025-08-03 22:16:48.120461429 +0200 @@ -1361,4 +1361,15 @@ break; // Pausing live video: + case kFastRew: + if (cOsd::IsOpen()) + break; + { + // 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: @@ -1367,5 +1378,5 @@ 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!")); }