#include "output_hls.h" #include #include namespace Mist { ///\brief Builds an index file for HTTP Live streaming. ///\return The index file for HTTP Live Streaming. std::string OutHLS::liveIndex(){ std::stringstream result; result << "#EXTM3U\r\n"; int audioId = -1; std::string audioName; for (std::map::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){ if (it->second.codec == "AAC"){ audioId = it->first; audioName = it->second.getIdentifier(); break; } } for (std::map::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){ if (it->second.codec == "H264"){ int bWidth = it->second.bps * 2; if (bWidth < 5){ bWidth = 5; } if (audioId != -1){ bWidth += myMeta.tracks[audioId].bps * 2; } result << "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" << bWidth * 10 << "\r\n"; result << it->first; if (audioId != -1){ result << "_" << audioId; } result << "/index.m3u8\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()){ DEBUG_MSG(DLVL_FAIL, "liveIndex called with track %d, which has no fragments!", tid); return ""; } for (std::deque::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 lines; for (std::deque::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){ 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()); } //skip the first fragment if live and there are more than 2 fragments. unsigned int skippedLines = 0; if (myMeta.live){ if (lines.size() > 2){ lines.pop_front(); skippedLines++; } //only print the last segment when VoD lines.pop_back(); } result << "#EXT-X-MEDIA-SEQUENCE:" << myMeta.tracks[tid].missedFrags + skippedLines << "\r\n"; while (lines.size()){ result << lines.front(); lines.pop_front(); } 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){ realTime = 0; } OutHLS::~OutHLS() {} 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["codecs"][0u][0u].append("H264"); capa["codecs"][0u][1u].append("AAC"); capa["codecs"][0u][1u].append("MP3"); capa["methods"][0u]["handler"] = "http"; capa["methods"][0u]["type"] = "html5/application/vnd.apple.mpegurl"; capa["methods"][0u]["priority"] = 9ll; } int OutHLS::canSeekms(unsigned int ms){ //no tracks? Frame too new by definition. if ( !myMeta.tracks.size()){ return 1; } //loop trough all the tracks for (std::map::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(){ if (H.url == "/crossdomain.xml"){ H.Clean(); H.SetHeader("Content-Type", "text/xml"); H.SetHeader("Server", "mistserver/" PACKAGE_VERSION "/" + Util::Config::libver); H.SetBody(""); H.SendResponse("200", "OK", myConn); H.Clean(); //clean for any possible next requests return; } //crossdomain.xml if (H.url.find("hls") == std::string::npos){ myConn.close(); return; } appleCompat = (H.GetHeader("User-Agent").find("Apple") != std::string::npos); bool VLCworkaround = false; 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')){ 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()); 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){ DEBUG_MSG(DLVL_MEDIUM, "Could not parse URL: %s", H.getUrl().c_str()); H.Clean(); 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{ selectedTracks.clear(); selectedTracks.insert(vidTrack); } }else{ selectedTracks.clear(); selectedTracks.insert(vidTrack); selectedTracks.insert(audTrack); } if (myMeta.live){ unsigned int timeout = 0; int seekable; do { seekable = canSeekms(from); /// \todo Detection of out-of-range parts. if (seekable > 0){ //time out after 21 seconds if (++timeout > 42){ myConn.close(); break; } Util::sleep(500); updateMeta(); } }while (myConn && seekable > 0); if (seekable < 0){ H.Clean(); H.SetBody("The requested fragment is no longer kept in memory on the server and cannot be served.\n"); myConn.SendNow(H.BuildResponse("412", "Fragment out of range")); H.Clean(); //clean for any possible next requests DEBUG_MSG(DLVL_WARN, "Fragment @ %llu too old", from); return; } } seek(from); lastVid = from * 90; H.SetHeader("Content-Type", "video/mp2t"); H.StartResponse(H, myConn, VLCworkaround); unsigned int fragCounter = myMeta.tracks[vidTrack].missedFrags; for (std::deque::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; } packCounter = 0; parseData = true; wantRequest = false; }else{ initialize(); std::string request = H.url.substr(H.url.find("/", 5) + 1); H.Clean(); if (H.url.find(".m3u8") != std::string::npos){ H.SetHeader("Content-Type", "audio/x-mpegurl"); }else{ H.SetHeader("Content-Type", "audio/mpegurl"); } H.SetHeader("Cache-Control", "no-cache"); std::string manifest; if (request.find("/") == std::string::npos){ manifest = liveIndex(); }else{ int selectId = atoi(request.substr(0,request.find("/")).c_str()); manifest = liveIndex(selectId); } H.SetBody(manifest); H.SendResponse("200", "OK", myConn); } } void OutHLS::sendTS(const char * tsData, unsigned int len){ H.Chunkify(tsData, len, myConn); } }