Support limiting output range for most outputs and outgoing pushes

This commit is contained in:
Thulinma 2023-05-16 02:56:45 +02:00
parent 3e2a17ff93
commit 7dbd60b208
21 changed files with 433 additions and 186 deletions

View file

@ -18,7 +18,7 @@
#define PRETTY_ARG_TIME(t) \ #define PRETTY_ARG_TIME(t) \
(int)(t) / 86400, ((int)(t) % 86400) / 3600, ((int)(t) % 3600) / 60, (int)(t) % 60 (int)(t) / 86400, ((int)(t) % 86400) / 3600, ((int)(t) % 3600) / 60, (int)(t) % 60
#define PRETTY_PRINT_MSTIME "%ud%.2uh%.2um%.2us.%.3u" #define PRETTY_PRINT_MSTIME "%ud%.2uh%.2um%.2us.%.3u"
#define PRETTY_ARG_MSTIME(t) PRETTY_ARG_TIME(t / 1000), (int)(t % 1000) #define PRETTY_ARG_MSTIME(t) PRETTY_ARG_TIME((t) / 1000), (int)((t) % 1000)
#if DEBUG > -1 #if DEBUG > -1
#define APPIDENT APPNAME "/" PACKAGE_VERSION #define APPIDENT APPNAME "/" PACKAGE_VERSION

View file

@ -896,6 +896,7 @@ namespace DTSC{
streamMemBuf = 0; streamMemBuf = 0;
isMemBuf = false; isMemBuf = false;
isMaster = true; isMaster = true;
removeLimiter();
reInit(_streamName, src); reInit(_streamName, src);
} }
@ -907,6 +908,7 @@ namespace DTSC{
streamMemBuf = 0; streamMemBuf = 0;
isMemBuf = false; isMemBuf = false;
isMaster = master; isMaster = master;
removeLimiter();
reInit(_streamName, master, autoBackOff); reInit(_streamName, master, autoBackOff);
} }
@ -916,6 +918,7 @@ namespace DTSC{
streamMemBuf = 0; streamMemBuf = 0;
isMemBuf = false; isMemBuf = false;
isMaster = true; isMaster = true;
removeLimiter();
reInit(_streamName, fileName); reInit(_streamName, fileName);
} }
@ -989,6 +992,7 @@ namespace DTSC{
// Unix Time at zero point of a stream // Unix Time at zero point of a stream
if (src.hasMember("unixzero")){ if (src.hasMember("unixzero")){
setBootMsOffset(src.getMember("unixzero").asInt() - Util::unixMS() + Util::bootMS()); setBootMsOffset(src.getMember("unixzero").asInt() - Util::unixMS() + Util::bootMS());
setUTCOffset(src.getMember("unixzero").asInt());
}else{ }else{
MEDIUM_MSG("No member \'unixzero\' found in DTSC::Scan. Calculating locally."); MEDIUM_MSG("No member \'unixzero\' found in DTSC::Scan. Calculating locally.");
int64_t nowMs = 0; int64_t nowMs = 0;
@ -2001,6 +2005,7 @@ namespace DTSC{
} }
uint64_t Meta::getFirstms(size_t trackIdx) const{ uint64_t Meta::getFirstms(size_t trackIdx) const{
const DTSC::Track &t = tracks.at(trackIdx); const DTSC::Track &t = tracks.at(trackIdx);
if (isLimited && limitMin > t.track.getInt(t.trackFirstmsField)){return limitMin;}
return t.track.getInt(t.trackFirstmsField); return t.track.getInt(t.trackFirstmsField);
} }
@ -2010,6 +2015,7 @@ namespace DTSC{
} }
uint64_t Meta::getLastms(size_t trackIdx) const{ uint64_t Meta::getLastms(size_t trackIdx) const{
const DTSC::Track &t = tracks.find(trackIdx)->second; const DTSC::Track &t = tracks.find(trackIdx)->second;
if (isLimited && limitMax < t.track.getInt(t.trackLastmsField)){return limitMax;}
return t.track.getInt(t.trackLastmsField); return t.track.getInt(t.trackLastmsField);
} }
@ -2023,6 +2029,7 @@ namespace DTSC{
} }
uint64_t Meta::getDuration(size_t trackIdx) const{ uint64_t Meta::getDuration(size_t trackIdx) const{
if (isLimited){return getLastms(trackIdx) - getFirstms(trackIdx);}
const DTSC::Track &t = tracks.at(trackIdx); const DTSC::Track &t = tracks.at(trackIdx);
return t.track.getInt(t.trackLastmsField) - t.track.getInt(t.trackFirstmsField); return t.track.getInt(t.trackLastmsField) - t.track.getInt(t.trackFirstmsField);
} }
@ -2117,12 +2124,16 @@ namespace DTSC{
void Meta::setVod(bool vod){ void Meta::setVod(bool vod){
stream.setInt(streamVodField, vod ? 1 : 0); stream.setInt(streamVodField, vod ? 1 : 0);
} }
bool Meta::getVod() const{return stream.getInt(streamVodField);} bool Meta::getVod() const{
return isLimited || stream.getInt(streamVodField);
}
void Meta::setLive(bool live){ void Meta::setLive(bool live){
stream.setInt(streamLiveField, live ? 1 : 0); stream.setInt(streamLiveField, live ? 1 : 0);
} }
bool Meta::getLive() const{return stream.getInt(streamLiveField);} bool Meta::getLive() const{
return (!isLimited || limitMax == 0xFFFFFFFFFFFFFFFFull) && stream.getInt(streamLiveField);
}
bool Meta::hasBFrames(size_t idx) const{ bool Meta::hasBFrames(size_t idx) const{
std::set<size_t> vTracks = getValidTracks(); std::set<size_t> vTracks = getValidTracks();
@ -2272,7 +2283,7 @@ namespace DTSC{
char thisPageName[NAME_BUFFER_SIZE]; char thisPageName[NAME_BUFFER_SIZE];
snprintf(thisPageName, NAME_BUFFER_SIZE, SHM_TRACK_DATA, streamName.c_str(), trackIdx, snprintf(thisPageName, NAME_BUFFER_SIZE, SHM_TRACK_DATA, streamName.c_str(), trackIdx,
(uint32_t)t.pages.getInt("firstkey", t.pages.getDeleted())); (uint32_t)t.pages.getInt("firstkey", t.pages.getDeleted()));
IPC::sharedPage p(thisPageName, 20971520); IPC::sharedPage p(thisPageName, 20971520, false, false);
p.master = true; p.master = true;
// Then delete the page entry // Then delete the page entry
@ -2554,6 +2565,13 @@ namespace DTSC{
const Util::RelAccX &Meta::parts(size_t idx) const{return tracks.at(idx).parts;} const Util::RelAccX &Meta::parts(size_t idx) const{return tracks.at(idx).parts;}
Util::RelAccX &Meta::keys(size_t idx){return tracks.at(idx).keys;} Util::RelAccX &Meta::keys(size_t idx){return tracks.at(idx).keys;}
const Util::RelAccX &Meta::keys(size_t idx) const{return tracks.at(idx).keys;} const Util::RelAccX &Meta::keys(size_t idx) const{return tracks.at(idx).keys;}
const Keys Meta::getKeys(size_t trackIdx) const{
DTSC::Keys k(keys(trackIdx));
if (isLimited){k.applyLimiter(limitMin, limitMax, DTSC::Parts(parts(trackIdx)));}
return k;
}
const Util::RelAccX &Meta::fragments(size_t idx) const{return tracks.at(idx).fragments;} const Util::RelAccX &Meta::fragments(size_t idx) const{return tracks.at(idx).fragments;}
const Util::RelAccX &Meta::pages(size_t idx) const{return tracks.at(idx).pages;} const Util::RelAccX &Meta::pages(size_t idx) const{return tracks.at(idx).pages;}
Util::RelAccX &Meta::pages(size_t idx){return tracks.at(idx).pages;} Util::RelAccX &Meta::pages(size_t idx){return tracks.at(idx).pages;}
@ -2652,7 +2670,8 @@ namespace DTSC{
uint64_t Meta::getSendLen(bool skipDynamic, std::set<size_t> selectedTracks) const{ uint64_t Meta::getSendLen(bool skipDynamic, std::set<size_t> selectedTracks) const{
uint64_t dataLen = 34; // + (merged ? 17 : 0) + (bufferWindow ? 24 : 0) + 21; uint64_t dataLen = 34; // + (merged ? 17 : 0) + (bufferWindow ? 24 : 0) + 21;
if (getVod()){dataLen += 14;} if (getVod()){dataLen += 14;}
if (getLive()){dataLen += 15 + 19;} // 19 for unixzero if (getLive()){dataLen += 15;}
if (getLive() || getUTCOffset()){dataLen += 19;} // unixzero field
for (std::map<size_t, Track>::const_iterator it = tracks.begin(); it != tracks.end(); it++){ for (std::map<size_t, Track>::const_iterator it = tracks.begin(); it != tracks.end(); it++){
if (!it->second.parts.getPresent()){continue;} if (!it->second.parts.getPresent()){continue;}
if (!selectedTracks.size() || selectedTracks.count(it->first)){ if (!selectedTracks.size() || selectedTracks.count(it->first)){
@ -2804,9 +2823,13 @@ namespace DTSC{
if (getLive()){conn.SendNow("\000\004live\001\000\000\000\000\000\000\000\001", 15);} if (getLive()){conn.SendNow("\000\004live\001\000\000\000\000\000\000\000\001", 15);}
conn.SendNow("\000\007version\001", 10); conn.SendNow("\000\007version\001", 10);
conn.SendNow(c64(DTSH_VERSION), 8); conn.SendNow(c64(DTSH_VERSION), 8);
if (getLive()){ if (getLive() || getUTCOffset()){
conn.SendNow("\000\010unixzero\001", 11); conn.SendNow("\000\010unixzero\001", 11);
conn.SendNow(c64(Util::unixMS() - Util::bootMS() + getBootMsOffset()), 8); if (getLive()){
conn.SendNow(c64(Util::unixMS() - Util::bootMS() + getBootMsOffset()), 8);
}else{
conn.SendNow(c64(getUTCOffset()), 8);
}
} }
if (lVarSize){ if (lVarSize){
conn.SendNow("\000\016inputLocalVars\002", 17); conn.SendNow("\000\016inputLocalVars\002", 17);
@ -3272,6 +3295,19 @@ namespace DTSC{
// return is by reference // return is by reference
} }
void Meta::removeLimiter(){
isLimited = false;
limitMin = 0;
limitMax = 0;
}
void Meta::applyLimiter(uint64_t min, uint64_t max){
isLimited = true;
limitMin = min;
limitMax = max;
INFO_MSG("Applied limiter from %" PRIu64 " to %" PRIu64, min, max);
}
/// Returns true if the tracks idx1 and idx2 are keyframe aligned /// Returns true if the tracks idx1 and idx2 are keyframe aligned
bool Meta::keyTimingsMatch(size_t idx1, size_t idx2) const { bool Meta::keyTimingsMatch(size_t idx1, size_t idx2) const {
const DTSC::Track &t1 = tracks.at(idx1); const DTSC::Track &t1 = tracks.at(idx1);
@ -3325,6 +3361,7 @@ namespace DTSC{
partsField = cKeys.getFieldData("parts"); partsField = cKeys.getFieldData("parts");
timeField = cKeys.getFieldData("time"); timeField = cKeys.getFieldData("time");
sizeField = cKeys.getFieldData("size"); sizeField = cKeys.getFieldData("size");
isLimited = false;
} }
Keys::Keys(const Util::RelAccX &_keys) : isConst(true), keys(empty), cKeys(_keys){ Keys::Keys(const Util::RelAccX &_keys) : isConst(true), keys(empty), cKeys(_keys){
@ -3335,23 +3372,143 @@ namespace DTSC{
partsField = cKeys.getFieldData("parts"); partsField = cKeys.getFieldData("parts");
timeField = cKeys.getFieldData("time"); timeField = cKeys.getFieldData("time");
sizeField = cKeys.getFieldData("size"); sizeField = cKeys.getFieldData("size");
isLimited = false;
} }
size_t Keys::getFirstValid() const{return cKeys.getDeleted();} size_t Keys::getFirstValid() const{
size_t Keys::getEndValid() const{return cKeys.getEndPos();} return isLimited ? limMin : cKeys.getDeleted();
}
size_t Keys::getEndValid() const{
return isLimited ? limMax : cKeys.getEndPos();
}
size_t Keys::getValidCount() const{return getEndValid() - getFirstValid();} size_t Keys::getValidCount() const{return getEndValid() - getFirstValid();}
size_t Keys::getFirstPart(size_t idx) const{return cKeys.getInt(firstPartField, idx);} size_t Keys::getFirstPart(size_t idx) const{
if (isLimited && idx == limMin){return limMinFirstPart;}
return cKeys.getInt(firstPartField, idx);
}
size_t Keys::getBpos(size_t idx) const{return cKeys.getInt(bposField, idx);} size_t Keys::getBpos(size_t idx) const{return cKeys.getInt(bposField, idx);}
uint64_t Keys::getDuration(size_t idx) const{return cKeys.getInt(durationField, idx);} uint64_t Keys::getDuration(size_t idx) const{
if (isLimited && idx + 1 == limMax){return limMaxDuration;}
if (isLimited && idx == limMin){return limMinDuration;}
return cKeys.getInt(durationField, idx);
}
size_t Keys::getNumber(size_t idx) const{return cKeys.getInt(numberField, idx);} size_t Keys::getNumber(size_t idx) const{return cKeys.getInt(numberField, idx);}
size_t Keys::getParts(size_t idx) const{return cKeys.getInt(partsField, idx);} size_t Keys::getParts(size_t idx) const{
uint64_t Keys::getTime(size_t idx) const{return cKeys.getInt(timeField, idx);} if (isLimited && idx + 1 == limMax){return limMaxParts;}
if (isLimited && idx == limMin){return limMinParts;}
return cKeys.getInt(partsField, idx);
}
uint64_t Keys::getTime(size_t idx) const{
if (isLimited && idx == limMin){return limMinTime;}
return cKeys.getInt(timeField, idx);
}
void Keys::setSize(size_t idx, size_t _size){ void Keys::setSize(size_t idx, size_t _size){
if (isConst){return;} if (isConst){return;}
keys.setInt(sizeField, _size, idx); keys.setInt(sizeField, _size, idx);
} }
size_t Keys::getSize(size_t idx) const{return cKeys.getInt(sizeField, idx);} size_t Keys::getSize(size_t idx) const{
if (isLimited && idx + 1 == limMax){return limMaxSize;}
if (isLimited && idx == limMin){return limMinSize;}
return cKeys.getInt(sizeField, idx);
}
uint64_t Keys::getTotalPartCount(){
return getParts(getEndValid()-1) + getFirstPart(getEndValid()-1) - getFirstPart(getFirstValid());
}
uint32_t Keys::getIndexForTime(uint64_t timestamp){
uint32_t firstKey = getFirstValid();
uint32_t endKey = getEndValid();
for (size_t i = firstKey; i < endKey; i++){
if (getTime(i) + getDuration(i) > timestamp){return i;}
}
return endKey;
}
void Keys::applyLimiter(uint64_t _min, uint64_t _max, DTSC::Parts _p){
// Determine first and last key available within the limits
// Note: limMax replaces getEndValid(), and is thus one _past_ the end key index!
limMin = getFirstValid();
limMax = getEndValid();
for (size_t i = limMin; i < limMax; i++){
if (getTime(i) <= _min){limMin = i;}
if (getTime(i) >= _max){
limMax = i;
break;
}
}
// We can't have 0 keys, so force at least 1 key in cases where min >= max.
if (limMin >= limMax){limMax = limMin + 1;}
// If the first key is the last key, the override calculation is a little trickier
if (limMin + 1 == limMax){
//Calculate combined first/last key override
{
limMinDuration = 0;
limMinParts = 0;
limMinSize = 0;
limMinFirstPart = getFirstPart(limMin);
limMinTime = getTime(limMin);
size_t partNo = limMinFirstPart;
size_t truePartEnd = partNo + getParts(limMin);
while (partNo < truePartEnd){
if (limMinTime >= _min){
if (limMinTime + limMinDuration >= _max){break;}
++limMinParts;
limMinDuration += _p.getDuration(partNo);
limMinSize += _p.getSize(partNo);
}else{
++limMinFirstPart;
limMinTime += _p.getDuration(partNo);
}
++partNo;
}
limMaxSize = limMinSize;
limMaxParts = limMinParts;
limMaxDuration = limMinDuration;
}
}else{
//Calculate first key overrides
{
limMinDuration = getDuration(limMin);
limMinParts = getParts(limMin);
limMinSize = getSize(limMin);
limMinFirstPart = getFirstPart(limMin);
limMinTime = getTime(limMin);
size_t partNo = limMinFirstPart;
size_t truePartEnd = partNo + limMinParts;
while (partNo < truePartEnd){
if (limMinTime >= _min){break;}
--limMinParts;
limMinDuration -= _p.getDuration(partNo);
limMinSize -= _p.getSize(partNo);
++limMinFirstPart;
limMinTime += _p.getDuration(partNo);
++partNo;
}
}
//Calculate last key overrides
{
limMaxDuration = limMaxParts = limMaxSize = 0;
size_t partNo = getFirstPart(limMax-1);
size_t truePartEnd = partNo + getParts(limMax-1);
uint64_t endTime = getTime(limMax-1);
while (partNo < truePartEnd){
if (endTime + limMaxDuration >= _max){break;}
++limMaxParts;
limMaxDuration += _p.getDuration(partNo);
limMaxSize += _p.getSize(partNo);
++partNo;
}
}
}
HIGH_MSG("Key limiter applied from %" PRIu64 " to %" PRIu64 ", key times %" PRIu64 " to %" PRIu64 ", %lld parts, %lld parts", _min, _max, getTime(limMin), getTime(limMax-1), (long long)limMinParts-(long long)getParts(limMin), (long long)limMaxParts-(long long)getParts(limMax-1));
isLimited = true;
}
Fragments::Fragments(const Util::RelAccX &_fragments) : fragments(_fragments){} Fragments::Fragments(const Util::RelAccX &_fragments) : fragments(_fragments){}
size_t Fragments::getFirstValid() const{return fragments.getDeleted();} size_t Fragments::getFirstValid() const{return fragments.getDeleted();}

View file

@ -191,8 +191,27 @@ namespace DTSC{
void setSize(size_t idx, size_t _size); void setSize(size_t idx, size_t _size);
size_t getSize(size_t idx) const; size_t getSize(size_t idx) const;
uint64_t getTotalPartCount();
uint32_t getIndexForTime(uint64_t timestamp);
void applyLimiter(uint64_t _min, uint64_t _max, DTSC::Parts _p);
private: private:
bool isConst; bool isConst;
bool isLimited;
size_t limMin;
size_t limMax;
//Overrides for max key
size_t limMaxParts;
uint64_t limMaxDuration;
size_t limMaxSize;
//Overrides for min key
size_t limMinParts;
size_t limMinFirstPart;
uint64_t limMinDuration;
uint64_t limMinTime;
size_t limMinSize;
Util::RelAccX empty; Util::RelAccX empty;
Util::RelAccX &keys; Util::RelAccX &keys;
@ -477,6 +496,8 @@ namespace DTSC{
Util::RelAccX &pages(size_t idx); Util::RelAccX &pages(size_t idx);
const Util::RelAccX &pages(size_t idx) const; const Util::RelAccX &pages(size_t idx) const;
const Keys getKeys(size_t trackIdx) const;
std::string toPrettyString() const; std::string toPrettyString() const;
void remap(const std::string &_streamName = ""); void remap(const std::string &_streamName = "");
@ -495,6 +516,9 @@ namespace DTSC{
void getHealthJSON(JSON::Value & returnReference) const; void getHealthJSON(JSON::Value & returnReference) const;
void removeLimiter();
void applyLimiter(uint64_t min, uint64_t max);
protected: protected:
void sBufMem(size_t trackCount = DEFAULT_TRACK_COUNT); void sBufMem(size_t trackCount = DEFAULT_TRACK_COUNT);
void sBufShm(const std::string &_streamName, size_t trackCount = DEFAULT_TRACK_COUNT, bool master = true, bool autoBackOff = true); void sBufShm(const std::string &_streamName, size_t trackCount = DEFAULT_TRACK_COUNT, bool master = true, bool autoBackOff = true);
@ -509,6 +533,9 @@ namespace DTSC{
std::map<size_t, IPC::sharedPage> tM; std::map<size_t, IPC::sharedPage> tM;
bool isMaster; bool isMaster;
uint64_t limitMin;
uint64_t limitMax;
bool isLimited;
char *streamMemBuf; char *streamMemBuf;
bool isMemBuf; bool isMemBuf;

View file

@ -502,13 +502,11 @@ bool FLV::Tag::DTSCMetaInit(const DTSC::Meta &M, std::set<size_t> &selTracks){
int i = 0; int i = 0;
uint64_t mediaLen = 0; uint64_t mediaLen = 0;
for (std::set<size_t>::iterator it = selTracks.begin(); it != selTracks.end(); it++){ for (std::set<size_t>::iterator it = selTracks.begin(); it != selTracks.end(); it++){
if (M.getLastms(*it) - M.getFirstms(*it) > mediaLen){ if (M.getDuration(*it) > mediaLen){mediaLen = M.getDuration(*it);}
mediaLen = M.getLastms(*it) - M.getFirstms(*it);
}
if (M.getType(*it) == "video"){ if (M.getType(*it) == "video"){
trinfo.addContent(AMF::Object("", AMF::AMF0_OBJECT)); trinfo.addContent(AMF::Object("", AMF::AMF0_OBJECT));
trinfo.getContentP(i)->addContent(AMF::Object( trinfo.getContentP(i)->addContent(AMF::Object(
"length", ((double)M.getLastms(*it) / 1000) * ((double)M.getFpks(*it) / 1000.0), AMF::AMF0_NUMBER)); "length", ((double)M.getDuration(*it) / 1000) * ((double)M.getFpks(*it) / 1000.0), AMF::AMF0_NUMBER));
trinfo.getContentP(i)->addContent(AMF::Object("timescale", ((double)M.getFpks(*it) / 1000), AMF::AMF0_NUMBER)); trinfo.getContentP(i)->addContent(AMF::Object("timescale", ((double)M.getFpks(*it) / 1000), AMF::AMF0_NUMBER));
trinfo.getContentP(i)->addContent(AMF::Object("sampledescription", AMF::AMF0_STRICT_ARRAY)); trinfo.getContentP(i)->addContent(AMF::Object("sampledescription", AMF::AMF0_STRICT_ARRAY));
amfdata.getContentP(1)->addContent(AMF::Object("hasVideo", 1, AMF::AMF0_BOOL)); amfdata.getContentP(1)->addContent(AMF::Object("hasVideo", 1, AMF::AMF0_BOOL));
@ -552,7 +550,7 @@ bool FLV::Tag::DTSCMetaInit(const DTSC::Meta &M, std::set<size_t> &selTracks){
if (M.getType(*it) == "audio"){ if (M.getType(*it) == "audio"){
trinfo.addContent(AMF::Object("", AMF::AMF0_OBJECT)); trinfo.addContent(AMF::Object("", AMF::AMF0_OBJECT));
trinfo.getContentP(i)->addContent( trinfo.getContentP(i)->addContent(
AMF::Object("length", (double)(M.getLastms(*it) * M.getRate(*it)), AMF::AMF0_NUMBER)); AMF::Object("length", (double)(M.getDuration(*it) * M.getRate(*it)), AMF::AMF0_NUMBER));
trinfo.getContentP(i)->addContent(AMF::Object("timescale", M.getRate(*it), AMF::AMF0_NUMBER)); trinfo.getContentP(i)->addContent(AMF::Object("timescale", M.getRate(*it), AMF::AMF0_NUMBER));
trinfo.getContentP(i)->addContent(AMF::Object("sampledescription", AMF::AMF0_STRICT_ARRAY)); trinfo.getContentP(i)->addContent(AMF::Object("sampledescription", AMF::AMF0_STRICT_ARRAY));
amfdata.getContentP(1)->addContent(AMF::Object("hasAudio", 1, AMF::AMF0_BOOL)); amfdata.getContentP(1)->addContent(AMF::Object("hasAudio", 1, AMF::AMF0_BOOL));
@ -575,7 +573,7 @@ bool FLV::Tag::DTSCMetaInit(const DTSC::Meta &M, std::set<size_t> &selTracks){
} }
} }
if (M.getVod()){ if (M.getVod()){
amfdata.getContentP(1)->addContent(AMF::Object("duration", mediaLen / 1000, AMF::AMF0_NUMBER)); amfdata.getContentP(1)->addContent(AMF::Object("duration", mediaLen / 1000.0, AMF::AMF0_NUMBER));
} }
amfdata.getContentP(1)->addContent(trinfo); amfdata.getContentP(1)->addContent(trinfo);

View file

@ -1656,7 +1656,7 @@ namespace Mist{
} }
bufferFinalize(idx, page); bufferFinalize(idx, page);
bufferTimer = Util::bootMS() - bufferTimer; bufferTimer = Util::bootMS() - bufferTimer;
if (packCounter != tPages.getInt("parts", pageIdx)){ if (packCounter < tPages.getInt("parts", pageIdx)){
FAIL_MSG("Track %zu, page %" PRIu32 " (" PRETTY_PRINT_MSTIME " - " PRETTY_PRINT_MSTIME ") NOT FULLY buffered in %" PRIu64 "ms - erasing for later retry", FAIL_MSG("Track %zu, page %" PRIu32 " (" PRETTY_PRINT_MSTIME " - " PRETTY_PRINT_MSTIME ") NOT FULLY buffered in %" PRIu64 "ms - erasing for later retry",
idx, pageNumber, PRETTY_ARG_MSTIME(tPages.getInt("firsttime", pageIdx)), PRETTY_ARG_MSTIME(thisTime), bufferTimer); idx, pageNumber, PRETTY_ARG_MSTIME(tPages.getInt("firsttime", pageIdx)), PRETTY_ARG_MSTIME(thisTime), bufferTimer);
INFO_MSG(" (%" PRIu32 "/%" PRIu64 " parts, %" PRIu64 " bytes)", packCounter, INFO_MSG(" (%" PRIu32 "/%" PRIu64 " parts, %" PRIu64 " bytes)", packCounter,

View file

@ -762,7 +762,10 @@ namespace Mist{
Util::logExitReason(ER_UNKNOWN, "Failed to load HLS playlist, aborting"); Util::logExitReason(ER_UNKNOWN, "Failed to load HLS playlist, aborting");
return; return;
} }
uint64_t oldBootMsOffset = M.getBootMsOffset();
meta.reInit(isSingular() ? streamName : "", false); meta.reInit(isSingular() ? streamName : "", false);
meta.setUTCOffset(zUTC);
meta.setBootMsOffset(oldBootMsOffset);
INFO_MSG("Parsing live stream to create header..."); INFO_MSG("Parsing live stream to create header...");
TS::Packet packet; // to analyse and extract data TS::Packet packet; // to analyse and extract data
int pidCounter = 1; int pidCounter = 1;
@ -831,7 +834,7 @@ namespace Mist{
INFO_MSG("Could not read existing header, regenerating"); INFO_MSG("Could not read existing header, regenerating");
return false; return false;
} }
if (!M.inputLocalVars.isMember("version") || M.inputLocalVars["version"].asInt() < 3){ if (!M.inputLocalVars.isMember("version") || M.inputLocalVars["version"].asInt() < 4){
INFO_MSG("Header needs update, regenerating"); INFO_MSG("Header needs update, regenerating");
return false; return false;
} }
@ -888,9 +891,9 @@ namespace Mist{
pidMapping[val] = key; pidMapping[val] = key;
} }
// Set bootMsOffset in order to display the program time correctly in the player // Set bootMsOffset in order to display the program time correctly in the player
streamOffset = M.inputLocalVars["streamoffset"].asInt(); zUTC = M.inputLocalVars["zUTC"].asInt();
if (meta.getLive()){meta.setUTCOffset(streamOffset + (Util::unixMS() - Util::bootMS()));} meta.setUTCOffset(zUTC);
meta.setBootMsOffset(streamOffset); if (M.getLive()){meta.setBootMsOffset(streamOffset);}
return true; return true;
} }
@ -999,12 +1002,12 @@ namespace Mist{
if (!config->is_active){return false;} if (!config->is_active){return false;}
// set bootMsOffset in order to display the program time correctly in the player // set bootMsOffset in order to display the program time correctly in the player
if (meta.getLive()){meta.setUTCOffset(streamOffset + (Util::unixMS() - Util::bootMS()));} meta.setUTCOffset(zUTC);
meta.setBootMsOffset(streamOffset); if (M.getLive()){meta.setBootMsOffset(streamOffset);}
if (streamIsLive || isLiveDVR){return true;} if (streamIsLive || isLiveDVR){return true;}
// Set local vars used for parsing existing headers // Set local vars used for parsing existing headers
meta.inputLocalVars["version"] = 3; meta.inputLocalVars["version"] = 4;
// Write playlist entry info // Write playlist entry info
JSON::Value allEntries; JSON::Value allEntries;
@ -1029,7 +1032,7 @@ namespace Mist{
} }
meta.inputLocalVars["playlist_urls"] = playlist_urls; meta.inputLocalVars["playlist_urls"] = playlist_urls;
meta.inputLocalVars["playlistEntries"] = allEntries; meta.inputLocalVars["playlistEntries"] = allEntries;
meta.inputLocalVars["streamoffset"] = streamOffset; meta.inputLocalVars["zUTC"] = zUTC;
// Write packet ID mappings // Write packet ID mappings
JSON::Value thisMappingsR; JSON::Value thisMappingsR;
@ -1599,6 +1602,8 @@ namespace Mist{
INFO_MSG("Setting program unix start time to '%s' (%" PRIu64 ")", line.substr(pos + 1).c_str(), zUTC); INFO_MSG("Setting program unix start time to '%s' (%" PRIu64 ")", line.substr(pos + 1).c_str(), zUTC);
// store offset so that we can set it after reading the header // store offset so that we can set it after reading the header
streamOffset = zUTC - (Util::unixMS() - Util::bootMS()); streamOffset = zUTC - (Util::unixMS() - Util::bootMS());
meta.setUTCOffset(zUTC);
if (M.getLive()){meta.setBootMsOffset(streamOffset);}
}else{ }else{
// ignore wrong lines // ignore wrong lines
VERYHIGH_MSG("ignore wrong line: %s", line.c_str()); VERYHIGH_MSG("ignore wrong line: %s", line.c_str());

View file

@ -917,8 +917,9 @@ namespace Mist{
/// For live, it seeks to the last sync'ed keyframe of the main track, no closer than /// For live, it seeks to the last sync'ed keyframe of the main track, no closer than
/// needsLookAhead+minKeepAway ms from the end. Unless lastms < 5000, then it seeks to the first /// needsLookAhead+minKeepAway ms from the end. Unless lastms < 5000, then it seeks to the first
/// keyframe of the main track. Aborts if there is no main track or it has no keyframes. /// keyframe of the main track. Aborts if there is no main track or it has no keyframes.
void Output::initialSeek(){ void Output::initialSeek(bool dryRun){
if (!meta){return;} if (!meta){return;}
meta.removeLimiter();
uint64_t seekPos = 0; uint64_t seekPos = 0;
if (meta.getLive() && buffer.getSyncMode()){ if (meta.getLive() && buffer.getSyncMode()){
size_t mainTrack = getMainSelectedTrack(); size_t mainTrack = getMainSelectedTrack();
@ -967,28 +968,59 @@ namespace Mist{
MEDIUM_MSG("Stream currently contains data from %" PRIu64 " ms to %" PRIu64 " ms", startTime(), endTime()); MEDIUM_MSG("Stream currently contains data from %" PRIu64 " ms to %" PRIu64 " ms", startTime(), endTime());
} }
// Overwrite recstart/recstop with recstartunix/recstopunix if set // Overwrite recstart/recstop with recstartunix/recstopunix if set
if (M.getLive() && if (M.getLive() && (
(targetParams.count("recstartunix") || targetParams.count("recstopunix"))){ targetParams.count("recstartunix") || targetParams.count("recstopunix") ||
uint64_t unixStreamBegin = Util::epoch() - endTime()/1000; targetParams.count("startunix") || targetParams.count("stopunix") ||
if (targetParams.count("recstartunix")){ targetParams.count("unixstart") || targetParams.count("unixstop")
uint64_t startUnix = atoll(targetParams["recstartunix"].c_str()); )){
if (startUnix < unixStreamBegin){ uint64_t zUTC = M.getUTCOffset();
WARN_MSG("Recording start time is earlier than stream begin - starting earliest possible"); if (!zUTC){
targetParams["recstart"] = "-1"; if (!M.getLive()){
WARN_MSG("Attempting to set unix-based start/stop time for a VoD asset without known UTC timestamp! This will likely not work as you expect, since we have nothing to base the timestamps on");
}else{ }else{
targetParams["recstart"] = JSON::Value((startUnix - unixStreamBegin) * 1000).asString(); zUTC = M.getBootMsOffset() + Util::getGlobalConfig("systemBoot").asInt();
} }
} }
if (targetParams.count("recstartunix")){
int64_t startUnix = atoll(targetParams["recstartunix"].c_str()) * 1000;
// If the time is before the first 10 hours of unix epoch, assume relative time
if (startUnix <= 36000000){startUnix += Util::unixMS();}
targetParams["recstart"] = JSON::Value(startUnix - zUTC).asString();
}
if (targetParams.count("recstopunix")){ if (targetParams.count("recstopunix")){
uint64_t stopUnix = atoll(targetParams["recstopunix"].c_str()); int64_t stopUnix = atoll(targetParams["recstopunix"].c_str()) * 1000;
if (stopUnix < unixStreamBegin){ // If the time is before the first 10 hours of unix epoch, assume relative time
onFail("Recording stop time is earlier than stream begin - aborting", true); if (stopUnix <= 36000000){stopUnix += Util::unixMS();}
return; targetParams["recstop"] = JSON::Value(stopUnix - zUTC).asString();
}else{ }
targetParams["recstop"] = JSON::Value((stopUnix - unixStreamBegin) * 1000).asString(); if (targetParams.count("unixstart")){
} int64_t startUnix = atoll(targetParams["unixstart"].c_str()) * 1000;
// If the time is before the first 10 hours of unix epoch, assume relative time
if (startUnix <= 36000000){startUnix += Util::unixMS();}
targetParams["recstart"] = JSON::Value(startUnix - zUTC).asString();
}
if (targetParams.count("unixstop")){
int64_t stopUnix = atoll(targetParams["unixstop"].c_str()) * 1000;
// If the time is before the first 10 hours of unix epoch, assume relative time
if (stopUnix <= 36000000){stopUnix += Util::unixMS();}
targetParams["recstop"] = JSON::Value(stopUnix - zUTC).asString();
}
if (targetParams.count("startunix")){
int64_t startUnix = atoll(targetParams["startunix"].c_str()) * 1000;
// If the time is before the first 10 hours of unix epoch, assume relative time
if (startUnix <= 36000000){startUnix += Util::unixMS();}
targetParams["recstart"] = JSON::Value(startUnix - zUTC).asString();
}
if (targetParams.count("stopunix")){
int64_t stopUnix = atoll(targetParams["stopunix"].c_str()) * 1000;
// If the time is before the first 10 hours of unix epoch, assume relative time
if (stopUnix <= 36000000){stopUnix += Util::unixMS();}
targetParams["recstop"] = JSON::Value(stopUnix - zUTC).asString();
} }
} }
//Autoconvert start/stop to recstart/recstop to improve usability
if (targetParams.count("start") && !targetParams.count("recstart")){targetParams["recstart"] = targetParams["start"];}
if (targetParams.count("stop") && !targetParams.count("recstop")){targetParams["recstop"] = targetParams["stop"];}
// Check recstart/recstop for correctness // Check recstart/recstop for correctness
if (targetParams.count("recstop")){ if (targetParams.count("recstop")){
uint64_t endRec = atoll(targetParams["recstop"].c_str()); uint64_t endRec = atoll(targetParams["recstop"].c_str());
@ -1012,7 +1044,15 @@ namespace Mist{
" ms instead", atoll(targetParams["recstart"].c_str()), startRec); " ms instead", atoll(targetParams["recstart"].c_str()), startRec);
targetParams["recstart"] = JSON::Value(startRec).asString(); targetParams["recstart"] = JSON::Value(startRec).asString();
} }
seekPos = startRec; size_t mainTrack = getMainSelectedTrack();
if (M.getType(mainTrack) == "video"){
seekPos = M.getTimeForKeyIndex(mainTrack, M.getKeyIndexForTime(mainTrack, startRec));
if (seekPos != startRec){
INFO_MSG("Shifting recording start from %" PRIu64 " to %" PRIu64 " so that it starts with a keyframe", startRec, seekPos);
}
}else{
seekPos = startRec;
}
} }
if (targetParams.count("split")){ if (targetParams.count("split")){
@ -1020,10 +1060,15 @@ namespace Mist{
INFO_MSG("Will split recording every %lld seconds", atoll(targetParams["split"].c_str())); INFO_MSG("Will split recording every %lld seconds", atoll(targetParams["split"].c_str()));
targetParams["nxt-split"] = JSON::Value((int64_t)(seekPos + endRec)).asString(); targetParams["nxt-split"] = JSON::Value((int64_t)(seekPos + endRec)).asString();
} }
// Duration to record in seconds. Oversides recstop. // Duration to record in seconds. Overrides recstop.
if (targetParams.count("duration")){ if (targetParams.count("duration")){
long long endRec = atoll(targetParams["duration"].c_str()) * 1000; int64_t endRec;
targetParams["recstop"] = JSON::Value((int64_t)(seekPos + endRec)).asString(); if (targetParams.count("recstart")){
endRec = atoll(targetParams["recstart"].c_str()) + atoll(targetParams["duration"].c_str()) * 1000;
}else{
endRec = seekPos + atoll(targetParams["duration"].c_str()) * 1000;
}
targetParams["recstop"] = JSON::Value(endRec).asString();
// Recheck recording end time // Recheck recording end time
endRec = atoll(targetParams["recstop"].c_str()); endRec = atoll(targetParams["recstop"].c_str());
if (endRec < 0 || endRec < startTime()){ if (endRec < 0 || endRec < startTime()){
@ -1034,8 +1079,7 @@ namespace Mist{
// Print calculated start and stop time // Print calculated start and stop time
if (targetParams.count("recstart")){ if (targetParams.count("recstart")){
INFO_MSG("Recording will start at timestamp %llu ms", atoll(targetParams["recstart"].c_str())); INFO_MSG("Recording will start at timestamp %llu ms", atoll(targetParams["recstart"].c_str()));
} } else{
else{
INFO_MSG("Recording will start at timestamp %" PRIu64 " ms", endTime()); INFO_MSG("Recording will start at timestamp %" PRIu64 " ms", endTime());
} }
if (targetParams.count("recstop")){ if (targetParams.count("recstop")){
@ -1056,6 +1100,14 @@ namespace Mist{
} }
} }
} }
// If we have a stop position and it's within available range,
// apply a limiter to the stream to make it appear like a VoD asset
if (targetParams.count("recstop") || !M.getLive()){
size_t mainTrack = getMainSelectedTrack();
uint64_t stopPos = M.getLastms(mainTrack);
if (targetParams.count("recstop")){stopPos = atoll(targetParams["recstop"].c_str());}
if (!M.getLive() || stopPos <= M.getLastms(mainTrack)){meta.applyLimiter(seekPos, stopPos);}
}
}else{ }else{
if (M.getLive() && targetParams.count("pushdelay")){ if (M.getLive() && targetParams.count("pushdelay")){
INFO_MSG("Converting pushdelay syntax into corresponding recstart+realtime options"); INFO_MSG("Converting pushdelay syntax into corresponding recstart+realtime options");
@ -1082,51 +1134,28 @@ namespace Mist{
targetParams["realtime"] = "1"; //force real-time speed targetParams["realtime"] = "1"; //force real-time speed
maxSkipAhead = 1; maxSkipAhead = 1;
} }
if (M.getLive() && (targetParams.count("startunix") || targetParams.count("stopunix"))){ if (targetParams.count("startunix") || targetParams.count("stopunix")){
uint64_t unixStreamBegin = Util::epoch() - endTime()/1000; uint64_t zUTC = M.getUTCOffset();
size_t mainTrack = getMainSelectedTrack(); if (!zUTC){
int64_t streamAvail = M.getNowms(mainTrack); if (!M.getLive()){
if (targetParams.count("startunix")){ WARN_MSG("Attempting to set unix-based start/stop time for a VoD asset without known UTC timestamp! This will likely not work as you expect, since we have nothing to base the timestamps on");
int64_t startUnix = atoll(targetParams["startunix"].c_str());
if (startUnix < 0){
int64_t origStartUnix = startUnix;
startUnix += Util::epoch();
if (startUnix < unixStreamBegin){
INFO_MSG("Waiting for stream to reach playback starting point. Current last ms is '%" PRIu64 "'", streamAvail);
while (startUnix < Util::epoch() - (endTime() / 1000) && keepGoing()){
Util::wait(1000);
stats();
startUnix = origStartUnix + Util::epoch();
HIGH_MSG("Waiting for stream to reach playback starting point. Current last ms is '%" PRIu64 "'", streamAvail);
}
}
}
if (startUnix < unixStreamBegin){
WARN_MSG("Start time (%" PRId64 ") is earlier than stream begin (%" PRId64 ") - starting earliest possible", startUnix, unixStreamBegin);
targetParams["start"] = "-1";
}else{ }else{
targetParams["start"] = JSON::Value((startUnix - unixStreamBegin) * 1000).asString(); zUTC = M.getBootMsOffset() + Util::getGlobalConfig("systemBoot").asInt();
} }
} }
if (targetParams.count("startunix")){
int64_t startUnix = atoll(targetParams["startunix"].c_str()) * 1000;
// If the time is before the first 10 hours of unix epoch, assume relative time
if (startUnix <= 36000000){startUnix += Util::unixMS();}
targetParams["start"] = JSON::Value(startUnix - zUTC).asString();
}
if (targetParams.count("stopunix")){ if (targetParams.count("stopunix")){
int64_t stopUnix = atoll(targetParams["stopunix"].c_str()); int64_t stopUnix = atoll(targetParams["stopunix"].c_str()) * 1000;
if (stopUnix < 0){stopUnix += Util::epoch();} // If the time is before the first 10 hours of unix epoch, assume relative time
if (stopUnix < unixStreamBegin){ if (stopUnix <= 36000000){stopUnix += Util::unixMS();}
onFail("Stop time is earlier than stream begin - aborting", true); targetParams["stop"] = JSON::Value(stopUnix - zUTC).asString();
return;
}else{
targetParams["stop"] = JSON::Value((stopUnix - unixStreamBegin) * 1000).asString();
}
} }
} }
if (targetParams.count("stop")){
int64_t endRec = atoll(targetParams["stop"].c_str());
if (endRec < 0 || endRec < startTime()){
onFail("Entire range is in the past", true);
return;
}
INFO_MSG("Playback will stop at %" PRIu64, endRec);
}
if (targetParams.count("start") && atoll(targetParams["start"].c_str()) != 0){ if (targetParams.count("start") && atoll(targetParams["start"].c_str()) != 0){
size_t mainTrack = getMainSelectedTrack(); size_t mainTrack = getMainSelectedTrack();
int64_t startRec = atoll(targetParams["start"].c_str()); int64_t startRec = atoll(targetParams["start"].c_str());
@ -1137,11 +1166,11 @@ namespace Mist{
} }
int64_t streamAvail = M.getNowms(mainTrack); int64_t streamAvail = M.getNowms(mainTrack);
int64_t lastUpdated = Util::getMS(); int64_t lastUpdated = Util::getMS();
INFO_MSG("Waiting for stream to reach playback starting point. Current last ms is '%" PRIu64 "'", streamAvail); INFO_MSG("Waiting for stream to reach playback starting point (%" PRIu64 " -> %" PRIu64 "). Time left: " PRETTY_PRINT_MSTIME, startRec, streamAvail, PRETTY_ARG_MSTIME(startRec - streamAvail));
while (Util::getMS() - lastUpdated < 5000 && startRec > streamAvail && keepGoing()){ while (Util::getMS() - lastUpdated < 5000 && startRec > streamAvail && keepGoing()){
Util::sleep(500); Util::sleep(500);
if (M.getNowms(mainTrack) > streamAvail){ if (M.getNowms(mainTrack) > streamAvail){
HIGH_MSG("Waiting for stream to reach playback starting point. Current last ms is '%" PRIu64 "'", streamAvail); HIGH_MSG("Waiting for stream to reach playback starting point (%" PRIu64 " -> %" PRIu64 "). Time left: " PRETTY_PRINT_MSTIME, startRec, streamAvail, PRETTY_ARG_MSTIME(startRec - streamAvail));
stats(); stats();
streamAvail = M.getNowms(mainTrack); streamAvail = M.getNowms(mainTrack);
lastUpdated = Util::getMS(); lastUpdated = Util::getMS();
@ -1154,10 +1183,54 @@ namespace Mist{
startRec, startTime()); startRec, startTime());
startRec = startTime(); startRec = startTime();
} }
INFO_MSG("Playback will start at %" PRIu64, startRec); if (M.getType(mainTrack) == "video"){
seekPos = startRec; seekPos = M.getTimeForKeyIndex(mainTrack, M.getKeyIndexForTime(mainTrack, startRec));
if (seekPos != startRec){
INFO_MSG("Shifting recording start from %" PRIu64 " to %" PRIu64 " so that it starts with a keyframe", startRec, seekPos);
}
}else{
seekPos = startRec;
}
INFO_MSG("Playback will start at %" PRIu64, seekPos);
}
// Duration to record in seconds. Overrides stop.
if (targetParams.count("duration")){
int64_t endRec;
if (targetParams.count("start")){
endRec = atoll(targetParams["start"].c_str()) + atoll(targetParams["duration"].c_str()) * 1000;
}else{
endRec = seekPos + atoll(targetParams["duration"].c_str()) * 1000;
}
targetParams["stop"] = JSON::Value(endRec).asString();
}
if (targetParams.count("stop")){
int64_t endRec = atoll(targetParams["stop"].c_str());
if (endRec < 0 || endRec < startTime()){
onFail("Entire range is in the past", true);
return;
}
INFO_MSG("Playback will stop at %" PRIu64, endRec);
}
// If we have a stop position and it's within available range,
// apply a limiter to the stream to make it appear like a VoD asset
if (targetParams.count("stop") || !M.getLive()){
size_t mainTrack = getMainSelectedTrack();
uint64_t stopPos = M.getLastms(mainTrack);
if (targetParams.count("stop")){stopPos = atoll(targetParams["stop"].c_str());}
if (!M.getLive() || stopPos <= M.getLastms(mainTrack)){
meta.applyLimiter(seekPos, stopPos);
}else{
// End point past end of track? Don't limit the end point.
meta.applyLimiter(seekPos, 0xFFFFFFFFFFFFFFFFull);
}
}else{
// No stop point, only apply limiter if a start point is set, and never limit the end point.
if (targetParams.count("start")){
meta.applyLimiter(seekPos, 0xFFFFFFFFFFFFFFFFull);
}
} }
} }
if (dryRun){return;}
/*LTS-END*/ /*LTS-END*/
if (!keepGoing()){ if (!keepGoing()){
ERROR_MSG("Aborting seek to %" PRIu64 " since the stream is no longer active", seekPos); ERROR_MSG("Aborting seek to %" PRIu64 " since the stream is no longer active", seekPos);
@ -1581,11 +1654,11 @@ namespace Mist{
break; break;
} }
} }
if (!sought){initialSeek();}
if (!sentHeader && keepGoing()){ if (!sentHeader && keepGoing()){
DONTEVEN_MSG("sendHeader"); DONTEVEN_MSG("sendHeader");
sendHeader(); sendHeader();
} }
if (!sought){initialSeek();}
if (prepareNext()){ if (prepareNext()){
if (thisPacket){ if (thisPacket){
lastPacketTime = thisTime; lastPacketTime = thisTime;

View file

@ -60,7 +60,7 @@ namespace Mist{
virtual void dropTrack(size_t trackId, const std::string &reason, bool probablyBad = true); virtual void dropTrack(size_t trackId, const std::string &reason, bool probablyBad = true);
virtual void onRequest(); virtual void onRequest();
static void listener(Util::Config &conf, int (*callback)(Socket::Connection &S)); static void listener(Util::Config &conf, int (*callback)(Socket::Connection &S));
virtual void initialSeek(); virtual void initialSeek(bool dryRun = false);
uint64_t getMinKeepAway(); uint64_t getMinKeepAway();
virtual bool liveSeek(bool rateOnly = false); virtual bool liveSeek(bool rateOnly = false);
virtual bool onFinish(){return false;} virtual bool onFinish(){return false;}

View file

@ -26,13 +26,14 @@ namespace Mist{
cfg->addOption("target", opt); cfg->addOption("target", opt);
} }
void OutAAC::initialSeek(){ void OutAAC::initialSeek(bool dryRun){
if (!meta){return;} if (!meta){return;}
maxSkipAhead = 30000; maxSkipAhead = 30000;
if (targetParams.count("buffer")){ if (targetParams.count("buffer")){
maxSkipAhead = atof(targetParams["buffer"].c_str())*1000; maxSkipAhead = atof(targetParams["buffer"].c_str())*1000;
} }
Output::initialSeek(); Output::initialSeek(dryRun);
if (dryRun){return;}
uint64_t cTime = currentTime(); uint64_t cTime = currentTime();
if (M.getLive() && cTime > maxSkipAhead){ if (M.getLive() && cTime > maxSkipAhead){
seek(cTime-maxSkipAhead); seek(cTime-maxSkipAhead);

View file

@ -7,7 +7,7 @@ namespace Mist{
static void init(Util::Config *cfg); static void init(Util::Config *cfg);
void respondHTTP(const HTTP::Parser & req, bool headersOnly); void respondHTTP(const HTTP::Parser & req, bool headersOnly);
void sendNext(); void sendNext();
void initialSeek(); void initialSeek(bool dryRun = false);
private: private:
virtual bool inlineRestartCapable() const{return true;} virtual bool inlineRestartCapable() const{return true;}

View file

@ -64,9 +64,11 @@ namespace Mist{
for (std::map<size_t, Comms::Users>::iterator it = userSelect.begin(); for (std::map<size_t, Comms::Users>::iterator it = userSelect.begin();
it != userSelect.end(); it++){ it != userSelect.end(); it++){
if (M.getType(it->first) == "video" && tag.DTSCVideoInit(meta, it->first)){ if (M.getType(it->first) == "video" && tag.DTSCVideoInit(meta, it->first)){
tag.tagTime(thisTime);
myConn.SendNow(tag.data, tag.len); myConn.SendNow(tag.data, tag.len);
} }
if (M.getType(it->first) == "audio" && tag.DTSCAudioInit(meta.getCodec(it->first), meta.getRate(it->first), meta.getSize(it->first), meta.getChannels(it->first), meta.getInit(it->first))){ if (M.getType(it->first) == "audio" && tag.DTSCAudioInit(meta.getCodec(it->first), meta.getRate(it->first), meta.getSize(it->first), meta.getChannels(it->first), meta.getInit(it->first))){
tag.tagTime(thisTime);
myConn.SendNow(tag.data, tag.len); myConn.SendNow(tag.data, tag.len);
} }
} }
@ -114,12 +116,15 @@ namespace Mist{
selectedTracks.insert(it->first); selectedTracks.insert(it->first);
} }
tag.DTSCMetaInit(M, selectedTracks); tag.DTSCMetaInit(M, selectedTracks);
tag.tagTime(startTime());
myConn.SendNow(tag.data, tag.len); myConn.SendNow(tag.data, tag.len);
for (std::set<size_t>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ for (std::set<size_t>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){
if (M.getType(*it) == "video" && tag.DTSCVideoInit(meta, *it)){ if (M.getType(*it) == "video" && tag.DTSCVideoInit(meta, *it)){
tag.tagTime(startTime());
myConn.SendNow(tag.data, tag.len); myConn.SendNow(tag.data, tag.len);
} }
if (M.getType(*it) == "audio" && tag.DTSCAudioInit(meta.getCodec(*it), meta.getRate(*it), meta.getSize(*it), meta.getChannels(*it), meta.getInit(*it))){ if (M.getType(*it) == "audio" && tag.DTSCAudioInit(meta.getCodec(*it), meta.getRate(*it), meta.getSize(*it), meta.getChannels(*it), meta.getInit(*it))){
tag.tagTime(startTime());
myConn.SendNow(tag.data, tag.len); myConn.SendNow(tag.data, tag.len);
} }
} }

View file

@ -30,6 +30,14 @@ namespace Mist{
} }
std::string tknStr; std::string tknStr;
if (tkn.size() && Comms::tknMode & 0x04){tknStr = "?tkn=" + tkn;} if (tkn.size() && Comms::tknMode & 0x04){tknStr = "?tkn=" + tkn;}
if (targetParams.count("start")){
if (tknStr.size()){ tknStr += "&"; }else{ tknStr = "?"; }
tknStr += "start="+targetParams["start"];
}
if (targetParams.count("stop")){
if (tknStr.size()){ tknStr += "&"; }else{ tknStr = "?"; }
tknStr += "stop="+targetParams["stop"];
}
for (std::map<size_t, Comms::Users>::iterator it = userSelect.begin(); it != userSelect.end(); ++it){ for (std::map<size_t, Comms::Users>::iterator it = userSelect.begin(); it != userSelect.end(); ++it){
if (M.getType(it->first) == "video"){ if (M.getType(it->first) == "video"){
++vidTracks; ++vidTracks;
@ -102,6 +110,9 @@ namespace Mist{
size_t keyNumber = fragments.getFirstKey(i); size_t keyNumber = fragments.getFirstKey(i);
uint64_t startTime = keys.getTime(keyNumber); uint64_t startTime = keys.getTime(keyNumber);
if (!duration){duration = M.getLastms(timingTid) - startTime;} if (!duration){duration = M.getLastms(timingTid) - startTime;}
if (startTime + duration < M.getFirstms(timingTid)){continue;}
if (startTime >= M.getLastms(timingTid)){continue;}
if (startTime + duration > M.getLastms(timingTid)){duration = M.getLastms(timingTid) - startTime;}
double floatDur = (double)duration / 1000; double floatDur = (double)duration / 1000;
char lineBuf[400]; char lineBuf[400];
@ -373,6 +384,7 @@ namespace Mist{
ts_from = from; ts_from = from;
}else{ }else{
initialize(); initialize();
initialSeek(true);
std::string request = H.url.substr(H.url.find("/", 5) + 1); std::string request = H.url.substr(H.url.find("/", 5) + 1);
H.setCORSHeaders(); H.setCORSHeaders();
H.SetHeader("Content-Type", "application/vnd.apple.mpegurl"); H.SetHeader("Content-Type", "application/vnd.apple.mpegurl");

View file

@ -352,6 +352,7 @@ namespace Mist{
if (H.GetVar("stop") != ""){targetParams["stop"] = H.GetVar("stop");} if (H.GetVar("stop") != ""){targetParams["stop"] = H.GetVar("stop");}
if (H.GetVar("startunix") != ""){targetParams["startunix"] = H.GetVar("startunix");} if (H.GetVar("startunix") != ""){targetParams["startunix"] = H.GetVar("startunix");}
if (H.GetVar("stopunix") != ""){targetParams["stopunix"] = H.GetVar("stopunix");} if (H.GetVar("stopunix") != ""){targetParams["stopunix"] = H.GetVar("stopunix");}
if (H.GetVar("duration") != ""){targetParams["duration"] = H.GetVar("duration");}
// allow setting of max lead time through buffer variable. // allow setting of max lead time through buffer variable.
// max lead time is set in MS, but the variable is in integer seconds for simplicity. // max lead time is set in MS, but the variable is in integer seconds for simplicity.
if (H.GetVar("buffer") != ""){ if (H.GetVar("buffer") != ""){

View file

@ -52,10 +52,10 @@ namespace Mist{
OutHTTPTS::~OutHTTPTS(){} OutHTTPTS::~OutHTTPTS(){}
void OutHTTPTS::initialSeek(){ void OutHTTPTS::initialSeek(bool dryRun){
// Adds passthrough support to the regular initialSeek function // Adds passthrough support to the regular initialSeek function
if (targetParams.count("passthrough")){selectAllTracks();} if (targetParams.count("passthrough")){selectAllTracks();}
Output::initialSeek(); Output::initialSeek(dryRun);
} }
void OutHTTPTS::init(Util::Config *cfg){ void OutHTTPTS::init(Util::Config *cfg){

View file

@ -9,7 +9,7 @@ namespace Mist{
static void init(Util::Config *cfg); static void init(Util::Config *cfg);
void onHTTP(); void onHTTP();
void sendTS(const char *tsData, size_t len = 188); void sendTS(const char *tsData, size_t len = 188);
void initialSeek(); void initialSeek(bool dryRun = false);
private: private:
bool isRecording(); bool isRecording();

View file

@ -60,7 +60,7 @@ namespace Mist{
/// Pretends the stream is always ready to play - we don't care about waiting times or whatever /// Pretends the stream is always ready to play - we don't care about waiting times or whatever
bool OutJPG::isReadyForPlay(){return true;} bool OutJPG::isReadyForPlay(){return true;}
void OutJPG::initialSeek(){ void OutJPG::initialSeek(bool dryRun){
size_t mainTrack = getMainSelectedTrack(); size_t mainTrack = getMainSelectedTrack();
if (mainTrack == INVALID_TRACK_ID){return;} if (mainTrack == INVALID_TRACK_ID){return;}
INFO_MSG("Doing initial seek"); INFO_MSG("Doing initial seek");

View file

@ -10,7 +10,7 @@ namespace Mist{
private: private:
void generate(); void generate();
void initialSeek(); void initialSeek(bool dryRun = false);
void NoFFMPEG(); void NoFFMPEG();
std::string cachedir; std::string cachedir;
uint64_t cachetime; uint64_t cachetime;

View file

@ -152,7 +152,7 @@ namespace Mist{
uint64_t OutMP4::estimateFileSize() const{ uint64_t OutMP4::estimateFileSize() const{
uint64_t retVal = 0; uint64_t retVal = 0;
for (std::map<size_t, Comms::Users>::const_iterator it = userSelect.begin(); it != userSelect.end(); it++){ for (std::map<size_t, Comms::Users>::const_iterator it = userSelect.begin(); it != userSelect.end(); it++){
DTSC::Keys keys(M.keys(it->first)); DTSC::Keys keys = M.getKeys(it->first);
size_t endKey = keys.getEndValid(); size_t endKey = keys.getEndValid();
for (size_t i = 0; i < endKey; i++){ for (size_t i = 0; i < endKey; i++){
retVal += keys.getSize(i); // Handle number as index, faster for VoD retVal += keys.getSize(i); // Handle number as index, faster for VoD
@ -178,9 +178,8 @@ namespace Mist{
subIt != userSelect.end(); subIt++){ subIt != userSelect.end(); subIt++){
tmpRes += 8 + 20; // TRAF + TFHD Box tmpRes += 8 + 20; // TRAF + TFHD Box
DTSC::Keys keys(M.keys(subIt->first)); DTSC::Keys keys = M.getKeys(subIt->first);
DTSC::Parts parts(M.parts(subIt->first)); DTSC::Parts parts(M.parts(subIt->first));
DTSC::Fragments fragments(M.fragments(subIt->first));
uint32_t startKey = M.getKeyIndexForTime(subIt->first, startFragmentTime); uint32_t startKey = M.getKeyIndexForTime(subIt->first, startFragmentTime);
uint32_t endKey = M.getKeyIndexForTime(subIt->first, endFragmentTime) + 1; uint32_t endKey = M.getKeyIndexForTime(subIt->first, endFragmentTime) + 1;
@ -233,8 +232,9 @@ namespace Mist{
for (std::map<size_t, Comms::Users>::const_iterator it = userSelect.begin(); it != userSelect.end(); it++){ for (std::map<size_t, Comms::Users>::const_iterator it = userSelect.begin(); it != userSelect.end(); it++){
const std::string tType = M.getType(it->first); const std::string tType = M.getType(it->first);
uint64_t tmpRes = 0; uint64_t tmpRes = 0;
DTSC::Keys keys = M.getKeys(it->first);
DTSC::Parts parts(M.parts(it->first)); DTSC::Parts parts(M.parts(it->first));
uint64_t partCount = parts.getValidCount(); uint64_t partCount = keys.getTotalPartCount();
tmpRes += 8 + 92 // TRAK + TKHD Boxes tmpRes += 8 + 92 // TRAK + TKHD Boxes
+ 36 // EDTS Box + 36 // EDTS Box
@ -260,7 +260,6 @@ namespace Mist{
+ 16 // PASP + 16 // PASP
+ 8 + M.getInit(it->first).size(); // avcC + 8 + M.getInit(it->first).size(); // avcC
if (!fragmented){ if (!fragmented){
DTSC::Keys keys(M.keys(it->first));
tmpRes += 16 + (keys.getValidCount() * 4); // STSS tmpRes += 16 + (keys.getValidCount() * 4); // STSS
} }
} }
@ -283,16 +282,17 @@ namespace Mist{
if (!fragmented){ if (!fragmented){
// Unfortunately, for our STTS and CTTS boxes, we need to loop through all parts of the // Unfortunately, for our STTS and CTTS boxes, we need to loop through all parts of the
// track // track
size_t firstPart = keys.getFirstPart(keys.getFirstValid());
uint64_t sttsCount = 1; uint64_t sttsCount = 1;
uint64_t prevDur = parts.getDuration(0); uint64_t prevDur = parts.getDuration(firstPart);
uint64_t prevOffset = parts.getOffset(0); uint64_t prevOffset = parts.getOffset(firstPart);
uint64_t cttsCount = 1; uint64_t cttsCount = 1;
fileSize += parts.getSize(0); fileSize += parts.getSize(firstPart);
bool isMeta = (tType == "meta"); bool isMeta = (tType == "meta");
for (unsigned int part = 1; part < partCount; ++part){ for (unsigned int part = 1; part < partCount; ++part){
uint64_t partDur = parts.getDuration(part); uint64_t partDur = parts.getDuration(firstPart + part);
uint64_t partOffset = parts.getOffset(part); uint64_t partOffset = parts.getOffset(firstPart + part);
uint64_t partSize = parts.getSize(part)+(isMeta?2:0); uint64_t partSize = parts.getSize(firstPart + part)+(isMeta?2:0);
if (prevDur != partDur){ if (prevDur != partDur){
prevDur = partDur; prevDur = partDur;
++sttsCount; ++sttsCount;
@ -397,7 +397,8 @@ namespace Mist{
for (std::map<size_t, Comms::Users>::const_iterator it = userSelect.begin(); it != userSelect.end(); it++){ for (std::map<size_t, Comms::Users>::const_iterator it = userSelect.begin(); it != userSelect.end(); it++){
if (prevVidTrack != INVALID_TRACK_ID && it->first == prevVidTrack){continue;} if (prevVidTrack != INVALID_TRACK_ID && it->first == prevVidTrack){continue;}
DTSC::Parts parts(M.parts(it->first)); DTSC::Parts parts(M.parts(it->first));
size_t partCount = parts.getValidCount(); DTSC::Keys keys = M.getKeys(it->first);
size_t partCount = keys.getTotalPartCount();
uint64_t tDuration = M.getLastms(it->first) - M.getFirstms(it->first); uint64_t tDuration = M.getLastms(it->first) - M.getFirstms(it->first);
std::string tType = M.getType(it->first); std::string tType = M.getType(it->first);
@ -522,26 +523,28 @@ namespace Mist{
MP4::CTTS cttsBox; MP4::CTTS cttsBox;
cttsBox.setVersion(0); cttsBox.setVersion(0);
size_t firstPart = keys.getFirstPart(keys.getFirstValid());
size_t totalEntries = 0; size_t totalEntries = 0;
MP4::CTTSEntry tmpEntry; MP4::CTTSEntry tmpEntry;
tmpEntry.sampleCount = 0; tmpEntry.sampleCount = 0;
tmpEntry.sampleOffset = parts.getOffset(0); tmpEntry.sampleOffset = parts.getOffset(firstPart);
size_t sttsCounter = 0; size_t sttsCounter = 0;
MP4::STTSEntry sttsEntry; MP4::STTSEntry sttsEntry;
sttsEntry.sampleCount = 0; sttsEntry.sampleCount = 0;
sttsEntry.sampleDelta = parts.getSize(0);; sttsEntry.sampleDelta = parts.getSize(firstPart);
//Calculate amount of entries for CTTS/STTS boxes so we can set the last entry first //Calculate amount of entries for CTTS/STTS boxes so we can set the last entry first
//Our MP4 box implementations dynamically reallocate to fit the data you put inside them, //Our MP4 box implementations dynamically reallocate to fit the data you put inside them,
//Which means setting the last entry first prevents constant reallocs and slowness. //Which means setting the last entry first prevents constant reallocs and slowness.
for (size_t part = 0; part < partCount; ++part){ for (size_t part = 0; part < partCount; ++part){
uint64_t partOffset = parts.getOffset(part); uint64_t partOffset = parts.getOffset(firstPart + part);
if (partOffset != tmpEntry.sampleOffset){ if (partOffset != tmpEntry.sampleOffset){
++totalEntries; ++totalEntries;
tmpEntry.sampleOffset = partOffset; tmpEntry.sampleOffset = partOffset;
} }
uint64_t partDur = parts.getDuration(part); uint64_t partDur = parts.getDuration(firstPart + part);
if (partDur != sttsEntry.sampleDelta){ if (partDur != sttsEntry.sampleDelta){
++sttsCounter; ++sttsCounter;
sttsEntry.sampleDelta = partDur; sttsEntry.sampleDelta = partDur;
@ -562,14 +565,14 @@ namespace Mist{
//Reset the values we just used, first. //Reset the values we just used, first.
totalEntries = 0; totalEntries = 0;
tmpEntry.sampleCount = 0; tmpEntry.sampleCount = 0;
tmpEntry.sampleOffset = parts.getOffset(0); tmpEntry.sampleOffset = parts.getOffset(firstPart);
sttsCounter = 0; sttsCounter = 0;
sttsEntry.sampleCount = 0; sttsEntry.sampleCount = 0;
sttsEntry.sampleDelta = parts.getDuration(0); sttsEntry.sampleDelta = parts.getDuration(firstPart);
bool isMeta = (tType == "meta"); bool isMeta = (tType == "meta");
for (size_t part = 0; part < partCount; ++part){ for (size_t part = 0; part < partCount; ++part){
uint64_t partDur = parts.getDuration(part); uint64_t partDur = parts.getDuration(firstPart + part);
if (sttsEntry.sampleDelta != partDur){ if (sttsEntry.sampleDelta != partDur){
// If the duration of this and previous part differ, write current values and reset // If the duration of this and previous part differ, write current values and reset
sttsBox.setSTTSEntry(sttsEntry, sttsCounter++); sttsBox.setSTTSEntry(sttsEntry, sttsCounter++);
@ -578,12 +581,12 @@ namespace Mist{
} }
sttsEntry.sampleCount++; sttsEntry.sampleCount++;
uint64_t partSize = parts.getSize(part)+(isMeta?2:0); uint64_t partSize = parts.getSize(firstPart + part)+(isMeta?2:0);
stszBox.setEntrySize(partSize, part); stszBox.setEntrySize(partSize, part);
size += partSize; size += partSize;
if (hasCTTS){ if (hasCTTS){
uint64_t partOffset = parts.getOffset(part); uint64_t partOffset = parts.getOffset(firstPart + part);
if (partOffset != tmpEntry.sampleOffset){ if (partOffset != tmpEntry.sampleOffset){
// If the offset of this and previous part differ, write current values and reset // If the offset of this and previous part differ, write current values and reset
cttsBox.setCTTSEntry(tmpEntry, totalEntries++); cttsBox.setCTTSEntry(tmpEntry, totalEntries++);
@ -610,11 +613,10 @@ namespace Mist{
if (tType == "video" && !fragmented){ if (tType == "video" && !fragmented){
MP4::STSS stssBox(0); MP4::STSS stssBox(0);
size_t tmpCount = 0; size_t tmpCount = 0;
DTSC::Keys keys(M.keys(it->first)); size_t firstKey = keys.getFirstValid();
uint32_t firstKey = keys.getFirstValid(); size_t endKey = keys.getEndValid();
uint32_t endKey = keys.getEndValid();
for (size_t i = firstKey; i < endKey; ++i){ for (size_t i = firstKey; i < endKey; ++i){
stssBox.setSampleNumber(tmpCount + 1, i); stssBox.setSampleNumber(tmpCount + 1, i-firstKey);
tmpCount += keys.getParts(i); tmpCount += keys.getParts(i);
} }
stblBox.setContent(stssBox, stblOffset++); stblBox.setContent(stssBox, stblOffset++);
@ -714,8 +716,11 @@ namespace Mist{
if (prevVidTrack != INVALID_TRACK_ID && subIt->first == prevVidTrack){continue;} if (prevVidTrack != INVALID_TRACK_ID && subIt->first == prevVidTrack){continue;}
keyPart temp; keyPart temp;
temp.trackID = subIt->first; temp.trackID = subIt->first;
temp.time = M.getFirstms(subIt->first);
temp.index = 0; DTSC::Keys keys = M.getKeys(subIt->first);
temp.time = keys.getTime(keys.getFirstValid());
temp.index = keys.getFirstPart(keys.getFirstValid());
temp.firstIndex = temp.index;
sortSet.insert(temp); sortSet.insert(temp);
} }
while (!sortSet.empty()){ while (!sortSet.empty()){
@ -727,16 +732,15 @@ namespace Mist{
DTSC::Parts & parts = *tL.parts; DTSC::Parts & parts = *tL.parts;
// setting the right STCO size in the STCO box // setting the right STCO size in the STCO box
if (useLargeBoxes){// Re-using the previously defined boolean for speedup if (useLargeBoxes){// Re-using the previously defined boolean for speedup
tL.co64Box.setChunkOffset(dataOffset + dataSize, temp.index); tL.co64Box.setChunkOffset(dataOffset + dataSize, temp.index - temp.firstIndex);
}else{ }else{
tL.stcoBox.setChunkOffset(dataOffset + dataSize, temp.index); tL.stcoBox.setChunkOffset(dataOffset + dataSize, temp.index - temp.firstIndex);
} }
dataSize += parts.getSize(temp.index); dataSize += parts.getSize(temp.index);
if (M.getType(temp.trackID) == "meta"){dataSize += 2;} if (M.getType(temp.trackID) == "meta"){dataSize += 2;}
// add next keyPart to sortSet // add next keyPart to sortSet, if we have not yet reached the end time
if (temp.index + 1 < parts.getEndValid()){// Only create new element, when there are new if (temp.time + parts.getDuration(temp.index) < M.getLastms(temp.trackID)){
// elements to be added
temp.time += parts.getDuration(temp.index); temp.time += parts.getDuration(temp.index);
++temp.index; ++temp.index;
sortSet.insert(temp); sortSet.insert(temp);
@ -762,7 +766,6 @@ namespace Mist{
/// Calculate a seekPoint, based on byteStart, metadata, tracks and headerSize. /// Calculate a seekPoint, based on byteStart, metadata, tracks and headerSize.
/// The seekPoint will be set to the timestamp of the first packet to send. /// The seekPoint will be set to the timestamp of the first packet to send.
void OutMP4::findSeekPoint(uint64_t byteStart, uint64_t &seekPoint, uint64_t headerSize){ void OutMP4::findSeekPoint(uint64_t byteStart, uint64_t &seekPoint, uint64_t headerSize){
seekPoint = 0;
// if we're starting in the header, seekPoint is always zero. // if we're starting in the header, seekPoint is always zero.
if (byteStart <= headerSize){return;} if (byteStart <= headerSize){return;}
// okay, we're past the header. Substract the headersize from the starting postion. // okay, we're past the header. Substract the headersize from the starting postion.
@ -792,7 +795,7 @@ namespace Mist{
// otherwise, set currPos to where we are now and continue // otherwise, set currPos to where we are now and continue
currPos += partSize; currPos += partSize;
if (temp.index + 1 < parts.getEndValid()){// only insert when there are parts left if (temp.time + parts.getDuration(temp.index) < M.getLastms(temp.trackID)){// only insert when there are parts left
temp.time += parts.getDuration(temp.index); temp.time += parts.getDuration(temp.index);
++temp.index; ++temp.index;
sortSet.insert(temp); sortSet.insert(temp);
@ -924,9 +927,8 @@ namespace Mist{
for (std::map<size_t, Comms::Users>::const_iterator subIt = userSelect.begin(); for (std::map<size_t, Comms::Users>::const_iterator subIt = userSelect.begin();
subIt != userSelect.end(); subIt++){ subIt != userSelect.end(); subIt++){
DTSC::Keys keys(M.keys(subIt->first)); DTSC::Keys keys = M.getKeys(subIt->first);
DTSC::Parts parts(M.parts(subIt->first)); DTSC::Parts parts(M.parts(subIt->first));
DTSC::Fragments fragments(M.fragments(subIt->first));
uint32_t startKey = M.getKeyIndexForTime(subIt->first, startFragmentTime); uint32_t startKey = M.getKeyIndexForTime(subIt->first, startFragmentTime);
uint32_t endKey = M.getKeyIndexForTime(subIt->first, endFragmentTime) + 1; uint32_t endKey = M.getKeyIndexForTime(subIt->first, endFragmentTime) + 1;
@ -1063,6 +1065,7 @@ namespace Mist{
void OutMP4::respondHTTP(const HTTP::Parser & req, bool headersOnly){ void OutMP4::respondHTTP(const HTTP::Parser & req, bool headersOnly){
//Set global defaults, first //Set global defaults, first
HTTPOutput::respondHTTP(req, headersOnly); HTTPOutput::respondHTTP(req, headersOnly);
initialSeek();
H.SetHeader("Content-Type", "video/MP4"); H.SetHeader("Content-Type", "video/MP4");
if (!M.getLive()){H.SetHeader("Accept-Ranges", "bytes, parsec");} if (!M.getLive()){H.SetHeader("Accept-Ranges", "bytes, parsec");}
@ -1077,50 +1080,13 @@ namespace Mist{
return; return;
} }
DTSC::Fragments fragments(M.fragments(mainTrack));
if (req.GetVar("startfrag") != ""){
realTime = 0;
size_t startFrag = JSON::Value(req.GetVar("startfrag")).asInt();
if (startFrag >= fragments.getFirstValid() && startFrag < fragments.getEndValid()){
startTime = M.getTimeForFragmentIndex(mainTrack, startFrag);
// Set endTime to one fragment further, can receive override from next parameter check
if (startFrag + 1 < fragments.getEndValid()){
endTime = M.getTimeForFragmentIndex(mainTrack, startFrag + 1);
}else{
endTime = M.getLastms(mainTrack);
}
}else{
startTime = M.getLastms(mainTrack);
}
}
if (req.GetVar("endfrag") != ""){
size_t endFrag = JSON::Value(req.GetVar("endfrag")).asInt();
if (endFrag < fragments.getEndValid()){
endTime = M.getTimeForFragmentIndex(mainTrack, endFrag);
}else{
endTime = M.getLastms(mainTrack);
}
}
if (req.GetVar("starttime") != ""){
startTime = std::max((uint64_t)JSON::Value(req.GetVar("starttime")).asInt(), M.getFirstms(mainTrack));
}
if (req.GetVar("endtime") != ""){
endTime = std::min((uint64_t)JSON::Value(req.GetVar("endtime")).asInt(), M.getLastms(mainTrack));
}
// Check if the url contains .3gp --> if yes, we will send a 3gp header // Check if the url contains .3gp --> if yes, we will send a 3gp header
sending3GP = (H.url.find(".3gp") != std::string::npos); sending3GP = (req.url.find(".3gp") != std::string::npos);
fileSize = 0; fileSize = 0;
headerSize = mp4HeaderSize(fileSize, M.getLive()); headerSize = mp4HeaderSize(fileSize, M.getLive());
seekPoint = 0; seekPoint = Output::startTime();
// for live we use fragmented mode // for live we use fragmented mode
if (M.getLive()){fragSeqNum = 0;} if (M.getLive()){fragSeqNum = 0;}
@ -1129,8 +1095,9 @@ namespace Mist{
subIt != userSelect.end(); subIt++){ subIt != userSelect.end(); subIt++){
keyPart temp; keyPart temp;
temp.trackID = subIt->first; temp.trackID = subIt->first;
temp.time = M.getFirstms(subIt->first); DTSC::Keys keys = M.getKeys(subIt->first);
temp.index = 0; temp.time = keys.getTime(keys.getFirstValid());
temp.index = keys.getFirstPart(keys.getFirstValid());
sortSet.insert(temp); sortSet.insert(temp);
} }
@ -1314,7 +1281,7 @@ namespace Mist{
keyPart temp = *sortSet.begin(); keyPart temp = *sortSet.begin();
sortSet.erase(sortSet.begin()); sortSet.erase(sortSet.begin());
currPos += parts.getSize(temp.index); currPos += parts.getSize(temp.index);
if (temp.index + 1 < parts.getEndValid()){// only insert when there are parts left if (temp.time + parts.getDuration(temp.index) < M.getLastms(temp.trackID)){// only insert when there are parts left
temp.time += parts.getDuration(temp.index); temp.time += parts.getDuration(temp.index);
++temp.index; ++temp.index;
sortSet.insert(temp); sortSet.insert(temp);

View file

@ -15,6 +15,7 @@ namespace Mist{
uint64_t time; uint64_t time;
uint64_t byteOffset; // Stores relative bpos for fragmented MP4 uint64_t byteOffset; // Stores relative bpos for fragmented MP4
uint64_t index; uint64_t index;
uint64_t firstIndex;
size_t sampleSize; size_t sampleSize;
uint16_t sampleDuration; uint16_t sampleDuration;
uint16_t sampleOffset; uint16_t sampleOffset;

View file

@ -216,10 +216,10 @@ namespace Mist{
config->addOption("datatrack", opt); config->addOption("datatrack", opt);
} }
void OutTS::initialSeek(){ void OutTS::initialSeek(bool dryRun){
// Adds passthrough support to the regular initialSeek function // Adds passthrough support to the regular initialSeek function
if (targetParams.count("passthrough")){selectAllTracks();} if (targetParams.count("passthrough")){selectAllTracks();}
Output::initialSeek(); Output::initialSeek(dryRun);
} }
void OutTS::sendTS(const char *tsData, size_t len){ void OutTS::sendTS(const char *tsData, size_t len){

View file

@ -9,7 +9,7 @@ namespace Mist{
static void init(Util::Config *cfg); static void init(Util::Config *cfg);
void sendTS(const char *tsData, size_t len = 188); void sendTS(const char *tsData, size_t len = 188);
static bool listenMode(); static bool listenMode();
virtual void initialSeek(); virtual void initialSeek(bool dryRun = false);
bool isReadyForPlay(); bool isReadyForPlay();
void onRequest(); void onRequest();
std::string getConnectedHost(); std::string getConnectedHost();