From c92de13d0666bcad050989c0d81d67878876ac52 Mon Sep 17 00:00:00 2001 From: Frank Schmirler Date: Thu, 26 Sep 2013 09:31:35 +0200 Subject: [PATCH] Select start position for replaying a recording by parameter pos= Based on offset_5.diff from hivdr@vdrportal with the following modifications: - indenting - replaced isyslog with Dprintf - left out HTTP header "Server:" for the moment --- CONTRIBUTORS | 3 ++ HISTORY | 3 ++ server/connectionHTTP.c | 47 +++++++++++++++++++++++++++-- server/connectionHTTP.h | 2 ++ server/recplayer.c | 45 ++++++++++++++++++++++++++++ server/recplayer.h | 3 ++ server/recstreamer.c | 66 ++++++++++++++++++++++++++++++++++++++++- server/recstreamer.h | 5 +++- 8 files changed, 169 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index ed94b7c..0451378 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -217,3 +217,6 @@ macmenot thomasjfox for fixing cSuspendCtl preventing idle shutdown + +hivdr + for adding the pos= parameter for replaying recordings from a certain position diff --git a/HISTORY b/HISTORY index 3fcd666..d6030c8 100644 --- a/HISTORY +++ b/HISTORY @@ -1,6 +1,9 @@ VDR Plugin 'streamdev' Revision History --------------------------------------- +- Select start position for replaying a recording by parameter pos=. Supported + values are resume, mark.#, time.#, frame.# or a plain # representing a + percentage if < 100 or a byte position otherwise (thanks to hivdr) - Start cSuspendCtl hidden or it will prevent idle shutdown (thanks to thomasjfox) - Fixed recordings menu inode numbers: ino_t is a long long on some systems diff --git a/server/connectionHTTP.c b/server/connectionHTTP.c index c3b3f90..73029d7 100644 --- a/server/connectionHTTP.c +++ b/server/connectionHTTP.c @@ -199,19 +199,51 @@ bool cConnectionHTTP::ProcessRequest(void) } else if (m_Recording != NULL) { Dprintf("GET recording\n"); - cStreamdevRecStreamer* recStreamer = new cStreamdevRecStreamer(m_Recording, this); + cStreamdevRecStreamer* recStreamer = new cStreamdevRecStreamer(m_Recording, this, m_ReplayPos); m_Streamer = recStreamer; int64_t from, to; uint64_t total = recStreamer->GetLength(); if (ParseRange(from, to)) { + Dprintf("parsed from-to: %lld - %lld\n", (long long)from, (long long)to); int64_t length = recStreamer->SetRange(from, to); + int64_t fromByPos = recStreamer->GetFromByPos(); + if (fromByPos > 0) { + if (from == 0) { + from = fromByPos; + to = total - 1; + Dprintf("from byte: %lld\n", (long long)from); + } + else if (m_ReplayPos.find("full_") != 0) { + from += fromByPos; + to += fromByPos; + } + Dprintf("part of recording: %lld-%lld/%lld\n", (long long)from, (long long)to, (long long)total); + length = recStreamer->SetRange(from, to); + Dprintf("part of recording: %lld-%lld/%lld, len %lld\n", (long long)from, (long long)to, (long long)total, (long long)length); + } + if (m_ReplayPos.find("full_") != 0 && fromByPos > 0) { + from -= fromByPos; + to -= fromByPos; + total -= fromByPos; + } + Dprintf("range response: %lld-%lld/%lld, len %lld\n", (long long)from, (long long)to, (long long)total, (long long)length); if (length < 0L) return HttpResponse(416, true, "video/mpeg", "Accept-Ranges: bytes\r\nContent-Range: bytes */%llu", (unsigned long long) total); else return HttpResponse(206, false, "video/mpeg", "Accept-Ranges: bytes\r\nContent-Range: bytes %lld-%lld/%llu\r\nContent-Length: %lld", (long long) from, (long long) to, (unsigned long long) total, (long long) length); } - else + else { + int64_t fromByPos = recStreamer->GetFromByPos(); + if (fromByPos > 0) { + from = fromByPos; + to = total - 1; + Dprintf("from byte: %lld\n", (long long)from); + int64_t length = recStreamer->SetRange(from, to); + Dprintf("part of recording: %lld-%lld/%lld, len %lld\n", (long long)from, (long long)to, (long long)total, (long long)length); + } + return HttpResponse(200, false, "video/mpeg", "Accept-Ranges: bytes"); + } } else { return HttpResponse(404, true); @@ -240,7 +272,7 @@ bool cConnectionHTTP::ProcessRequest(void) } else if (m_Recording != NULL) { Dprintf("HEAD recording\n"); - cStreamdevRecStreamer *recStreamer = new cStreamdevRecStreamer(m_Recording, this); + cStreamdevRecStreamer *recStreamer = new cStreamdevRecStreamer(m_Recording, this, m_ReplayPos); m_Streamer = recStreamer; int64_t from, to; uint64_t total = recStreamer->GetLength(); @@ -330,6 +362,7 @@ bool cConnectionHTTP::ParseRange(int64_t &From, int64_t &To) const From = To = 0L; tStrStrMap::const_iterator it = Headers().find(RANGE); if (it != Headers().end()) { + Dprintf("%s: %s\n", it->first.c_str(), it->second.c_str()); size_t b = it->second.find("bytes="); if (b != std::string::npos) { char* e = NULL; @@ -521,6 +554,14 @@ bool cConnectionHTTP::ProcessURI(const std::string& PathInfo) return true; } else if ((m_Recording = RecordingFromString(filespec.c_str(), fileext.c_str())) != NULL) { Dprintf("Recording %s found\n", m_Recording->Name()); + tStrStrMap::iterator it = m_Params.begin(); + while (it != m_Params.end()) { + if (it->first == "pos") { + m_ReplayPos = it->second; + Dprintf("pos: %s\n", m_ReplayPos.c_str()); + break; + } + } return true; } else if ((m_Channel = ChannelFromString(filespec.c_str(), &m_Apid[0], &m_Dpid[0])) != NULL) { Dprintf("Channel found. Apid/Dpid is %d/%d\n", m_Apid[0], m_Dpid[0]); diff --git a/server/connectionHTTP.h b/server/connectionHTTP.h index e946242..3161cfc 100644 --- a/server/connectionHTTP.h +++ b/server/connectionHTTP.h @@ -35,6 +35,7 @@ private: int m_Dpid[2]; // job: replay cRecording *m_Recording; + std::string m_ReplayPos; // job: listing cMenuList *m_MenuList; @@ -69,6 +70,7 @@ public: virtual bool Abort(void) const; virtual void Flushed(void); + inline std::string GetReplayPos() { return m_ReplayPos; } }; inline bool cConnectionHTTP::Abort(void) const diff --git a/server/recplayer.c b/server/recplayer.c index f4fe1cd..f85ed45 100644 --- a/server/recplayer.c +++ b/server/recplayer.c @@ -19,6 +19,10 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#include +#include +#include + #include "recplayer.h" // for TSPLAY patch detection @@ -210,6 +214,47 @@ cRecording* RecPlayer::getCurrentRecording() return recording; } +int RecPlayer::frameFromResume() +{ + int frame = 0; + char fileName[2048]; + snprintf(fileName, 2047, "%s/resume", recording->FileName()); + std::ifstream ifs; + ifs.open(fileName); + if (!ifs.is_open()) return 0; + std::string sFrame; + getline(ifs, sFrame); + ifs.close(); + sFrame=sFrame.substr(2); + frame=atoi(sFrame.c_str()); + return frame; +} + +int RecPlayer::frameFromMark(int index) +{ + char fileName[2048]; + snprintf(fileName, 2047, "%s/marks", recording->FileName()); + std::ifstream ifs; + ifs.open(fileName); + if (!ifs.is_open()) return 0; + std::string sTime; + for (int i=0; i<=index; i++) { + getline(ifs, sTime); + if (ifs.eof()) break; + } + ifs.close(); + int seconds = 0, minutes = 0, hours = 0; + hours = atoi(sTime.substr(0, sTime.find(":")).c_str()); + minutes = atoi(sTime.substr(sTime.find(":") + 1, 2).c_str()); + seconds = atoi(sTime.substr(sTime.rfind(":") + 1, 2).c_str()); + return frameFromSeconds(seconds + minutes * 60 + hours * 3600); +} + +int RecPlayer::frameFromSeconds(int seconds) +{ + return 25 * seconds; // 25fps +} + uint64_t RecPlayer::positionFromFrameNumber(uint32_t frameNumber) { if (!indexFile) return 0; diff --git a/server/recplayer.h b/server/recplayer.h index 3da6c89..d0a3ff9 100644 --- a/server/recplayer.h +++ b/server/recplayer.h @@ -46,6 +46,9 @@ class RecPlayer cRecording* getCurrentRecording(); void scan(); uint64_t positionFromFrameNumber(uint32_t frameNumber); + int frameFromResume(); + int frameFromMark(int index); + int frameFromSeconds(int seconds); uint32_t frameNumberFromPosition(uint64_t position); bool getNextIFrame(uint32_t frameNumber, uint32_t direction, uint64_t* rfilePosition, uint32_t* rframeNumber, uint32_t* rframeLength); diff --git a/server/recstreamer.c b/server/recstreamer.c index 73af53b..8a5a7ad 100644 --- a/server/recstreamer.c +++ b/server/recstreamer.c @@ -12,13 +12,14 @@ using namespace Streamdev; // --- cStreamdevRecStreamer ------------------------------------------------- -cStreamdevRecStreamer::cStreamdevRecStreamer(cRecording *Rec, const cServerConnection *Connection): +cStreamdevRecStreamer::cStreamdevRecStreamer(cRecording *Rec, const cServerConnection *Connection, std::string pos): cStreamdevStreamer("streamdev-recstreaming", Connection), m_RecPlayer(Rec), m_From(0L) { Dprintf("New rec streamer\n"); m_To = (int64_t) m_RecPlayer.getLengthBytes() - 1; + m_Pos = pos; } cStreamdevRecStreamer::~cStreamdevRecStreamer() @@ -52,6 +53,69 @@ int64_t cStreamdevRecStreamer::SetRange(int64_t &From, int64_t &To) return m_To - m_From + 1; } +int32_t cStreamdevRecStreamer::getIFrameBeforeFrame(int32_t frame) +{ + uint32_t iframe, len; + uint64_t pos; + m_RecPlayer.getNextIFrame(frame + 1, 0, &pos, &iframe, &len); + Dprintf("pos: frame %i -> start at iFrame %i\n", frame, iframe); + return iframe; +} + +int64_t cStreamdevRecStreamer::GetFromByPos() +{ + if (m_Pos.empty()) return 0; + + std::string pos = m_Pos; + + // cut prefix (if any) + if (pos.find('_') != std::string::npos) { + pos = pos.substr(pos.find('_') + 1); + } + + // resume file + if (pos == "resume") { + int frame = getIFrameBeforeFrame(m_RecPlayer.frameFromResume()); + Dprintf("pos: frame from resume: %i\n", frame); + return m_RecPlayer.positionFromFrameNumber(frame); + } + + // mark + if (pos.find("mark.") == 0) { + int index = atoi(pos.substr(5).c_str()); + int frame = getIFrameBeforeFrame(m_RecPlayer.frameFromMark(index)); + Dprintf("pos: mark %i - frame %i\n", index, frame); + return m_RecPlayer.positionFromFrameNumber(frame); + } + + // time + if (pos.find("time.") == 0) { + int seconds = atoi(pos.substr(5).c_str()); + int frame = getIFrameBeforeFrame(m_RecPlayer.frameFromSeconds(seconds)); + Dprintf("pos: %i seconds - frame %i\n", seconds, frame); + return m_RecPlayer.positionFromFrameNumber(frame); + } + + // frame number + if (pos.find("frame.") == 0) { + int frame = getIFrameBeforeFrame(atoi(pos.substr(6).c_str())); + Dprintf("pos: frame %i\n", frame); + return m_RecPlayer.positionFromFrameNumber(frame); + } + + // default: byte index or percent + // as "%" is the url escape character, interpret <100 as percent + // if (pos.find("%") != std::string::npos) { + // int percent = atoi(pos.substr(0, pos.find("%")).c_str()); + int64_t number = atol(pos.c_str()); + if (number < 100) { + Dprintf("pos: %lld percent\n", (long long)number); + int64_t offset = m_RecPlayer.getLengthBytes() * number / 100; + return offset; + } + return number; +} + uchar* cStreamdevRecStreamer::GetFromReceiver(int &Count) { if (m_From <= m_To) { diff --git a/server/recstreamer.h b/server/recstreamer.h index 83df8c1..6c53a61 100644 --- a/server/recstreamer.h +++ b/server/recstreamer.h @@ -14,6 +14,7 @@ private: RecPlayer m_RecPlayer; int64_t m_From; int64_t m_To; + std::string m_Pos; uchar m_Buffer[RECBUFSIZE]; protected: @@ -25,7 +26,9 @@ public: inline uint64_t GetLength() { return m_RecPlayer.getLengthBytes(); } int64_t SetRange(int64_t &From, int64_t &To); virtual cString ToText() const; - cStreamdevRecStreamer(cRecording *Recording, const cServerConnection *Connection); + int64_t GetFromByPos(); + int32_t getIFrameBeforeFrame(int32_t frame); + cStreamdevRecStreamer(cRecording *Recording, const cServerConnection *Connection, std::string pos); virtual ~cStreamdevRecStreamer(); };