Recording, HLS Push, UDP (Multicast) Input, Threaded TS
This commit is contained in:
parent
1c3e143709
commit
c25a533729
29 changed files with 1809 additions and 815 deletions
|
@ -38,6 +38,7 @@ int main(int argc, char * argv[]) {
|
|||
std::cout << mistOut::capa.toString() << std::endl;
|
||||
return -1;
|
||||
}
|
||||
conf.activate();
|
||||
if (mistOut::listenMode()){
|
||||
conf.serveForkedSocket(spawnForked);
|
||||
}else{
|
||||
|
|
|
@ -163,8 +163,8 @@ namespace Mist {
|
|||
if (lock){
|
||||
liveMeta.wait();
|
||||
}
|
||||
if (metaPages[0].mapped){
|
||||
DTSC::Packet tmpMeta(metaPages[0].mapped, metaPages[0].len, true);
|
||||
if (nProxy.metaPages[0].mapped){
|
||||
DTSC::Packet tmpMeta(nProxy.metaPages[0].mapped, nProxy.metaPages[0].len, true);
|
||||
if (tmpMeta.getVersion()){
|
||||
myMeta.reinit(tmpMeta);
|
||||
}
|
||||
|
@ -197,7 +197,7 @@ namespace Mist {
|
|||
if (isInitialized){
|
||||
return;
|
||||
}
|
||||
if (metaPages[0].mapped){
|
||||
if (nProxy.metaPages[0].mapped){
|
||||
return;
|
||||
}
|
||||
if (streamName.size() < 1){
|
||||
|
@ -233,8 +233,8 @@ namespace Mist {
|
|||
/// Connects or reconnects to the stream.
|
||||
/// Assumes streamName class member has been set already.
|
||||
/// Will start input if not currently active, calls onFail() if this does not succeed.
|
||||
/// After assuring stream is online, clears metaPages, then sets metaPages[0], statsPage and userClient to (hopefully) valid handles.
|
||||
/// Finally, calls updateMeta() and stats()
|
||||
/// After assuring stream is online, clears nProxy.metaPages, then sets nProxy.metaPages[0], statsPage and nProxy.userClient to (hopefully) valid handles.
|
||||
/// Finally, calls updateMeta()
|
||||
void Output::reconnect(){
|
||||
if (!Util::startInput(streamName)){
|
||||
DEBUG_MSG(DLVL_FAIL, "Opening stream failed - aborting initalization");
|
||||
|
@ -243,9 +243,9 @@ namespace Mist {
|
|||
}
|
||||
char pageId[NAME_BUFFER_SIZE];
|
||||
snprintf(pageId, NAME_BUFFER_SIZE, SHM_STREAM_INDEX, streamName.c_str());
|
||||
metaPages.clear();
|
||||
metaPages[0].init(pageId, DEFAULT_META_PAGE_SIZE);
|
||||
if (!metaPages[0].mapped){
|
||||
nProxy.metaPages.clear();
|
||||
nProxy.metaPages[0].init(pageId, DEFAULT_META_PAGE_SIZE);
|
||||
if (!nProxy.metaPages[0].mapped){
|
||||
FAIL_MSG("Could not connect to server for %s", streamName.c_str());
|
||||
onFail();
|
||||
return;
|
||||
|
@ -254,13 +254,12 @@ namespace Mist {
|
|||
statsPage.finish();
|
||||
}
|
||||
statsPage = IPC::sharedClient(SHM_STATISTICS, STAT_EX_SIZE, true);
|
||||
if (userClient.getData()){
|
||||
userClient.finish();
|
||||
if (nProxy.userClient.getData()){
|
||||
nProxy.userClient.finish();
|
||||
}
|
||||
char userPageName[NAME_BUFFER_SIZE];
|
||||
snprintf(userPageName, NAME_BUFFER_SIZE, SHM_USERS, streamName.c_str());
|
||||
userClient = IPC::sharedClient(userPageName, PLAY_EX_SIZE, true);
|
||||
stats();
|
||||
nProxy.userClient = IPC::sharedClient(userPageName, PLAY_EX_SIZE, true);
|
||||
updateMeta();
|
||||
}
|
||||
|
||||
|
@ -409,14 +408,14 @@ namespace Mist {
|
|||
}
|
||||
|
||||
int Output::pageNumForKey(long unsigned int trackId, long long int keyNum){
|
||||
if (!metaPages.count(trackId)){
|
||||
if (!nProxy.metaPages.count(trackId)){
|
||||
char id[NAME_BUFFER_SIZE];
|
||||
snprintf(id, NAME_BUFFER_SIZE, SHM_TRACK_INDEX, streamName.c_str(), trackId);
|
||||
metaPages[trackId].init(id, 8 * 1024);
|
||||
nProxy.metaPages[trackId].init(id, 8 * 1024);
|
||||
}
|
||||
int len = metaPages[trackId].len / 8;
|
||||
int len = nProxy.metaPages[trackId].len / 8;
|
||||
for (int i = 0; i < len; i++){
|
||||
int * tmpOffset = (int *)(metaPages[trackId].mapped + (i * 8));
|
||||
int * tmpOffset = (int *)(nProxy.metaPages[trackId].mapped + (i * 8));
|
||||
long amountKey = ntohl(tmpOffset[1]);
|
||||
if (amountKey == 0){continue;}
|
||||
long tmpKey = ntohl(tmpOffset[0]);
|
||||
|
@ -429,7 +428,7 @@ namespace Mist {
|
|||
|
||||
void Output::loadPageForKey(long unsigned int trackId, long long int keyNum){
|
||||
if (myMeta.vod && keyNum > myMeta.tracks[trackId].keys.rbegin()->getNumber()){
|
||||
curPage.erase(trackId);
|
||||
nProxy.curPage.erase(trackId);
|
||||
currKeyOpen.erase(trackId);
|
||||
return;
|
||||
}
|
||||
|
@ -448,7 +447,7 @@ namespace Mist {
|
|||
}
|
||||
if (timeout > 100){
|
||||
DEBUG_MSG(DLVL_FAIL, "Timeout while waiting for requested page %lld for track %lu. Aborting.", keyNum, trackId);
|
||||
curPage.erase(trackId);
|
||||
nProxy.curPage.erase(trackId);
|
||||
currKeyOpen.erase(trackId);
|
||||
return;
|
||||
}
|
||||
|
@ -475,9 +474,9 @@ namespace Mist {
|
|||
}
|
||||
char id[NAME_BUFFER_SIZE];
|
||||
snprintf(id, NAME_BUFFER_SIZE, SHM_TRACK_DATA, streamName.c_str(), trackId, pageNum);
|
||||
curPage[trackId].init(id, DEFAULT_DATA_PAGE_SIZE);
|
||||
if (!(curPage[trackId].mapped)){
|
||||
DEBUG_MSG(DLVL_FAIL, "Initializing page %s failed", curPage[trackId].name.c_str());
|
||||
nProxy.curPage[trackId].init(id, DEFAULT_DATA_PAGE_SIZE);
|
||||
if (!(nProxy.curPage[trackId].mapped)){
|
||||
DEBUG_MSG(DLVL_FAIL, "Initializing page %s failed", nProxy.curPage[trackId].name.c_str());
|
||||
return;
|
||||
}
|
||||
currKeyOpen[trackId] = pageNum;
|
||||
|
@ -503,7 +502,7 @@ namespace Mist {
|
|||
|
||||
bool Output::seek(unsigned int tid, unsigned long long pos, bool getNextKey){
|
||||
loadPageForKey(tid, getKeyForTime(tid, pos) + (getNextKey?1:0));
|
||||
if (!curPage.count(tid) || !curPage[tid].mapped){
|
||||
if (!nProxy.curPage.count(tid) || !nProxy.curPage[tid].mapped){
|
||||
INFO_MSG("Aborting seek to %llums in track %u, not available.", pos, tid);
|
||||
return false;
|
||||
}
|
||||
|
@ -511,9 +510,9 @@ namespace Mist {
|
|||
tmp.tid = tid;
|
||||
tmp.offset = 0;
|
||||
DTSC::Packet tmpPack;
|
||||
tmpPack.reInit(curPage[tid].mapped + tmp.offset, 0, true);
|
||||
tmpPack.reInit(nProxy.curPage[tid].mapped + tmp.offset, 0, true);
|
||||
tmp.time = tmpPack.getTime();
|
||||
char * mpd = curPage[tid].mapped;
|
||||
char * mpd = nProxy.curPage[tid].mapped;
|
||||
while ((long long)tmp.time < pos && tmpPack){
|
||||
tmp.offset += tmpPack.getDataLen();
|
||||
tmpPack.reInit(mpd + tmp.offset, 0, true);
|
||||
|
@ -524,15 +523,15 @@ namespace Mist {
|
|||
return true;
|
||||
}else{
|
||||
//don't print anything for empty packets - not sign of corruption, just unfinished stream.
|
||||
if (curPage[tid].mapped[tmp.offset] != 0){
|
||||
if (nProxy.curPage[tid].mapped[tmp.offset] != 0){
|
||||
DEBUG_MSG(DLVL_FAIL, "Noes! Couldn't find packet on track %d because of some kind of corruption error or somesuch.", tid);
|
||||
}else{
|
||||
VERYHIGH_MSG("Track %d no data (key %u @ %u) - waiting...", tid, getKeyForTime(tid, pos) + (getNextKey?1:0), tmp.offset);
|
||||
unsigned int i = 0;
|
||||
while (curPage[tid].mapped[tmp.offset] == 0 && ++i < 42){
|
||||
while (nProxy.curPage[tid].mapped[tmp.offset] == 0 && ++i < 42){
|
||||
Util::wait(100);
|
||||
}
|
||||
if (curPage[tid].mapped[tmp.offset] == 0){
|
||||
if (nProxy.curPage[tid].mapped[tmp.offset] == 0){
|
||||
FAIL_MSG("Track %d no data (key %u) - timeout", tid, getKeyForTime(tid, pos) + (getNextKey?1:0));
|
||||
}else{
|
||||
return seek(tid, pos, getNextKey);
|
||||
|
@ -879,7 +878,7 @@ namespace Mist {
|
|||
/*LTS-END*/
|
||||
|
||||
stats();
|
||||
userClient.finish();
|
||||
nProxy.userClient.finish();
|
||||
statsPage.finish();
|
||||
myConn.close();
|
||||
return 0;
|
||||
|
@ -941,15 +940,15 @@ namespace Mist {
|
|||
|
||||
DEBUG_MSG(DLVL_DONTEVEN, "Loading track %u (next=%lu), %llu ms", nxt.tid, nxtKeyNum[nxt.tid], nxt.time);
|
||||
|
||||
if (nxt.offset >= curPage[nxt.tid].len){
|
||||
if (nxt.offset >= nProxy.curPage[nxt.tid].len){
|
||||
nxtKeyNum[nxt.tid] = getKeyForTime(nxt.tid, thisPacket.getTime());
|
||||
loadPageForKey(nxt.tid, ++nxtKeyNum[nxt.tid]);
|
||||
nxt.offset = 0;
|
||||
if (curPage.count(nxt.tid) && curPage[nxt.tid].mapped){
|
||||
if (getDTSCTime(curPage[nxt.tid].mapped, nxt.offset) < nxt.time){
|
||||
if (nProxy.curPage.count(nxt.tid) && nProxy.curPage[nxt.tid].mapped){
|
||||
if (getDTSCTime(nProxy.curPage[nxt.tid].mapped, nxt.offset) < nxt.time){
|
||||
ERROR_MSG("Time going backwards in track %u - dropping track.", nxt.tid);
|
||||
}else{
|
||||
nxt.time = getDTSCTime(curPage[nxt.tid].mapped, nxt.offset);
|
||||
nxt.time = getDTSCTime(nProxy.curPage[nxt.tid].mapped, nxt.offset);
|
||||
buffer.insert(nxt);
|
||||
}
|
||||
prepareNext();
|
||||
|
@ -957,7 +956,7 @@ namespace Mist {
|
|||
}
|
||||
}
|
||||
|
||||
if (!curPage.count(nxt.tid) || !curPage[nxt.tid].mapped){
|
||||
if (!nProxy.curPage.count(nxt.tid) || !nProxy.curPage[nxt.tid].mapped){
|
||||
//mapping failure? Drop this track and go to next.
|
||||
//not an error - usually means end of stream.
|
||||
DEBUG_MSG(DLVL_DEVEL, "Track %u no page - dropping track.", nxt.tid);
|
||||
|
@ -966,7 +965,7 @@ namespace Mist {
|
|||
}
|
||||
|
||||
//have we arrived at the end of the memory page? (4 zeroes mark the end)
|
||||
if (!memcmp(curPage[nxt.tid].mapped + nxt.offset, "\000\000\000\000", 4)){
|
||||
if (!memcmp(nProxy.curPage[nxt.tid].mapped + nxt.offset, "\000\000\000\000", 4)){
|
||||
//if we don't currently know where we are, we're lost. We should drop the track.
|
||||
if (!nxt.time){
|
||||
DEBUG_MSG(DLVL_DEVEL, "Timeless empty packet on track %u - dropping track.", nxt.tid);
|
||||
|
@ -994,8 +993,8 @@ namespace Mist {
|
|||
nxtKeyNum[nxt.tid] = getKeyForTime(nxt.tid, thisPacket.getTime());
|
||||
loadPageForKey(nxt.tid, ++nxtKeyNum[nxt.tid]);
|
||||
nxt.offset = 0;
|
||||
if (curPage.count(nxt.tid) && curPage[nxt.tid].mapped){
|
||||
unsigned long long nextTime = getDTSCTime(curPage[nxt.tid].mapped, nxt.offset);
|
||||
if (nProxy.curPage.count(nxt.tid) && nProxy.curPage[nxt.tid].mapped){
|
||||
unsigned long long nextTime = getDTSCTime(nProxy.curPage[nxt.tid].mapped, nxt.offset);
|
||||
if (nextTime && nextTime < nxt.time){
|
||||
DEBUG_MSG(DLVL_DEVEL, "Time going backwards in track %u - dropping track.", nxt.tid);
|
||||
}else{
|
||||
|
@ -1012,7 +1011,7 @@ namespace Mist {
|
|||
prepareNext();
|
||||
return;
|
||||
}
|
||||
thisPacket.reInit(curPage[nxt.tid].mapped + nxt.offset, 0, true);
|
||||
thisPacket.reInit(nProxy.curPage[nxt.tid].mapped + nxt.offset, 0, true);
|
||||
if (thisPacket){
|
||||
if (thisPacket.getTime() != nxt.time && nxt.time){
|
||||
WARN_MSG("Loaded track %ld@%llu instead of %ld@%llu", thisPacket.getTrackId(), thisPacket.getTime(), nxt.tid, nxt.time);
|
||||
|
@ -1073,9 +1072,9 @@ namespace Mist {
|
|||
completeKeyReadyTimeOut = false;
|
||||
}
|
||||
}
|
||||
if (curPage[nxt.tid]){
|
||||
if (nxt.offset < curPage[nxt.tid].len){
|
||||
unsigned long long nextTime = getDTSCTime(curPage[nxt.tid].mapped, nxt.offset);
|
||||
if (nProxy.curPage[nxt.tid]){
|
||||
if (nxt.offset < nProxy.curPage[nxt.tid].len){
|
||||
unsigned long long nextTime = getDTSCTime(nProxy.curPage[nxt.tid].mapped, nxt.offset);
|
||||
if (nextTime){
|
||||
nxt.time = nextTime;
|
||||
}else{
|
||||
|
@ -1123,25 +1122,25 @@ namespace Mist {
|
|||
}
|
||||
}
|
||||
int tNum = 0;
|
||||
if (!userClient.getData()){
|
||||
if (!nProxy.userClient.getData()){
|
||||
char userPageName[NAME_BUFFER_SIZE];
|
||||
snprintf(userPageName, NAME_BUFFER_SIZE, SHM_USERS, streamName.c_str());
|
||||
userClient = IPC::sharedClient(userPageName, PLAY_EX_SIZE, true);
|
||||
if (!userClient.getData()){
|
||||
nProxy.userClient = IPC::sharedClient(userPageName, PLAY_EX_SIZE, true);
|
||||
if (!nProxy.userClient.getData()){
|
||||
DEBUG_MSG(DLVL_WARN, "Player connection failure - aborting output");
|
||||
myConn.close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!trackMap.size()){
|
||||
IPC::userConnection userConn(userClient.getData());
|
||||
if (!nProxy.trackMap.size()){
|
||||
IPC::userConnection userConn(nProxy.userClient.getData());
|
||||
for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end() && tNum < SIMUL_TRACKS; it++){
|
||||
userConn.setTrackId(tNum, *it);
|
||||
userConn.setKeynum(tNum, nxtKeyNum[*it]);
|
||||
tNum ++;
|
||||
}
|
||||
}
|
||||
userClient.keepAlive();
|
||||
nProxy.userClient.keepAlive();
|
||||
if (tNum > SIMUL_TRACKS){
|
||||
DEBUG_MSG(DLVL_WARN, "Too many tracks selected, using only first %d", SIMUL_TRACKS);
|
||||
}
|
||||
|
|
|
@ -5,38 +5,36 @@
|
|||
namespace Mist {
|
||||
///\brief Builds an index file for HTTP Live streaming.
|
||||
///\return The index file for HTTP Live Streaming.
|
||||
std::string OutHLS::liveIndex(){
|
||||
std::string OutHLS::liveIndex() {
|
||||
std::stringstream result;
|
||||
result << "#EXTM3U\r\n";
|
||||
int audioId = -1;
|
||||
std::string audioName;
|
||||
for (std::map<unsigned int,DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){
|
||||
if (it->second.codec == "AAC" || it->second.codec == "MP3" || it->second.codec == "AC3"){
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) {
|
||||
if (it->second.codec == "AAC" || it->second.codec == "MP3" || it->second.codec == "AC3") {
|
||||
audioId = it->first;
|
||||
audioName = it->second.getIdentifier();
|
||||
break;
|
||||
}
|
||||
}
|
||||
unsigned int vidTracks = 0;
|
||||
for (std::map<unsigned int,DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){
|
||||
if (it->second.codec == "H264" || it->second.codec == "HEVC"){
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) {
|
||||
if (it->second.codec == "H264" || it->second.codec == "HEVC") {
|
||||
vidTracks++;
|
||||
int bWidth = it->second.bps;
|
||||
if (bWidth < 5){
|
||||
if (bWidth < 5) {
|
||||
bWidth = 5;
|
||||
}
|
||||
if (audioId != -1){
|
||||
if (audioId != -1) {
|
||||
bWidth += myMeta.tracks[audioId].bps;
|
||||
}
|
||||
result << "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" << (bWidth * 8) << "\r\n";
|
||||
result << it->first;
|
||||
if (audioId != -1){
|
||||
if (audioId != -1) {
|
||||
result << "_" << audioId;
|
||||
}
|
||||
result << "/index.m3u8\r\n";
|
||||
}
|
||||
}
|
||||
if (!vidTracks && audioId){
|
||||
if (!vidTracks && audioId) {
|
||||
result << "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" << (myMeta.tracks[audioId].bps * 8) << "\r\n";
|
||||
result << audioId << "/index.m3u8\r\n";
|
||||
}
|
||||
|
@ -44,83 +42,242 @@ namespace Mist {
|
|||
return result.str();
|
||||
}
|
||||
|
||||
std::string OutHLS::liveIndex(int tid){
|
||||
std::string OutHLS::pushLiveIndex(){
|
||||
std::stringstream result;
|
||||
result << "#EXTM3U\r\n";
|
||||
std::set<unsigned int> audioTracks;
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) {
|
||||
if (it->second.codec == "AAC" || it->second.codec == "MP3" || it->second.codec == "AC3") {
|
||||
audioTracks.insert(it->first);
|
||||
}
|
||||
}
|
||||
if (!audioTracks.size()){
|
||||
audioTracks.insert(-1);
|
||||
}
|
||||
unsigned int vidTracks = 0;
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) {
|
||||
if (it->second.codec == "H264" || it->second.codec == "HEVC") {
|
||||
for (std::set<unsigned int>::iterator audIt = audioTracks.begin(); audIt != audioTracks.end(); audIt++){
|
||||
vidTracks++;
|
||||
int bWidth = it->second.bps;
|
||||
if (bWidth < 5) {
|
||||
bWidth = 5;
|
||||
}
|
||||
if (*audIt != -1) {
|
||||
bWidth += myMeta.tracks[*audIt].bps;
|
||||
}
|
||||
result << "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" << (bWidth * 8) << "\r\n";
|
||||
result << it->first;
|
||||
if (*audIt != -1) {
|
||||
result << "_" << *audIt;
|
||||
}
|
||||
result << "/index.m3u8\r\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!vidTracks && audioTracks.size()) {
|
||||
result << "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" << (myMeta.tracks[*audioTracks.begin()].bps * 8) << "\r\n";
|
||||
result << *audioTracks.begin() << "/index.m3u8\r\n";
|
||||
}
|
||||
DEBUG_MSG(DLVL_HIGH, "Sending this index: %s", result.str().c_str());
|
||||
return result.str();
|
||||
}
|
||||
|
||||
std::string OutHLS::pushLiveIndex(int tid, unsigned long bTime, unsigned long eTime){
|
||||
updateMeta();
|
||||
if (!myMeta.tracks[tid].fragments.size()) {
|
||||
DEBUG_MSG(DLVL_FAIL, "liveIndex called with track %d, which has no fragments!", tid);
|
||||
return "";
|
||||
}
|
||||
std::stringstream result;
|
||||
//parse single track
|
||||
int longestFragment = 0;
|
||||
for (std::deque<DTSC::Fragment>::iterator it = myMeta.tracks[tid].fragments.begin(); (it + 1) != myMeta.tracks[tid].fragments.end(); it++) {
|
||||
if (it->getDuration() > longestFragment) {
|
||||
longestFragment = it->getDuration();
|
||||
}
|
||||
}
|
||||
if ((myMeta.tracks[tid].lastms - myMeta.tracks[tid].firstms) / myMeta.tracks[tid].fragments.size() > longestFragment) {
|
||||
longestFragment = (myMeta.tracks[tid].lastms - myMeta.tracks[tid].firstms) / myMeta.tracks[tid].fragments.size();
|
||||
}
|
||||
result << "#EXTM3U\r\n#EXT-X-TARGETDURATION:" << (longestFragment / 1000) + 1 << "\r\n";
|
||||
|
||||
std::deque<std::string> lines;
|
||||
unsigned int skippedLines = 0;
|
||||
for (std::deque<DTSC::Fragment>::iterator it = myMeta.tracks[tid].fragments.begin(); it != myMeta.tracks[tid].fragments.end(); it++) {
|
||||
long long int starttime = myMeta.tracks[tid].getKey(it->getNumber()).getTime();
|
||||
long long duration = it->getDuration();
|
||||
if (duration <= 0) {
|
||||
duration = myMeta.tracks[tid].lastms - starttime;
|
||||
}
|
||||
if (starttime < bTime){
|
||||
skippedLines++;
|
||||
}
|
||||
if (starttime >= bTime && (starttime + duration) <= eTime){
|
||||
char lineBuf[400];
|
||||
snprintf(lineBuf, 400, "#EXTINF:%lld, no desc\r\n%lld_%lld.ts\r\n", ((duration + 500) / 1000), starttime, starttime + duration);
|
||||
lines.push_back(lineBuf);
|
||||
}
|
||||
}
|
||||
|
||||
result << "#EXT-X-MEDIA-SEQUENCE:" << myMeta.tracks[tid].missedFrags + skippedLines << "\r\n";
|
||||
|
||||
while (lines.size()) {
|
||||
result << lines.front();
|
||||
lines.pop_front();
|
||||
}
|
||||
if (!myMeta.live && eTime >= myMeta.tracks[tid].lastms) {
|
||||
result << "#EXT-X-ENDLIST\r\n";
|
||||
}
|
||||
DEBUG_MSG(DLVL_HIGH, "Sending this index: %s", result.str().c_str());
|
||||
return result.str();
|
||||
}
|
||||
|
||||
|
||||
std::string OutHLS::liveIndex(int tid) {
|
||||
updateMeta();
|
||||
std::stringstream result;
|
||||
//parse single track
|
||||
int longestFragment = 0;
|
||||
if (!myMeta.tracks[tid].fragments.size()){
|
||||
if (!myMeta.tracks[tid].fragments.size()) {
|
||||
DEBUG_MSG(DLVL_FAIL, "liveIndex called with track %d, which has no fragments!", tid);
|
||||
return "";
|
||||
}
|
||||
for (std::deque<DTSC::Fragment>::iterator it = myMeta.tracks[tid].fragments.begin(); (it + 1) != myMeta.tracks[tid].fragments.end(); it++){
|
||||
if (it->getDuration() > longestFragment){
|
||||
for (std::deque<DTSC::Fragment>::iterator it = myMeta.tracks[tid].fragments.begin(); (it + 1) != myMeta.tracks[tid].fragments.end(); it++) {
|
||||
if (it->getDuration() > longestFragment) {
|
||||
longestFragment = it->getDuration();
|
||||
}
|
||||
}
|
||||
if ((myMeta.tracks[tid].lastms - myMeta.tracks[tid].firstms) / myMeta.tracks[tid].fragments.size() > longestFragment){
|
||||
if ((myMeta.tracks[tid].lastms - myMeta.tracks[tid].firstms) / myMeta.tracks[tid].fragments.size() > longestFragment) {
|
||||
longestFragment = (myMeta.tracks[tid].lastms - myMeta.tracks[tid].firstms) / myMeta.tracks[tid].fragments.size();
|
||||
}
|
||||
result << "#EXTM3U\r\n#EXT-X-TARGETDURATION:" << (longestFragment / 1000) + 1 << "\r\n";
|
||||
|
||||
|
||||
std::deque<std::string> lines;
|
||||
for (std::deque<DTSC::Fragment>::iterator it = myMeta.tracks[tid].fragments.begin(); it != myMeta.tracks[tid].fragments.end(); it++){
|
||||
for (std::deque<DTSC::Fragment>::iterator it = myMeta.tracks[tid].fragments.begin(); it != myMeta.tracks[tid].fragments.end(); it++) {
|
||||
long long int starttime = myMeta.tracks[tid].getKey(it->getNumber()).getTime();
|
||||
std::stringstream line;
|
||||
long long duration = it->getDuration();
|
||||
if (duration <= 0){
|
||||
if (duration <= 0) {
|
||||
duration = myMeta.tracks[tid].lastms - starttime;
|
||||
}
|
||||
line << "#EXTINF:" << ((duration + 500) / 1000) << ", no desc\r\n" << starttime << "_" << duration + starttime << ".ts\r\n";
|
||||
lines.push_back(line.str());
|
||||
char lineBuf[400];
|
||||
snprintf(lineBuf, 400, "#EXTINF:%lld, no desc\r\n%lld_%lld,ts\r\n", ((duration + 500) / 1000), starttime, starttime + duration);
|
||||
lines.push_back(lineBuf);
|
||||
}
|
||||
|
||||
//skip the first fragment if live and there are more than 2 fragments.
|
||||
unsigned int skippedLines = 0;
|
||||
if (myMeta.live){
|
||||
//only print the last segment when VoD
|
||||
lines.pop_back();
|
||||
/*LTS-START*/
|
||||
unsigned int skip = (( myMeta.tracks[tid].fragments.size()-1) * config->getInteger("startpos")) / 1000u;
|
||||
while (skippedLines < skip && lines.size()){
|
||||
if (myMeta.live) {
|
||||
if (lines.size() > 2) {
|
||||
lines.pop_front();
|
||||
skippedLines++;
|
||||
}
|
||||
if (config->getInteger("listlimit")){
|
||||
//only print the last segment when VoD
|
||||
lines.pop_back();
|
||||
/*LTS-START*/
|
||||
unsigned int skip = ((myMeta.tracks[tid].fragments.size() - 1) * config->getInteger("startpos")) / 1000u;
|
||||
while (skippedLines < skip && lines.size()) {
|
||||
lines.pop_front();
|
||||
skippedLines++;
|
||||
}
|
||||
if (config->getInteger("listlimit")) {
|
||||
unsigned long listlimit = config->getInteger("listlimit");
|
||||
while (lines.size() > listlimit){
|
||||
while (lines.size() > listlimit) {
|
||||
lines.pop_front();
|
||||
skippedLines++;
|
||||
}
|
||||
}
|
||||
/*LTS-END*/
|
||||
}
|
||||
|
||||
|
||||
result << "#EXT-X-MEDIA-SEQUENCE:" << myMeta.tracks[tid].missedFrags + skippedLines << "\r\n";
|
||||
|
||||
while (lines.size()){
|
||||
|
||||
while (lines.size()) {
|
||||
result << lines.front();
|
||||
lines.pop_front();
|
||||
}
|
||||
if ( !myMeta.live){
|
||||
if (!myMeta.live) {
|
||||
result << "#EXT-X-ENDLIST\r\n";
|
||||
}
|
||||
DEBUG_MSG(DLVL_HIGH, "Sending this index: %s", result.str().c_str());
|
||||
return result.str();
|
||||
} //liveIndex
|
||||
|
||||
|
||||
OutHLS::OutHLS(Socket::Connection & conn) : TSOutput(conn){
|
||||
|
||||
std::string OutHLS::generatePushList() {
|
||||
updateMeta();
|
||||
std::set<unsigned int> videoTracks;
|
||||
std::set<unsigned int> audioTracks;
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) {
|
||||
if (it->second.codec == "AAC" || it->second.codec == "MP3" || it->second.codec == "AC3") {
|
||||
audioTracks.insert(it->first);
|
||||
}
|
||||
if (it->second.codec == "H264" || it->second.codec == "H265"){
|
||||
videoTracks.insert(it->first);
|
||||
}
|
||||
}
|
||||
JSON::Value result;
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) {
|
||||
std::stringstream tid;
|
||||
tid << it->second.trackID;
|
||||
result["tracks"][tid.str()] = it->second.toJSON(true);
|
||||
}
|
||||
for(std::set<unsigned int>::iterator it = videoTracks.begin(); it != videoTracks.end(); it++){
|
||||
for(std::set<unsigned int>::iterator it2 = audioTracks.begin(); it2 != audioTracks.end(); it2++){
|
||||
JSON::Value quality;
|
||||
std::stringstream identifier;
|
||||
identifier << "/" << *it << "_" << *it2;
|
||||
quality["index"] = "/push" + identifier.str() + "/index_\%llu_\%llu.m3u8";
|
||||
quality["segment"] = identifier.str() + "/\%llu_\%llu.ts";
|
||||
quality["video"] = *it;
|
||||
quality["audio"] = *it2;
|
||||
quality["id"] = identifier.str();
|
||||
std::deque<DTSC::Fragment>::iterator it3 = myMeta.tracks[*it].fragments.begin();
|
||||
for (int i = 0; i < 2; i++){
|
||||
if (it3 != myMeta.tracks[*it].fragments.end()){
|
||||
++it3;
|
||||
}
|
||||
}
|
||||
for (; it3 != myMeta.tracks[*it].fragments.end(); it3++) {
|
||||
if (myMeta.live && it3 == (myMeta.tracks[*it].fragments.end() - 1)){
|
||||
//Skip the current last fragment if we are live
|
||||
continue;
|
||||
}
|
||||
long long int starttime = myMeta.tracks[*it].getKey(it3->getNumber()).getTime();
|
||||
std::stringstream line;
|
||||
long long duration = it3->getDuration();
|
||||
if (duration <= 0) {
|
||||
duration = myMeta.tracks[*it].lastms - starttime;
|
||||
}
|
||||
std::stringstream segmenturl;
|
||||
segmenturl << identifier.str() << "/" << starttime << "_" << duration + starttime << ".ts";
|
||||
JSON::Value segment;
|
||||
//segment["url"] = segmenturl.str();
|
||||
segment["time"] = starttime;
|
||||
segment["duration"] = duration;
|
||||
segment["number"] = (unsigned int)it3->getNumber();
|
||||
quality["segments"].append(segment);
|
||||
}
|
||||
result["qualities"].append(quality);
|
||||
}
|
||||
}
|
||||
return result.toString();;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
OutHLS::OutHLS(Socket::Connection & conn) : TSOutput(conn) {
|
||||
realTime = 0;
|
||||
}
|
||||
|
||||
|
||||
OutHLS::~OutHLS() {}
|
||||
|
||||
void OutHLS::init(Util::Config * cfg){
|
||||
|
||||
void OutHLS::init(Util::Config * cfg) {
|
||||
HTTPOutput::init(cfg);
|
||||
capa["name"] = "HLS";
|
||||
capa["desc"] = "Enables HTTP protocol Apple-specific streaming (also known as HLS).";
|
||||
capa["url_rel"] = "/hls/$/index.m3u8";
|
||||
capa["url_prefix"] = "/hls/$/";
|
||||
capa["url_pushlist"] = "/hls/$/push/list";
|
||||
capa["codecs"][0u][0u].append("HEVC");
|
||||
capa["codecs"][0u][0u].append("H264");
|
||||
capa["codecs"][0u][1u].append("AAC");
|
||||
|
@ -139,29 +296,29 @@ namespace Mist {
|
|||
/*LTS-END*/
|
||||
}
|
||||
|
||||
int OutHLS::canSeekms(unsigned int ms){
|
||||
int OutHLS::canSeekms(unsigned int ms) {
|
||||
//no tracks? Frame too new by definition.
|
||||
if ( !myMeta.tracks.size()){
|
||||
if (!myMeta.tracks.size()) {
|
||||
return 1;
|
||||
}
|
||||
//check main track
|
||||
DTSC::Track & mainTrack = myMeta.tracks[*selectedTracks.begin()];
|
||||
//return "too late" if one track is past this point
|
||||
if (ms < mainTrack.firstms){
|
||||
return -1;
|
||||
}
|
||||
//return "too early" if one track is not yet at this point
|
||||
if (ms > mainTrack.lastms){
|
||||
return 1;
|
||||
//loop trough all the tracks
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) {
|
||||
//return "too late" if one track is past this point
|
||||
if (ms < it->second.firstms) {
|
||||
return -1;
|
||||
}
|
||||
//return "too early" if one track is not yet at this point
|
||||
if (ms > it->second.lastms) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void OutHLS::onHTTP(){
|
||||
void OutHLS::onHTTP() {
|
||||
std::string method = H.method;
|
||||
|
||||
|
||||
if (H.url == "/crossdomain.xml"){
|
||||
|
||||
if (H.url == "/crossdomain.xml") {
|
||||
H.Clean();
|
||||
H.SetHeader("Content-Type", "text/xml");
|
||||
H.SetHeader("Server", "MistServer/" PACKAGE_VERSION);
|
||||
|
@ -176,27 +333,81 @@ namespace Mist {
|
|||
H.Clean(); //clean for any possible next requests
|
||||
return;
|
||||
} //crossdomain.xml
|
||||
|
||||
if (H.url.find("hls") == std::string::npos){
|
||||
|
||||
if (H.method == "OPTIONS") {
|
||||
H.Clean();
|
||||
H.SetHeader("Content-Type", "application/octet-stream");
|
||||
H.SetHeader("Cache-Control", "no-cache");
|
||||
H.setCORSHeaders();
|
||||
H.SetBody("");
|
||||
H.SendResponse("200", "OK", myConn);
|
||||
H.Clean();
|
||||
return;
|
||||
}
|
||||
|
||||
if (H.url.find("hls") == std::string::npos) {
|
||||
myConn.close();
|
||||
return;
|
||||
}
|
||||
|
||||
appleCompat = (H.GetHeader("User-Agent").find("iPad") != std::string::npos) || (H.GetHeader("User-Agent").find("iPod") != std::string::npos)|| (H.GetHeader("User-Agent").find("iPhone") != std::string::npos);
|
||||
|
||||
|
||||
appleCompat = (H.GetHeader("User-Agent").find("iPad") != std::string::npos) || (H.GetHeader("User-Agent").find("iPod") != std::string::npos) || (H.GetHeader("User-Agent").find("iPhone") != std::string::npos);
|
||||
bool VLCworkaround = false;
|
||||
if (H.GetHeader("User-Agent").substr(0, 3) == "VLC"){
|
||||
if (H.GetHeader("User-Agent").substr(0, 3) == "VLC") {
|
||||
std::string vlcver = H.GetHeader("User-Agent").substr(4);
|
||||
if (vlcver[0] == '0' || vlcver[0] == '1' || (vlcver[0] == '2' && vlcver[2] < '2')){
|
||||
if (vlcver[0] == '0' || vlcver[0] == '1' || (vlcver[0] == '2' && vlcver[2] < '2')) {
|
||||
DEBUG_MSG(DLVL_INFO, "Enabling VLC version < 2.2.0 bug workaround.");
|
||||
VLCworkaround = true;
|
||||
}
|
||||
}
|
||||
initialize();
|
||||
if (H.url.find(".m3u") == std::string::npos){
|
||||
std::string tmpStr = H.getUrl().substr(5+streamName.size());
|
||||
if (H.url.substr(5 + streamName.size(), 5) == "/push"){
|
||||
std::string relPushUrl = H.url.substr(10 + streamName.size());
|
||||
H.Clean();
|
||||
if (relPushUrl == "/list"){
|
||||
H.SetBody(generatePushList());
|
||||
H.SendResponse("200", "OK", myConn);
|
||||
H.Clean();
|
||||
return;
|
||||
}
|
||||
if (relPushUrl.find(".m3u8") != std::string::npos) {
|
||||
H.SetHeader("Content-Type", "audio/x-mpegurl");
|
||||
} else {
|
||||
H.SetHeader("Content-Type", "audio/mpegurl");
|
||||
}
|
||||
if (relPushUrl == "/index.m3u8"){
|
||||
H.SetHeader("Cache-Control", "no-cache");
|
||||
H.setCORSHeaders();
|
||||
H.SetBody(pushLiveIndex());
|
||||
H.SendResponse("200", "OK", myConn);
|
||||
H.Clean(); //clean for any possible next requests
|
||||
return;
|
||||
}else {
|
||||
unsigned int vTrack;
|
||||
unsigned int aTrack;
|
||||
unsigned long long bTime;
|
||||
unsigned long long eTime;
|
||||
if (sscanf(relPushUrl.c_str(), "/%u_%u/index_%llu_%llu.m3u", &vTrack, &aTrack, &bTime, &eTime) == 4) {
|
||||
if (eTime < bTime){
|
||||
eTime = bTime;
|
||||
}
|
||||
H.SetHeader("Cache-Control", "no-cache");
|
||||
H.setCORSHeaders();
|
||||
H.SetBody(pushLiveIndex(vTrack, bTime, eTime));
|
||||
H.SendResponse("200", "OK", myConn);
|
||||
H.Clean(); //clean for any possible next requests
|
||||
return;
|
||||
}
|
||||
}
|
||||
H.SetBody("The HLS URL wasn't understood - what did you want, exactly?\n");
|
||||
myConn.SendNow(H.BuildResponse("404", "URL mismatch"));
|
||||
H.Clean(); //clean for any possible next requests
|
||||
return;
|
||||
}else if (H.url.find(".m3u") == std::string::npos) {
|
||||
std::string tmpStr = H.getUrl().substr(5 + streamName.size());
|
||||
long long unsigned int from;
|
||||
if (sscanf(tmpStr.c_str(), "/%u_%u/%llu_%llu.ts", &vidTrack, &audTrack, &from, &until) != 4){
|
||||
if (sscanf(tmpStr.c_str(), "/%u/%llu_%llu.ts", &vidTrack, &from, &until) != 3){
|
||||
if (sscanf(tmpStr.c_str(), "/%u_%u/%llu_%llu.ts", &vidTrack, &audTrack, &from, &until) != 4) {
|
||||
if (sscanf(tmpStr.c_str(), "/%u/%llu_%llu.ts", &vidTrack, &from, &until) != 3) {
|
||||
DEBUG_MSG(DLVL_MEDIUM, "Could not parse URL: %s", H.getUrl().c_str());
|
||||
H.Clean();
|
||||
H.setCORSHeaders();
|
||||
|
@ -204,33 +415,38 @@ namespace Mist {
|
|||
myConn.SendNow(H.BuildResponse("404", "URL mismatch"));
|
||||
H.Clean(); //clean for any possible next requests
|
||||
return;
|
||||
}else{
|
||||
} else {
|
||||
selectedTracks.clear();
|
||||
selectedTracks.insert(vidTrack);
|
||||
}
|
||||
}else{
|
||||
} else {
|
||||
selectedTracks.clear();
|
||||
selectedTracks.insert(vidTrack);
|
||||
selectedTracks.insert(audTrack);
|
||||
}
|
||||
|
||||
if (myMeta.live){
|
||||
for (std::map<unsigned int,DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){
|
||||
if (it->second.codec == "ID3"){
|
||||
selectedTracks.insert(it->first);
|
||||
}
|
||||
}
|
||||
|
||||
if (myMeta.live) {
|
||||
unsigned int timeout = 0;
|
||||
int seekable;
|
||||
do {
|
||||
seekable = canSeekms(from);
|
||||
/// \todo Detection of out-of-range parts.
|
||||
if (seekable > 0){
|
||||
if (seekable > 0) {
|
||||
//time out after 21 seconds
|
||||
if (++timeout > 42){
|
||||
if (++timeout > 42) {
|
||||
myConn.close();
|
||||
break;
|
||||
}
|
||||
Util::sleep(500);
|
||||
updateMeta();
|
||||
}
|
||||
}while (myConn && seekable > 0);
|
||||
if (seekable < 0){
|
||||
} while (myConn && seekable > 0);
|
||||
if (seekable < 0) {
|
||||
H.Clean();
|
||||
H.setCORSHeaders();
|
||||
H.SetBody("The requested fragment is no longer kept in memory on the server and cannot be served.\n");
|
||||
|
@ -240,7 +456,7 @@ namespace Mist {
|
|||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
seek(from);
|
||||
ts_from = from;
|
||||
lastVid = from * 90;
|
||||
|
@ -256,12 +472,12 @@ namespace Mist {
|
|||
H.StartResponse(H, myConn, VLCworkaround);
|
||||
|
||||
unsigned int fragCounter = myMeta.tracks[vidTrack].missedFrags;
|
||||
for (std::deque<DTSC::Fragment>::iterator it = myMeta.tracks[vidTrack].fragments.begin(); it != myMeta.tracks[vidTrack].fragments.end(); it++){
|
||||
long long int starttime = myMeta.tracks[vidTrack].getKey(it->getNumber()).getTime();
|
||||
if (starttime <= from && starttime + it->getDuration() > from){
|
||||
EXTREME_MSG("setting continuity counter for PAT/PMT to %d",fragCounter);
|
||||
contCounters[0]=fragCounter; //PAT continuity counter
|
||||
contCounters[4096]=fragCounter; //PMT continuity counter
|
||||
for (std::deque<DTSC::Fragment>::iterator it = myMeta.tracks[vidTrack].fragments.begin(); it != myMeta.tracks[vidTrack].fragments.end(); it++) {
|
||||
long long int starttime = myMeta.tracks[vidTrack].getKey(it->getNumber()).getTime();
|
||||
if (starttime <= from && starttime + it->getDuration() > from) {
|
||||
EXTREME_MSG("setting continuity counter for PAT/PMT to %d", fragCounter);
|
||||
contCounters[0] = fragCounter; //PAT continuity counter
|
||||
contCounters[4096] = fragCounter; //PMT continuity counter
|
||||
break;
|
||||
}
|
||||
++fragCounter;
|
||||
|
@ -269,13 +485,13 @@ namespace Mist {
|
|||
packCounter = 0;
|
||||
parseData = true;
|
||||
wantRequest = false;
|
||||
}else{
|
||||
} else {
|
||||
initialize();
|
||||
std::string request = H.url.substr(H.url.find("/", 5) + 1);
|
||||
H.Clean();
|
||||
if (H.url.find(".m3u8") != std::string::npos){
|
||||
if (H.url.find(".m3u8") != std::string::npos) {
|
||||
H.SetHeader("Content-Type", "audio/x-mpegurl");
|
||||
}else{
|
||||
} else {
|
||||
H.SetHeader("Content-Type", "audio/mpegurl");
|
||||
}
|
||||
H.SetHeader("Cache-Control", "no-cache");
|
||||
|
@ -286,10 +502,10 @@ namespace Mist {
|
|||
return;
|
||||
}
|
||||
std::string manifest;
|
||||
if (request.find("/") == std::string::npos){
|
||||
if (request.find("/") == std::string::npos) {
|
||||
manifest = liveIndex();
|
||||
}else{
|
||||
int selectId = atoi(request.substr(0,request.find("/")).c_str());
|
||||
} else {
|
||||
int selectId = atoi(request.substr(0, request.find("/")).c_str());
|
||||
manifest = liveIndex(selectId);
|
||||
}
|
||||
H.SetBody(manifest);
|
||||
|
@ -298,7 +514,7 @@ namespace Mist {
|
|||
}
|
||||
|
||||
|
||||
void OutHLS::sendTS(const char * tsData, unsigned int len){
|
||||
void OutHLS::sendTS(const char * tsData, unsigned int len) {
|
||||
H.Chunkify(tsData, len, myConn);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,13 @@ namespace Mist {
|
|||
protected:
|
||||
std::string liveIndex();
|
||||
std::string liveIndex(int tid);
|
||||
|
||||
|
||||
std::string pushLiveIndex();
|
||||
std::string pushLiveIndex(int tid, unsigned long bTime, unsigned long eTime);
|
||||
|
||||
|
||||
std::string generatePushList();
|
||||
int canSeekms(unsigned int ms);
|
||||
int keysToSend;
|
||||
unsigned int vidTrack;
|
||||
|
|
|
@ -280,7 +280,7 @@ namespace Mist {
|
|||
moof_box.setContent(mfhd_box, 0);
|
||||
moof_box.setContent(traf_box, 1);
|
||||
/*LTS-START*/
|
||||
if (encrypt){
|
||||
if (nProxy.encrypt){
|
||||
MP4::UUID_SampleEncryption sEnc;
|
||||
sEnc.setVersion(0);
|
||||
if (myMeta.tracks[tid].type == "audio") {
|
||||
|
@ -340,10 +340,10 @@ namespace Mist {
|
|||
//Load the encryption data page
|
||||
char pageName[NAME_BUFFER_SIZE];
|
||||
snprintf(pageName, NAME_BUFFER_SIZE, SHM_STREAM_ENCRYPT, streamName.c_str());
|
||||
encryptionPage.init(pageName, 8 * 1024 * 1024, false, false);
|
||||
if (encryptionPage.mapped) {
|
||||
vmData.read(encryptionPage.mapped);
|
||||
encrypt = true;
|
||||
nProxy.encryptionPage.init(pageName, 8 * 1024 * 1024, false, false);
|
||||
if (nProxy.encryptionPage.mapped) {
|
||||
nProxy.vmData.read(nProxy.encryptionPage.mapped);
|
||||
nProxy.encrypt = true;
|
||||
}
|
||||
encryptionLoaded = true;
|
||||
}
|
||||
|
@ -352,9 +352,9 @@ namespace Mist {
|
|||
std::string OutHSS::protectionHeader() {
|
||||
loadEncryption();
|
||||
std::string xmlGen = "<WRMHEADER xmlns=\"http://schemas.microsoft.com/DRM/2007/03/PlayReadyHeader\" version=\"4.0.0.0\"><DATA><PROTECTINFO><KEYLEN>16</KEYLEN><ALGID>AESCTR</ALGID></PROTECTINFO><KID>";
|
||||
xmlGen += vmData.keyid;
|
||||
xmlGen += nProxy.vmData.keyid;
|
||||
xmlGen += "</KID><LA_URL>";
|
||||
xmlGen += vmData.laurl;
|
||||
xmlGen += nProxy.vmData.laurl;
|
||||
xmlGen += "</LA_URL></DATA></WRMHEADER>";
|
||||
std::string tmp = toUTF16(xmlGen);
|
||||
tmp = tmp.substr(2);
|
||||
|
@ -512,7 +512,7 @@ namespace Mist {
|
|||
Result << "</StreamIndex>\n";
|
||||
}
|
||||
/*LTS-START*/
|
||||
if (encrypt) {
|
||||
if (nProxy.encrypt) {
|
||||
Result << "<Protection><ProtectionHeader SystemID=\"9a04f079-9840-4286-ab92-e65be0885f95\">";
|
||||
Result << protectionHeader();
|
||||
Result << "</ProtectionHeader></Protection>";
|
||||
|
|
|
@ -173,7 +173,7 @@ namespace Mist {
|
|||
if (handler != capa["name"].asStringRef() || H.GetVar("stream") != streamName){
|
||||
DEBUG_MSG(DLVL_MEDIUM, "Switching from %s (%s) to %s (%s)", capa["name"].asStringRef().c_str(), streamName.c_str(), handler.c_str(), H.GetVar("stream").c_str());
|
||||
streamName = H.GetVar("stream");
|
||||
userClient.finish();
|
||||
nProxy.userClient.finish();
|
||||
statsPage.finish();
|
||||
reConnector(handler);
|
||||
H.Clean();
|
||||
|
@ -396,8 +396,6 @@ namespace Mist {
|
|||
return trustedProxies.count(ip);
|
||||
}
|
||||
/*LTS-END*/
|
||||
|
||||
|
||||
/*begin-roxlu*/
|
||||
void HTTPOutput::sendResponse(std::string message, std::string code) {
|
||||
|
||||
|
|
346
src/output/output_push.cpp
Normal file
346
src/output/output_push.cpp
Normal file
|
@ -0,0 +1,346 @@
|
|||
#include "output_push.h"
|
||||
#include <mist/http_parser.h>
|
||||
#include <mist/shared_memory.h>
|
||||
#include <sys/stat.h>
|
||||
#include <mist/tinythread.h>
|
||||
|
||||
|
||||
#define PUSH_INDEX_SIZE 5 //Build index based on most recent X segments
|
||||
|
||||
Util::Config * pConf;
|
||||
std::string sName;
|
||||
|
||||
std::string baseURL;
|
||||
|
||||
long long srcPort;
|
||||
std::string srcHost;
|
||||
|
||||
std::string dstHost;
|
||||
long long dstPort;
|
||||
std::string dstUrl;
|
||||
|
||||
//Used to keep track of all segments that can be pushed
|
||||
std::map<std::string, std::map<int, std::string> > pushableSegments;
|
||||
//Used to keep track of the timestamp of each pushableSegment
|
||||
std::map<std::string, std::map<int, int> > pushableTimes;
|
||||
//Used to keep track of the duration of each pushableSegment
|
||||
std::map<std::string, std::map<int, int> > pushableDurations;
|
||||
|
||||
|
||||
//For each quality, store the latest number found in the push list
|
||||
std::map<std::string, int> latestNumber;
|
||||
|
||||
//For each quality, store whether it is currently being pushed.
|
||||
std::map<std::string, bool> parsing;
|
||||
|
||||
//For each quality, store an fprint-style string of the relative url to the index_<beginTime>_<endTime>.m3u8
|
||||
std::map<std::string, std::string> qualityIndex;
|
||||
//For each quality, store an fprint-style string of the relative url to the segment.
|
||||
std::map<std::string, std::string> qualitySegment;
|
||||
|
||||
//For each quality, store the last PUSH_INDEX_SIZE - 1 timestamps. Used to generate a time-constrained index.m3u8.
|
||||
std::map<std::string, std::deque<int> > qualityBeginTimes;
|
||||
|
||||
|
||||
//Parses a uri of the form 'http://<host>[:<port>]/<url>, and split it into variables
|
||||
void parseURI(const std::string & uri, std::string & host, long long & port, std::string & url){
|
||||
int loc = 0;
|
||||
if (uri.find("http://") == 0){
|
||||
loc += 7;
|
||||
}
|
||||
host = uri.substr(loc, uri.find_first_of(":/", 7) - 7);
|
||||
loc += host.size();
|
||||
if (uri[loc] == ':'){
|
||||
port = atoll(uri.c_str() + loc + 1);
|
||||
loc = uri.find("/", loc);
|
||||
}
|
||||
url = uri.substr(loc);
|
||||
}
|
||||
|
||||
//Do an HTTP request, and route it into a post request on a different socket.
|
||||
void proxyToPost(Socket::Connection & src, const std::string & srcUrl, Socket::Connection & dst, const std::string & dstUrl){
|
||||
INFO_MSG("Routing %s to %s", srcUrl.c_str(), dstUrl.c_str());
|
||||
|
||||
//Send the initial request
|
||||
HTTP::Parser H;
|
||||
H.url = srcUrl;
|
||||
H.SendRequest(src);
|
||||
H.Clean();
|
||||
|
||||
//Read only the headers of the reply
|
||||
H.headerOnly = true;
|
||||
while (src.connected()){
|
||||
if (src.Received().size() || src.spool()){
|
||||
if (H.Read(src)){
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
H.headerOnly = false;
|
||||
|
||||
INFO_MSG("Reply from %s: %s %s", src.getHost().c_str(), H.url.c_str(), H.method.c_str());
|
||||
|
||||
//Change the headers of the reply to form a post request
|
||||
H.method = "POST";
|
||||
H.url = dstUrl;
|
||||
H.protocol = "HTTP/1.1";
|
||||
H.SetHeader("Host", dstHost);
|
||||
//Start the post request
|
||||
H.SendRequest(dst);
|
||||
//Route the original payload.
|
||||
H.Proxy(src, dst);
|
||||
|
||||
H.Clean();
|
||||
while (dst.connected()){
|
||||
if (dst.Received().size() || dst.spool()){
|
||||
if (H.Read(dst)){
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
INFO_MSG("Reply from %s: %s %s", dst.getHost().c_str(), H.url.c_str(), H.method.c_str());
|
||||
}
|
||||
|
||||
|
||||
///Push the first registered segment for this quality
|
||||
void pushFirstElement(std::string qId) {
|
||||
std::string semName = "MstPushLock" + sName;
|
||||
IPC::semaphore pushLock(semName.c_str(), O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
|
||||
std::string url;
|
||||
int time;
|
||||
int beginTime;
|
||||
int duration;
|
||||
//Wait for exclusive access to all globals
|
||||
pushLock.wait();
|
||||
//Retrieve all globals for the segment to be pushed
|
||||
if (pushableSegments[qId].size()){
|
||||
url = pushableSegments[qId].begin()->second;
|
||||
time = pushableTimes[qId].begin()->second;
|
||||
duration = pushableDurations[qId].begin()->second;
|
||||
if (qualityBeginTimes[qId].size()){
|
||||
beginTime = qualityBeginTimes[qId].front();
|
||||
}else{
|
||||
beginTime = time;
|
||||
}
|
||||
}
|
||||
//Give up exclusive access to all globals
|
||||
pushLock.post();
|
||||
//Return if we do not have a segment to push
|
||||
if (url == ""){
|
||||
return;
|
||||
}
|
||||
|
||||
//Create both source and destination connections
|
||||
Socket::Connection srcConn(srcHost, srcPort, true);
|
||||
Socket::Connection dstConn(dstHost, dstPort, true);
|
||||
|
||||
//Set the locations to push to for this segment
|
||||
std::string srcLocation = baseURL + url;
|
||||
std::string dstLocation = dstUrl.substr(0, dstUrl.rfind("/")) + url;
|
||||
|
||||
//Push the segment
|
||||
proxyToPost(srcConn, srcLocation, dstConn, dstLocation);
|
||||
|
||||
|
||||
srcConn = Socket::Connection(srcHost, srcPort, true);
|
||||
|
||||
//Set the location to push to for the index containing this segment.
|
||||
//The index will contain (at most) the last PUSH_INDEX_SIZE segments.
|
||||
char srcIndex[200];
|
||||
snprintf(srcIndex, 200, qualityIndex[qId].c_str(), beginTime , time + duration);
|
||||
srcLocation = baseURL + srcIndex;
|
||||
dstLocation = dstLocation.substr(0, dstLocation.rfind("/")) + "/index.m3u8";
|
||||
|
||||
//Push the index
|
||||
proxyToPost(srcConn, srcLocation, dstConn, dstLocation);
|
||||
|
||||
|
||||
srcConn = Socket::Connection(srcHost, srcPort, true);
|
||||
|
||||
//Set the location to push to for the global index containing all qualities.
|
||||
srcLocation = baseURL + "/push/index.m3u8";
|
||||
dstLocation = dstLocation.substr(0, dstLocation.rfind("/"));
|
||||
dstLocation = dstLocation.substr(0, dstLocation.rfind("/")) + "/index.m3u8";
|
||||
|
||||
//Push the global index
|
||||
proxyToPost(srcConn, srcLocation, dstConn, dstLocation);
|
||||
|
||||
//Close both connections
|
||||
///\todo Make the dstConn "persistent" for each thread?
|
||||
srcConn.close();
|
||||
dstConn.close();
|
||||
|
||||
//Wait for exclusive access to all globals
|
||||
pushLock.wait();
|
||||
//Update all globals to indicate the segment has been pushed correctly
|
||||
pushableSegments[qId].erase(pushableSegments[qId].begin());
|
||||
pushableTimes[qId].erase(pushableTimes[qId].begin());
|
||||
pushableDurations[qId].erase(pushableDurations[qId].begin());
|
||||
qualityBeginTimes[qId].push_back(time);
|
||||
//Remove the first elements fromt he beginTimes map to make sure we have PUSH_INDEX_SIZE elements in our index.
|
||||
//We use -1 here, because we use the segment to currently push as well as everything stored in the map
|
||||
while (qualityBeginTimes[qId].size() > PUSH_INDEX_SIZE - 1){
|
||||
qualityBeginTimes[qId].pop_front();
|
||||
}
|
||||
//Give up exclusive access to all globals
|
||||
pushLock.post();
|
||||
}
|
||||
|
||||
///Thread used to push data.
|
||||
void pushThread(void * nullPointer){
|
||||
std::string myThread;
|
||||
|
||||
//Attempt to claim a non-claimed quality.
|
||||
std::string semName = "MstPushClaim" + sName;
|
||||
IPC::semaphore pushThreadLock(semName.c_str(), O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
|
||||
pushThreadLock.wait();
|
||||
for (std::map<std::string, std::map<int, std::string> >::iterator it = pushableSegments.begin(); it != pushableSegments.end(); it++){
|
||||
if (it->second.size()){//Make sure we dont try to "claim" pushing an empty track
|
||||
if (!parsing.count(it->first) || !parsing[it->first]){
|
||||
INFO_MSG("Claiming thread %s", it->first.c_str());
|
||||
myThread = it->first;
|
||||
parsing[it->first] = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
pushThreadLock.post();
|
||||
|
||||
//Return if we were unable to claim a quality
|
||||
if (myThread == ""){
|
||||
INFO_MSG("No thread claimed");
|
||||
return;
|
||||
}
|
||||
|
||||
//While this output is active, push the first element in the list
|
||||
while (pConf->is_active){
|
||||
pushFirstElement(myThread);
|
||||
if (!pushableSegments[myThread].size()){
|
||||
Util::sleep(1000);
|
||||
}
|
||||
}
|
||||
|
||||
parsing[myThread] = false;
|
||||
}
|
||||
|
||||
|
||||
namespace Mist {
|
||||
|
||||
OutPush::OutPush(Socket::Connection & conn) : Output(conn){
|
||||
config->activate();
|
||||
}
|
||||
OutPush::~OutPush(){}
|
||||
|
||||
void OutPush::requestHandler() {
|
||||
//Set aal basic data only the first time.
|
||||
if (streamName == ""){
|
||||
srcPort = 80;
|
||||
parseURI(config->getString("pushlist"), srcHost, srcPort, pushURL);
|
||||
dstPort = 80;
|
||||
parseURI(config->getString("destination"), dstHost, dstPort, dstUrl);
|
||||
|
||||
|
||||
//Strip "/push/list" from the URL
|
||||
baseURL = pushURL.substr(0, pushURL.rfind("/"));
|
||||
baseURL = baseURL.substr(0, baseURL.rfind("/"));
|
||||
|
||||
//Locate the streamname from the pushURL
|
||||
int loc = baseURL.find("/", 1) + 1;
|
||||
streamName = pushURL.substr(loc, pushURL.rfind("/") - loc);
|
||||
sName = streamName;
|
||||
|
||||
INFO_MSG("host: %s, port %lld, url %s, baseURL %s, streamName %s", srcHost.c_str(), srcPort, pushURL.c_str(), baseURL.c_str(), streamName.c_str());
|
||||
}
|
||||
//Reconnect when disconnected
|
||||
if (!listConn.connected()){
|
||||
listConn = Socket::Connection(srcHost, srcPort, true);
|
||||
}
|
||||
//Request the push list
|
||||
if (listConn.connected()){
|
||||
HTTP::Parser hReq;
|
||||
hReq.url = baseURL + "/push/list";
|
||||
hReq.SendRequest(listConn);
|
||||
hReq.Clean();
|
||||
//Read the entire response, not just the headers!
|
||||
while (!hReq.Read(listConn) && listConn.connected()){
|
||||
Util::sleep(100);
|
||||
listConn.spool();
|
||||
}
|
||||
|
||||
//Construct and parse the json list
|
||||
JSON::Value reply = JSON::fromString(hReq.body);
|
||||
int numQualities = reply["qualities"].size();
|
||||
for (int i = 0; i < numQualities; i++){
|
||||
JSON::Value & qRef = reply["qualities"][i];
|
||||
std::string qId = qRef["id"].asString();
|
||||
|
||||
//Set both the index and segment urls when not yet set.
|
||||
if (!qualityIndex.count(qId)){
|
||||
qualityIndex[qId] = qRef["index"].asString();
|
||||
qualitySegment[qId] = qRef["segment"].asString();
|
||||
}
|
||||
|
||||
//Save latest segment number before parsing
|
||||
int curLatestNumber = latestNumber[qId];
|
||||
|
||||
//Loop over all segments
|
||||
for (int j = 0; j < qRef["segments"].size(); j++){
|
||||
JSON::Value & segRef = qRef["segments"][j];
|
||||
int thisNumber = segRef["number"].asInt();
|
||||
|
||||
//Check if this segment is newer than the newest segment before parsing
|
||||
if (thisNumber > curLatestNumber){
|
||||
//If it is the highest so far, store its number
|
||||
if (thisNumber > latestNumber[qId]){
|
||||
latestNumber[qId] = thisNumber;
|
||||
}
|
||||
//If it is not yet added, add it.
|
||||
if (!pushableSegments[qId].count(thisNumber)){
|
||||
char segmentUrl[200];
|
||||
//The qualitySegment map contains a printf-style string
|
||||
snprintf(segmentUrl, 200, qualitySegment[qId].c_str(), segRef["time"].asInt(), segRef["time"].asInt() + segRef["duration"].asInt());
|
||||
pushableSegments[qId][segRef["number"].asInt()] = segmentUrl;
|
||||
pushableTimes[qId][segRef["number"].asInt()] = segRef["time"].asInt();
|
||||
pushableDurations[qId][segRef["number"].asInt()] = segRef["duration"].asInt();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//Calculate how many qualities are not yet being pushed
|
||||
int threadsToSpawn = pushableSegments.size();
|
||||
for (std::map<std::string, std::map<int, std::string> >::iterator it = pushableSegments.begin(); it != pushableSegments.end(); it++){
|
||||
if (parsing.count(it->first) && parsing[it->first]){
|
||||
threadsToSpawn --;
|
||||
}
|
||||
}
|
||||
//And start a thread for each unpushed quality.
|
||||
//Threads determine which quality to push for themselves.
|
||||
for (int i = 0; i < threadsToSpawn; i++){
|
||||
tthread::thread thisThread(pushThread, 0);
|
||||
thisThread.detach();
|
||||
}
|
||||
Util::sleep(100);
|
||||
}
|
||||
|
||||
void OutPush::init(Util::Config * cfg){
|
||||
Output::init(cfg);
|
||||
capa["name"] = "Push";
|
||||
capa["desc"] = "Enables HTTP Pushing.";
|
||||
capa["required"]["pushlist"]["name"] = "URL location of the pushing list";
|
||||
capa["required"]["pushlist"]["help"] = "This is the location that will be checked for pushable data.";
|
||||
capa["required"]["pushlist"]["option"] = "--pushlist";
|
||||
capa["required"]["pushlist"]["type"] = "str";
|
||||
cfg->addOption("pushlist", JSON::fromString("{\"arg\":\"string\",\"short\":\"p\",\"long\":\"pushlist\",\"help\":\"This is the location that will be checked for pushable data.\"}"));
|
||||
capa["required"]["destination"]["name"] = "URL location of the destination";
|
||||
capa["required"]["destination"]["help"] = "This is the location that the date will be pushed to.";
|
||||
capa["required"]["destination"]["option"] = "--destination";
|
||||
capa["required"]["destination"]["type"] = "str";
|
||||
cfg->addOption("destination", JSON::fromString("{\"arg\":\"string\",\"short\":\"D\",\"long\":\"destination\",\"help\":\"This is the location that the data will be checked for pushed to.\"}"));
|
||||
cfg->addBasicConnectorOptions(capa);
|
||||
pConf = cfg;
|
||||
config = cfg;
|
||||
}
|
||||
}
|
19
src/output/output_push.h
Normal file
19
src/output/output_push.h
Normal file
|
@ -0,0 +1,19 @@
|
|||
#include <mist/socket.h>
|
||||
|
||||
#include "output.h"
|
||||
|
||||
namespace Mist {
|
||||
class OutPush : public Output {
|
||||
public:
|
||||
OutPush(Socket::Connection & conn);
|
||||
~OutPush();
|
||||
static bool listenMode(){return false;}
|
||||
virtual void requestHandler();
|
||||
static void init(Util::Config * cfg);
|
||||
protected:
|
||||
Socket::Connection listConn;
|
||||
std::string pushURL;
|
||||
};
|
||||
}
|
||||
|
||||
typedef Mist::OutPush mistOut;
|
|
@ -903,12 +903,13 @@ namespace Mist {
|
|||
}
|
||||
JSON::Value pack_out = F.toJSON(myMeta, *amf_storage, next.cs_id*3 + (F.data[0] == 0x09 ? 0 : (F.data[0] == 0x08 ? 1 : 2) ));
|
||||
if ( !pack_out.isNull()){
|
||||
if (!userClient.getData()){
|
||||
if (!nProxy.userClient.getData()){
|
||||
char userPageName[NAME_BUFFER_SIZE];
|
||||
snprintf(userPageName, NAME_BUFFER_SIZE, SHM_USERS, streamName.c_str());
|
||||
userClient = IPC::sharedClient(userPageName, 30, true);
|
||||
nProxy.userClient = IPC::sharedClient(userPageName, PLAY_EX_SIZE, true);
|
||||
}
|
||||
continueNegotiate(pack_out["trackid"].asInt());
|
||||
nProxy.streamName = streamName;
|
||||
bufferLivePacket(pack_out);
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -33,7 +33,7 @@ namespace Mist {
|
|||
|
||||
if (packData.getBytesFree() == 184){
|
||||
packData.clear();
|
||||
packData.setPID(0x100 - 1 + thisPacket.getTrackId());
|
||||
packData.setPID(thisPacket.getTrackId());
|
||||
packData.setContinuityCounter(++contCounters[packData.getPID()]);
|
||||
if (first[thisPacket.getTrackId()]){
|
||||
packData.setUnitStart(1);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue