diff --git a/src/Makefile.am b/src/Makefile.am
index 196c5ddf..fc0ad10f 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -8,7 +8,7 @@ EXTRA_DIST=server.html server.html.h embed.js.h
AM_CPPFLAGS = $(global_CFLAGS) $(MIST_CFLAGS)
LDADD = $(MIST_LIBS)
SUBDIRS=converters analysers
-bin_PROGRAMS=MistBuffer MistController MistConnRAW MistConnRTMP MistConnHTTP MistConnHTTPProgressive MistConnHTTPDynamic MistPlayer
+bin_PROGRAMS=MistBuffer MistController MistConnRAW MistConnRTMP MistConnHTTP MistConnHTTPProgressive MistConnHTTPDynamic MistConnHTTPSmooth MistPlayer
MistBuffer_SOURCES=buffer.cpp buffer_user.h buffer_user.cpp buffer_stream.h buffer_stream.cpp tinythread.cpp tinythread.h ../VERSION
MistBuffer_LDADD=$(MIST_LIBS) -lpthread
MistController_SOURCES=controller.cpp ../VERSION ./server.html.h
@@ -18,6 +18,7 @@ MistConnHTTP_SOURCES=conn_http.cpp tinythread.cpp tinythread.h ../VERSION ./embe
MistConnHTTP_LDADD=$(MIST_LIBS) -lpthread
MistConnHTTPProgressive_SOURCES=conn_http_progressive.cpp ../VERSION
MistConnHTTPDynamic_SOURCES=conn_http_dynamic.cpp ../VERSION
+MistConnHTTPSmooth_SOURCES=conn_http_smooth.cpp ../VERSION
MistPlayer_SOURCES=player.cpp
MistPlayer_LDADD=$(MIST_LIBS)
diff --git a/src/conn_http.cpp b/src/conn_http.cpp
index c56daa5a..ce00bdf1 100644
--- a/src/conn_http.cpp
+++ b/src/conn_http.cpp
@@ -318,6 +318,12 @@ namespace Connector_HTTP{
H.SetVar("stream", streamname);
return "dynamic";
}
+ if (url.find("/smooth/") != std::string::npos ) {
+ std::string streamname = url.substr(8,url.find("/",8)-8);
+ Util::Stream::sanitizeName(streamname);
+ H.SetVar("stream", streamname);
+ return "smooth";
+ }
if (url.length() > 4){
std::string ext = url.substr(url.length() - 4, 4);
if (ext == ".flv" || ext == ".mp3"){
@@ -404,7 +410,8 @@ int main(int argc, char ** argv){
//start progressive and dynamic handlers from the same folder as this application
Util::Procs::Start("progressive", Util::getMyPath() + "MistConnHTTPProgressive -n");
Util::Procs::Start("dynamic", Util::getMyPath() + "MistConnHTTPDynamic -n");
-
+ Util::Procs::Start("smooth", Util::getMyPath() + "MistConnHTTPSmooth -n");
+
while (server_socket.connected() && conf.is_active){
Socket::Connection S = server_socket.accept();
if (S.connected()){//check if the new connection is valid
diff --git a/src/conn_http_smooth.cpp b/src/conn_http_smooth.cpp
index c92d15e8..2536da5f 100644
--- a/src/conn_http_smooth.cpp
+++ b/src/conn_http_smooth.cpp
@@ -25,136 +25,55 @@
/// Holds everything unique to HTTP Dynamic Connector.
namespace Connector_HTTP{
-
- std::string GenerateBootstrap(std::string & MovieId, JSON::Value & metadata, int fragnum, int starttime, int endtime){
- std::string empty;
-
- MP4::ASRT asrt;
- if (starttime == 0){
- asrt.setUpdate(false);
- }else{
- asrt.setUpdate(true);
- }
- asrt.setVersion(1);
- asrt.setQualityEntry(empty, 0);
- if (!metadata.isMember("keytime") || metadata["keytime"].size() == 0){
- asrt.setSegmentRun(1, 20000, 0);
- }else{
- asrt.setSegmentRun(1, metadata["keytime"].size(), 0);
- }
-
-
- MP4::AFRT afrt;
- if (starttime == 0){
- afrt.setUpdate(false);
- }else{
- afrt.setUpdate(true);
- }
- afrt.setVersion(1);
- afrt.setTimeScale(1000);
- afrt.setQualityEntry(empty, 0);
- MP4::afrt_runtable afrtrun;
- if (!metadata.isMember("keytime") || metadata["keytime"].size() == 0){
- afrtrun.firstFragment = 1;
- afrtrun.firstTimestamp = 0;
- if (!metadata.isMember("video") || !metadata["video"].isMember("keyms") || metadata["video"]["keyms"].asInt() == 0){
- afrtrun.duration = 2000;
- }else{
- afrtrun.duration = metadata["video"]["keyms"].asInt();
- }
- afrt.setFragmentRun(afrtrun, 0);
- }else{
- for (int i = 0; i < metadata["keytime"].size(); i++){
- afrtrun.firstFragment = i+1;
- afrtrun.firstTimestamp = metadata["keytime"][i].asInt();
- if (i+1 < metadata["keytime"].size()){
- afrtrun.duration = metadata["keytime"][i+1].asInt() - metadata["keytime"][i].asInt();
- }else{
- if (metadata["lastms"].asInt()){
- afrtrun.duration = metadata["lastms"].asInt() - metadata["keytime"][i].asInt();
- }else{
- afrtrun.duration = 3000;//guess 3 seconds if unknown
- }
- }
- afrt.setFragmentRun(afrtrun, i);
- }
- }
-
- MP4::ABST abst;
- abst.setVersion(1);
- abst.setBootstrapinfoVersion(1);
- abst.setProfile(0);
- if (starttime == 0){
- abst.setUpdate(false);
- }else{
- abst.setUpdate(true);
- }
- abst.setTimeScale(1000);
- if (metadata.isMember("length") && metadata["length"].asInt() > 0){
- abst.setLive(false);
- if (metadata["lastms"].asInt()){
- abst.setCurrentMediaTime(metadata["lastms"].asInt());
- }else{
- abst.setCurrentMediaTime(1000*metadata["length"].asInt());
- }
- }else{
- abst.setLive(true);
- abst.setCurrentMediaTime(0xFFFFFFFF);
- }
- abst.setSmpteTimeCodeOffset(0);
- abst.setMovieIdentifier(MovieId);
- abst.setServerEntry(empty, 0);
- abst.setQualityEntry(empty, 0);
- abst.setDrmData(empty);
- abst.setMetaData(empty);
- abst.setSegmentRunTable(asrt, 0);
- abst.setFragmentRunTable(afrt, 0);
-
- #if DEBUG >= 8
- std::cout << "Sending bootstrap:" << std::endl << abst.toPrettyString(0) << std::endl;
- #endif
- return std::string((char*)abst.asBox(), (int)abst.boxedSize());
- }
-
-
- /// Returns a F4M-format manifest file
+ /// Returns a Smooth-format manifest file
std::string BuildManifest(std::string & MovieId, JSON::Value & metadata){
- std::string Result;
- if (metadata.isMember("length") && metadata["length"].asInt() > 0){
- Result="\n"
- "\n"
- "" + MovieId + "\n"
- "" + metadata["video"]["width"].asString() + "\n"
- "" + metadata["video"]["height"].asString() + "\n"
- "" + metadata["length"].asString() + ".000\n"
- "video/mp4\n"
- "recorded\n"
- "streaming\n"
- "" + Base64::encode(GenerateBootstrap(MovieId, metadata, 1, 0, 0)) + "\n"
- "\n"
- "AgAKb25NZXRhRGF0YQgAAAAAAAl0cmFja2luZm8KAAAAAgMACXRpbWVzY2FsZQBA+GoAAAAAAAAGbGVuZ3RoAEGMcHoQAAAAAAhsYW5ndWFnZQIAA2VuZwARc2FtcGxlZGVzY3JpcHRpb24KAAAAAQMACnNhbXBsZXR5cGUCAARhdmMxAAAJAAAJAwAJdGltZXNjYWxlAEDncAAAAAAAAAZsZW5ndGgAQXtNvTAAAAAACGxhbmd1YWdlAgADZW5nABFzYW1wbGVkZXNjcmlwdGlvbgoAAAABAwAKc2FtcGxldHlwZQIABG1wNGEAAAkAAAkADWF1ZGlvY2hhbm5lbHMAQAAAAAAAAAAAD2F1ZGlvc2FtcGxlcmF0ZQBA53AAAAAAAAAOdmlkZW9mcmFtZXJhdGUAQDf/gi5SciUABmFhY2FvdABAAAAAAAAAAAAIYXZjbGV2ZWwAQD8AAAAAAAAACmF2Y3Byb2ZpbGUAQFNAAAAAAAAADGF1ZGlvY29kZWNpZAIABG1wNGEADHZpZGVvY29kZWNpZAIABGF2YzEABXdpZHRoAECQ4AAAAAAAAAZoZWlnaHQAQIMAAAAAAAAACmZyYW1lV2lkdGgAQJDgAAAAAAAAC2ZyYW1lSGVpZ2h0AECDAAAAAAAAAAxkaXNwbGF5V2lkdGgAQJDgAAAAAAAADWRpc3BsYXlIZWlnaHQAQIMAAAAAAAAADG1vb3Zwb3NpdGlvbgBBmxq2uAAAAAAIZHVyYXRpb24AQIKjqW3oyhIAAAk=\n"
- "\n"
- "\n";
- }else{
- Result="\n"
- "\n"
- "" + MovieId + "\n"
- "video/mp4\n"
- "live\n"
- "streaming\n"
- "" + Base64::encode(GenerateBootstrap(MovieId, metadata, 1, 0, 0)) + "\n"
- "\n"
- "\n";
+ std::stringstream Result;
+ Result << "\n";
+ Result << "\n";
+ if( metadata.isMember( "audio" ) ) {
+ Result << " \n";
+ Result << " \n";
+ for( int i = 0; i < metadata["keytime"].size(); i++ ) {
+ Result << " \n";
+ }
+ Result << " \n";
}
+ if( metadata.isMember( "video" ) ) {
+ Result << " \n";
+ Result << " \n";
+ for( int i = 0; i < metadata["keytime"].size(); i++ ) {
+ Result << " \n";
+ }
+ Result << " \n";
+ }
+ Result << "\n";
+
#if DEBUG >= 8
std::cerr << "Sending this manifest:" << std::endl << Result << std::endl;
#endif
- return Result;
+ return Result.str();
}//BuildManifest
/// Main function for Connector_HTTP_Dynamic
int Connector_HTTP_Dynamic(Socket::Connection conn){
std::deque FlashBuf;
+ std::vector Timestamps;
int FlashBufSize = 0;
long long int FlashBufTime = 0;
FLV::Tag tmp;//temporary tag
@@ -170,10 +89,14 @@ namespace Connector_HTTP{
std::string streamname;
std::string recBuffer = "";
+ bool wantsVideo = false;
+ bool wantsAudio = false;
+
std::string Quality;
int Segment = -1;
int ReqFragment = -1;
int temp;
+ std::string tempStr;
int Flash_RequestPending = 0;
unsigned int lastStats = 0;
conn.setBlocking(false);//do not block on conn.spool() when no data is available
@@ -195,8 +118,8 @@ namespace Connector_HTTP{
std::cout << "Received request: " << HTTP_R.getUrl() << std::endl;
#endif
conn.setHost(HTTP_R.GetHeader("X-Origin"));
- if (HTTP_R.url.find("f4m") == std::string::npos){
- streamname = HTTP_R.url.substr(1,HTTP_R.url.find("/",1)-1);
+ if (HTTP_R.url.find("Manifest") == std::string::npos){
+ streamname = HTTP_R.url.substr(8,HTTP_R.url.find("/",8)-8);
if (!ss){
ss = Util::Stream::getStream(streamname);
if (!ss.connected()){
@@ -205,7 +128,7 @@ namespace Connector_HTTP{
#endif
ss.close();
HTTP_S.Clean();
- HTTP_S.SetBody("No such stream is available on the system. Please try again.\n");
+ HTTP_S.SetBody("No such stream " + streamname + " is available on the system. Please try again.\n");
conn.SendNow(HTTP_S.BuildResponse("404", "Not found"));
ready4data = false;
continue;
@@ -213,21 +136,22 @@ namespace Connector_HTTP{
ss.setBlocking(false);
inited = true;
}
- Quality = HTTP_R.url.substr( HTTP_R.url.find("/",1)+1 );
- Quality = Quality.substr(0, Quality.find("Seg"));
- temp = HTTP_R.url.find("Seg") + 3;
- Segment = atoi( HTTP_R.url.substr(temp,HTTP_R.url.find("-",temp)-temp).c_str());
- temp = HTTP_R.url.find("Frag") + 4;
- ReqFragment = atoi( HTTP_R.url.substr(temp).c_str() );
+ Quality = HTTP_R.url.substr( HTTP_R.url.find("/Q(",8)+3 );
+ Quality = Quality.substr(0, Quality.find(")"));
+ tempStr = HTTP_R.url.substr( HTTP_R.url.find(")/") + 2 );
+ if( tempStr[0] == 'A' ) { wantsAudio = true; }
+ if( tempStr[0] == 'V' ) { wantsVideo = true; }
+ tempStr = tempStr.find("(") + 1;
+ ReqFragment = atoi( tempStr.substr(0,tempStr.find(")")).c_str() );
#if DEBUG >= 4
- printf( "Quality: %s, Seg %d Frag %d\n", Quality.c_str(), Segment, ReqFragment);
+ printf( "Quality: %s, Frag %d\n", Quality.c_str(), ReqFragment);
#endif
std::stringstream sstream;
- sstream << "f " << ReqFragment << "\no \n";
+ sstream << "s " << ReqFragment << "\no \n";
ss.SendNow(sstream.str().c_str());
Flash_RequestPending++;
}else{
- streamname = HTTP_R.url.substr(1,HTTP_R.url.find("/",1)-1);
+ streamname = HTTP_R.url.substr(8,HTTP_R.url.find("/",8)-8);
if (!Strm.metadata.isNull()){
HTTP_S.Clean();
HTTP_S.SetHeader("Content-Type","text/xml");
@@ -264,7 +188,7 @@ namespace Connector_HTTP{
#endif
ss.close();
HTTP_S.Clean();
- HTTP_S.SetBody("No such stream is available on the system. Please try again.\n");
+ HTTP_S.SetBody("No such stream " + streamname + " is available on the system. Please try again.\n");
conn.SendNow(HTTP_S.BuildResponse("404", "Not found"));
ready4data = false;
continue;
@@ -278,7 +202,7 @@ namespace Connector_HTTP{
unsigned int now = Util::epoch();
if (now != lastStats){
lastStats = now;
- ss.SendNow(conn.getStats("HTTP_Dynamic").c_str());
+ ss.SendNow(conn.getStats("HTTP_Smooth").c_str());
}
if (ss.spool()){
while (Strm.parsePacket(ss.Received())){
@@ -306,6 +230,9 @@ namespace Connector_HTTP{
pending_manifest = false;
}
if (!receive_marks && Strm.metadata.isMember("length")){receive_marks = true;}
+ if ( Strm.lastType() == DTSC::PAUSEMARK ) {
+ Timestamps.push_back( Strm.getPacket(0)["time"].asInt() );
+ }
if ((Strm.getPacket(0).isMember("keyframe") && !receive_marks) || Strm.lastType() == DTSC::PAUSEMARK){
#if DEBUG >= 4
fprintf(stderr, "Received a %s fragment of %i bytes.\n", Strm.getPacket(0)["datatype"].asString().c_str(), FlashBufSize);
@@ -319,21 +246,51 @@ namespace Connector_HTTP{
HTTP_S.Clean();
HTTP_S.SetHeader("Content-Type", "video/mp4");
HTTP_S.SetBody("");
- HTTP_S.SetHeader("Content-Length", FlashBufSize+8);//32+33+btstrp.size());
+
+ MP4::MFHD mfhd_box;
+ mfhd_box.setSequenceNumber( 1 );
+
+ MP4::TFHD tfhd_box;
+ tfhd_box.setFlags( MP4::tfhdSampleFlag );
+ tfhd_box.setTrackID( 1 );
+ tfhd_box.setDefaultSampleFlags( MP4::noIPicture | MP4::noDisposable | MP4::noKeySample );
+
+ MP4::TRUN trun_box;
+ //maybe reinsert dataOffset
+ trun_box.setFlags( MP4::trunfirstSampleFlags | MP4::trunsampleDuration | MP4::trunsampleSize );
+ trun_box.setFirstSampleFlags( MP4::isIPicture | MP4::noDisposable | MP4::isKeySample );
+ std::deque< std::string >::iterator FlashBufIter = FlashBuf.begin();
+ for( int i = 0; i < FlashBuf.size(); i++ ) {
+ MP4::trunSampleInformation trunSample;
+ trunSample.sampleSize = (*FlashBufIter).size();
+ trunSample.sampleDuration = Timestamps[i+1]-Timestamps[i];
+ trun_box.setSampleInformation( trunSample, i );
+ FlashBufIter ++;
+ }
+
+ MP4::Box sdtp_box;
+ sdtp_box.setType( "sdtp" );
+ sdtp_box.setInt32( 0, 0 );
+ sdtp_box.setInt8( 0x24, 4 );
+ for( int i = 1; i < FlashBuf.size(); i++ ) {
+ sdtp_box.setInt8( 0x14, i+4 );
+ }
+
+ MP4::TRAF traf_box;
+ traf_box.setContent( tfhd_box, 0 );
+ traf_box.setContent( trun_box, 1 );
+ traf_box.setContent( sdtp_box, 2 );
+
+ MP4::MOOF moof_box;
+ moof_box.setContent( mfhd_box, 0 );
+ moof_box.setContent( traf_box, 1 );
+
+ HTTP_S.SetHeader("Content-Length", FlashBufSize+8+moof_box.boxedSize());//32+33+btstrp.size());
conn.SendNow(HTTP_S.BuildResponse("200", "OK"));
- //conn.SendNow("\x00\x00\x00\x21" "afra\x00\x00\x00\x00\x00\x00\x00\x03\xE8\x00\x00\x00\x01", 21);
- //unsigned long tmptime = htonl(FlashBufTime << 32);
- //conn.SendNow((char*)&tmptime, 4);
- //tmptime = htonl(FlashBufTime & 0xFFFFFFFF);
- //conn.SendNow((char*)&tmptime, 4);
- //tmptime = htonl(65);
- //conn.SendNow((char*)&tmptime, 4);
+
- //conn.SendNow(btstrp);
-
- //conn.SendNow("\x00\x00\x00\x18moof\x00\x00\x00\x10mfhd\x00\x00\x00\x00", 20);
- //unsigned long fragno = htonl(ReqFragment);
- //conn.SendNow((char*)&fragno, 4);
+ conn.SendNow( moof_box.asBox(), moof_box.boxedSize() );
+
unsigned long size = htonl(FlashBufSize+8);
conn.SendNow((char*)&size, 4);
conn.SendNow("mdat", 4);
@@ -349,26 +306,15 @@ namespace Connector_HTTP{
FlashBuf.clear();
FlashBufSize = 0;
}
- if (Strm.lastType() == DTSC::VIDEO || Strm.lastType() == DTSC::AUDIO){
- if (FlashBufSize == 0){
- //fill buffer with init data, if needed.
- if (Strm.metadata.isMember("audio") && Strm.metadata["audio"].isMember("init")){
- tmp.DTSCAudioInit(Strm);
- tmp.tagTime(Strm.getPacket(0)["time"].asInt());
- FlashBuf.push_back(std::string(tmp.data, tmp.len));
- FlashBufSize += tmp.len;
- }
- if (Strm.metadata.isMember("video") && Strm.metadata["video"].isMember("init")){
- tmp.DTSCVideoInit(Strm);
- tmp.tagTime(Strm.getPacket(0)["time"].asInt());
- FlashBuf.push_back(std::string(tmp.data, tmp.len));
- FlashBufSize += tmp.len;
- }
- FlashBufTime = Strm.getPacket(0)["time"].asInt();
- }
- tmp.DTSCLoader(Strm);
- FlashBuf.push_back(std::string(tmp.data, tmp.len));
- FlashBufSize += tmp.len;
+ if ( wantsVideo && Strm.lastType() == DTSC::VIDEO ) {
+ FlashBuf.push_back( Strm.lastData() );
+ FlashBufSize += Strm.lastData().size();
+ Timestamps.push_back( Strm.getPacket(0)["time"].asInt() );
+ }
+ if ( wantsAudio && Strm.lastType() == DTSC::AUDIO ) {
+ FlashBuf.push_back( Strm.lastData() );
+ FlashBufSize += Strm.lastData().size();
+ Timestamps.push_back( Strm.getPacket(0)["time"].asInt() );
}
}
if (pending_manifest && !Strm.metadata.isNull()){
@@ -389,7 +335,7 @@ namespace Connector_HTTP{
}
}
conn.close();
- ss.SendNow(conn.getStats("HTTP_Dynamic").c_str());
+ ss.SendNow(conn.getStats("HTTP_Smooth").c_str());
ss.close();
#if DEBUG >= 1
if (FLV::Parse_Error){fprintf(stderr, "FLV Parser Error: %s\n", FLV::Error_Str.c_str());}
@@ -405,15 +351,15 @@ namespace Connector_HTTP{
}
#endif
return 0;
- }//Connector_HTTP_Dynamic main function
+ }//Connector_HTTP_Smooth main function
-};//Connector_HTTP_Dynamic namespace
+};//Connector_HTTP_Smooth namespace
int main(int argc, char ** argv){
Util::Config conf(argv[0], PACKAGE_VERSION);
conf.addConnectorOptions(1935);
conf.parseArgs(argc, argv);
- Socket::Server server_socket = Socket::Server("/tmp/mist/http_dynamic");
+ Socket::Server server_socket = Socket::Server("/tmp/mist/http_smooth");
if (!server_socket.connected()){return 1;}
conf.activate();