#include "input_hls.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SEM_TS_CLAIM "/MstTSIN%s" namespace Mist{ // remove trailing \r for windows generated playlist files int cleanLine(std::string &s){ if (s.length() > 0 && s.at(s.length() - 1) == '\r'){s.erase(s.size() - 1);} } Playlist::Playlist(const std::string &uriSrc){ lastFileIndex = 0; entryCount = 0; waitTime = 2; playlistEnd = false; noChangeCount = 0; initDone = false; lastTimestamp = 0; uri = uriSrc; startTime = Util::bootSecs(); if (uri.size()){ std::string line; std::string key; std::string val; int count = 0; uint64_t totalBytes = 0; uri_root = uri.substr(0, uri.rfind("/") + 1); playlistType = LIVE; // Temporary value INFO_MSG("readplaylist: %s", uri.c_str()); std::istringstream urlSource; std::ifstream fileSource; if (isUrl()){ loadURL(uri); urlSource.str(source); }else{ fileSource.open(uri.c_str()); } std::istream &input = (isUrl() ? (std::istream &)urlSource : (std::istream &)fileSource); std::getline(input, line); while (std::getline(input, line)){ cleanLine(line); if (!line.empty()){ if (line.compare(0, 7, "#EXT-X-") == 0){ size_t pos = line.find(":"); key = line.substr(7, pos - 7); val = line.c_str() + pos + 1; if (key == "VERSION"){version = atoi(val.c_str());} if (key == "TARGETDURATION"){waitTime = atoi(val.c_str());} if (key == "MEDIA-SEQUENCE"){ media_sequence = atoi(val.c_str()); lastFileIndex = media_sequence; } if (key == "PLAYLIST-TYPE"){ if (val == "VOD"){ playlistType = VOD; }else if (val == "LIVE"){ playlistType = LIVE; }else if (val == "EVENT"){ playlistType = EVENT; } } if (key == "ENDLIST"){ // end of playlist reached! playlistEnd = true; playlistType = VOD; } continue; }else if (line.compare(0, 7, "#EXTINF") != 0){ VERYHIGH_MSG("ignoring wrong line: %s.", line.c_str()); continue; } float f = atof(line.c_str() + 8); std::string filename; std::getline(input, filename); addEntry(filename, f, totalBytes); count++; } } if (isUrl()){ playlistType = LIVE; // VOD over HTTP needs to be processed as LIVE. fileSource.close(); } } initDone = true; } bool Playlist::atEnd() const{return (packetPtr - source.data() + 188) > source.size();} bool Playlist::isUrl() const{ return (uri_root.size() ? uri_root.find("http://") == 0 : uri.find("http://") == 0); } bool Playlist::loadURL(const std::string &loadUrl){ HIGH_MSG("opening URL: %s", loadUrl.c_str()); HTTP::URL url(loadUrl); if (url.protocol != "http"){ FAIL_MSG("Protocol %s is not supported", url.protocol.c_str()); return false; } Socket::Connection conn(url.host, url.getPort(), false); if (!conn){ FAIL_MSG("Failed to reach %s on port %lu", url.host.c_str(), url.getPort()); return false; } HTTP::Parser http; http.url = "/" + url.path; http.method = "GET"; http.SetHeader("Host", url.host); http.SetHeader("X-MistServer", PACKAGE_VERSION); conn.SendNow(http.BuildRequest()); http.Clean(); uint64_t startTime = Util::epoch(); source.clear(); packetPtr = 0; while ((Util::epoch() - startTime < 10) && (conn || conn.Received().size())){ if (conn.spool() || conn.Received().size()){ if (http.Read(conn)){ source = http.body; packetPtr = source.data(); conn.close(); return true; } } } FAIL_MSG("Failed to load %s: %s", loadUrl.c_str(), conn ? "timeout" : "connection closed"); if (conn){conn.close();} return false; } /// Function for reloading the playlist in case of live streams. bool Playlist::reload(){ int skip = lastFileIndex - media_sequence; bool ret = false; std::string line; std::string key; std::string val; int count = 0; uint64_t totalBytes = 0; std::istringstream urlSource; std::ifstream fileSource; if (isUrl()){ loadURL(uri.c_str()); // get size only! urlSource.str(source); }else{ fileSource.open(uri.c_str()); } std::istream &input = (isUrl() ? (std::istream &)urlSource : (std::istream &)fileSource); std::getline(input, line); while (std::getline(input, line)){ cleanLine(line); if (line.compare(0, 21, "#EXT-X-MEDIA-SEQUENCE") == 0){ media_sequence = atoi(line.c_str() + line.find(":") + 1); skip = (lastFileIndex - media_sequence); continue; } if (line.compare(0, 7, "#EXTINF") != 0){continue;} float f = atof(line.c_str() + 8); // next line belongs to this item std::string filename; std::getline(input, filename); // check for already added segments if (skip){ skip--; }else{ cleanLine(filename); addEntry(filename, f, totalBytes); count++; } } if (!isUrl()){fileSource.close();} ret = (count > 0); if (ret){ noChangeCount = 0; }else{ ++noChangeCount; if (noChangeCount > 3){VERYHIGH_MSG("enough!");} } return ret; } /// function for adding segments to the playlist to be processed. used for VOD and live void Playlist::addEntry(const std::string &filename, float duration, uint64_t &totalBytes){ playListEntries entry; entry.filename = filename; cleanLine(entry.filename); std::string test = uri_root + entry.filename; std::istringstream urlSource; std::ifstream fileSource; if (isUrl()){ urlSource.str(source); }else{ fileSource.open(test.c_str(), std::ios::ate | std::ios::binary); if ((fileSource.rdstate() & std::ifstream::failbit) != 0){ WARN_MSG("file: %s, error: %s", test.c_str(), strerror(errno)); } } entry.bytePos = totalBytes; entry.duration = duration; if (!isUrl()){totalBytes += fileSource.tellg();} if (initDone){ lastTimestamp += duration; entry.timestamp = lastTimestamp + startTime; entry.wait = entryCount * duration; }else{ entry.timestamp = 0; // read all segments immediatly at the beginning, then use delays } ++entryCount; entries.push_back(entry); ++lastFileIndex; } /// Constructor of HLS Input inputHLS::inputHLS(Util::Config *cfg) : Input(cfg){ currentPlaylist = 0; capa["name"] = "HLS"; capa["decs"] = "Enables HLS Input"; capa["source_match"].append("/*.m3u8"); capa["source_match"].append("http://*.m3u8"); //These two can/may be set to always-on mode capa["always_match"].append("/*.m3u8"); capa["always_match"].append("http://*.m3u8"); capa["priority"] = 9ll; capa["codecs"][0u][0u].append("H264"); capa["codecs"][0u][1u].append("AAC"); capa["codecs"][0u][1u].append("AC3"); capa["codecs"][0u][1u].append("MP3"); inFile = NULL; } inputHLS::~inputHLS(){ if (inFile){fclose(inFile);} } bool inputHLS::setup(){ if (config->getString("input") == "-"){return false;} if (!initPlaylist(config->getString("input"))){return false;} if (Util::Config::printDebugLevel >= DLVL_HIGH){ for (std::vector::iterator pListIt = playlists.begin(); pListIt != playlists.end(); pListIt++){ std::cout << pListIt->id << ": " << pListIt->uri << std::endl; int j = 0; for (std::deque::iterator entryIt = pListIt->entries.begin(); entryIt != pListIt->entries.end(); entryIt++){ std::cout << " " << j++ << ": " << entryIt->filename << " bytePos: " << entryIt->bytePos << std::endl; } } } return true; } void inputHLS::trackSelect(std::string trackSpec){ selectedTracks.clear(); size_t index; while (trackSpec != ""){ index = trackSpec.find(' '); selectedTracks.insert(atoi(trackSpec.substr(0, index).c_str())); if (index != std::string::npos){ trackSpec.erase(0, index + 1); }else{ trackSpec = ""; } } } void inputHLS::parseStreamHeader(){ bool hasHeader = false; if (!hasHeader){myMeta = DTSC::Meta();} TS::Packet packet; // to analyse and extract data int counter = 1; int packetId = 0; char *data; unsigned int dataLen; bool keepReading = false; for (std::vector::iterator pListIt = playlists.begin(); pListIt != playlists.end(); pListIt++){ if (!pListIt->entries.size()){continue;} std::deque::iterator entryIt = pListIt->entries.begin(); tsStream.clear(); uint64_t lastBpos = entryIt->bytePos; if (pListIt->isUrl()){ pListIt->loadURL(pListIt->uri_root + entryIt->filename); keepReading = packet.FromPointer(pListIt->packetPtr); pListIt->packetPtr += 188; }else{ in.open((pListIt->uri_root + entryIt->filename).c_str()); keepReading = packet.FromStream(in); } while (keepReading){ tsStream.parse(packet, lastBpos); if (pListIt->isUrl()){ lastBpos = entryIt->bytePos + pListIt->source.size(); ///\todo get size... }else{ lastBpos = entryIt->bytePos + in.tellg(); } while (tsStream.hasPacketOnEachTrack()){ DTSC::Packet headerPack; tsStream.getEarliestPacket(headerPack); int tmpTrackId = headerPack.getTrackId(); packetId = pidMapping[(pListIt->id << 16) + tmpTrackId]; if (packetId == 0){ pidMapping[(pListIt->id << 16) + headerPack.getTrackId()] = counter; pidMappingR[counter] = (pListIt->id << 16) + headerPack.getTrackId(); packetId = counter; HIGH_MSG("Added file %s, trackid: %d, mapped to: %d", (pListIt->uri_root + entryIt->filename).c_str(), headerPack.getTrackId(), counter); counter++; } myMeta.live = (playlists.size() && playlists[0].playlistType == LIVE); myMeta.vod = !myMeta.live; // myMeta.live = true; // myMeta.vod = false; myMeta.live = false; myMeta.vod = true; if (!hasHeader && (!myMeta.tracks.count(packetId) || !myMeta.tracks[packetId].codec.size())){ tsStream.initializeMetadata(myMeta, tmpTrackId, packetId); } } if (pListIt->isUrl()){ keepReading = !pListIt->atEnd(); if (keepReading){ packet.FromPointer(pListIt->packetPtr); pListIt->packetPtr += 188; } }else{ keepReading = packet.FromStream(in); } } in.close(); } tsStream.clear(); INFO_MSG("end stream header tracks: %d", myMeta.tracks.size()); if (hasHeader){return;} // myMeta.live = true; // myMeta.vod = false; in.close(); } bool inputHLS::readHeader(){ if (playlists.size() && playlists[0].playlistType == LIVE){return true;} std::istringstream urlSource; std::ifstream fileSource; bool endOfFile = false; bool hasHeader = false; // See whether a separate header file exists. DTSC::File tmp(config->getString("input") + ".dtsh"); if (tmp){ myMeta = tmp.getMeta(); if (myMeta){hasHeader = true;} } if (!hasHeader){myMeta = DTSC::Meta();} TS::Packet packet; // to analyse and extract data int counter = 1; int packetId = 0; char *data; unsigned int dataLen; for (std::vector::iterator pListIt = playlists.begin(); pListIt != playlists.end(); pListIt++){ tsStream.clear(); uint32_t entId = 0; for (std::deque::iterator entryIt = pListIt->entries.begin(); entryIt != pListIt->entries.end(); entryIt++){ // WORK tsStream.partialClear(); endOfFile = false; if (pListIt->isUrl()){ pListIt->loadURL(pListIt->uri_root + entryIt->filename); urlSource.str(pListIt->source); endOfFile = !pListIt->atEnd(); if (!endOfFile){packet.FromPointer(pListIt->packetPtr);} pListIt->packetPtr += 188; }else{ in.close(); in.open((pListIt->uri_root + entryIt->filename).c_str()); packet.FromStream(in); endOfFile = in.eof(); } entId++; uint64_t lastBpos = entryIt->bytePos; while (!endOfFile){ tsStream.parse(packet, lastBpos); if (pListIt->isUrl()){ lastBpos = entryIt->bytePos + pListIt->source.size(); }else{ lastBpos = entryIt->bytePos + in.tellg(); } while (tsStream.hasPacketOnEachTrack()){ DTSC::Packet headerPack; tsStream.getEarliestPacket(headerPack); int tmpTrackId = headerPack.getTrackId(); packetId = pidMapping[(pListIt->id << 16) + tmpTrackId]; if (packetId == 0){ pidMapping[(pListIt->id << 16) + headerPack.getTrackId()] = counter; pidMappingR[counter] = (pListIt->id << 16) + headerPack.getTrackId(); packetId = counter; INFO_MSG("Added file %s, trackid: %d, mapped to: %d", (pListIt->uri_root + entryIt->filename).c_str(), headerPack.getTrackId(), counter); counter++; } if (!hasHeader && (!myMeta.tracks.count(packetId) || !myMeta.tracks[packetId].codec.size())){ tsStream.initializeMetadata(myMeta, tmpTrackId, packetId); } if (!hasHeader){ headerPack.getString("data", data, dataLen); uint64_t pBPos = headerPack.getInt("bpos"); // keyframe data exists, so always add 19 bytes keyframedata. long long packOffset = headerPack.hasMember("offset") ? headerPack.getInt("offset") : 0; long long packSendSize = 24 + (packOffset ? 17 : 0) + (entId >= 0 ? 15 : 0) + 19 + dataLen + 11; myMeta.update(headerPack.getTime(), packOffset, packetId, dataLen, entId, headerPack.hasMember("keyframe"), packSendSize); } } if (pListIt->isUrl()){ endOfFile = pListIt->atEnd(); if (!endOfFile){ packet.FromPointer(pListIt->packetPtr); pListIt->packetPtr += 188; } }else{ packet.FromStream(in); endOfFile = in.eof(); } } // get last packets tsStream.finish(); DTSC::Packet headerPack; tsStream.getEarliestPacket(headerPack); while (headerPack){ int tmpTrackId = headerPack.getTrackId(); packetId = pidMapping[(pListIt->id << 16) + tmpTrackId]; if (packetId == 0){ pidMapping[(pListIt->id << 16) + headerPack.getTrackId()] = counter; pidMappingR[counter] = (pListIt->id << 16) + headerPack.getTrackId(); packetId = counter; INFO_MSG("Added file %s, trackid: %d, mapped to: %d", (pListIt->uri_root + entryIt->filename).c_str(), headerPack.getTrackId(), counter); counter++; } if (!hasHeader && (!myMeta.tracks.count(packetId) || !myMeta.tracks[packetId].codec.size())){ tsStream.initializeMetadata(myMeta, tmpTrackId, packetId); } if (!hasHeader){ headerPack.getString("data", data, dataLen); uint64_t pBPos = headerPack.getInt("bpos"); // keyframe data exists, so always add 19 bytes keyframedata. long long packOffset = headerPack.hasMember("offset") ? headerPack.getInt("offset") : 0; long long packSendSize = 24 + (packOffset ? 17 : 0) + (entId >= 0 ? 15 : 0) + 19 + dataLen + 11; myMeta.update(headerPack.getTime(), packOffset, packetId, dataLen, entId, headerPack.hasMember("keyframe"), packSendSize); } tsStream.getEarliestPacket(headerPack); } if (!pListIt->isUrl()){in.close();} if (hasHeader){break;} } } if (hasHeader || (playlists.size() && playlists[0].isUrl())){return true;} INFO_MSG("write header file..."); std::ofstream oFile((config->getString("input") + ".dtsh").c_str()); oFile << myMeta.toJSON().toNetPacked(); oFile.close(); in.close(); return true; } bool inputHLS::needsLock(){ if (playlists.size() && playlists[0].isUrl()){return false;} return (playlists.size() <= currentPlaylist) || !(playlists[currentPlaylist].playlistType == LIVE); } bool inputHLS::openStreamSource(){return true;} int inputHLS::getFirstPlaylistToReload(){ // at this point, we need to check which playlist we need to reload, and keep reading from that // playlist until EndOfPlaylist std::vector::iterator result = std::min_element(reloadNext.begin(), reloadNext.end()); return std::distance(reloadNext.begin(), result); } void inputHLS::getNext(bool smart){ INSANE_MSG("Getting next"); uint32_t tid; bool hasPacket = false; bool keepReading = false; bool endOfFile = false; bool doReload = false; thisPacket.null(); while (!hasPacket && config->is_active && (needsLock() || nProxy.userClient.isAlive())){ if (playlists[currentPlaylist].isUrl()){ endOfFile = playlists[currentPlaylist].atEnd(); if (!endOfFile){ tsBuf.FromPointer(playlists[currentPlaylist].packetPtr); playlists[currentPlaylist].packetPtr += 188; } }else{ tsBuf.FromStream(in); endOfFile = in.eof(); } // eof flag is set after unsuccesful read, so check again if (endOfFile){tsStream.finish();} if (playlists[currentPlaylist].playlistType == LIVE){ hasPacket = tsStream.hasPacketOnEachTrack() || (endOfFile && tsStream.hasPacket()); }else{ if (!selectedTracks.size()){return;} tid = *selectedTracks.begin(); hasPacket = tsStream.hasPacket(getMappedTrackId(tid)); } if (endOfFile && !hasPacket){ if (playlists[currentPlaylist].playlistType == LIVE){ int a = getFirstPlaylistToReload(); int segmentTime = 30; HIGH_MSG("need to reload playlist %d, time: %d", a, reloadNext[a] - Util::bootSecs()); int f = firstSegment(); if (f >= 0){segmentTime = playlists[f].entries.front().timestamp - Util::bootSecs();} int playlistTime = reloadNext.at(currentPlaylist) - Util::bootSecs() - 1; if (playlistTime < segmentTime){ while (playlistTime > 0 && (needsLock() || nProxy.userClient.isAlive())){ Util::wait(900); nProxy.userClient.keepAlive(); playlistTime--; } // update reloadTime before reading the playlist reloadNext.at(playlists[a].id) = Util::bootSecs() + playlists[a].waitTime; playlists[a].reload(); } waitForNextSegment(); } int b = Util::bootSecs(); if (!readNextFile()){ if (playlists[currentPlaylist].playlistType != LIVE){return;} // need to reload all available playlists. update the map with the amount of ms to wait // before the next check. // set specific elements with the correct bootsecs() reloadNext.at(currentPlaylist) = b + playlists[currentPlaylist].waitTime; int timeToWait = reloadNext.at(currentPlaylist) - Util::bootSecs(); // at this point, we need to check which playlist we need to reload, and keep reading from // that playlist until EndOfPlaylist std::vector::iterator result = std::min_element(reloadNext.begin(), reloadNext.end()); int playlistToReload = std::distance(reloadNext.begin(), result); currentPlaylist = playlistToReload; // dont wait the first time. if (timeToWait > 0 && playlists[currentPlaylist].initDone && playlists[currentPlaylist].noChangeCount > 0){ if (timeToWait > playlists[currentPlaylist].waitTime){ WARN_MSG("something is not right..."); return; } if (playlists[currentPlaylist].noChangeCount < 2){ timeToWait /= 2; // wait half of the segment size when no segments are found. } } if (playlists[currentPlaylist].playlistEnd){ INFO_MSG("Playlist %d has reached his end!"); thisPacket.null(); return; } } if (playlists[currentPlaylist].isUrl()){ endOfFile = playlists[currentPlaylist].atEnd(); if (!endOfFile){ tsBuf.FromPointer(playlists[currentPlaylist].packetPtr); playlists[currentPlaylist].packetPtr += 188; } }else{ tsBuf.FromStream(in); endOfFile = in.eof(); } } if (!endOfFile){ tsStream.parse(tsBuf, 0); if (playlists[currentPlaylist].playlistType == LIVE){ hasPacket = tsStream.hasPacketOnEachTrack() || (endOfFile && tsStream.hasPacket()); }else{ hasPacket = tsStream.hasPacket(getMappedTrackId(tid)); } } } if (playlists[currentPlaylist].playlistType == LIVE){ tsStream.getEarliestPacket(thisPacket); tid = getOriginalTrackId(currentPlaylist, thisPacket.getTrackId()); }else{ tsStream.getPacket(getMappedTrackId(tid), thisPacket); } if (!thisPacket){ FAIL_MSG("Could not getNExt TS packet!"); return; } // overwrite trackId Bit::htobl(thisPacket.getData() + 8, tid); } void inputHLS::readPMT(){ if (playlists[currentPlaylist].isUrl()){ size_t bpos; TS::Packet tsBuffer; const char *tmpPtr = playlists[currentPlaylist].source.data(); while (!tsStream.hasPacketOnEachTrack() && (tmpPtr - playlists[currentPlaylist].source.c_str() + 188 <= playlists[currentPlaylist].source.size())){ tsBuffer.FromPointer(tmpPtr); tsStream.parse(tsBuffer, 0); tmpPtr += 188; } tsStream.partialClear(); }else{ size_t bpos = in.tellg(); in.seekg(0, in.beg); TS::Packet tsBuffer; while (!tsStream.hasPacketOnEachTrack() && tsBuffer.FromStream(in)){ tsStream.parse(tsBuffer, 0); } // tsStream.clear(); tsStream.partialClear(); //?? partialclear gebruiken?, input raakt hierdoor inconsistent.. in.seekg(bpos, in.beg); } } // Note: bpos is overloaded here for playlist entry! void inputHLS::seek(int seekTime){ INFO_MSG("SEEK"); tsStream.clear(); readPMT(); int trackId = 0; unsigned long plistEntry = 0xFFFFFFFFull; for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ unsigned long thisBPos = 0; for (std::deque::iterator keyIt = myMeta.tracks[*it].keys.begin(); keyIt != myMeta.tracks[*it].keys.end(); keyIt++){ if (keyIt->getTime() > seekTime){break;} thisBPos = keyIt->getBpos(); } if (thisBPos < plistEntry){ plistEntry = thisBPos; trackId = *it; } } if (plistEntry < 1){ WARN_MSG("attempted to seek outside the file"); return; } currentIndex = plistEntry - 1; currentPlaylist = getMappedTrackPlaylist(trackId); Playlist &curPlaylist = playlists[currentPlaylist]; playListEntries &entry = curPlaylist.entries.at(currentIndex); if (curPlaylist.isUrl()){ curPlaylist.loadURL(curPlaylist.uri_root + entry.filename); }else{ in.close(); in.open((curPlaylist.uri_root + entry.filename).c_str()); } } int inputHLS::getEntryId(int playlistId, uint64_t bytePos){ if (bytePos == 0){return 0;} for (int i = 0; i < playlists[playlistId].entries.size(); i++){ if (playlists[playlistId].entries.at(i).bytePos > bytePos){return i - 1;} } return playlists[playlistId].entries.size() - 1; } int inputHLS::getOriginalTrackId(int playlistId, int id){ return pidMapping[(playlistId << 16) + id]; } int inputHLS::getMappedTrackId(int id){return (pidMappingR[id] & 0xFFFF);} int inputHLS::getMappedTrackPlaylist(int id){return (pidMappingR[id] >> 16);} /// Very first function to be called on a regular playlist or variant playlist. bool inputHLS::initPlaylist(const std::string &uri){ std::string line; bool ret = false; startTime = Util::bootSecs(); std::string init_source; std::string playlistRootPath = uri.substr(0, uri.rfind("/") + 1); std::istringstream urlSource; std::ifstream fileSource; bool isUrl = false; if (uri.compare(0, 7, "http://") == 0){ isUrl = true; Playlist p; p.loadURL(uri); init_source = p.source; urlSource.str(init_source); }else{ fileSource.open(uri.c_str()); } std::istream &input = (isUrl ? (std::istream &)urlSource : (std::istream &)fileSource); std::getline(input, line); while (std::getline(input, line)){ if (!line.empty()){// skip empty lines in the playlist if (line.compare(0, 17, "#EXT-X-STREAM-INF") == 0){ // this is a variant playlist file.. next line is an uri to a playlist file std::getline(input, line); ret = readPlaylist(playlistRootPath + line); }else if (line.compare(0, 12, "#EXT-X-MEDIA") == 0){ // this is also a variant playlist, but streams need to be processed another way std::string mediafile; if (line.compare(18, 5, "AUDIO") == 0){ // find URI attribute int pos = line.find("URI"); if (pos != std::string::npos){ mediafile = line.substr(pos + 5, line.length() - pos - 6); ret = readPlaylist(playlistRootPath + mediafile); } } }else if (line.compare(0, 7, "#EXTINF") == 0){ // current file is not a variant playlist, but regular playlist. ret = readPlaylist(uri); break; }else{ // ignore wrong lines WARN_MSG("ignore wrong line: %s", line.c_str()); } } } if (!isUrl){fileSource.close();} return ret; } /// Function for reading every playlist. bool inputHLS::readPlaylist(const std::string &uri){ Playlist p(uri); p.id = playlists.size(); // set size of reloadNext to playlist count with default value 0 playlists.push_back(p); if (reloadNext.size() < playlists.size()){reloadNext.resize(playlists.size());} reloadNext.at(p.id) = Util::bootSecs() + p.waitTime; return true; } /// Read next .ts file from the playlist. (from the list of entries which needs to be processed) bool inputHLS::readNextFile(){ tsStream.clear(); Playlist &curList = playlists[currentPlaylist]; if (!curList.entries.size()){ VERYHIGH_MSG("no entries found in playlist: %d!", currentPlaylist); return false; } std::string url = (curList.uri_root + curList.entries.front().filename).c_str(); if (curList.isUrl() && curList.loadURL(url)){ curList.entries.pop_front(); // remove the item which is opened for reading. } if (curList.playlistType == LIVE){ in.close(); in.open(url.c_str()); if (in.good()){ curList.entries.pop_front(); // remove the item which is opened for reading. return true; } return false; } ++currentIndex; if (curList.entries.size() <= currentIndex){ INFO_MSG("end of playlist reached!"); return false; } in.close(); url = curList.uri_root + curList.entries.at(currentIndex).filename; in.open(url.c_str()); return true; } /// return the playlist id from which we need to read the first upcoming segment by timestamp. /// this will keep the playlists in sync while reading segments. int inputHLS::firstSegment(){ uint64_t firstTimeStamp = 0; int tmpId = -1; for (std::vector::iterator pListIt = playlists.begin(); pListIt != playlists.end(); pListIt++){ if (pListIt->entries.size()){ if (pListIt->entries.front().timestamp < firstTimeStamp || tmpId < 0){ firstTimeStamp = pListIt->entries.front().timestamp; tmpId = pListIt->id; } } } return tmpId; } // read the next segment void inputHLS::waitForNextSegment(){ uint32_t pListId = firstSegment(); if (pListId == -1){ VERYHIGH_MSG("no segments found!"); return; } int segmentTime = playlists[pListId].entries.front().timestamp - Util::bootSecs(); if (segmentTime){ --segmentTime; while (segmentTime > 1 && (needsLock() || nProxy.userClient.isAlive())){ Util::wait(1000); --segmentTime; continueNegotiate(); nProxy.userClient.keepAlive(); } } } }