mistserver/lib/hls_support.cpp
Marco van Dijk 8ac486b815 Completed new sessions system
Co-authored-by: Thulinma <jaron@vietors.com>
2022-10-05 03:13:52 +02:00

785 lines
36 KiB
C++

#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 << "?tkn=" << 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 << "&tkn=" << 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 << "&tkn=" << 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 << "&tkn=" << 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.sessId.size()){result << "&tkn=" << 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.sessId.size()){result << "&tkn=" << 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