hls_support: A new library for (LL)HLS manifest generation
This commit is contained in:
parent
dac00f6555
commit
c54690d346
3 changed files with 876 additions and 1 deletions
|
@ -145,11 +145,15 @@ if (NOCRASHCHECK)
|
|||
add_definitions(-DNOCRASHCHECK=1)
|
||||
endif()
|
||||
|
||||
|
||||
if (DEFINED STATS_DELAY)
|
||||
add_definitions(-DSTATS_DELAY=${STATS_DELAY})
|
||||
endif()
|
||||
|
||||
option(NOLLHLS "Disable LLHLS")
|
||||
if (NOLLHLS)
|
||||
add_definitions(-DNOLLHLS=1)
|
||||
endif()
|
||||
|
||||
########################################
|
||||
# Build Variables - Prepare for Build #
|
||||
########################################
|
||||
|
@ -193,6 +197,7 @@ set(libHeaders
|
|||
lib/flv_tag.h
|
||||
lib/h264.h
|
||||
lib/h265.h
|
||||
lib/hls_support.h
|
||||
lib/http_parser.h
|
||||
lib/downloader.h
|
||||
lib/json.h
|
||||
|
@ -260,6 +265,7 @@ add_library (mist
|
|||
lib/flv_tag.cpp
|
||||
lib/h264.cpp
|
||||
lib/h265.cpp
|
||||
lib/hls_support.cpp
|
||||
lib/http_parser.cpp
|
||||
lib/downloader.cpp
|
||||
lib/json.cpp
|
||||
|
|
785
lib/hls_support.cpp
Normal file
785
lib/hls_support.cpp
Normal file
|
@ -0,0 +1,785 @@
|
|||
#include "hls_support.h"
|
||||
#include "langcodes.h" /*LTS*/
|
||||
#include "stream.h"
|
||||
#include <cstdlib>
|
||||
#include <iomanip>
|
||||
|
||||
namespace HLS{
|
||||
|
||||
// TODO: Prefix could be made better
|
||||
// Needed for grouping renditions in master manifest
|
||||
const std::string groupIdPrefix = "vid-";
|
||||
|
||||
// max partial fragment duration in s
|
||||
const float partDurationMax = (partDurationMaxMs + 4) / 1000.0;
|
||||
|
||||
// TODO: Advance Part Limit
|
||||
/*If the _HLS_msn is greater than the Media Sequence Number of the last
|
||||
Media Segment in the current Playlist plus two, or if the _HLS_partIf the _HLS_msn is greater than
|
||||
the Media Sequence Number of the last Media Segment in the current Playlist plus two, or if the
|
||||
_HLS_part exceeds the last Partial Segment in the current Playlist by the Advance Part Limit, then
|
||||
the server SHOULD immediately return Bad Request, such as HTTP 400 exceeds the last Partial
|
||||
Segment in the current Playlist by the Advance Part Limit, then the server SHOULD immediately
|
||||
return Bad Request, such as HTTP 400*/
|
||||
// NOTE: Not implementing this gives freedom to play with jitter duration
|
||||
const uint32_t advancePartLimit =
|
||||
std::min((int)std::ceil(3.0 / partDurationMax), 3); // Ref: RFC8216, 6.2.5.2
|
||||
|
||||
const struct{
|
||||
bool pdu; ///< Playlist Delta Update
|
||||
bool pduV2; ///< TODO: Playlist Delta Update v2: skips EXT-X-DATERANGE
|
||||
bool bpr; ///< Blocking Playlist Reload
|
||||
bool parts; ///< Partial Fragments
|
||||
bool tags; ///< True if any of the above is true
|
||||
}serverSupport ={
|
||||
#ifdef NOLLHLS
|
||||
false, false, false, false, false,
|
||||
#else
|
||||
true, false, true, true, true,
|
||||
#endif
|
||||
};
|
||||
|
||||
/// lastms calculation incorporating jitter duration
|
||||
/// Always ensures the (lastms <= current time - jitter duration)
|
||||
uint64_t getLastms(const DTSC::Meta &M, const std::map<size_t, Comms::Users> &userSelect,
|
||||
const size_t trackIdx, const uint64_t streamStartTime){
|
||||
std::map<size_t, Comms::Users>::const_iterator it = userSelect.begin();
|
||||
uint64_t maxJitter = 0;
|
||||
u_int64_t minKeepAway = 0;
|
||||
for (; it != userSelect.end(); it++){
|
||||
minKeepAway = M.getMinKeepAway(it->first);
|
||||
if (minKeepAway > maxJitter){maxJitter = minKeepAway;}
|
||||
}
|
||||
return std::min(M.getLastms(trackIdx), Util::unixMS() - streamStartTime - minKeepAway);
|
||||
}
|
||||
|
||||
/// Calculate HLS media playlist version compatibility
|
||||
/// \return version number
|
||||
uint16_t calcManifestVersion(const std::string &hlsSkip){
|
||||
// Server and Client support skipping media segments
|
||||
if (serverSupport.tags && serverSupport.pdu && (hlsSkip.compare("YES") == 0)){return 9;}
|
||||
// Server and Client support skipping ext-x-daterange along with media segments
|
||||
if (serverSupport.tags && serverSupport.pduV2 && (hlsSkip.compare("v2") == 0)){return 10;}
|
||||
// Default, lowest version supported
|
||||
return 6;
|
||||
}
|
||||
|
||||
/// returns the main track id provided in master manifest if valid
|
||||
/// else returns the current valid main track id
|
||||
size_t getTimingTrackId(const DTSC::Meta &M, const std::string &mTrack, const size_t mSelTrack){
|
||||
return (mTrack.size() && (M.getValidTracks().count(atoll(mTrack.c_str()))))
|
||||
? atoll(mTrack.c_str())
|
||||
: mSelTrack;
|
||||
}
|
||||
|
||||
/// Return live edge fragment duration
|
||||
uint64_t getLastFragDur(const DTSC::Meta &M, const std::map<size_t, Comms::Users> &userSelect, const TrackData &trackData, const uint64_t hlsMsnNr,
|
||||
const DTSC::Fragments &fragments, const DTSC::Keys &keys){
|
||||
return std::min(
|
||||
getLastms(M, userSelect, trackData.timingTrackId, trackData.systemBoot + trackData.bootMsOffset),
|
||||
getLastms(M, userSelect, trackData.requestTrackId,
|
||||
trackData.systemBoot + trackData.bootMsOffset)) -
|
||||
keys.getTime(fragments.getFirstKey(hlsMsnNr));
|
||||
}
|
||||
|
||||
/// Waits until the requested fragment & partial fragment are available
|
||||
/// Returns 400 if specific part is requested without a specific MSN
|
||||
/// Returns 400 if requested MSN > the real live edge MSN plus two
|
||||
/// Returns 503 if time spent in BPR > 3x Target Duration
|
||||
uint32_t blockPlaylistReload(const DTSC::Meta &M, const std::map<size_t, Comms::Users> &userSelect, const TrackData &trackData,
|
||||
const HlsSpecData &hlsSpecData, const DTSC::Fragments &fragments,
|
||||
const DTSC::Keys &keys){
|
||||
// Return if forced noLLHLS
|
||||
if (trackData.noLLHLS){return 0;}
|
||||
|
||||
// Check BPR request validity
|
||||
if (hlsSpecData.hlsMsn.empty() && hlsSpecData.hlsPart.size()){return 400;}
|
||||
if (atol(hlsSpecData.hlsMsn.c_str()) > (fragments.getEndValid() - 1 + 2)){return 400;}
|
||||
|
||||
// BPR logic only if live & _HLS_msn requested
|
||||
if (trackData.isLive && hlsSpecData.hlsMsn.size()){
|
||||
DEBUG_MSG(5, "Requesting media playlist: Track %zu, MSN %s, part: %s",
|
||||
trackData.timingTrackId, hlsSpecData.hlsMsn.c_str(), hlsSpecData.hlsPart.c_str());
|
||||
|
||||
uint64_t hlsMsnNr = atol(hlsSpecData.hlsMsn.c_str());
|
||||
uint64_t hlsPartNr = atol(hlsSpecData.hlsPart.c_str()) + 1; // base 1
|
||||
|
||||
// if hlsPart empty (HLS spec) OR if fragment hlsMsn is complete
|
||||
// THEN request part 1 of MSN++
|
||||
if (hlsSpecData.hlsPart.empty()){hlsPartNr = 1;}
|
||||
if (fragments.getDuration(hlsMsnNr)){
|
||||
hlsMsnNr++;
|
||||
hlsPartNr = 1;
|
||||
}
|
||||
|
||||
uint64_t lastFragmentDur = getLastFragDur(M, userSelect, trackData, hlsMsnNr, fragments, keys);
|
||||
std::ldiv_t res = std::ldiv(lastFragmentDur, partDurationMaxMs);
|
||||
DEBUG_MSG(5, "req MSN %" PRIu64 " fin MSN %zu, req Part %" PRIu64 " fin Part %zu", hlsMsnNr,
|
||||
(fragments.getEndValid() - 2), hlsPartNr, res.quot);
|
||||
|
||||
// BPR Time limit = 3x Target Duration (per HLS spec)
|
||||
// + Jitter duration (per Mist feature)
|
||||
// + 1x Target Duration (extra margin of safety for jitters)
|
||||
int64_t bprTimeLimit = (4 * trackData.targetDurationMax * 1000) +
|
||||
std::max(M.getMinKeepAway(trackData.timingTrackId),
|
||||
M.getMinKeepAway(trackData.requestTrackId));
|
||||
|
||||
while (hlsPartNr > res.quot){
|
||||
if (bprTimeLimit < 1){return 503;}
|
||||
DEBUG_MSG(5, "Part Block: req %" PRIu64 " fin %ld", hlsPartNr, res.quot);
|
||||
Util::wait(partDurationMaxMs - res.rem + 25);
|
||||
bprTimeLimit -= (partDurationMaxMs - res.rem + 25);
|
||||
lastFragmentDur = getLastFragDur(M, userSelect, trackData, hlsMsnNr, fragments, keys);
|
||||
res = std::ldiv(lastFragmentDur, partDurationMaxMs);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// Populate FragmentData struct to be used for media manifest generation
|
||||
void populateFragmentData(const DTSC::Meta &M, const std::map<size_t, Comms::Users> &userSelect, FragmentData &fragData, const TrackData &trackData,
|
||||
const DTSC::Fragments &fragments, const DTSC::Keys &keys){
|
||||
fragData.lastMs = std::min(
|
||||
getLastms(M, userSelect, trackData.requestTrackId, trackData.systemBoot + trackData.bootMsOffset),
|
||||
getLastms(M, userSelect, trackData.timingTrackId, trackData.systemBoot + trackData.bootMsOffset));
|
||||
fragData.firstFrag = fragments.getFirstValid();
|
||||
if (trackData.isLive){
|
||||
fragData.lastFrag = M.getFragmentIndexForTime(trackData.timingTrackId, fragData.lastMs);
|
||||
if (fragments.getEndValid() > fragData.lastFrag){
|
||||
fragData.lastFrag = fragments.getEndValid();
|
||||
}
|
||||
}else{
|
||||
// Override to last fragment if VOD
|
||||
fragData.lastFrag = fragments.getEndValid() - 1;
|
||||
}
|
||||
fragData.currentFrag = fragData.firstFrag;
|
||||
fragData.startTime = keys.getTime(fragments.getFirstKey(fragData.currentFrag));
|
||||
fragData.duration = fragments.getDuration(fragData.currentFrag);
|
||||
|
||||
// Playlist length limit logic:
|
||||
// Part 1: Limit any playlist with listlimit config
|
||||
if (trackData.listLimit &&
|
||||
(fragData.lastFrag - fragData.currentFrag > trackData.listLimit + 2)){
|
||||
fragData.currentFrag = fragData.lastFrag - trackData.listLimit;
|
||||
}
|
||||
|
||||
// Part 2: Limit a playlist depending on initial MSN data
|
||||
// see the NOTE at HLS::getLiveLengthLimit(args)
|
||||
if (trackData.isLive && (fragData.lastFrag - fragData.currentFrag) > 2){
|
||||
fragData.currentFrag = std::max(trackData.initMsn, fragData.currentFrag + 2);
|
||||
}
|
||||
}
|
||||
|
||||
/// Encryption logic to LLHLS playlist
|
||||
void hlsManifestMediaEncriptionTags(const DTSC::Meta &M, std::stringstream &result,
|
||||
const size_t timingTid){
|
||||
if (M.getEncryption(timingTid) == ""){
|
||||
result << "\r\n#EXT-X-KEY:METHOD=NONE";
|
||||
}else{
|
||||
// NOTE:
|
||||
// Defined encryption methods: NONE, AES-128, and SAMPLE-AES
|
||||
std::string method = M.getEncryption(timingTid);
|
||||
std::string uri = "asd";
|
||||
result << "\r\n#EXT-X-KEY:METHOD=" << method;
|
||||
result << ",URI=\"" << uri << "\"";
|
||||
// if (version >= 5){
|
||||
// result << "\",KEYFORMAT=\"com.apple.streamingkeydelivery\"";
|
||||
// result << "";
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
void addMsnTag(std::stringstream &result, const uint64_t msn){
|
||||
result << "#EXT-X-MEDIA-SEQUENCE:" << msn << "\r\n";
|
||||
}
|
||||
|
||||
/// Returns skip boundary duration, calculated as 6x max target duration
|
||||
uint32_t hlsSkipBoundary(const uint32_t targetDurationMax){return targetDurationMax * 6;}
|
||||
|
||||
/// Calculates the number full fragments that can be skipped from printing in the manifest
|
||||
/// and MUST REPLACE the associated tags
|
||||
void addMediaSkipTag(std::stringstream &result, FragmentData &fragData,
|
||||
const TrackData &trackData, const uint16_t version){
|
||||
// NOTE: Skips supported from version >= 9
|
||||
// Version >=9 supports SKIPPED-SEGMENTS
|
||||
|
||||
// TODO: Support for Version 10 playlists
|
||||
// NOTE: Not implemented only because there is no immediate demand from anyone.
|
||||
// Adds support for RECENTLY-REMOVED-DATERANGES
|
||||
|
||||
if (version >= 9){
|
||||
uint32_t skips = 0;
|
||||
const uint32_t skipsFromEnd =
|
||||
hlsSkipBoundary(trackData.targetDurationMax) / trackData.targetDurationMax + 2;
|
||||
if ((fragData.lastFrag - fragData.currentFrag) > skipsFromEnd){
|
||||
skips = ((fragData.lastFrag - fragData.currentFrag) - skipsFromEnd);
|
||||
}
|
||||
|
||||
if (version >= 10){
|
||||
// TODO: Implement logic for skip calculations date ranges
|
||||
skips += 0;
|
||||
}
|
||||
|
||||
if (skips){
|
||||
result << "#EXT-X-SKIP:SKIPPED-SEGMENTS=" << skips << "\r\n";
|
||||
// TODO: Update with version 10 playlist implementation
|
||||
// result << ",RECENTLY-REMOVED-DATERANGES=";
|
||||
fragData.currentFrag += skips;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Append result with tags that indicates the server supports LLHLS delivery
|
||||
void addServerSupportTags(std::stringstream &result, const TrackData &trackData){
|
||||
if (trackData.noLLHLS || !trackData.isLive){return;}
|
||||
|
||||
// TODO: Make ifdef
|
||||
if (serverSupport.tags){
|
||||
result << "#EXT-X-SERVER-CONTROL:";
|
||||
if (serverSupport.bpr){result << "CAN-BLOCK-RELOAD=YES,";}
|
||||
if (serverSupport.pdu){
|
||||
result << "CAN-SKIP-UNTIL=" << hlsSkipBoundary(trackData.targetDurationMax) << ",";
|
||||
}
|
||||
if (serverSupport.pduV2){result << "CAN-SKIP-DATERANGES=YES,";}
|
||||
if (serverSupport.parts){
|
||||
result << "PART-HOLD-BACK=" << partDurationMax * 3; // atleast 3x
|
||||
result << "\r\n#EXT-X-PART-INF:PART-TARGET=" << partDurationMax;
|
||||
}
|
||||
result << "\r\n";
|
||||
}
|
||||
}
|
||||
|
||||
void addTargetDuration(std::stringstream &result, const uint32_t targetDurationMax){
|
||||
result << "#EXT-X-TARGETDURATION:" << targetDurationMax << "\r\n";
|
||||
}
|
||||
|
||||
/// Appends result with encrytion / drm data
|
||||
void addEncriptionTags(std::stringstream &result, const std::string &encryptMethod){
|
||||
// TODO: Add support for media encryption
|
||||
if (encryptMethod.size()){
|
||||
// NOTE:
|
||||
// Defined encryption methods: NONE, AES-128, and SAMPLE-AES
|
||||
std::string uri = "asd";
|
||||
result << "#EXT-X-KEY:METHOD=" << encryptMethod;
|
||||
result << ",URI=\"" << uri << "\"\r\n";
|
||||
// if (version >= 5){
|
||||
// result << "\",KEYFORMAT=\"com.apple.streamingkeydelivery\"";
|
||||
// result << "";
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
void addInitTags(std::stringstream &result, const TrackData &trackData){
|
||||
// No init data for TS
|
||||
if (trackData.mediaFormat == ".ts"){return;}
|
||||
|
||||
result << "#EXT-X-MAP:URI=\"" << trackData.urlPrefix << "init" << trackData.mediaFormat;
|
||||
if (trackData.sessionId.size()){result << "?sessId=" << trackData.sessionId;}
|
||||
result << "\"\r\n";
|
||||
}
|
||||
|
||||
void addMediaBasicTags(std::stringstream &result, const uint16_t version){
|
||||
result << "#EXTM3U\r\n";
|
||||
result << "#EXT-X-VERSION:" << version << "\r\n";
|
||||
}
|
||||
|
||||
/// Append result with media meta tags that are in the beginning of the manifest
|
||||
void addStartingMetaTags(std::stringstream &result, FragmentData &fragData,
|
||||
const TrackData &trackData, const HlsSpecData &hlsSpecData){
|
||||
const uint16_t version = calcManifestVersion(hlsSpecData.hlsSkip);
|
||||
addMediaBasicTags(result, version);
|
||||
addServerSupportTags(result, trackData);
|
||||
addInitTags(result, trackData);
|
||||
addEncriptionTags(result, trackData.encryptMethod);
|
||||
addTargetDuration(result, trackData.targetDurationMax);
|
||||
addMsnTag(result, trackData.isLive ? fragData.currentFrag : fragData.firstFrag);
|
||||
// NOTE: DO NOT move the SKIP tag. Order must be respected per HLS spec.
|
||||
addMediaSkipTag(result, fragData, trackData, version);
|
||||
}
|
||||
|
||||
/// Appends result with prependStr and timestamp calculated from current time in ms
|
||||
void addDateTimeTag(std::stringstream &result, const std::string &prependStr,
|
||||
const uint64_t unixMs){
|
||||
time_t uSecs = unixMs / 1000;
|
||||
struct tm *ptm = gmtime(&uSecs);
|
||||
char dt_iso_8601[25];
|
||||
snprintf(dt_iso_8601, 25, "%.4d-%.2d-%.2dT%.2d:%.2d:%.2d.%.3dZ", ptm->tm_year + 1900,
|
||||
ptm->tm_mon + 1, ptm->tm_mday, ptm->tm_hour, ptm->tm_min, ptm->tm_sec,
|
||||
(int)(unixMs % 1000));
|
||||
result << prependStr << dt_iso_8601 << "\r\n";
|
||||
}
|
||||
|
||||
/// Add segment tag to LLHLS playlist
|
||||
void addFragmentTag(std::stringstream &result, const FragmentData &fragData,
|
||||
const TrackData &trackData){
|
||||
result << "#EXTINF:" << std::fixed << std::setprecision(3) << fragData.duration / 1000.0
|
||||
<< ",\r\n";
|
||||
|
||||
// NOTE: HLS spec says it isn't mandatory to add date time tag for every fragment.
|
||||
// Tests show that there is definitely an influence on consistency for live streams.
|
||||
// Printing the tag for every fragment tag was the best.
|
||||
if (trackData.isLive){
|
||||
addDateTimeTag(result, "#EXT-X-PROGRAM-DATE-TIME:",
|
||||
trackData.systemBoot + trackData.bootMsOffset + fragData.startTime);
|
||||
}
|
||||
|
||||
result << trackData.urlPrefix << "chunk_" << fragData.startTime << trackData.mediaFormat;
|
||||
result << "?msn=" << fragData.currentFrag;
|
||||
result << "&mTrack=" << trackData.timingTrackId;
|
||||
result << "&dur=" << fragData.duration;
|
||||
if (trackData.sessionId.size()){result << "&sessId=" << trackData.sessionId;}
|
||||
result << "\r\n";
|
||||
}
|
||||
|
||||
/// Add partial segment tag to LLHLS playlist
|
||||
void addPartialTag(std::stringstream &result, const DTSC::Meta &M, const DTSC::Keys &keys,
|
||||
const FragmentData &fragData, const TrackData &trackData,
|
||||
const uint32_t partCount, const uint32_t duration){
|
||||
result << "#EXT-X-PART:DURATION=" << duration / 1000.0;
|
||||
result << ",URI=\"" << trackData.urlPrefix;
|
||||
result << "chunk_" << fragData.startTime << "." << partCount << trackData.mediaFormat;
|
||||
result << "?msn=" << fragData.currentFrag;
|
||||
result << "&mTrack=" << trackData.timingTrackId;
|
||||
result << "&dur=" << duration;
|
||||
if (trackData.sessionId.size()){result << "&sessId=" << trackData.sessionId;}
|
||||
result << "\"";
|
||||
|
||||
// NOTE: INDEPENDENT tags, specified ONLY for VIDEO tracks, indicate the first partial fragment
|
||||
// closest to the before (live edge - PART-HOLD-BACK) time that a client starts playback from.
|
||||
if (trackData.isVideo){
|
||||
uint64_t partStartTime = fragData.startTime + partCount * partDurationMaxMs;
|
||||
uint32_t partKeyIdx = M.getKeyIndexForTime(trackData.timingTrackId, partStartTime);
|
||||
uint64_t partKeyIdxTime = M.getTimeForKeyIndex(trackData.timingTrackId, partKeyIdx);
|
||||
if (partKeyIdxTime == partStartTime){result << ",INDEPENDENT=YES";}
|
||||
}
|
||||
result << "\r\n";
|
||||
}
|
||||
|
||||
/// Appends result with partial fragment tags if supported/requested
|
||||
void addPartialFragmentTags(std::stringstream &result, const DTSC::Meta &M,
|
||||
FragmentData &fragData, const TrackData &trackData,
|
||||
const DTSC::Keys &keys){
|
||||
if (trackData.noLLHLS){return;}
|
||||
|
||||
// return if VOD, or no support for server tags, or no support for partial fragments
|
||||
if (!(trackData.isLive && serverSupport.tags && serverSupport.parts)){return;}
|
||||
|
||||
// if fragment is last-but-4th or later
|
||||
// OR if fragment is 3 target durations from the end
|
||||
if ((fragData.lastFrag - fragData.currentFrag < 5) ||
|
||||
((fragData.lastMs - fragData.startTime) <= 3 * trackData.targetDurationMax * 1000)){
|
||||
std::ldiv_t durationData = std::ldiv(fragData.duration, partDurationMaxMs);
|
||||
|
||||
// General case: all partial segments with duration equal to partDurationMax
|
||||
uint32_t partCount = 0;
|
||||
for (partCount = 0; partCount < durationData.quot; partCount++){
|
||||
addPartialTag(result, M, keys, fragData, trackData, partCount, partDurationMaxMs);
|
||||
}
|
||||
|
||||
// Special case: last partial segment (duration < partDurationMaxMs) in any fragment not at
|
||||
// live edge
|
||||
if (durationData.rem && (fragData.lastFrag - fragData.currentFrag > 1)){
|
||||
addPartialTag(result, M, keys, fragData, trackData, partCount, durationData.rem);
|
||||
}
|
||||
fragData.partNum = partCount;
|
||||
}
|
||||
}
|
||||
|
||||
/// Appends result with partial fragment tags, date-time tag and fragment tag for the current
|
||||
/// fragment
|
||||
void addMediaTags(std::stringstream &result, const DTSC::Meta &M, FragmentData &fragData,
|
||||
const TrackData &trackData, const DTSC::Keys &keys){
|
||||
addPartialFragmentTags(result, M, fragData, trackData, keys);
|
||||
|
||||
// do not add the last fragment media tag for the live streams
|
||||
if (trackData.isLive && (fragData.currentFrag == fragData.lastFrag - 1)){return;}
|
||||
|
||||
addFragmentTag(result, fragData, trackData);
|
||||
}
|
||||
|
||||
/// Appends result with partial fragment tags, date-time tags and fragment tags for all fragments
|
||||
void addMediaFragments(std::stringstream &result, const DTSC::Meta &M, FragmentData &fragData,
|
||||
const TrackData &trackData, const DTSC::Fragments &fragments,
|
||||
const DTSC::Keys &keys){
|
||||
for (; fragData.currentFrag < fragData.lastFrag; fragData.currentFrag++){
|
||||
fragData.startTime = keys.getTime(fragments.getFirstKey(fragData.currentFrag));
|
||||
|
||||
// adjust fragment start time for vod
|
||||
if (!trackData.isLive){fragData.startTime -= M.getFirstms(trackData.timingTrackId);}
|
||||
|
||||
fragData.duration = fragments.getDuration(fragData.currentFrag);
|
||||
// NOTE: If duration invalid, it's the last fragment, so calculate duration from live edge
|
||||
// Needed for LLHLS
|
||||
if (!fragData.duration){fragData.duration = fragData.lastMs - fragData.startTime;}
|
||||
|
||||
addMediaTags(result, M, fragData, trackData, keys);
|
||||
}
|
||||
}
|
||||
|
||||
void addVodEndingTags(std::stringstream &result){result << "#EXT-X-ENDLIST\r\n";}
|
||||
|
||||
/// Append result with information on alternate renditions, only for LLHLS
|
||||
void addAltRenditionReports(std::stringstream &result, const DTSC::Meta &M,
|
||||
const std::map<size_t, Comms::Users> &userSelect,
|
||||
const FragmentData &fragData, const TrackData &trackData){
|
||||
DTSC::Fragments fragments(M.fragments(trackData.timingTrackId));
|
||||
std::ldiv_t altPart =
|
||||
std::ldiv(fragments.getDuration(fragData.currentFrag - 2), partDurationMaxMs);
|
||||
std::map<size_t, Comms::Users>::const_iterator it = userSelect.end();
|
||||
for (; it != userSelect.end(); it++){
|
||||
if (it->first == trackData.timingTrackId){continue;}
|
||||
result << "#EXT-X-RENDITION-REPORT:";
|
||||
result << "URI=\"" << it->first << "/index.m3u8\"";
|
||||
if (fragData.partNum){
|
||||
result << ",LAST-MSN=" << fragData.currentFrag - 1;
|
||||
result << ",LAST-PART=" << fragData.partNum - 1 << "\r\n";
|
||||
}else{
|
||||
result << ",LAST-MSN=" << fragData.currentFrag - 2;
|
||||
result << ",LAST-PART=" << ((altPart.quot - 1) + (altPart.rem ? 1 : 0)) << "\r\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Append result with hinted part, only for LLHLS
|
||||
void addPreloadHintTag(std::stringstream &result, const FragmentData &fragData,
|
||||
const TrackData &trackData){
|
||||
result << "#EXT-X-PRELOAD-HINT:TYPE=PART,URI=\"" << trackData.urlPrefix << "chunk_";
|
||||
result << fragData.startTime << "." << fragData.partNum << trackData.mediaFormat;
|
||||
result << "?msn=" << fragData.currentFrag - 1;
|
||||
result << "&mTrack=" << trackData.timingTrackId;
|
||||
result << "&dur=" << partDurationMaxMs;
|
||||
if (trackData.sessionId.size()){result << "&sessId=" << trackData.sessionId;}
|
||||
result << "\"\r\n";
|
||||
}
|
||||
|
||||
/// Append result with live ending tags (supports llhls tags)
|
||||
void addLiveEndingTags(std::stringstream &result, const DTSC::Meta &M,
|
||||
const std::map<size_t, Comms::Users> &userSelect,
|
||||
const FragmentData &fragData, const TrackData &trackData){
|
||||
if (trackData.noLLHLS){return;}
|
||||
if (serverSupport.tags && serverSupport.parts){
|
||||
addPreloadHintTag(result, fragData, trackData);
|
||||
addAltRenditionReports(result, M, userSelect, fragData, trackData);
|
||||
}
|
||||
}
|
||||
|
||||
/// Add respective ending tags for VOD and Live streams
|
||||
void addEndingTags(std::stringstream &result, const DTSC::Meta &M,
|
||||
const std::map<size_t, Comms::Users> &userSelect, const FragmentData &fragData,
|
||||
const TrackData &trackData){
|
||||
trackData.isLive ? addLiveEndingTags(result, M, userSelect, fragData, trackData)
|
||||
: addVodEndingTags(result);
|
||||
}
|
||||
|
||||
/// Check if keyframes of given trackId are aligned with that of the main track
|
||||
/// returns true if aligned
|
||||
/// keyframe alginment is a MUST for LLHLS track switch
|
||||
bool checkFramesAlignment(std::stringstream &result, const DTSC::Meta &M,
|
||||
const MasterData &masterData, const size_t trackId){
|
||||
bool keyFramesAligned =
|
||||
masterData.mainTrack == trackId || M.keyTimingsMatch(masterData.mainTrack, trackId);
|
||||
if (!keyFramesAligned){
|
||||
result << "## NOTE: Track " << trackId
|
||||
<< " is available, but ignored because it is not aligned with track "
|
||||
<< masterData.mainTrack << ".\r\n";
|
||||
}
|
||||
return keyFramesAligned;
|
||||
}
|
||||
|
||||
/// Adds EXT-X-MEDIA tag for a given trackId
|
||||
void addExtXMediaTags(std::stringstream &result, const DTSC::Meta &M,
|
||||
const MasterData &masterData, const size_t trackId,
|
||||
const std::string &mediaType, const std::string &grpid,
|
||||
const uint64_t iFrag){
|
||||
std::string lang = "";
|
||||
lang = M.getLang(trackId).empty() ? "und" : M.getLang(trackId);
|
||||
std::string name = M.getCodec(trackId) + "-";
|
||||
if (lang == "und"){
|
||||
char intStr[10];
|
||||
snprintf(intStr, 10, "%zu", trackId);
|
||||
name += intStr;
|
||||
}else{
|
||||
name += lang;
|
||||
}
|
||||
result << "#EXT-X-MEDIA:TYPE=" << mediaType;
|
||||
result << ",GROUP-ID=\"" << grpid << "\"";
|
||||
result << ",LANGUAGE=\"" << lang;
|
||||
if (lang == "und"){result << "-" << trackId;}
|
||||
result << "\"";
|
||||
result << ",NAME=\"" << name << "\",URI=\"" << trackId << "/index.m3u8";
|
||||
result << "?mTrack=" << masterData.mainTrack;
|
||||
result << "&iMsn=" << iFrag;
|
||||
if (masterData.hasSessId){result << "&sessId=" << masterData.sessId;}
|
||||
if (masterData.noLLHLS){result << "&llhls=0";}
|
||||
result << "\"\r\n";
|
||||
}
|
||||
|
||||
/// Add HLS basic tags for master manifest
|
||||
void addMasterBasicTags(std::stringstream &result){
|
||||
result.str(std::string()); // reset the stream to empty
|
||||
result << "#EXTM3U\r\n#EXT-X-INDEPENDENT-SEGMENTS\r\n";
|
||||
}
|
||||
|
||||
void addInfTrackTag(std::stringstream &result, const MasterData &masterData,
|
||||
const std::set<size_t> &aTracks, const size_t tid, const uint64_t iFrag,
|
||||
const bool keyFramesAligned, const bool isVideo){
|
||||
result << (keyFramesAligned ? "" : "## DISABLED: ");
|
||||
result << tid;
|
||||
if (isVideo && masterData.isTS && aTracks.size() == 1){result << "_" << *aTracks.begin();}
|
||||
result << "/index.m3u8";
|
||||
result << "?mTrack=" << masterData.mainTrack;
|
||||
result << "&iMsn=" << iFrag;
|
||||
if (masterData.hasSessId){result << "&sessId=" << masterData.sessId;}
|
||||
if (masterData.noLLHLS){result << "&llhls=0";}
|
||||
result << "\r\n";
|
||||
}
|
||||
|
||||
void addInfBWidthTag(std::stringstream &result, const uint64_t bWidth){
|
||||
result << std::fixed << std::setprecision(0);
|
||||
result << ",BANDWIDTH=" << bWidth * 1.3 << ",AVERAGE-BANDWIDTH=" << bWidth * 1.1 << "\r\n";
|
||||
}
|
||||
|
||||
void addInfResolFrameRate(std::stringstream &result, const DTSC::Meta &M,
|
||||
const std::string &resolution, const size_t trackId){
|
||||
result << ",RESOLUTION=" << resolution;
|
||||
if (M.getFpks(trackId)){result << ",FRAME-RATE=" << (float)M.getFpks(trackId) / 1000;}
|
||||
}
|
||||
|
||||
void addInfCodecsTag(std::stringstream &result, const DTSC::Meta &M, const size_t tid,
|
||||
const std::string &audCodecsStr){
|
||||
result << "CODECS=\"" << Util::codecString(M.getCodec(tid), M.getInit(tid));
|
||||
result << audCodecsStr << "\"";
|
||||
}
|
||||
|
||||
/// creates group id based on the resolution of the track
|
||||
void getGroupId(std::stringstream &grpid, const DTSC::Meta &M, const size_t tid){
|
||||
grpid.str(std::string()); // reset the stream to empty
|
||||
grpid << groupIdPrefix << M.getWidth(tid) << "x" << M.getHeight(tid);
|
||||
}
|
||||
|
||||
void addInfMainTag(std::stringstream &result){result << "#EXT-X-STREAM-INF:";}
|
||||
|
||||
/// add #EXT-X-STREAM-INF for audio only streams
|
||||
void addAudInfStreamTags(std::stringstream &result, const DTSC::Meta &M,
|
||||
const MasterData &masterData, const std::set<size_t> &aTracks,
|
||||
const uint64_t iFrag){
|
||||
if (aTracks.size()){
|
||||
for (std::set<size_t>::iterator ita = aTracks.begin(); ita != aTracks.end(); ita++){
|
||||
uint64_t bWidth = M.getBps(*ita);
|
||||
bWidth = (bWidth < 5 ? 5 : bWidth) * 8;
|
||||
addInfMainTag(result);
|
||||
addInfCodecsTag(result, M, *ita, "");
|
||||
addInfBWidthTag(result, bWidth);
|
||||
addInfTrackTag(result, masterData, aTracks, *ita, iFrag, true, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Add #EXT-X-STREAM-INF tags for video groups
|
||||
void addVidInfStreamTags(std::stringstream &result, const DTSC::Meta &M,
|
||||
const MasterData &masterData, const std::set<std::string> &aCodecs,
|
||||
const std::set<size_t, std::less<size_t> > &vTracks,
|
||||
const std::set<size_t> &aTracks,
|
||||
const std::multimap<std::string, size_t> &vidGroups,
|
||||
const uint64_t asBWidth, const uint64_t iFrag,
|
||||
const uint32_t sTracksSize){
|
||||
// Create a comma separated string containing all audio codecs
|
||||
std::string audCodecsStr = ""; // comma separated string of "audioCodecs"
|
||||
if (aCodecs.size()){
|
||||
for (std::set<std::string>::iterator it = aCodecs.begin(); it != aCodecs.end(); ++it){
|
||||
audCodecsStr += ",";
|
||||
audCodecsStr += *it;
|
||||
}
|
||||
}
|
||||
|
||||
std::string assocGroupTag = "";
|
||||
// add associate group tags
|
||||
if ((!masterData.isTS && aTracks.size()) || (masterData.isTS && aTracks.size() > 1)){
|
||||
assocGroupTag += "AUDIO=\"aud\",";
|
||||
}
|
||||
if (sTracksSize){assocGroupTag += "SUBTITLES=\"sub\",";}
|
||||
|
||||
for (std::set<size_t>::iterator itr = vTracks.begin(); itr != vTracks.end(); itr++){
|
||||
std::map<std::string, size_t>::const_iterator it = vidGroups.begin();
|
||||
while (it != vidGroups.end()){
|
||||
if (*itr == it->second){break;}
|
||||
it++;
|
||||
}
|
||||
if (it == vidGroups.end()){continue;}
|
||||
|
||||
bool keyFramesAligned = checkFramesAlignment(result, M, masterData, it->second);
|
||||
if (keyFramesAligned){
|
||||
uint64_t bWidth = M.getBps(it->second);
|
||||
bWidth = ((bWidth < 5 ? 5 : bWidth) + asBWidth) * 8;
|
||||
|
||||
addInfMainTag(result);
|
||||
result << assocGroupTag;
|
||||
addInfCodecsTag(result, M, it->second, audCodecsStr);
|
||||
addInfResolFrameRate(result, M, it->first.substr(groupIdPrefix.size()), it->second);
|
||||
addInfBWidthTag(result, bWidth);
|
||||
addInfTrackTag(result, masterData, aTracks, it->second, iFrag, keyFramesAligned, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds EXT-X-MEDIA:TYPE=SUBTITLES tags to the manifest
|
||||
uint64_t addSubTags(std::stringstream &result, const DTSC::Meta &M, const MasterData &masterData,
|
||||
const std::set<size_t> &sTracks, const uint64_t iFrag){
|
||||
uint64_t subBWidth = 0;
|
||||
for (std::set<size_t>::iterator its = sTracks.begin(); its != sTracks.end(); its++){
|
||||
addExtXMediaTags(result, M, masterData, *its, "SUBTITLES", "sub", iFrag);
|
||||
subBWidth = std::max(subBWidth, M.getBps(*its));
|
||||
}
|
||||
return subBWidth;
|
||||
}
|
||||
|
||||
/// Adds EXT-X-MEDIA:TYPE=AUDIO tags to the manifest
|
||||
uint64_t addAudTags(std::stringstream &result, std::set<std::string> &aCodecs,
|
||||
const DTSC::Meta &M, const MasterData &masterData,
|
||||
const std::set<size_t> &aTracks, const uint64_t iFrag,
|
||||
const uint32_t vTracksLength){
|
||||
// if video tracks available, audio tracks as EXT-X-MEDIA, else as EXT-X-STREAM-INF
|
||||
uint64_t audBWidth = 0;
|
||||
if (vTracksLength){
|
||||
for (std::set<size_t>::iterator ita = aTracks.begin(); ita != aTracks.end(); ita++){
|
||||
if (!masterData.isTS || (masterData.isTS && aTracks.size() > 1)){
|
||||
addExtXMediaTags(result, M, masterData, *ita, "AUDIO", "aud", iFrag);
|
||||
}
|
||||
aCodecs.insert(Util::codecString(M.getCodec(*ita), M.getInit(*ita)));
|
||||
audBWidth = std::max(audBWidth, M.getBps(*ita));
|
||||
}
|
||||
}
|
||||
return audBWidth;
|
||||
}
|
||||
|
||||
/// Adds EXT-X-MEDIA:TYPE=VIDEO tags to the manifest
|
||||
void addVidTags(std::stringstream &result, std::stringstream &grpid, const DTSC::Meta &M,
|
||||
const MasterData &masterData, const std::set<size_t, std::less<size_t> > &vTracks,
|
||||
const std::multimap<std::string, size_t> &vidGroups, const uint64_t iFrag,
|
||||
const uint32_t aTracksSize){
|
||||
// if audio tracks available, video tracks are EXT-X-STREAM-INF
|
||||
std::set<size_t>::iterator itv = aTracksSize ? vTracks.end() : vTracks.begin();
|
||||
for (; itv != vTracks.end(); itv++){
|
||||
getGroupId(grpid, M, *itv);
|
||||
if (vidGroups.find(grpid.str()) != vidGroups.end() && vidGroups.count(grpid.str()) == 1){
|
||||
continue;
|
||||
}
|
||||
|
||||
if (checkFramesAlignment(result, M, masterData, *itv)){
|
||||
addExtXMediaTags(result, M, masterData, *itv, "VIDEO", grpid.str(), iFrag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Sorts all tracks into video, audio & subtitle sets & generate rendition groups
|
||||
void sortTracks(const DTSC::Meta &M, const std::map<size_t, Comms::Users> &userSelect,
|
||||
std::stringstream &grpid, std::set<size_t, std::less<size_t> > &vTracks,
|
||||
std::set<size_t> &aTracks, std::set<size_t> &sTracks,
|
||||
std::multimap<std::string, size_t> &vidGroups){
|
||||
std::map<size_t, Comms::Users>::const_iterator it = userSelect.begin();
|
||||
for (; it != userSelect.end(); it++){
|
||||
if (M.getType(it->first) == "video"){
|
||||
vTracks.insert(it->first);
|
||||
getGroupId(grpid, M, it->first);
|
||||
vidGroups.insert(std::pair<std::string, size_t>(grpid.str(), it->first));
|
||||
}
|
||||
if (M.getType(it->first) == "audio"){aTracks.insert(it->first);}
|
||||
if (M.getCodec(it->first) == "subtitle"){sTracks.insert(it->first);}
|
||||
}
|
||||
}
|
||||
|
||||
/// This is a hack to ensure the LLHLS playback starts as close as possible to the live edge
|
||||
u_int16_t getLiveLengthLimit(const MasterData &masterData){
|
||||
// NOTE:
|
||||
// TL;DR: Apple cleints receive the shortest media playlist to ensure a consistent playback at
|
||||
// least possible latency.
|
||||
// Long story: After experimentation, it was found that Apple clients start streaming
|
||||
// consistently at least latency when the first playlist is short, i.e., ~1 full fragment (+
|
||||
// partial fragment if any) short. From that point, the playlist can grow with the stream.
|
||||
// TODO: remove this when the above issue with apple clients is observed no more.
|
||||
return (masterData.userAgent.find(" Mac OS ") != std::string::npos) ? 3 : 6;
|
||||
}
|
||||
|
||||
/// Get the first fragment number to be printed in the playlist
|
||||
u_int64_t getInitFragment(const DTSC::Meta &M, const MasterData &masterData){
|
||||
if (M.getLive()){
|
||||
DTSC::Fragments fragments(M.fragments(masterData.mainTrack));
|
||||
DTSC::Keys keys(M.keys(masterData.mainTrack));
|
||||
u_int64_t iFrag = std::max(fragments.getEndValid() -
|
||||
(masterData.noLLHLS ? 10 : getLiveLengthLimit(masterData)),
|
||||
fragments.getFirstValid());
|
||||
uint64_t minDur =
|
||||
M.getLastms(masterData.mainTrack) - keys.getTime(fragments.getFirstKey(iFrag));
|
||||
if (minDur < HLS::partDurationMaxMs * 3){iFrag--;}
|
||||
return iFrag;
|
||||
}else{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// Appends master manifest to result
|
||||
void addMasterManifest(std::stringstream &result, const DTSC::Meta &M,
|
||||
const std::map<size_t, Comms::Users> &userSelect,
|
||||
const MasterData &masterData){
|
||||
std::set<size_t, std::less<size_t> > vTracks;
|
||||
std::set<size_t> aTracks;
|
||||
std::set<size_t> sTracks;
|
||||
std::stringstream grpid; ///< used for vidGroups.
|
||||
std::multimap<std::string, size_t> vidGroups; ///< stores 1 video track id from a groupid
|
||||
std::set<std::string> aCodecs; ///< a set to store unique audio codecs
|
||||
|
||||
sortTracks(M, userSelect, grpid, vTracks, aTracks, sTracks, vidGroups);
|
||||
|
||||
const uint64_t iFrag = getInitFragment(M, masterData);
|
||||
|
||||
addMasterBasicTags(result);
|
||||
|
||||
addVidTags(result, grpid, M, masterData, vTracks, vidGroups, iFrag, aTracks.size());
|
||||
|
||||
uint64_t audBWidth = addAudTags(result, aCodecs, M, masterData, aTracks, iFrag, vTracks.size());
|
||||
|
||||
uint64_t subBWidth = addSubTags(result, M, masterData, sTracks, iFrag);
|
||||
|
||||
if (vidGroups.size()){
|
||||
addVidInfStreamTags(result, M, masterData, aCodecs, vTracks, aTracks, vidGroups,
|
||||
audBWidth + subBWidth, iFrag, sTracks.size());
|
||||
}else{
|
||||
addAudInfStreamTags(result, M, masterData, aTracks, iFrag);
|
||||
}
|
||||
}
|
||||
|
||||
/// returns the end time for a given partial fragment
|
||||
/// returns 0 for a hinted part which never got created
|
||||
uint64_t getPartTargetTime(const DTSC::Meta &M, const uint32_t idx, const uint32_t mTrack,
|
||||
const uint64_t startTime, const uint64_t msn, const uint32_t part){
|
||||
DTSC::Fragments fragments(M.fragments(mTrack));
|
||||
|
||||
// Estimate the target end time for a given part
|
||||
// 50 ms is margin of safety to accommodate inconsistencies
|
||||
const uint64_t calcTargetTime = startTime + (part + 1) * partDurationMaxMs + 50;
|
||||
|
||||
uint64_t lastms = std::min(M.getLastms(mTrack), M.getLastms(idx));
|
||||
uint16_t count = 0;
|
||||
|
||||
// wait until estimated target end time is <= lastms for the track
|
||||
while (calcTargetTime > lastms && count++ < 50){
|
||||
Util::wait(calcTargetTime - lastms);
|
||||
lastms = std::min(M.getLastms(mTrack), M.getLastms(idx));
|
||||
}
|
||||
|
||||
// Duration maybe invalid, indicating msn is not complete
|
||||
// But the part is ready. So return the end time
|
||||
uint64_t duration = fragments.getDuration(msn);
|
||||
if (!duration){return startTime + ((part + 1) * partDurationMaxMs);}
|
||||
|
||||
// If duration valid, MSN is fully finished
|
||||
// Possible that the last partial fragment duration < partDurationMaxMs
|
||||
// Find the exact duration of the last partial fragment
|
||||
uint64_t partTargetTime =
|
||||
std::min(startTime + duration, startTime + ((part + 1) * partDurationMaxMs));
|
||||
|
||||
if (duration && (partTargetTime - startTime) > duration){return 0;}
|
||||
return partTargetTime;
|
||||
}
|
||||
|
||||
}// namespace HLS
|
84
lib/hls_support.h
Normal file
84
lib/hls_support.h
Normal file
|
@ -0,0 +1,84 @@
|
|||
#include "comms.h"
|
||||
#include "dtsc.h"
|
||||
#include <cmath>
|
||||
|
||||
namespace HLS{
|
||||
// TODO: Implement logic to detect ideal partial fragment size
|
||||
const uint32_t partDurationMaxMs = 500; ///< max partial fragment duration in ms
|
||||
|
||||
/// A struct containing data regarding fragments in a particular track
|
||||
/// needed for media manifest generation
|
||||
struct FragmentData{
|
||||
uint64_t firstFrag;
|
||||
uint64_t lastFrag;
|
||||
uint64_t currentFrag;
|
||||
uint64_t startTime;
|
||||
uint64_t duration;
|
||||
uint64_t lastMs;
|
||||
uint64_t partNum; ///< partial fragment number in base 0
|
||||
};
|
||||
|
||||
/// A struct containing data regarding a particular track for media manifest generation
|
||||
struct TrackData{
|
||||
bool isLive;
|
||||
bool isVideo;
|
||||
bool noLLHLS;
|
||||
std::string mediaFormat; ///< ".m4s" or ".ts"
|
||||
std::string encryptMethod; ///< "NONE", "AES-128", or "SAMPLE-AES"
|
||||
std::string sessionId; ///< "" if not applicable
|
||||
size_t timingTrackId;
|
||||
size_t requestTrackId;
|
||||
uint32_t targetDurationMax; ///< Max duration of any fragment in the stream in s
|
||||
uint64_t initMsn; ///< Initial fragment to start with in the media manifest
|
||||
uint64_t listLimit; ///< Max number of fragments to include in the media manifest
|
||||
std::string urlPrefix; ///< for CDN chunk serving
|
||||
uint64_t systemBoot; ///< duration in ms since boot
|
||||
int64_t bootMsOffset; ///< time diff between systemBoot & stream's 0 time in ms
|
||||
};
|
||||
|
||||
/// A struct containing http variable data for LLHLS, can be empty strings
|
||||
struct HlsSpecData{
|
||||
std::string hlsSkip; ///< "YES" or "v2"
|
||||
std::string hlsMsn; ///< requested fragment number
|
||||
std::string hlsPart; ///< requested partial fragment number
|
||||
};
|
||||
|
||||
/// A struct containing stream related data needed to generate master manifest
|
||||
struct MasterData{
|
||||
bool hasSessId;
|
||||
bool noLLHLS;
|
||||
bool isTS;
|
||||
size_t mainTrack;
|
||||
std::string userAgent; ///< See HLS::getLiveLengthLimit(<args>) for more info
|
||||
std::string sessId; ///< Session ID if applicable
|
||||
uint64_t systemBoot; ///< duration in ms since boot
|
||||
int64_t bootMsOffset; ///< time diff between systemBoot & stream's 0 time in ms
|
||||
};
|
||||
|
||||
uint32_t blockPlaylistReload(const DTSC::Meta &M, const std::map<size_t, Comms::Users> &userSelect, const TrackData &trackData,
|
||||
const HlsSpecData &hlsSpecData, const DTSC::Fragments &fragments,
|
||||
const DTSC::Keys &keys);
|
||||
|
||||
void populateFragmentData(const DTSC::Meta &M, const std::map<size_t, Comms::Users> &userSelect, FragmentData &fragData, const TrackData &trackData,
|
||||
const DTSC::Fragments &fragments, const DTSC::Keys &keys);
|
||||
|
||||
void addEndingTags(std::stringstream &result, const DTSC::Meta &M,
|
||||
const std::map<size_t, Comms::Users> &userSelect, const FragmentData &fragData,
|
||||
const TrackData &trackData);
|
||||
|
||||
size_t getTimingTrackId(const DTSC::Meta &M, const std::string &mTrack, const size_t mSelTrack);
|
||||
|
||||
void addStartingMetaTags(std::stringstream &result, FragmentData &fragData,
|
||||
const TrackData &trackData, const HlsSpecData &hlsSpecData);
|
||||
|
||||
void addMediaFragments(std::stringstream &result, const DTSC::Meta &M, FragmentData &fragData,
|
||||
const TrackData &trackData, const DTSC::Fragments &fragments,
|
||||
const DTSC::Keys &keys);
|
||||
|
||||
void addMasterManifest(std::stringstream &result, const DTSC::Meta &M,
|
||||
const std::map<size_t, Comms::Users> &userSelect,
|
||||
const MasterData &masterData);
|
||||
|
||||
uint64_t getPartTargetTime(const DTSC::Meta &M, const uint32_t idx, const uint32_t mTrack,
|
||||
const uint64_t startTime, const uint64_t msn, const uint32_t part);
|
||||
}// namespace HLS
|
Loading…
Add table
Reference in a new issue