mirror of
https://projects.vdr-developer.org/git/vdr-plugin-streamdev.git
synced 2023-10-10 19:16:51 +02:00
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
This commit is contained in:
parent
d7652d89ca
commit
c92de13d06
@ -217,3 +217,6 @@ macmenot
|
|||||||
|
|
||||||
thomasjfox
|
thomasjfox
|
||||||
for fixing cSuspendCtl preventing idle shutdown
|
for fixing cSuspendCtl preventing idle shutdown
|
||||||
|
|
||||||
|
hivdr
|
||||||
|
for adding the pos= parameter for replaying recordings from a certain position
|
||||||
|
3
HISTORY
3
HISTORY
@ -1,6 +1,9 @@
|
|||||||
VDR Plugin 'streamdev' Revision History
|
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
|
- Start cSuspendCtl hidden or it will prevent idle shutdown (thanks to
|
||||||
thomasjfox)
|
thomasjfox)
|
||||||
- Fixed recordings menu inode numbers: ino_t is a long long on some systems
|
- Fixed recordings menu inode numbers: ino_t is a long long on some systems
|
||||||
|
@ -199,20 +199,52 @@ bool cConnectionHTTP::ProcessRequest(void)
|
|||||||
}
|
}
|
||||||
else if (m_Recording != NULL) {
|
else if (m_Recording != NULL) {
|
||||||
Dprintf("GET recording\n");
|
Dprintf("GET recording\n");
|
||||||
cStreamdevRecStreamer* recStreamer = new cStreamdevRecStreamer(m_Recording, this);
|
cStreamdevRecStreamer* recStreamer = new cStreamdevRecStreamer(m_Recording, this, m_ReplayPos);
|
||||||
m_Streamer = recStreamer;
|
m_Streamer = recStreamer;
|
||||||
int64_t from, to;
|
int64_t from, to;
|
||||||
uint64_t total = recStreamer->GetLength();
|
uint64_t total = recStreamer->GetLength();
|
||||||
if (ParseRange(from, to)) {
|
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 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)
|
if (length < 0L)
|
||||||
return HttpResponse(416, true, "video/mpeg", "Accept-Ranges: bytes\r\nContent-Range: bytes */%llu", (unsigned long long) total);
|
return HttpResponse(416, true, "video/mpeg", "Accept-Ranges: bytes\r\nContent-Range: bytes */%llu", (unsigned long long) total);
|
||||||
else
|
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);
|
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");
|
return HttpResponse(200, false, "video/mpeg", "Accept-Ranges: bytes");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
return HttpResponse(404, true);
|
return HttpResponse(404, true);
|
||||||
}
|
}
|
||||||
@ -240,7 +272,7 @@ bool cConnectionHTTP::ProcessRequest(void)
|
|||||||
}
|
}
|
||||||
else if (m_Recording != NULL) {
|
else if (m_Recording != NULL) {
|
||||||
Dprintf("HEAD recording\n");
|
Dprintf("HEAD recording\n");
|
||||||
cStreamdevRecStreamer *recStreamer = new cStreamdevRecStreamer(m_Recording, this);
|
cStreamdevRecStreamer *recStreamer = new cStreamdevRecStreamer(m_Recording, this, m_ReplayPos);
|
||||||
m_Streamer = recStreamer;
|
m_Streamer = recStreamer;
|
||||||
int64_t from, to;
|
int64_t from, to;
|
||||||
uint64_t total = recStreamer->GetLength();
|
uint64_t total = recStreamer->GetLength();
|
||||||
@ -330,6 +362,7 @@ bool cConnectionHTTP::ParseRange(int64_t &From, int64_t &To) const
|
|||||||
From = To = 0L;
|
From = To = 0L;
|
||||||
tStrStrMap::const_iterator it = Headers().find(RANGE);
|
tStrStrMap::const_iterator it = Headers().find(RANGE);
|
||||||
if (it != Headers().end()) {
|
if (it != Headers().end()) {
|
||||||
|
Dprintf("%s: %s\n", it->first.c_str(), it->second.c_str());
|
||||||
size_t b = it->second.find("bytes=");
|
size_t b = it->second.find("bytes=");
|
||||||
if (b != std::string::npos) {
|
if (b != std::string::npos) {
|
||||||
char* e = NULL;
|
char* e = NULL;
|
||||||
@ -521,6 +554,14 @@ bool cConnectionHTTP::ProcessURI(const std::string& PathInfo)
|
|||||||
return true;
|
return true;
|
||||||
} else if ((m_Recording = RecordingFromString(filespec.c_str(), fileext.c_str())) != NULL) {
|
} else if ((m_Recording = RecordingFromString(filespec.c_str(), fileext.c_str())) != NULL) {
|
||||||
Dprintf("Recording %s found\n", m_Recording->Name());
|
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;
|
return true;
|
||||||
} else if ((m_Channel = ChannelFromString(filespec.c_str(), &m_Apid[0], &m_Dpid[0])) != NULL) {
|
} 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]);
|
Dprintf("Channel found. Apid/Dpid is %d/%d\n", m_Apid[0], m_Dpid[0]);
|
||||||
|
@ -35,6 +35,7 @@ private:
|
|||||||
int m_Dpid[2];
|
int m_Dpid[2];
|
||||||
// job: replay
|
// job: replay
|
||||||
cRecording *m_Recording;
|
cRecording *m_Recording;
|
||||||
|
std::string m_ReplayPos;
|
||||||
// job: listing
|
// job: listing
|
||||||
cMenuList *m_MenuList;
|
cMenuList *m_MenuList;
|
||||||
|
|
||||||
@ -69,6 +70,7 @@ public:
|
|||||||
|
|
||||||
virtual bool Abort(void) const;
|
virtual bool Abort(void) const;
|
||||||
virtual void Flushed(void);
|
virtual void Flushed(void);
|
||||||
|
inline std::string GetReplayPos() { return m_ReplayPos; }
|
||||||
};
|
};
|
||||||
|
|
||||||
inline bool cConnectionHTTP::Abort(void) const
|
inline bool cConnectionHTTP::Abort(void) const
|
||||||
|
@ -19,6 +19,10 @@
|
|||||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
#include "recplayer.h"
|
#include "recplayer.h"
|
||||||
|
|
||||||
// for TSPLAY patch detection
|
// for TSPLAY patch detection
|
||||||
@ -210,6 +214,47 @@ cRecording* RecPlayer::getCurrentRecording()
|
|||||||
return recording;
|
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)
|
uint64_t RecPlayer::positionFromFrameNumber(uint32_t frameNumber)
|
||||||
{
|
{
|
||||||
if (!indexFile) return 0;
|
if (!indexFile) return 0;
|
||||||
|
@ -46,6 +46,9 @@ class RecPlayer
|
|||||||
cRecording* getCurrentRecording();
|
cRecording* getCurrentRecording();
|
||||||
void scan();
|
void scan();
|
||||||
uint64_t positionFromFrameNumber(uint32_t frameNumber);
|
uint64_t positionFromFrameNumber(uint32_t frameNumber);
|
||||||
|
int frameFromResume();
|
||||||
|
int frameFromMark(int index);
|
||||||
|
int frameFromSeconds(int seconds);
|
||||||
uint32_t frameNumberFromPosition(uint64_t position);
|
uint32_t frameNumberFromPosition(uint64_t position);
|
||||||
bool getNextIFrame(uint32_t frameNumber, uint32_t direction, uint64_t* rfilePosition, uint32_t* rframeNumber, uint32_t* rframeLength);
|
bool getNextIFrame(uint32_t frameNumber, uint32_t direction, uint64_t* rfilePosition, uint32_t* rframeNumber, uint32_t* rframeLength);
|
||||||
|
|
||||||
|
@ -12,13 +12,14 @@ using namespace Streamdev;
|
|||||||
|
|
||||||
// --- cStreamdevRecStreamer -------------------------------------------------
|
// --- cStreamdevRecStreamer -------------------------------------------------
|
||||||
|
|
||||||
cStreamdevRecStreamer::cStreamdevRecStreamer(cRecording *Rec, const cServerConnection *Connection):
|
cStreamdevRecStreamer::cStreamdevRecStreamer(cRecording *Rec, const cServerConnection *Connection, std::string pos):
|
||||||
cStreamdevStreamer("streamdev-recstreaming", Connection),
|
cStreamdevStreamer("streamdev-recstreaming", Connection),
|
||||||
m_RecPlayer(Rec),
|
m_RecPlayer(Rec),
|
||||||
m_From(0L)
|
m_From(0L)
|
||||||
{
|
{
|
||||||
Dprintf("New rec streamer\n");
|
Dprintf("New rec streamer\n");
|
||||||
m_To = (int64_t) m_RecPlayer.getLengthBytes() - 1;
|
m_To = (int64_t) m_RecPlayer.getLengthBytes() - 1;
|
||||||
|
m_Pos = pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
cStreamdevRecStreamer::~cStreamdevRecStreamer()
|
cStreamdevRecStreamer::~cStreamdevRecStreamer()
|
||||||
@ -52,6 +53,69 @@ int64_t cStreamdevRecStreamer::SetRange(int64_t &From, int64_t &To)
|
|||||||
return m_To - m_From + 1;
|
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)
|
uchar* cStreamdevRecStreamer::GetFromReceiver(int &Count)
|
||||||
{
|
{
|
||||||
if (m_From <= m_To) {
|
if (m_From <= m_To) {
|
||||||
|
@ -14,6 +14,7 @@ private:
|
|||||||
RecPlayer m_RecPlayer;
|
RecPlayer m_RecPlayer;
|
||||||
int64_t m_From;
|
int64_t m_From;
|
||||||
int64_t m_To;
|
int64_t m_To;
|
||||||
|
std::string m_Pos;
|
||||||
uchar m_Buffer[RECBUFSIZE];
|
uchar m_Buffer[RECBUFSIZE];
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
@ -25,7 +26,9 @@ public:
|
|||||||
inline uint64_t GetLength() { return m_RecPlayer.getLengthBytes(); }
|
inline uint64_t GetLength() { return m_RecPlayer.getLengthBytes(); }
|
||||||
int64_t SetRange(int64_t &From, int64_t &To);
|
int64_t SetRange(int64_t &From, int64_t &To);
|
||||||
virtual cString ToText() const;
|
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();
|
virtual ~cStreamdevRecStreamer();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user