Finish splitting controller into multiple files, universalised coding style across all files.
This commit is contained in:
parent
0db5f60b95
commit
0920b3259b
30 changed files with 1616 additions and 1246 deletions
|
@ -11,7 +11,7 @@ SUBDIRS=converters analysers
|
||||||
bin_PROGRAMS=MistBuffer MistController MistConnRAW MistConnRTMP MistConnHTTP MistConnHTTPProgressive MistConnHTTPDynamic MistConnHTTPSmooth 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_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
|
MistBuffer_LDADD=$(MIST_LIBS) -lpthread
|
||||||
MistController_SOURCES=controller.cpp controller_connectors.h controller_connectors.cpp controller_storage.h controller_storage.cpp ../VERSION ./server.html.h
|
MistController_SOURCES=controller.cpp controller_connectors.h controller_connectors.cpp controller_storage.h controller_storage.cpp controller_streams.h controller_streams.cpp controller_capabilities.h controller_capabilities.cpp ../VERSION ./server.html.h
|
||||||
MistConnRAW_SOURCES=conn_raw.cpp ../VERSION
|
MistConnRAW_SOURCES=conn_raw.cpp ../VERSION
|
||||||
MistConnRTMP_SOURCES=conn_rtmp.cpp ../VERSION
|
MistConnRTMP_SOURCES=conn_rtmp.cpp ../VERSION
|
||||||
MistConnHTTP_SOURCES=conn_http.cpp tinythread.cpp tinythread.h ../VERSION ./embed.js.h
|
MistConnHTTP_SOURCES=conn_http.cpp tinythread.cpp tinythread.h ../VERSION ./embed.js.h
|
||||||
|
|
|
@ -11,14 +11,16 @@
|
||||||
|
|
||||||
/// Debugging tool for AMF data.
|
/// Debugging tool for AMF data.
|
||||||
/// Expects AMF data through stdin, outputs human-readable information to stderr.
|
/// Expects AMF data through stdin, outputs human-readable information to stderr.
|
||||||
int main(int argc, char ** argv) {
|
int main(int argc, char ** argv){
|
||||||
Util::Config conf = Util::Config(argv[0], PACKAGE_VERSION);
|
Util::Config conf = Util::Config(argv[0], PACKAGE_VERSION);
|
||||||
conf.parseArgs(argc, argv);
|
conf.parseArgs(argc, argv);
|
||||||
std::string temp;
|
std::string temp;
|
||||||
while (std::cin.good()){temp += std::cin.get();}//read all of std::cin to temp
|
while (std::cin.good()){
|
||||||
temp.erase(temp.size()-1, 1);//strip the invalid last character
|
temp += std::cin.get();
|
||||||
AMF::Object amfdata = AMF::parse(temp);//parse temp into an AMF::Object
|
} //read all of std::cin to temp
|
||||||
amfdata.Print();//pretty-print the object
|
temp.erase(temp.size() - 1, 1); //strip the invalid last character
|
||||||
|
AMF::Object amfdata = AMF::parse(temp); //parse temp into an AMF::Object
|
||||||
|
amfdata.Print(); //pretty-print the object
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,15 +36,21 @@ int main(int argc, char ** argv){
|
||||||
long long unsigned int bps = 0;
|
long long unsigned int bps = 0;
|
||||||
|
|
||||||
F.seekNext();
|
F.seekNext();
|
||||||
while (!F.getJSON().isNull()){
|
while ( !F.getJSON().isNull()){
|
||||||
std::cout << F.getJSON().toPrettyString() << std::endl;
|
std::cout << F.getJSON().toPrettyString() << std::endl;
|
||||||
nowpack = F.getJSON()["time"].asInt();
|
nowpack = F.getJSON()["time"].asInt();
|
||||||
if (firstpack == 0){firstpack = nowpack;}
|
if (firstpack == 0){
|
||||||
|
firstpack = nowpack;
|
||||||
|
}
|
||||||
if (F.getJSON()["datatype"].asString() == "audio"){
|
if (F.getJSON()["datatype"].asString() == "audio"){
|
||||||
if (lastaudio != 0 && (nowpack - lastaudio) != 0){
|
if (lastaudio != 0 && (nowpack - lastaudio) != 0){
|
||||||
bps = F.getJSON()["data"].asString().size() / (nowpack - lastaudio);
|
bps = F.getJSON()["data"].asString().size() / (nowpack - lastaudio);
|
||||||
if (bps < aud_min){aud_min = bps;}
|
if (bps < aud_min){
|
||||||
if (bps > aud_max){aud_max = bps;}
|
aud_min = bps;
|
||||||
|
}
|
||||||
|
if (bps > aud_max){
|
||||||
|
aud_max = bps;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
totalaudio += F.getJSON()["data"].asString().size();
|
totalaudio += F.getJSON()["data"].asString().size();
|
||||||
lastaudio = nowpack;
|
lastaudio = nowpack;
|
||||||
|
@ -52,22 +58,34 @@ int main(int argc, char ** argv){
|
||||||
if (F.getJSON()["datatype"].asString() == "video"){
|
if (F.getJSON()["datatype"].asString() == "video"){
|
||||||
if (lastvideo != 0 && (nowpack - lastvideo) != 0){
|
if (lastvideo != 0 && (nowpack - lastvideo) != 0){
|
||||||
bps = F.getJSON()["data"].asString().size() / (nowpack - lastvideo);
|
bps = F.getJSON()["data"].asString().size() / (nowpack - lastvideo);
|
||||||
if (bps < vid_min){vid_min = bps;}
|
if (bps < vid_min){
|
||||||
if (bps > vid_max){vid_max = bps;}
|
vid_min = bps;
|
||||||
|
}
|
||||||
|
if (bps > vid_max){
|
||||||
|
vid_max = bps;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (F.getJSON()["keyframe"].asInt() != 0){
|
if (F.getJSON()["keyframe"].asInt() != 0){
|
||||||
if (lastkey != 0){
|
if (lastkey != 0){
|
||||||
bps = nowpack - lastkey;
|
bps = nowpack - lastkey;
|
||||||
if (bps < key_min){key_min = bps;}
|
if (bps < key_min){
|
||||||
if (bps > key_max){key_max = bps;}
|
key_min = bps;
|
||||||
|
}
|
||||||
|
if (bps > key_max){
|
||||||
|
key_max = bps;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
keyframes++;
|
keyframes++;
|
||||||
lastkey = nowpack;
|
lastkey = nowpack;
|
||||||
}
|
}
|
||||||
if (F.getJSON()["offset"].asInt() != 0){
|
if (F.getJSON()["offset"].asInt() != 0){
|
||||||
bps = F.getJSON()["offset"].asInt();
|
bps = F.getJSON()["offset"].asInt();
|
||||||
if (bps < bfrm_min){bfrm_min = bps;}
|
if (bps < bfrm_min){
|
||||||
if (bps > bfrm_max){bfrm_max = bps;}
|
bfrm_min = bps;
|
||||||
|
}
|
||||||
|
if (bps > bfrm_max){
|
||||||
|
bfrm_max = bps;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
totalvideo += F.getJSON()["data"].asString().size();
|
totalvideo += F.getJSON()["data"].asString().size();
|
||||||
lastvideo = nowpack;
|
lastvideo = nowpack;
|
||||||
|
@ -76,7 +94,7 @@ int main(int argc, char ** argv){
|
||||||
}
|
}
|
||||||
|
|
||||||
std::cout << std::endl << "Summary:" << std::endl;
|
std::cout << std::endl << "Summary:" << std::endl;
|
||||||
meta["length"] = (long long int)((nowpack - firstpack)/1000);
|
meta["length"] = (long long int)((nowpack - firstpack) / 1000);
|
||||||
if (meta.isMember("audio")){
|
if (meta.isMember("audio")){
|
||||||
meta["audio"]["bps"] = (long long int)(totalaudio / ((lastaudio - firstpack) / 1000));
|
meta["audio"]["bps"] = (long long int)(totalaudio / ((lastaudio - firstpack) / 1000));
|
||||||
std::cout << " Audio: " << meta["audio"]["codec"].asString() << std::endl;
|
std::cout << " Audio: " << meta["audio"]["codec"].asString() << std::endl;
|
||||||
|
@ -96,4 +114,4 @@ int main(int argc, char ** argv){
|
||||||
std::cout << " B-frames: " << bfrm_min << " - " << bfrm_max << std::endl;
|
std::cout << " B-frames: " << bfrm_min << " - " << bfrm_max << std::endl;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}//main
|
} //main
|
||||||
|
|
|
@ -15,20 +15,20 @@
|
||||||
#include <mist/config.h>
|
#include <mist/config.h>
|
||||||
|
|
||||||
/// Reads FLV from stdin and outputs human-readable information to stderr.
|
/// Reads FLV from stdin and outputs human-readable information to stderr.
|
||||||
int main(int argc, char ** argv) {
|
int main(int argc, char ** argv){
|
||||||
Util::Config conf = Util::Config(argv[0], PACKAGE_VERSION);
|
Util::Config conf = Util::Config(argv[0], PACKAGE_VERSION);
|
||||||
conf.parseArgs(argc, argv);
|
conf.parseArgs(argc, argv);
|
||||||
FLV::Tag FLV_in; // Temporary storage for incoming FLV data.
|
FLV::Tag FLV_in; // Temporary storage for incoming FLV data.
|
||||||
std::ofstream vData( "vData" );
|
std::ofstream vData("vData");
|
||||||
std::ofstream aData( "aData" );
|
std::ofstream aData("aData");
|
||||||
while (!feof(stdin)){
|
while ( !feof(stdin)){
|
||||||
if (FLV_in.FileLoader(stdin)){
|
if (FLV_in.FileLoader(stdin)){
|
||||||
std::cout << "Tag: " << FLV_in.tagType() << "\n\tTime: " << FLV_in.tagTime() << std::endl;
|
std::cout << "Tag: " << FLV_in.tagType() << "\n\tTime: " << FLV_in.tagTime() << std::endl;
|
||||||
if( FLV_in.data[0] == 0x08 ) {//Audio
|
if (FLV_in.data[0] == 0x08){ //Audio
|
||||||
aData.write( FLV_in.data + 13, FLV_in.len - 17 );
|
aData.write(FLV_in.data + 13, FLV_in.len - 17);
|
||||||
}
|
}
|
||||||
if( FLV_in.data[0] == 0x09 ) {//Video
|
if (FLV_in.data[0] == 0x09){ //Video
|
||||||
vData.write( FLV_in.data + 16, FLV_in.len - 20 );
|
vData.write(FLV_in.data + 16, FLV_in.len - 20);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,21 +12,23 @@
|
||||||
|
|
||||||
/// Debugging tool for MP4 data.
|
/// Debugging tool for MP4 data.
|
||||||
/// Expects MP4 data through stdin, outputs human-readable information to stderr.
|
/// Expects MP4 data through stdin, outputs human-readable information to stderr.
|
||||||
int main(int argc, char ** argv) {
|
int main(int argc, char ** argv){
|
||||||
Util::Config conf = Util::Config(argv[0], PACKAGE_VERSION);
|
Util::Config conf = Util::Config(argv[0], PACKAGE_VERSION);
|
||||||
conf.parseArgs(argc, argv);
|
conf.parseArgs(argc, argv);
|
||||||
|
|
||||||
std::string temp;
|
std::string temp;
|
||||||
while (std::cin.good()){temp += std::cin.get();}//read all of std::cin to temp
|
while (std::cin.good()){
|
||||||
temp.erase(temp.size()-1, 1);//strip the invalid last character
|
temp += std::cin.get();
|
||||||
|
} //read all of std::cin to temp
|
||||||
|
temp.erase(temp.size() - 1, 1); //strip the invalid last character
|
||||||
|
|
||||||
MP4::Box mp4data;
|
MP4::Box mp4data;
|
||||||
while (mp4data.read(temp)){
|
while (mp4data.read(temp)){
|
||||||
std::cerr << mp4data.toPrettyString(0) << std::endl;
|
std::cerr << mp4data.toPrettyString(0) << std::endl;
|
||||||
if( mp4data.isType( "mdat" ) ) {
|
if (mp4data.isType("mdat")){
|
||||||
std::ofstream oFile;
|
std::ofstream oFile;
|
||||||
oFile.open( "mdat" );
|
oFile.open("mdat");
|
||||||
oFile << std::string( mp4data.payload(), mp4data.payloadSize() );
|
oFile << std::string(mp4data.payload(), mp4data.payloadSize());
|
||||||
oFile.close();
|
oFile.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,9 @@ int Detail = 0;
|
||||||
/// Automatically skips 3073 bytes of handshake data.
|
/// Automatically skips 3073 bytes of handshake data.
|
||||||
int main(int argc, char ** argv){
|
int main(int argc, char ** argv){
|
||||||
Util::Config conf = Util::Config(argv[0], PACKAGE_VERSION);
|
Util::Config conf = Util::Config(argv[0], PACKAGE_VERSION);
|
||||||
conf.addOption("detail", JSON::fromString("{\"arg_num\":1, \"arg\":\"integer\", \"default\":0, \"help\":\"Bitmask, 1 = Reconstruct, 2 = Explicit media info, 4 = Verbose chunks\"}"));
|
conf.addOption("detail",
|
||||||
|
JSON::fromString(
|
||||||
|
"{\"arg_num\":1, \"arg\":\"integer\", \"default\":0, \"help\":\"Bitmask, 1 = Reconstruct, 2 = Explicit media info, 4 = Verbose chunks\"}"));
|
||||||
conf.parseArgs(argc, argv);
|
conf.parseArgs(argc, argv);
|
||||||
|
|
||||||
Detail = conf.getInteger("detail");
|
Detail = conf.getInteger("detail");
|
||||||
|
@ -49,36 +51,38 @@ int main(int argc, char ** argv){
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string inbuffer;
|
std::string inbuffer;
|
||||||
while (std::cin.good()){inbuffer += std::cin.get();}//read all of std::cin to temp
|
while (std::cin.good()){
|
||||||
inbuffer.erase(0, 3073);//strip the handshake part
|
inbuffer += std::cin.get();
|
||||||
|
} //read all of std::cin to temp
|
||||||
|
inbuffer.erase(0, 3073); //strip the handshake part
|
||||||
RTMPStream::Chunk next;
|
RTMPStream::Chunk next;
|
||||||
FLV::Tag F;//FLV holder
|
FLV::Tag F; //FLV holder
|
||||||
AMF::Object amfdata("empty", AMF::AMF0_DDV_CONTAINER);
|
AMF::Object amfdata("empty", AMF::AMF0_DDV_CONTAINER);
|
||||||
AMF::Object3 amf3data("empty", AMF::AMF3_DDV_CONTAINER);
|
AMF::Object3 amf3data("empty", AMF::AMF3_DDV_CONTAINER);
|
||||||
|
|
||||||
|
|
||||||
while (next.Parse(inbuffer)){
|
while (next.Parse(inbuffer)){
|
||||||
if ((Detail & DETAIL_VERBOSE) == DETAIL_VERBOSE){
|
if ((Detail & DETAIL_VERBOSE) == DETAIL_VERBOSE){
|
||||||
fprintf(stderr, "Chunk info: [%#2X] CS ID %u, timestamp %u, len %u, type ID %u, Stream ID %u\n", next.headertype, next.cs_id, next.timestamp, next.len, next.msg_type_id, next.msg_stream_id);
|
fprintf(stderr, "Chunk info: [%#2X] CS ID %u, timestamp %u, len %u, type ID %u, Stream ID %u\n", next.headertype, next.cs_id, next.timestamp,
|
||||||
|
next.len, next.msg_type_id, next.msg_stream_id);
|
||||||
}
|
}
|
||||||
switch (next.msg_type_id){
|
switch (next.msg_type_id){
|
||||||
case 0://does not exist
|
case 0: //does not exist
|
||||||
fprintf(stderr, "Error chunk - %i, %i, %i, %i, %i\n", next.cs_id, next.timestamp, next.real_len, next.len_left, next.msg_stream_id);
|
fprintf(stderr, "Error chunk - %i, %i, %i, %i, %i\n", next.cs_id, next.timestamp, next.real_len, next.len_left, next.msg_stream_id);
|
||||||
//return 0;
|
//return 0;
|
||||||
break;//happens when connection breaks unexpectedly
|
break; //happens when connection breaks unexpectedly
|
||||||
case 1://set chunk size
|
case 1: //set chunk size
|
||||||
RTMPStream::chunk_rec_max = ntohl(*(int*)next.data.c_str());
|
RTMPStream::chunk_rec_max = ntohl(*(int*)next.data.c_str());
|
||||||
fprintf(stderr, "CTRL: Set chunk size: %i\n", RTMPStream::chunk_rec_max);
|
fprintf(stderr, "CTRL: Set chunk size: %i\n", RTMPStream::chunk_rec_max);
|
||||||
break;
|
break;
|
||||||
case 2://abort message - we ignore this one
|
case 2: //abort message - we ignore this one
|
||||||
fprintf(stderr, "CTRL: Abort message: %i\n", ntohl(*(int*)next.data.c_str()));
|
fprintf(stderr, "CTRL: Abort message: %i\n", ntohl(*(int*)next.data.c_str()));
|
||||||
//4 bytes of stream id to drop
|
//4 bytes of stream id to drop
|
||||||
break;
|
break;
|
||||||
case 3://ack
|
case 3: //ack
|
||||||
RTMPStream::snd_window_at = ntohl(*(int*)next.data.c_str());
|
RTMPStream::snd_window_at = ntohl(*(int*)next.data.c_str());
|
||||||
fprintf(stderr, "CTRL: Acknowledgement: %i\n", RTMPStream::snd_window_at);
|
fprintf(stderr, "CTRL: Acknowledgement: %i\n", RTMPStream::snd_window_at);
|
||||||
break;
|
break;
|
||||||
case 4:{
|
case 4: {
|
||||||
short int ucmtype = ntohs(*(short int*)next.data.c_str());
|
short int ucmtype = ntohs(*(short int*)next.data.c_str());
|
||||||
switch (ucmtype){
|
switch (ucmtype){
|
||||||
case 0:
|
case 0:
|
||||||
|
@ -106,8 +110,9 @@ int main(int argc, char ** argv){
|
||||||
fprintf(stderr, "CTRL: User control message: UNKNOWN %hu - %u\n", ucmtype, ntohl(*(unsigned int*)(next.data.c_str()+2)));
|
fprintf(stderr, "CTRL: User control message: UNKNOWN %hu - %u\n", ucmtype, ntohl(*(unsigned int*)(next.data.c_str()+2)));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} break;
|
}
|
||||||
case 5://window size of other end
|
break;
|
||||||
|
case 5: //window size of other end
|
||||||
RTMPStream::rec_window_size = ntohl(*(int*)next.data.c_str());
|
RTMPStream::rec_window_size = ntohl(*(int*)next.data.c_str());
|
||||||
RTMPStream::rec_window_at = RTMPStream::rec_cnt;
|
RTMPStream::rec_window_at = RTMPStream::rec_cnt;
|
||||||
fprintf(stderr, "CTRL: Window size: %i\n", RTMPStream::rec_window_size);
|
fprintf(stderr, "CTRL: Window size: %i\n", RTMPStream::rec_window_size);
|
||||||
|
@ -147,7 +152,7 @@ int main(int argc, char ** argv){
|
||||||
case 16:
|
case 16:
|
||||||
fprintf(stderr, "Received AFM3 shared object\n");
|
fprintf(stderr, "Received AFM3 shared object\n");
|
||||||
break;
|
break;
|
||||||
case 17:{
|
case 17: {
|
||||||
fprintf(stderr, "Received AFM3 command message:\n");
|
fprintf(stderr, "Received AFM3 command message:\n");
|
||||||
char soort = next.data[0];
|
char soort = next.data[0];
|
||||||
next.data = next.data.substr(1);
|
next.data = next.data.substr(1);
|
||||||
|
@ -158,8 +163,9 @@ int main(int argc, char ** argv){
|
||||||
amf3data = AMF::parse3(next.data);
|
amf3data = AMF::parse3(next.data);
|
||||||
amf3data.Print();
|
amf3data.Print();
|
||||||
}
|
}
|
||||||
} break;
|
}
|
||||||
case 18:{
|
break;
|
||||||
|
case 18: {
|
||||||
fprintf(stderr, "Received AFM0 data message (metadata):\n");
|
fprintf(stderr, "Received AFM0 data message (metadata):\n");
|
||||||
amfdata = AMF::parse(next.data);
|
amfdata = AMF::parse(next.data);
|
||||||
amfdata.Print();
|
amfdata.Print();
|
||||||
|
@ -167,15 +173,17 @@ int main(int argc, char ** argv){
|
||||||
F.ChunkLoader(next);
|
F.ChunkLoader(next);
|
||||||
std::cout.write(F.data, F.len);
|
std::cout.write(F.data, F.len);
|
||||||
}
|
}
|
||||||
} break;
|
}
|
||||||
|
break;
|
||||||
case 19:
|
case 19:
|
||||||
fprintf(stderr, "Received AFM0 shared object\n");
|
fprintf(stderr, "Received AFM0 shared object\n");
|
||||||
break;
|
break;
|
||||||
case 20:{//AMF0 command message
|
case 20: { //AMF0 command message
|
||||||
fprintf(stderr, "Received AFM0 command message:\n");
|
fprintf(stderr, "Received AFM0 command message:\n");
|
||||||
amfdata = AMF::parse(next.data);
|
amfdata = AMF::parse(next.data);
|
||||||
std::cerr << amfdata.Print() << std::endl;
|
std::cerr << amfdata.Print() << std::endl;
|
||||||
} break;
|
}
|
||||||
|
break;
|
||||||
case 22:
|
case 22:
|
||||||
fprintf(stderr, "Received aggregate message\n");
|
fprintf(stderr, "Received aggregate message\n");
|
||||||
break;
|
break;
|
||||||
|
@ -183,8 +191,8 @@ int main(int argc, char ** argv){
|
||||||
fprintf(stderr, "Unknown chunk received! Probably protocol corruption, stopping parsing of incoming data.\n");
|
fprintf(stderr, "Unknown chunk received! Probably protocol corruption, stopping parsing of incoming data.\n");
|
||||||
return 1;
|
return 1;
|
||||||
break;
|
break;
|
||||||
}//switch for type of chunk
|
} //switch for type of chunk
|
||||||
}//while chunk parsed
|
} //while chunk parsed
|
||||||
fprintf(stderr, "No more readable data\n");
|
fprintf(stderr, "No more readable data\n");
|
||||||
return 0;
|
return 0;
|
||||||
}//main
|
} //main
|
||||||
|
|
116
src/buffer.cpp
116
src/buffer.cpp
|
@ -17,7 +17,7 @@
|
||||||
#include <mist/stream.h>
|
#include <mist/stream.h>
|
||||||
|
|
||||||
/// Holds all code unique to the Buffer.
|
/// Holds all code unique to the Buffer.
|
||||||
namespace Buffer{
|
namespace Buffer {
|
||||||
|
|
||||||
volatile bool buffer_running = true; ///< Set to false when shutting down.
|
volatile bool buffer_running = true; ///< Set to false when shutting down.
|
||||||
Stream * thisStream = 0;
|
Stream * thisStream = 0;
|
||||||
|
@ -26,19 +26,20 @@ namespace Buffer{
|
||||||
/// Gets the current system time in milliseconds.
|
/// Gets the current system time in milliseconds.
|
||||||
long long int getNowMS(){
|
long long int getNowMS(){
|
||||||
timeval t;
|
timeval t;
|
||||||
gettimeofday(&t, 0);
|
gettimeofday( &t, 0);
|
||||||
return t.tv_sec * 1000 + t.tv_usec/1000;
|
return t.tv_sec * 1000 + t.tv_usec / 1000;
|
||||||
}//getNowMS
|
} //getNowMS
|
||||||
|
|
||||||
|
|
||||||
void handleStats(void * empty){
|
void handleStats(void * empty){
|
||||||
if (empty != 0){return;}
|
if (empty != 0){
|
||||||
|
return;
|
||||||
|
}
|
||||||
std::string double_newline = "\n\n";
|
std::string double_newline = "\n\n";
|
||||||
Socket::Connection StatsSocket = Socket::Connection("/tmp/mist/statistics", true);
|
Socket::Connection StatsSocket = Socket::Connection("/tmp/mist/statistics", true);
|
||||||
while (buffer_running){
|
while (buffer_running){
|
||||||
usleep(1000000); //sleep one second
|
usleep(1000000); //sleep one second
|
||||||
Stream::get()->cleanUsers();
|
Stream::get()->cleanUsers();
|
||||||
if (!StatsSocket.connected()){
|
if ( !StatsSocket.connected()){
|
||||||
StatsSocket = Socket::Connection("/tmp/mist/statistics", true);
|
StatsSocket = Socket::Connection("/tmp/mist/statistics", true);
|
||||||
}
|
}
|
||||||
if (StatsSocket.connected()){
|
if (StatsSocket.connected()){
|
||||||
|
@ -52,9 +53,9 @@ namespace Buffer{
|
||||||
|
|
||||||
void handleUser(void * v_usr){
|
void handleUser(void * v_usr){
|
||||||
user * usr = (user*)v_usr;
|
user * usr = (user*)v_usr;
|
||||||
#if DEBUG >= 4
|
#if DEBUG >= 4
|
||||||
std::cerr << "Thread launched for user " << usr->MyStr << ", socket number " << usr->S.getSocket() << std::endl;
|
std::cerr << "Thread launched for user " << usr->MyStr << ", socket number " << usr->S.getSocket() << std::endl;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
usr->myRing = thisStream->getRing();
|
usr->myRing = thisStream->getRing();
|
||||||
if (thisStream->getHeader().size() > 0){
|
if (thisStream->getHeader().size() > 0){
|
||||||
|
@ -66,19 +67,19 @@ namespace Buffer{
|
||||||
usr->Send();
|
usr->Send();
|
||||||
if (usr->S.spool() && usr->S.Received().size()){
|
if (usr->S.spool() && usr->S.Received().size()){
|
||||||
//delete anything that doesn't end with a newline
|
//delete anything that doesn't end with a newline
|
||||||
if (!usr->S.Received().get().empty() && *(usr->S.Received().get().rbegin()) != '\n'){
|
if ( !usr->S.Received().get().empty() && *(usr->S.Received().get().rbegin()) != '\n'){
|
||||||
usr->S.Received().get().clear();
|
usr->S.Received().get().clear();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
usr->S.Received().get().resize(usr->S.Received().get().size() - 1);
|
usr->S.Received().get().resize(usr->S.Received().get().size() - 1);
|
||||||
if (!usr->S.Received().get().empty()){
|
if ( !usr->S.Received().get().empty()){
|
||||||
switch (usr->S.Received().get()[0]){
|
switch (usr->S.Received().get()[0]){
|
||||||
case 'P':{ //Push
|
case 'P': { //Push
|
||||||
std::cout << "Push attempt from IP " << usr->S.Received().get().substr(2) << std::endl;
|
std::cout << "Push attempt from IP " << usr->S.Received().get().substr(2) << std::endl;
|
||||||
if (thisStream->checkWaitingIP(usr->S.Received().get().substr(2))){
|
if (thisStream->checkWaitingIP(usr->S.Received().get().substr(2))){
|
||||||
if (thisStream->setInput(usr->S)){
|
if (thisStream->setInput(usr->S)){
|
||||||
std::cout << "Push accepted!" << std::endl;
|
std::cout << "Push accepted!" << std::endl;
|
||||||
usr->S = Socket::Connection(-1);
|
usr->S = Socket::Connection( -1);
|
||||||
return;
|
return;
|
||||||
}else{
|
}else{
|
||||||
usr->Disconnect("Push denied - push already in progress!");
|
usr->Disconnect("Push denied - push already in progress!");
|
||||||
|
@ -86,31 +87,40 @@ namespace Buffer{
|
||||||
}else{
|
}else{
|
||||||
usr->Disconnect("Push denied - invalid IP address!");
|
usr->Disconnect("Push denied - invalid IP address!");
|
||||||
}
|
}
|
||||||
} break;
|
}
|
||||||
case 'S':{ //Stats
|
break;
|
||||||
|
case 'S': { //Stats
|
||||||
usr->tmpStats = Stats(usr->S.Received().get().substr(2));
|
usr->tmpStats = Stats(usr->S.Received().get().substr(2));
|
||||||
unsigned int secs = usr->tmpStats.conntime - usr->lastStats.conntime;
|
unsigned int secs = usr->tmpStats.conntime - usr->lastStats.conntime;
|
||||||
if (secs < 1){secs = 1;}
|
if (secs < 1){
|
||||||
|
secs = 1;
|
||||||
|
}
|
||||||
usr->curr_up = (usr->tmpStats.up - usr->lastStats.up) / secs;
|
usr->curr_up = (usr->tmpStats.up - usr->lastStats.up) / secs;
|
||||||
usr->curr_down = (usr->tmpStats.down - usr->lastStats.down) / secs;
|
usr->curr_down = (usr->tmpStats.down - usr->lastStats.down) / secs;
|
||||||
usr->lastStats = usr->tmpStats;
|
usr->lastStats = usr->tmpStats;
|
||||||
thisStream->saveStats(usr->MyStr, usr->tmpStats);
|
thisStream->saveStats(usr->MyStr, usr->tmpStats);
|
||||||
} break;
|
}
|
||||||
case 's':{ //second-seek
|
break;
|
||||||
|
case 's': { //second-seek
|
||||||
//ignored for now
|
//ignored for now
|
||||||
} break;
|
}
|
||||||
case 'f':{ //frame-seek
|
break;
|
||||||
|
case 'f': { //frame-seek
|
||||||
//ignored for now
|
//ignored for now
|
||||||
} break;
|
}
|
||||||
case 'p':{ //play
|
break;
|
||||||
|
case 'p': { //play
|
||||||
//ignored for now
|
//ignored for now
|
||||||
} break;
|
}
|
||||||
case 'o':{ //once-play
|
break;
|
||||||
|
case 'o': { //once-play
|
||||||
//ignored for now
|
//ignored for now
|
||||||
} break;
|
}
|
||||||
case 'q':{ //quit-playing
|
break;
|
||||||
|
case 'q': { //quit-playing
|
||||||
//ignored for now
|
//ignored for now
|
||||||
} break;
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,11 +130,13 @@ namespace Buffer{
|
||||||
|
|
||||||
/// Loop reading DTSC data from stdin and processing it at the correct speed.
|
/// Loop reading DTSC data from stdin and processing it at the correct speed.
|
||||||
void handleStdin(void * empty){
|
void handleStdin(void * empty){
|
||||||
if (empty != 0){return;}
|
if (empty != 0){
|
||||||
long long int timeDiff = 0;//difference between local time and stream time
|
return;
|
||||||
unsigned int lastPacket = 0;//last parsed packet timestamp
|
}
|
||||||
|
long long int timeDiff = 0; //difference between local time and stream time
|
||||||
|
unsigned int lastPacket = 0; //last parsed packet timestamp
|
||||||
std::string inBuffer;
|
std::string inBuffer;
|
||||||
char charBuffer[1024*10];
|
char charBuffer[1024 * 10];
|
||||||
unsigned int charCount;
|
unsigned int charCount;
|
||||||
long long int now;
|
long long int now;
|
||||||
|
|
||||||
|
@ -142,7 +154,7 @@ namespace Buffer{
|
||||||
thisStream->dropWriteLock(true);
|
thisStream->dropWriteLock(true);
|
||||||
}else{
|
}else{
|
||||||
thisStream->dropWriteLock(false);
|
thisStream->dropWriteLock(false);
|
||||||
std::cin.read(charBuffer, 1024*10);
|
std::cin.read(charBuffer, 1024 * 10);
|
||||||
charCount = std::cin.gcount();
|
charCount = std::cin.gcount();
|
||||||
inBuffer.append(charBuffer, charCount);
|
inBuffer.append(charBuffer, charCount);
|
||||||
}
|
}
|
||||||
|
@ -157,7 +169,9 @@ namespace Buffer{
|
||||||
/// Loop reading DTSC data from an IP push address.
|
/// Loop reading DTSC data from an IP push address.
|
||||||
/// No changes to the speed are made.
|
/// No changes to the speed are made.
|
||||||
void handlePushin(void * empty){
|
void handlePushin(void * empty){
|
||||||
if (empty != 0){return;}
|
if (empty != 0){
|
||||||
|
return;
|
||||||
|
}
|
||||||
while (buffer_running){
|
while (buffer_running){
|
||||||
if (thisStream->getIPInput().connected()){
|
if (thisStream->getIPInput().connected()){
|
||||||
if (thisStream->getIPInput().spool()){
|
if (thisStream->getIPInput().spool()){
|
||||||
|
@ -167,30 +181,34 @@ namespace Buffer{
|
||||||
thisStream->dropWriteLock(true);
|
thisStream->dropWriteLock(true);
|
||||||
}else{
|
}else{
|
||||||
thisStream->dropWriteLock(false);
|
thisStream->dropWriteLock(false);
|
||||||
usleep(1000);//1ms wait
|
usleep(1000); //1ms wait
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
usleep(1000);//1ms wait
|
usleep(1000); //1ms wait
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
usleep(1000000);//1s wait
|
usleep(1000000); //1s wait
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SS.close();
|
SS.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Starts a loop, waiting for connections to send data to.
|
/// Starts a loop, waiting for connections to send data to.
|
||||||
int Start(int argc, char ** argv) {
|
int Start(int argc, char ** argv){
|
||||||
Util::Config conf = Util::Config(argv[0], PACKAGE_VERSION);
|
Util::Config conf = Util::Config(argv[0], PACKAGE_VERSION);
|
||||||
conf.addOption("stream_name", JSON::fromString("{\"arg_num\":1, \"arg\":\"string\", \"help\":\"Name of the stream this buffer will be providing.\"}"));
|
conf.addOption("stream_name",
|
||||||
conf.addOption("awaiting_ip", JSON::fromString("{\"arg_num\":2, \"arg\":\"string\", \"default\":\"\", \"help\":\"IP address to expect incoming data from. This will completely disable reading from standard input if used.\"}"));
|
JSON::fromString("{\"arg_num\":1, \"arg\":\"string\", \"help\":\"Name of the stream this buffer will be providing.\"}"));
|
||||||
conf.addOption("reportstats", JSON::fromString("{\"default\":0, \"help\":\"Report stats to a controller process.\", \"short\":\"s\", \"long\":\"reportstats\"}"));
|
conf.addOption("awaiting_ip",
|
||||||
|
JSON::fromString(
|
||||||
|
"{\"arg_num\":2, \"arg\":\"string\", \"default\":\"\", \"help\":\"IP address to expect incoming data from. This will completely disable reading from standard input if used.\"}"));
|
||||||
|
conf.addOption("reportstats",
|
||||||
|
JSON::fromString("{\"default\":0, \"help\":\"Report stats to a controller process.\", \"short\":\"s\", \"long\":\"reportstats\"}"));
|
||||||
conf.parseArgs(argc, argv);
|
conf.parseArgs(argc, argv);
|
||||||
|
|
||||||
std::string name = conf.getString("stream_name");
|
std::string name = conf.getString("stream_name");
|
||||||
|
|
||||||
SS = Util::Stream::makeLive(name);
|
SS = Util::Stream::makeLive(name);
|
||||||
if (!SS.connected()) {
|
if ( !SS.connected()){
|
||||||
perror("Could not create stream socket");
|
perror("Could not create stream socket");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
@ -201,7 +219,9 @@ namespace Buffer{
|
||||||
Socket::Connection std_input(fileno(stdin));
|
Socket::Connection std_input(fileno(stdin));
|
||||||
|
|
||||||
tthread::thread * StatsThread = 0;
|
tthread::thread * StatsThread = 0;
|
||||||
if (conf.getBool("reportstats")){StatsThread = new tthread::thread(handleStats, 0);}
|
if (conf.getBool("reportstats")){
|
||||||
|
StatsThread = new tthread::thread(handleStats, 0);
|
||||||
|
}
|
||||||
tthread::thread * StdinThread = 0;
|
tthread::thread * StdinThread = 0;
|
||||||
std::string await_ip = conf.getString("awaiting_ip");
|
std::string await_ip = conf.getString("awaiting_ip");
|
||||||
if (await_ip == ""){
|
if (await_ip == ""){
|
||||||
|
@ -220,7 +240,7 @@ namespace Buffer{
|
||||||
thisStream->addUser(usr_ptr);
|
thisStream->addUser(usr_ptr);
|
||||||
usr_ptr->Thread = new tthread::thread(handleUser, (void *)usr_ptr);
|
usr_ptr->Thread = new tthread::thread(handleUser, (void *)usr_ptr);
|
||||||
}
|
}
|
||||||
}//main loop
|
} //main loop
|
||||||
|
|
||||||
// disconnect listener
|
// disconnect listener
|
||||||
buffer_running = false;
|
buffer_running = false;
|
||||||
|
@ -230,16 +250,20 @@ namespace Buffer{
|
||||||
StatsThread->join();
|
StatsThread->join();
|
||||||
delete StatsThread;
|
delete StatsThread;
|
||||||
}
|
}
|
||||||
if (thisStream->getIPInput().connected()){thisStream->getIPInput().close();}
|
if (thisStream->getIPInput().connected()){
|
||||||
|
thisStream->getIPInput().close();
|
||||||
|
}
|
||||||
StdinThread->join();
|
StdinThread->join();
|
||||||
delete StdinThread;
|
delete StdinThread;
|
||||||
delete thisStream;
|
delete thisStream;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
};//Buffer namespace
|
}
|
||||||
|
;
|
||||||
|
//Buffer namespace
|
||||||
|
|
||||||
/// Entry point for Buffer, simply calls Buffer::Start().
|
/// Entry point for Buffer, simply calls Buffer::Start().
|
||||||
int main(int argc, char ** argv){
|
int main(int argc, char ** argv){
|
||||||
return Buffer::Start(argc, argv);
|
return Buffer::Start(argc, argv);
|
||||||
}//main
|
} //main
|
||||||
|
|
|
@ -13,7 +13,9 @@ Buffer::Stream * Buffer::Stream::get(){
|
||||||
if (ref == 0){
|
if (ref == 0){
|
||||||
//prevent creating two at the same time
|
//prevent creating two at the same time
|
||||||
creator.lock();
|
creator.lock();
|
||||||
if (ref == 0){ref = new Stream();}
|
if (ref == 0){
|
||||||
|
ref = new Stream();
|
||||||
|
}
|
||||||
creator.unlock();
|
creator.unlock();
|
||||||
}
|
}
|
||||||
return ref;
|
return ref;
|
||||||
|
@ -31,9 +33,9 @@ Buffer::Stream::~Stream(){
|
||||||
while (users.size() > 0){
|
while (users.size() > 0){
|
||||||
stats_mutex.lock();
|
stats_mutex.lock();
|
||||||
for (usersIt = users.begin(); usersIt != users.end(); usersIt++){
|
for (usersIt = users.begin(); usersIt != users.end(); usersIt++){
|
||||||
if ((**usersIt).S.connected()){
|
if (( * *usersIt).S.connected()){
|
||||||
(**usersIt).S.close();
|
( * *usersIt).S.close();
|
||||||
printf("Closing user %s\n", (**usersIt).MyStr.c_str());
|
printf("Closing user %s\n", ( * *usersIt).MyStr.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stats_mutex.unlock();
|
stats_mutex.unlock();
|
||||||
|
@ -51,8 +53,8 @@ std::string & Buffer::Stream::getStats(){
|
||||||
stats_mutex.lock();
|
stats_mutex.lock();
|
||||||
if (users.size() > 0){
|
if (users.size() > 0){
|
||||||
for (usersIt = users.begin(); usersIt != users.end(); usersIt++){
|
for (usersIt = users.begin(); usersIt != users.end(); usersIt++){
|
||||||
tot_down += (**usersIt).curr_down;
|
tot_down += ( * *usersIt).curr_down;
|
||||||
tot_up += (**usersIt).curr_up;
|
tot_up += ( * *usersIt).curr_up;
|
||||||
tot_count++;
|
tot_count++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,8 +64,12 @@ std::string & Buffer::Stream::getStats(){
|
||||||
Storage["totals"]["now"] = now;
|
Storage["totals"]["now"] = now;
|
||||||
Storage["buffer"] = name;
|
Storage["buffer"] = name;
|
||||||
Storage["meta"] = Strm->metadata;
|
Storage["meta"] = Strm->metadata;
|
||||||
if (Storage["meta"].isMember("audio")){Storage["meta"]["audio"].removeMember("init");}
|
if (Storage["meta"].isMember("audio")){
|
||||||
if (Storage["meta"].isMember("video")){Storage["meta"]["video"].removeMember("init");}
|
Storage["meta"]["audio"].removeMember("init");
|
||||||
|
}
|
||||||
|
if (Storage["meta"].isMember("video")){
|
||||||
|
Storage["meta"]["video"].removeMember("init");
|
||||||
|
}
|
||||||
ret = Storage.toString();
|
ret = Storage.toString();
|
||||||
Storage["log"].null();
|
Storage["log"].null();
|
||||||
stats_mutex.unlock();
|
stats_mutex.unlock();
|
||||||
|
@ -92,7 +98,7 @@ void Buffer::Stream::setWaitingIP(std::string ip){
|
||||||
|
|
||||||
/// Check if this is the IP address to accept push data from.
|
/// Check if this is the IP address to accept push data from.
|
||||||
bool Buffer::Stream::checkWaitingIP(std::string ip){
|
bool Buffer::Stream::checkWaitingIP(std::string ip){
|
||||||
if (ip == waiting_ip || ip == "::ffff:"+waiting_ip){
|
if (ip == waiting_ip || ip == "::ffff:" + waiting_ip){
|
||||||
return true;
|
return true;
|
||||||
}else{
|
}else{
|
||||||
std::cout << ip << " != " << waiting_ip << std::endl;
|
std::cout << ip << " != " << waiting_ip << std::endl;
|
||||||
|
@ -115,7 +121,6 @@ Socket::Connection & Buffer::Stream::getIPInput(){
|
||||||
return ip_input;
|
return ip_input;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Stores intermediate statistics.
|
/// Stores intermediate statistics.
|
||||||
void Buffer::Stream::saveStats(std::string username, Stats & stats){
|
void Buffer::Stream::saveStats(std::string username, Stats & stats){
|
||||||
stats_mutex.lock();
|
stats_mutex.lock();
|
||||||
|
@ -133,9 +138,10 @@ void Buffer::Stream::clearStats(std::string username, Stats & stats, std::string
|
||||||
stats_mutex.lock();
|
stats_mutex.lock();
|
||||||
if (Storage["curr"].isMember(username)){
|
if (Storage["curr"].isMember(username)){
|
||||||
Storage["curr"].removeMember(username);
|
Storage["curr"].removeMember(username);
|
||||||
#if DEBUG >= 4
|
#if DEBUG >= 4
|
||||||
std::cout << "Disconnected user " << username << ": " << reason << ". " << stats.connector << " transferred " << stats.up << " up and " << stats.down << " down in " << stats.conntime << " seconds to " << stats.host << std::endl;
|
std::cout << "Disconnected user " << username << ": " << reason << ". " << stats.connector << " transferred " << stats.up << " up and "
|
||||||
#endif
|
<< stats.down << " down in " << stats.conntime << " seconds to " << stats.host << std::endl;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
Storage["log"][username]["connector"] = stats.connector;
|
Storage["log"][username]["connector"] = stats.connector;
|
||||||
Storage["log"][username]["up"] = stats.up;
|
Storage["log"][username]["up"] = stats.up;
|
||||||
|
@ -154,23 +160,23 @@ void Buffer::Stream::cleanUsers(){
|
||||||
repeat = false;
|
repeat = false;
|
||||||
if (users.size() > 0){
|
if (users.size() > 0){
|
||||||
for (usersIt = users.begin(); usersIt != users.end(); usersIt++){
|
for (usersIt = users.begin(); usersIt != users.end(); usersIt++){
|
||||||
if ((**usersIt).Thread == 0 && !(**usersIt).S.connected()){
|
if (( * *usersIt).Thread == 0 && !( * *usersIt).S.connected()){
|
||||||
delete *usersIt;
|
delete *usersIt;
|
||||||
users.erase(usersIt);
|
users.erase(usersIt);
|
||||||
repeat = true;
|
repeat = true;
|
||||||
break;
|
break;
|
||||||
}else{
|
}else{
|
||||||
if (!(**usersIt).S.connected()){
|
if ( !( * *usersIt).S.connected()){
|
||||||
if ((**usersIt).Thread->joinable()){
|
if (( * *usersIt).Thread->joinable()){
|
||||||
(**usersIt).Thread->join();
|
( * *usersIt).Thread->join();
|
||||||
delete (**usersIt).Thread;
|
delete ( * *usersIt).Thread;
|
||||||
(**usersIt).Thread = 0;
|
( * *usersIt).Thread = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}while(repeat);
|
}while (repeat);
|
||||||
stats_mutex.unlock();
|
stats_mutex.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,7 +196,9 @@ void Buffer::Stream::dropWriteLock(bool newpackets_available){
|
||||||
writers--;
|
writers--;
|
||||||
rw_mutex.unlock();
|
rw_mutex.unlock();
|
||||||
rw_change.notify_all();
|
rw_change.notify_all();
|
||||||
if (newpackets_available){moreData.notify_all();}
|
if (newpackets_available){
|
||||||
|
moreData.notify_all();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Blocks until reading is safe.
|
/// Blocks until reading is safe.
|
||||||
|
|
|
@ -3,11 +3,13 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <mist/dtsc.h>
|
||||||
#include <mist/json.h>
|
#include <mist/json.h>
|
||||||
|
#include <mist/socket.h>
|
||||||
#include "tinythread.h"
|
#include "tinythread.h"
|
||||||
#include "buffer_user.h"
|
#include "buffer_user.h"
|
||||||
|
|
||||||
namespace Buffer{
|
namespace Buffer {
|
||||||
/// Keeps track of a single streams inputs and outputs, taking care of thread safety and all other related issues.
|
/// Keeps track of a single streams inputs and outputs, taking care of thread safety and all other related issues.
|
||||||
class Stream{
|
class Stream{
|
||||||
public:
|
public:
|
||||||
|
@ -54,8 +56,8 @@ namespace Buffer{
|
||||||
/// Cleanup function
|
/// Cleanup function
|
||||||
~Stream();
|
~Stream();
|
||||||
private:
|
private:
|
||||||
volatile int readers;///< Current count of active readers;
|
volatile int readers; ///< Current count of active readers;
|
||||||
volatile int writers;///< Current count of waiting/active writers.
|
volatile int writers; ///< Current count of waiting/active writers.
|
||||||
tthread::mutex rw_mutex; ///< Mutex for read/write locking.
|
tthread::mutex rw_mutex; ///< Mutex for read/write locking.
|
||||||
tthread::condition_variable rw_change; ///< Triggered when reader/writer count changes.
|
tthread::condition_variable rw_change; ///< Triggered when reader/writer count changes.
|
||||||
static Stream * ref;
|
static Stream * ref;
|
||||||
|
@ -70,4 +72,5 @@ namespace Buffer{
|
||||||
std::string name; ///< Name for this buffer.
|
std::string name; ///< Name for this buffer.
|
||||||
tthread::condition_variable moreData; ///< Triggered when more data becomes available.
|
tthread::condition_variable moreData; ///< Triggered when more data becomes available.
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
;
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
#include "buffer_stream.h"
|
#include "buffer_stream.h"
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <stdlib.h> //for atoi and friends
|
#include <stdlib.h> //for atoi and friends
|
||||||
|
|
||||||
int Buffer::user::UserCount = 0;
|
int Buffer::user::UserCount = 0;
|
||||||
|
|
||||||
/// Creates a new user from a newly connected socket.
|
/// Creates a new user from a newly connected socket.
|
||||||
|
@ -21,42 +20,54 @@ Buffer::user::user(Socket::Connection fd){
|
||||||
currsend = 0;
|
currsend = 0;
|
||||||
myRing = 0;
|
myRing = 0;
|
||||||
Thread = 0;
|
Thread = 0;
|
||||||
}//constructor
|
gotproperaudio = false;
|
||||||
|
lastpointer = 0;
|
||||||
|
} //constructor
|
||||||
|
|
||||||
/// Drops held DTSC::Ring class, if one is held.
|
/// Drops held DTSC::Ring class, if one is held.
|
||||||
Buffer::user::~user(){
|
Buffer::user::~user(){
|
||||||
Stream::get()->dropRing(myRing);
|
Stream::get()->dropRing(myRing);
|
||||||
}//destructor
|
} //destructor
|
||||||
|
|
||||||
/// Disconnects the current user. Doesn't do anything if already disconnected.
|
/// Disconnects the current user. Doesn't do anything if already disconnected.
|
||||||
/// Prints "Disconnected user" to stdout if disconnect took place.
|
/// Prints "Disconnected user" to stdout if disconnect took place.
|
||||||
void Buffer::user::Disconnect(std::string reason) {
|
void Buffer::user::Disconnect(std::string reason){
|
||||||
if (S.connected()){S.close();}
|
if (S.connected()){
|
||||||
|
S.close();
|
||||||
|
}
|
||||||
Stream::get()->clearStats(MyStr, lastStats, reason);
|
Stream::get()->clearStats(MyStr, lastStats, reason);
|
||||||
}//Disconnect
|
} //Disconnect
|
||||||
|
|
||||||
/// Tries to send the current buffer, returns true if success, false otherwise.
|
/// Tries to send the current buffer, returns true if success, false otherwise.
|
||||||
/// Has a side effect of dropping the connection if send will never complete.
|
/// Has a side effect of dropping the connection if send will never complete.
|
||||||
bool Buffer::user::doSend(const char * ptr, int len){
|
bool Buffer::user::doSend(const char * ptr, int len){
|
||||||
if (!len){return false;}//do not do empty sends
|
if ( !len){
|
||||||
int r = S.iwrite(ptr+currsend, len-currsend);
|
return false;
|
||||||
|
} //do not do empty sends
|
||||||
|
int r = S.iwrite(ptr + currsend, len - currsend);
|
||||||
if (r <= 0){
|
if (r <= 0){
|
||||||
if (errno == EWOULDBLOCK){return false;}
|
if (errno == EWOULDBLOCK){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
Disconnect(S.getError());
|
Disconnect(S.getError());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
currsend += r;
|
currsend += r;
|
||||||
return (currsend == len);
|
return (currsend == len);
|
||||||
}//doSend
|
} //doSend
|
||||||
|
|
||||||
/// Try to send data to this user. Disconnects if any problems occur.
|
/// Try to send data to this user. Disconnects if any problems occur.
|
||||||
void Buffer::user::Send(){
|
void Buffer::user::Send(){
|
||||||
if (!myRing){return;}//no ring!
|
if ( !myRing){
|
||||||
if (!S.connected()){return;}//cancel if not connected
|
return;
|
||||||
|
} //no ring!
|
||||||
|
if ( !S.connected()){
|
||||||
|
return;
|
||||||
|
} //cancel if not connected
|
||||||
if (myRing->waiting){
|
if (myRing->waiting){
|
||||||
Stream::get()->waitForData();
|
Stream::get()->waitForData();
|
||||||
return;
|
return;
|
||||||
}//still waiting for next buffer?
|
} //still waiting for next buffer?
|
||||||
if (myRing->starved){
|
if (myRing->starved){
|
||||||
//if corrupt data, warn and get new DTSC::Ring
|
//if corrupt data, warn and get new DTSC::Ring
|
||||||
std::cout << "Warning: User " << MyNum << " was send corrupt video data and send to the next keyframe!" << std::endl;
|
std::cout << "Warning: User " << MyNum << " was send corrupt video data and send to the next keyframe!" << std::endl;
|
||||||
|
@ -69,11 +80,14 @@ void Buffer::user::Send(){
|
||||||
if (doSend(Stream::get()->getStream()->outPacket(myRing->b).c_str(), Stream::get()->getStream()->outPacket(myRing->b).length())){
|
if (doSend(Stream::get()->getStream()->outPacket(myRing->b).c_str(), Stream::get()->getStream()->outPacket(myRing->b).length())){
|
||||||
//switch to next buffer
|
//switch to next buffer
|
||||||
currsend = 0;
|
currsend = 0;
|
||||||
if (myRing->b <= 0){myRing->waiting = true; return;}//no next buffer? go in waiting mode.
|
if (myRing->b <= 0){
|
||||||
|
myRing->waiting = true;
|
||||||
|
return;
|
||||||
|
} //no next buffer? go in waiting mode.
|
||||||
myRing->b--;
|
myRing->b--;
|
||||||
}//completed a send
|
} //completed a send
|
||||||
Stream::get()->dropReadLock();
|
Stream::get()->dropReadLock();
|
||||||
}//send
|
} //send
|
||||||
|
|
||||||
/// Default constructor - should not be in use.
|
/// Default constructor - should not be in use.
|
||||||
Buffer::Stats::Stats(){
|
Buffer::Stats::Stats(){
|
||||||
|
@ -87,22 +101,22 @@ Buffer::Stats::Stats(std::string s){
|
||||||
size_t f = s.find(' ');
|
size_t f = s.find(' ');
|
||||||
if (f != std::string::npos){
|
if (f != std::string::npos){
|
||||||
host = s.substr(0, f);
|
host = s.substr(0, f);
|
||||||
s.erase(0, f+1);
|
s.erase(0, f + 1);
|
||||||
}
|
}
|
||||||
f = s.find(' ');
|
f = s.find(' ');
|
||||||
if (f != std::string::npos){
|
if (f != std::string::npos){
|
||||||
connector = s.substr(0, f);
|
connector = s.substr(0, f);
|
||||||
s.erase(0, f+1);
|
s.erase(0, f + 1);
|
||||||
}
|
}
|
||||||
f = s.find(' ');
|
f = s.find(' ');
|
||||||
if (f != std::string::npos){
|
if (f != std::string::npos){
|
||||||
conntime = atoi(s.substr(0, f).c_str());
|
conntime = atoi(s.substr(0, f).c_str());
|
||||||
s.erase(0, f+1);
|
s.erase(0, f + 1);
|
||||||
}
|
}
|
||||||
f = s.find(' ');
|
f = s.find(' ');
|
||||||
if (f != std::string::npos){
|
if (f != std::string::npos){
|
||||||
up = atoi(s.substr(0, f).c_str());
|
up = atoi(s.substr(0, f).c_str());
|
||||||
s.erase(0, f+1);
|
s.erase(0, f + 1);
|
||||||
down = atoi(s.c_str());
|
down = atoi(s.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
#include <mist/socket.h>
|
#include <mist/socket.h>
|
||||||
#include "tinythread.h"
|
#include "tinythread.h"
|
||||||
|
|
||||||
namespace Buffer{
|
namespace Buffer {
|
||||||
/// Converts a stats line to up, down, host, connector and conntime values.
|
/// Converts a stats line to up, down, host, connector and conntime values.
|
||||||
class Stats{
|
class Stats{
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
#include <mist/socket.h>
|
#include <mist/socket.h>
|
||||||
#include <mist/http_parser.h>
|
#include <mist/http_parser.h>
|
||||||
#include <mist/config.h>
|
#include <mist/config.h>
|
||||||
#include <mist/procs.h>
|
|
||||||
#include <mist/stream.h>
|
#include <mist/stream.h>
|
||||||
#include <mist/timing.h>
|
#include <mist/timing.h>
|
||||||
#include <mist/auth.h>
|
#include <mist/auth.h>
|
||||||
|
@ -22,7 +21,7 @@
|
||||||
#include "embed.js.h"
|
#include "embed.js.h"
|
||||||
|
|
||||||
/// Holds everything unique to HTTP Connector.
|
/// Holds everything unique to HTTP Connector.
|
||||||
namespace Connector_HTTP{
|
namespace Connector_HTTP {
|
||||||
|
|
||||||
/// Class for keeping track of connections to connectors.
|
/// Class for keeping track of connections to connectors.
|
||||||
class ConnConn{
|
class ConnConn{
|
||||||
|
@ -34,12 +33,14 @@ namespace Connector_HTTP{
|
||||||
ConnConn(){
|
ConnConn(){
|
||||||
conn = 0;
|
conn = 0;
|
||||||
lastuse = 0;
|
lastuse = 0;
|
||||||
};
|
}
|
||||||
|
;
|
||||||
/// Constructor that sets lastuse to 0, but socket to s.
|
/// Constructor that sets lastuse to 0, but socket to s.
|
||||||
ConnConn(Socket::Connection * s){
|
ConnConn(Socket::Connection * s){
|
||||||
conn = s;
|
conn = s;
|
||||||
lastuse = 0;
|
lastuse = 0;
|
||||||
};
|
}
|
||||||
|
;
|
||||||
/// Destructor that deletes the socket if non-null.
|
/// Destructor that deletes the socket if non-null.
|
||||||
~ConnConn(){
|
~ConnConn(){
|
||||||
if (conn){
|
if (conn){
|
||||||
|
@ -47,7 +48,8 @@ namespace Connector_HTTP{
|
||||||
delete conn;
|
delete conn;
|
||||||
}
|
}
|
||||||
conn = 0;
|
conn = 0;
|
||||||
};
|
}
|
||||||
|
;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::map<std::string, ConnConn *> connconn; ///< Connections to connectors
|
std::map<std::string, ConnConn *> connconn; ///< Connections to connectors
|
||||||
|
@ -59,7 +61,7 @@ namespace Connector_HTTP{
|
||||||
tthread::thread * timeouter = 0; ///< Thread that times out connections to connectors.
|
tthread::thread * timeouter = 0; ///< Thread that times out connections to connectors.
|
||||||
|
|
||||||
void Timeout_Thread(void * n){
|
void Timeout_Thread(void * n){
|
||||||
n = 0;//prevent unused variable warning
|
n = 0; //prevent unused variable warning
|
||||||
tthread::lock_guard<tthread::mutex> guard(timeout_mutex);
|
tthread::lock_guard<tthread::mutex> guard(timeout_mutex);
|
||||||
while (true){
|
while (true){
|
||||||
{
|
{
|
||||||
|
@ -69,19 +71,21 @@ namespace Connector_HTTP{
|
||||||
}
|
}
|
||||||
std::map<std::string, ConnConn*>::iterator it;
|
std::map<std::string, ConnConn*>::iterator it;
|
||||||
for (it = connconn.begin(); it != connconn.end(); it++){
|
for (it = connconn.begin(); it != connconn.end(); it++){
|
||||||
if (!it->second->conn->connected() || it->second->lastuse++ > 15){
|
if ( !it->second->conn->connected() || it->second->lastuse++ > 15){
|
||||||
if (it->second->in_use.try_lock()){
|
if (it->second->in_use.try_lock()){
|
||||||
it->second->in_use.unlock();
|
it->second->in_use.unlock();
|
||||||
delete it->second;
|
delete it->second;
|
||||||
connconn.erase(it);
|
connconn.erase(it);
|
||||||
it = connconn.begin();//get a valid iterator
|
it = connconn.begin(); //get a valid iterator
|
||||||
if (it == connconn.end()){return;}
|
if (it == connconn.end()){
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
conn_mutex.unlock();
|
conn_mutex.unlock();
|
||||||
}
|
}
|
||||||
usleep(1000000);//sleep 1 second and re-check
|
usleep(1000000); //sleep 1 second and re-check
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,14 +93,16 @@ namespace Connector_HTTP{
|
||||||
void Handle_None(HTTP::Parser & H, Socket::Connection * conn){
|
void Handle_None(HTTP::Parser & H, Socket::Connection * conn){
|
||||||
H.Clean();
|
H.Clean();
|
||||||
H.SetHeader("Server", "mistserver/" PACKAGE_VERSION "/" + Util::Config::libver);
|
H.SetHeader("Server", "mistserver/" PACKAGE_VERSION "/" + Util::Config::libver);
|
||||||
H.SetBody("<!DOCTYPE html><html><head><title>Unsupported Media Type</title></head><body><h1>Unsupported Media Type</h1>The server isn't quite sure what you wanted to receive from it.</body></html>");
|
H.SetBody(
|
||||||
|
"<!DOCTYPE html><html><head><title>Unsupported Media Type</title></head><body><h1>Unsupported Media Type</h1>The server isn't quite sure what you wanted to receive from it.</body></html>");
|
||||||
conn->SendNow(H.BuildResponse("415", "Unsupported Media Type"));
|
conn->SendNow(H.BuildResponse("415", "Unsupported Media Type"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Handle_Timeout(HTTP::Parser & H, Socket::Connection * conn){
|
void Handle_Timeout(HTTP::Parser & H, Socket::Connection * conn){
|
||||||
H.Clean();
|
H.Clean();
|
||||||
H.SetHeader("Server", "mistserver/" PACKAGE_VERSION "/" + Util::Config::libver);
|
H.SetHeader("Server", "mistserver/" PACKAGE_VERSION "/" + Util::Config::libver);
|
||||||
H.SetBody("<!DOCTYPE html><html><head><title>Gateway timeout</title></head><body><h1>Gateway timeout</h1>Though the server understood your request and attempted to handle it, somehow handling it took longer than it should. Your request has been cancelled - please try again later.</body></html>");
|
H.SetBody(
|
||||||
|
"<!DOCTYPE html><html><head><title>Gateway timeout</title></head><body><h1>Gateway timeout</h1>Though the server understood your request and attempted to handle it, somehow handling it took longer than it should. Your request has been cancelled - please try again later.</body></html>");
|
||||||
conn->SendNow(H.BuildResponse("504", "Gateway Timeout"));
|
conn->SendNow(H.BuildResponse("504", "Gateway Timeout"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,21 +115,24 @@ namespace Connector_HTTP{
|
||||||
H.Clean();
|
H.Clean();
|
||||||
H.SetHeader("Content-Type", "text/xml");
|
H.SetHeader("Content-Type", "text/xml");
|
||||||
H.SetHeader("Server", "mistserver/" PACKAGE_VERSION "/" + Util::Config::libver);
|
H.SetHeader("Server", "mistserver/" PACKAGE_VERSION "/" + Util::Config::libver);
|
||||||
H.SetBody("<?xml version=\"1.0\"?><!DOCTYPE cross-domain-policy SYSTEM \"http://www.adobe.com/xml/dtds/cross-domain-policy.dtd\"><cross-domain-policy><allow-access-from domain=\"*\" /><site-control permitted-cross-domain-policies=\"all\"/></cross-domain-policy>");
|
H.SetBody(
|
||||||
|
"<?xml version=\"1.0\"?><!DOCTYPE cross-domain-policy SYSTEM \"http://www.adobe.com/xml/dtds/cross-domain-policy.dtd\"><cross-domain-policy><allow-access-from domain=\"*\" /><site-control permitted-cross-domain-policies=\"all\"/></cross-domain-policy>");
|
||||||
conn->SendNow(H.BuildResponse("200", "OK"));
|
conn->SendNow(H.BuildResponse("200", "OK"));
|
||||||
return;
|
return;
|
||||||
}//crossdomain.xml
|
} //crossdomain.xml
|
||||||
|
|
||||||
if (url == "/clientaccesspolicy.xml"){
|
if (url == "/clientaccesspolicy.xml"){
|
||||||
H.Clean();
|
H.Clean();
|
||||||
H.SetHeader("Content-Type", "text/xml");
|
H.SetHeader("Content-Type", "text/xml");
|
||||||
H.SetHeader("Server", "mistserver/" PACKAGE_VERSION "/" + Util::Config::libver);
|
H.SetHeader("Server", "mistserver/" PACKAGE_VERSION "/" + Util::Config::libver);
|
||||||
H.SetBody("<?xml version=\"1.0\" encoding=\"utf-8\"?><access-policy><cross-domain-access><policy><allow-from http-methods=\"*\" http-request-headers=\"*\"><domain uri=\"*\"/></allow-from><grant-to><resource path=\"/\" include-subpaths=\"true\"/></grant-to></policy></cross-domain-access></access-policy>");
|
H.SetBody(
|
||||||
|
"<?xml version=\"1.0\" encoding=\"utf-8\"?><access-policy><cross-domain-access><policy><allow-from http-methods=\"*\" http-request-headers=\"*\"><domain uri=\"*\"/></allow-from><grant-to><resource path=\"/\" include-subpaths=\"true\"/></grant-to></policy></cross-domain-access></access-policy>");
|
||||||
conn->SendNow(H.BuildResponse("200", "OK"));
|
conn->SendNow(H.BuildResponse("200", "OK"));
|
||||||
return;
|
return;
|
||||||
}//clientaccesspolicy.xml
|
} //clientaccesspolicy.xml
|
||||||
|
|
||||||
if ((url.length() > 9 && url.substr(0, 6) == "/info_" && url.substr(url.length() - 3, 3) == ".js") || (url.length() > 10 && url.substr(0, 7) == "/embed_" && url.substr(url.length() - 3, 3) == ".js")){
|
if ((url.length() > 9 && url.substr(0, 6) == "/info_" && url.substr(url.length() - 3, 3) == ".js")
|
||||||
|
|| (url.length() > 10 && url.substr(0, 7) == "/embed_" && url.substr(url.length() - 3, 3) == ".js")){
|
||||||
std::string streamname;
|
std::string streamname;
|
||||||
if (url.substr(0, 6) == "/info_"){
|
if (url.substr(0, 6) == "/info_"){
|
||||||
streamname = url.substr(6, url.length() - 9);
|
streamname = url.substr(6, url.length() - 9);
|
||||||
|
@ -134,7 +143,9 @@ namespace Connector_HTTP{
|
||||||
JSON::Value ServConf = JSON::fromFile("/tmp/mist/streamlist");
|
JSON::Value ServConf = JSON::fromFile("/tmp/mist/streamlist");
|
||||||
std::string response;
|
std::string response;
|
||||||
std::string host = H.GetHeader("Host");
|
std::string host = H.GetHeader("Host");
|
||||||
if (host.find(':')){host.resize(host.find(':'));}
|
if (host.find(':')){
|
||||||
|
host.resize(host.find(':'));
|
||||||
|
}
|
||||||
H.Clean();
|
H.Clean();
|
||||||
H.SetHeader("Server", "mistserver/" PACKAGE_VERSION "/" + Util::Config::libver);
|
H.SetHeader("Server", "mistserver/" PACKAGE_VERSION "/" + Util::Config::libver);
|
||||||
H.SetHeader("Content-Type", "application/javascript");
|
H.SetHeader("Content-Type", "application/javascript");
|
||||||
|
@ -145,72 +156,74 @@ namespace Connector_HTTP{
|
||||||
json_resp["height"] = ServConf["streams"][streamname]["meta"]["video"]["height"].asInt();
|
json_resp["height"] = ServConf["streams"][streamname]["meta"]["video"]["height"].asInt();
|
||||||
//first, see if we have RTMP working and output all the RTMP.
|
//first, see if we have RTMP working and output all the RTMP.
|
||||||
for (JSON::ArrIter it = ServConf["config"]["protocols"].ArrBegin(); it != ServConf["config"]["protocols"].ArrEnd(); it++){
|
for (JSON::ArrIter it = ServConf["config"]["protocols"].ArrBegin(); it != ServConf["config"]["protocols"].ArrEnd(); it++){
|
||||||
if ((*it)["connector"].asString() == "RTMP"){
|
if (( *it)["connector"].asString() == "RTMP"){
|
||||||
JSON::Value tmp;
|
JSON::Value tmp;
|
||||||
tmp["type"] = "rtmp";
|
tmp["type"] = "rtmp";
|
||||||
tmp["url"] = "rtmp://" + host + ":" + (*it)["port"].asString() + "/play/" + streamname;
|
tmp["url"] = "rtmp://" + host + ":" + ( *it)["port"].asString() + "/play/" + streamname;
|
||||||
json_resp["source"].append(tmp);
|
json_resp["source"].append(tmp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//then, see if we have HTTP working and output all the dynamic.
|
//then, see if we have HTTP working and output all the dynamic.
|
||||||
for (JSON::ArrIter it = ServConf["config"]["protocols"].ArrBegin(); it != ServConf["config"]["protocols"].ArrEnd(); it++){
|
for (JSON::ArrIter it = ServConf["config"]["protocols"].ArrBegin(); it != ServConf["config"]["protocols"].ArrEnd(); it++){
|
||||||
if ((*it)["connector"].asString() == "HTTP"){
|
if (( *it)["connector"].asString() == "HTTP"){
|
||||||
JSON::Value tmp;
|
JSON::Value tmp;
|
||||||
tmp["type"] = "f4v";
|
tmp["type"] = "f4v";
|
||||||
tmp["url"] = "http://" + host + ":" + (*it)["port"].asString() + "/"+streamname+"/manifest.f4m";
|
tmp["url"] = "http://" + host + ":" + ( *it)["port"].asString() + "/" + streamname + "/manifest.f4m";
|
||||||
json_resp["source"].append(tmp);
|
json_resp["source"].append(tmp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//and all the progressive.
|
//and all the progressive.
|
||||||
for (JSON::ArrIter it = ServConf["config"]["protocols"].ArrBegin(); it != ServConf["config"]["protocols"].ArrEnd(); it++){
|
for (JSON::ArrIter it = ServConf["config"]["protocols"].ArrBegin(); it != ServConf["config"]["protocols"].ArrEnd(); it++){
|
||||||
if ((*it)["connector"].asString() == "HTTP"){
|
if (( *it)["connector"].asString() == "HTTP"){
|
||||||
JSON::Value tmp;
|
JSON::Value tmp;
|
||||||
tmp["type"] = "flv";
|
tmp["type"] = "flv";
|
||||||
tmp["url"] = "http://" + host + ":" + (*it)["port"].asString() + "/"+streamname+".flv";
|
tmp["url"] = "http://" + host + ":" + ( *it)["port"].asString() + "/" + streamname + ".flv";
|
||||||
json_resp["source"].append(tmp);
|
json_resp["source"].append(tmp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
json_resp["error"] = "The specified stream is not available on this server.";
|
json_resp["error"] = "The specified stream is not available on this server.";
|
||||||
json_resp["bbq"] = "sauce";//for legacy purposes ^_^
|
json_resp["bbq"] = "sauce"; //for legacy purposes ^_^
|
||||||
}
|
}
|
||||||
response += "mistvideo['" + streamname + "'] = "+json_resp.toString()+";\n";
|
response += "mistvideo['" + streamname + "'] = " + json_resp.toString() + ";\n";
|
||||||
if (url.substr(0, 6) != "/info_" && !json_resp.isMember("error")){
|
if (url.substr(0, 6) != "/info_" && !json_resp.isMember("error")){
|
||||||
response.append("\n(");
|
response.append("\n(");
|
||||||
response.append((char*)embed_js, (size_t)embed_js_len-2);//remove trailing ";\n" from xxd conversion
|
response.append((char*)embed_js, (size_t)embed_js_len - 2); //remove trailing ";\n" from xxd conversion
|
||||||
response.append("(\"" + streamname + "\"));\n");
|
response.append("(\"" + streamname + "\"));\n");
|
||||||
}
|
}
|
||||||
H.SetBody(response);
|
H.SetBody(response);
|
||||||
conn->SendNow(H.BuildResponse("200", "OK"));
|
conn->SendNow(H.BuildResponse("200", "OK"));
|
||||||
return;
|
return;
|
||||||
}//embed code generator
|
} //embed code generator
|
||||||
|
|
||||||
Handle_None(H, conn);//anything else doesn't get handled
|
Handle_None(H, conn); //anything else doesn't get handled
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handles requests without associated handler, displaying a nice friendly error message.
|
/// Handles requests without associated handler, displaying a nice friendly error message.
|
||||||
void Handle_Through_Connector(HTTP::Parser & H, Socket::Connection * conn, std::string & connector){
|
void Handle_Through_Connector(HTTP::Parser & H, Socket::Connection * conn, std::string & connector){
|
||||||
//create a unique ID based on a hash of the user agent and host, followed by the stream name and connector
|
//create a unique ID based on a hash of the user agent and host, followed by the stream name and connector
|
||||||
std::string uid = Secure::md5(H.GetHeader("User-Agent")+conn->getHost())+"_"+H.GetVar("stream")+"_"+connector;
|
std::string uid = Secure::md5(H.GetHeader("User-Agent") + conn->getHost()) + "_" + H.GetVar("stream") + "_" + connector;
|
||||||
H.SetHeader("X-UID", uid);//add the UID to the headers before copying
|
H.SetHeader("X-UID", uid); //add the UID to the headers before copying
|
||||||
H.SetHeader("X-Origin", conn->getHost());//add the UID to the headers before copying
|
H.SetHeader("X-Origin", conn->getHost()); //add the UID to the headers before copying
|
||||||
std::string request = H.BuildRequest();//copy the request for later forwarding to the connector
|
std::string request = H.BuildRequest(); //copy the request for later forwarding to the connector
|
||||||
std::string orig_url = H.getUrl();
|
std::string orig_url = H.getUrl();
|
||||||
H.Clean();
|
H.Clean();
|
||||||
|
|
||||||
//check if a connection exists, and if not create one
|
//check if a connection exists, and if not create one
|
||||||
conn_mutex.lock();
|
conn_mutex.lock();
|
||||||
if (!connconn.count(uid) || !connconn[uid]->conn->connected()){
|
if ( !connconn.count(uid) || !connconn[uid]->conn->connected()){
|
||||||
if (connconn.count(uid)){connconn.erase(uid);}
|
if (connconn.count(uid)){
|
||||||
connconn[uid] = new ConnConn(new Socket::Connection("/tmp/mist/http_"+connector));
|
connconn.erase(uid);
|
||||||
connconn[uid]->conn->setBlocking(false);//do not block on spool() with no data
|
}
|
||||||
#if DEBUG >= 4
|
connconn[uid] = new ConnConn(new Socket::Connection("/tmp/mist/http_" + connector));
|
||||||
|
connconn[uid]->conn->setBlocking(false); //do not block on spool() with no data
|
||||||
|
#if DEBUG >= 4
|
||||||
std::cout << "Created new connection " << uid << std::endl;
|
std::cout << "Created new connection " << uid << std::endl;
|
||||||
#endif
|
#endif
|
||||||
}else{
|
}else{
|
||||||
#if DEBUG >= 4
|
#if DEBUG >= 4
|
||||||
std::cout << "Re-using connection " << uid << std::endl;
|
std::cout << "Re-using connection " << uid << std::endl;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
//start a new timeout thread, if neccesary
|
//start a new timeout thread, if neccesary
|
||||||
if (timeout_mutex.try_lock()){
|
if (timeout_mutex.try_lock()){
|
||||||
|
@ -226,7 +239,7 @@ namespace Connector_HTTP{
|
||||||
//lock the mutex for this connection, and handle the request
|
//lock the mutex for this connection, and handle the request
|
||||||
tthread::lock_guard<tthread::mutex> guard(connconn[uid]->in_use);
|
tthread::lock_guard<tthread::mutex> guard(connconn[uid]->in_use);
|
||||||
//if the server connection is dead, handle as timeout.
|
//if the server connection is dead, handle as timeout.
|
||||||
if (!connconn.count(uid) || !connconn[uid]->conn->connected()){
|
if ( !connconn.count(uid) || !connconn[uid]->conn->connected()){
|
||||||
Handle_Timeout(H, conn);
|
Handle_Timeout(H, conn);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -239,7 +252,7 @@ namespace Connector_HTTP{
|
||||||
conn->spool();
|
conn->spool();
|
||||||
if (connconn[uid]->conn->Received().size() || connconn[uid]->conn->spool()){
|
if (connconn[uid]->conn->Received().size() || connconn[uid]->conn->spool()){
|
||||||
//make sure we end in a \n
|
//make sure we end in a \n
|
||||||
if (*(connconn[uid]->conn->Received().get().rbegin()) != '\n'){
|
if ( *(connconn[uid]->conn->Received().get().rbegin()) != '\n'){
|
||||||
std::string tmp = connconn[uid]->conn->Received().get();
|
std::string tmp = connconn[uid]->conn->Received().get();
|
||||||
connconn[uid]->conn->Received().get().clear();
|
connconn[uid]->conn->Received().get().clear();
|
||||||
if (connconn[uid]->conn->Received().size()){
|
if (connconn[uid]->conn->Received().size()){
|
||||||
|
@ -250,7 +263,7 @@ namespace Connector_HTTP{
|
||||||
}
|
}
|
||||||
//check if the whole response was received
|
//check if the whole response was received
|
||||||
if (H.Read(connconn[uid]->conn->Received().get())){
|
if (H.Read(connconn[uid]->conn->Received().get())){
|
||||||
break;//continue down below this while loop
|
break; //continue down below this while loop
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
//keep trying unless the timeout triggers
|
//keep trying unless the timeout triggers
|
||||||
|
@ -263,7 +276,7 @@ namespace Connector_HTTP{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!connconn.count(uid) || !connconn[uid]->conn->connected() || !conn->connected()){
|
if ( !connconn.count(uid) || !connconn[uid]->conn->connected() || !conn->connected()){
|
||||||
//failure, disconnect and sent error to user
|
//failure, disconnect and sent error to user
|
||||||
Handle_Timeout(H, conn);
|
Handle_Timeout(H, conn);
|
||||||
return;
|
return;
|
||||||
|
@ -310,13 +323,13 @@ namespace Connector_HTTP{
|
||||||
std::string getHTTPType(HTTP::Parser & H){
|
std::string getHTTPType(HTTP::Parser & H){
|
||||||
std::string url = H.getUrl();
|
std::string url = H.getUrl();
|
||||||
if ((url.find("f4m") != std::string::npos) || ((url.find("Seg") != std::string::npos) && (url.find("Frag") != std::string::npos))){
|
if ((url.find("f4m") != std::string::npos) || ((url.find("Seg") != std::string::npos) && (url.find("Frag") != std::string::npos))){
|
||||||
std::string streamname = url.substr(1,url.find("/",1)-1);
|
std::string streamname = url.substr(1, url.find("/", 1) - 1);
|
||||||
Util::Stream::sanitizeName(streamname);
|
Util::Stream::sanitizeName(streamname);
|
||||||
H.SetVar("stream", streamname);
|
H.SetVar("stream", streamname);
|
||||||
return "dynamic";
|
return "dynamic";
|
||||||
}
|
}
|
||||||
if (url.find("/smooth/") != std::string::npos && url.find(".ism") != std::string::npos ) {
|
if (url.find("/smooth/") != std::string::npos && url.find(".ism") != std::string::npos){
|
||||||
std::string streamname = url.substr(8,url.find("/",8)-12);
|
std::string streamname = url.substr(8, url.find("/", 8) - 12);
|
||||||
Util::Stream::sanitizeName(streamname);
|
Util::Stream::sanitizeName(streamname);
|
||||||
H.SetVar("stream", streamname);
|
H.SetVar("stream", streamname);
|
||||||
return "smooth";
|
return "smooth";
|
||||||
|
@ -324,28 +337,36 @@ namespace Connector_HTTP{
|
||||||
if (url.length() > 4){
|
if (url.length() > 4){
|
||||||
std::string ext = url.substr(url.length() - 4, 4);
|
std::string ext = url.substr(url.length() - 4, 4);
|
||||||
if (ext == ".flv" || ext == ".mp3"){
|
if (ext == ".flv" || ext == ".mp3"){
|
||||||
std::string streamname = url.substr(1,url.length() - 5);
|
std::string streamname = url.substr(1, url.length() - 5);
|
||||||
Util::Stream::sanitizeName(streamname);
|
Util::Stream::sanitizeName(streamname);
|
||||||
H.SetVar("stream", streamname);
|
H.SetVar("stream", streamname);
|
||||||
return "progressive";
|
return "progressive";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (url == "/crossdomain.xml"){return "internal";}
|
if (url == "/crossdomain.xml"){
|
||||||
if (url == "/clientaccesspolicy.xml"){return "internal";}
|
return "internal";
|
||||||
if (url.length() > 10 && url.substr(0, 7) == "/embed_" && url.substr(url.length() - 3, 3) == ".js"){return "internal";}
|
}
|
||||||
if (url.length() > 9 && url.substr(0, 6) == "/info_" && url.substr(url.length() - 3, 3) == ".js"){return "internal";}
|
if (url == "/clientaccesspolicy.xml"){
|
||||||
|
return "internal";
|
||||||
|
}
|
||||||
|
if (url.length() > 10 && url.substr(0, 7) == "/embed_" && url.substr(url.length() - 3, 3) == ".js"){
|
||||||
|
return "internal";
|
||||||
|
}
|
||||||
|
if (url.length() > 9 && url.substr(0, 6) == "/info_" && url.substr(url.length() - 3, 3) == ".js"){
|
||||||
|
return "internal";
|
||||||
|
}
|
||||||
return "none";
|
return "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Thread for handling a single HTTP connection
|
/// Thread for handling a single HTTP connection
|
||||||
void Handle_HTTP_Connection(void * pointer){
|
void Handle_HTTP_Connection(void * pointer){
|
||||||
Socket::Connection * conn = (Socket::Connection *)pointer;
|
Socket::Connection * conn = (Socket::Connection *)pointer;
|
||||||
conn->setBlocking(false);//do not block on conn.spool() when no data is available
|
conn->setBlocking(false); //do not block on conn.spool() when no data is available
|
||||||
HTTP::Parser Client;
|
HTTP::Parser Client;
|
||||||
while (conn->connected()){
|
while (conn->connected()){
|
||||||
if (conn->spool() || conn->Received().size()){
|
if (conn->spool() || conn->Received().size()){
|
||||||
//make sure it ends in a \n
|
//make sure it ends in a \n
|
||||||
if (*(conn->Received().get().rbegin()) != '\n'){
|
if ( *(conn->Received().get().rbegin()) != '\n'){
|
||||||
std::string tmp = conn->Received().get();
|
std::string tmp = conn->Received().get();
|
||||||
conn->Received().get().clear();
|
conn->Received().get().clear();
|
||||||
if (conn->Received().size()){
|
if (conn->Received().size()){
|
||||||
|
@ -357,9 +378,10 @@ namespace Connector_HTTP{
|
||||||
if (Client.Read(conn->Received().get())){
|
if (Client.Read(conn->Received().get())){
|
||||||
std::string handler = getHTTPType(Client);
|
std::string handler = getHTTPType(Client);
|
||||||
long long int startms = Util::getMS();
|
long long int startms = Util::getMS();
|
||||||
#if DEBUG >= 4
|
#if DEBUG >= 4
|
||||||
std::cout << "Received request: " << Client.getUrl() << " (" << conn->getSocket() << ") => " << handler << " (" << Client.GetVar("stream") << ")" << std::endl;
|
std::cout << "Received request: " << Client.getUrl() << " (" << conn->getSocket() << ") => " << handler << " (" << Client.GetVar("stream")
|
||||||
#endif
|
<< ")" << std::endl;
|
||||||
|
#endif
|
||||||
if (handler == "none" || handler == "internal"){
|
if (handler == "none" || handler == "internal"){
|
||||||
if (handler == "internal"){
|
if (handler == "internal"){
|
||||||
Handle_Internal(Client, conn);
|
Handle_Internal(Client, conn);
|
||||||
|
@ -369,13 +391,13 @@ namespace Connector_HTTP{
|
||||||
}else{
|
}else{
|
||||||
Handle_Through_Connector(Client, conn, handler);
|
Handle_Through_Connector(Client, conn, handler);
|
||||||
}
|
}
|
||||||
#if DEBUG >= 4
|
#if DEBUG >= 4
|
||||||
std::cout << "Completed request (" << conn->getSocket() << ") " << handler << " in " << (Util::getMS() - startms) << " ms" << std::endl;
|
std::cout << "Completed request (" << conn->getSocket() << ") " << handler << " in " << (Util::getMS() - startms) << " ms" << std::endl;
|
||||||
#endif
|
#endif
|
||||||
Client.Clean(); //clean for any possible next requests
|
Client.Clean(); //clean for any possible next requests
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
usleep(10000);//sleep 10ms
|
usleep(10000); //sleep 10ms
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//close and remove the connection
|
//close and remove the connection
|
||||||
|
@ -384,8 +406,8 @@ namespace Connector_HTTP{
|
||||||
//remove this thread from active_threads and add it to done_threads.
|
//remove this thread from active_threads and add it to done_threads.
|
||||||
thread_mutex.lock();
|
thread_mutex.lock();
|
||||||
for (std::set<tthread::thread *>::iterator it = active_threads.begin(); it != active_threads.end(); it++){
|
for (std::set<tthread::thread *>::iterator it = active_threads.begin(); it != active_threads.end(); it++){
|
||||||
if ((*it)->get_id() == tthread::this_thread::get_id()){
|
if (( *it)->get_id() == tthread::this_thread::get_id()){
|
||||||
tthread::thread * T = (*it);
|
tthread::thread * T = ( *it);
|
||||||
active_threads.erase(T);
|
active_threads.erase(T);
|
||||||
done_threads.insert(T);
|
done_threads.insert(T);
|
||||||
break;
|
break;
|
||||||
|
@ -394,26 +416,27 @@ namespace Connector_HTTP{
|
||||||
thread_mutex.unlock();
|
thread_mutex.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
};//Connector_HTTP namespace
|
} //Connector_HTTP namespace
|
||||||
|
|
||||||
|
|
||||||
int main(int argc, char ** argv){
|
int main(int argc, char ** argv){
|
||||||
Util::Config conf(argv[0], PACKAGE_VERSION);
|
Util::Config conf(argv[0], PACKAGE_VERSION);
|
||||||
conf.addConnectorOptions(8080);
|
conf.addConnectorOptions(8080);
|
||||||
conf.parseArgs(argc, argv);
|
conf.parseArgs(argc, argv);
|
||||||
Socket::Server server_socket = Socket::Server(conf.getInteger("listen_port"), conf.getString("listen_interface"));
|
Socket::Server server_socket = Socket::Server(conf.getInteger("listen_port"), conf.getString("listen_interface"));
|
||||||
if (!server_socket.connected()){return 1;}
|
if ( !server_socket.connected()){
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
conf.activate();
|
conf.activate();
|
||||||
|
|
||||||
while (server_socket.connected() && conf.is_active){
|
while (server_socket.connected() && conf.is_active){
|
||||||
Socket::Connection S = server_socket.accept();
|
Socket::Connection S = server_socket.accept();
|
||||||
if (S.connected()){//check if the new connection is valid
|
if (S.connected()){ //check if the new connection is valid
|
||||||
//lock the thread mutex and spawn a new thread for this connection
|
//lock the thread mutex and spawn a new thread for this connection
|
||||||
Connector_HTTP::thread_mutex.lock();
|
Connector_HTTP::thread_mutex.lock();
|
||||||
tthread::thread * T = new tthread::thread(Connector_HTTP::Handle_HTTP_Connection, (void *)(new Socket::Connection(S)));
|
tthread::thread * T = new tthread::thread(Connector_HTTP::Handle_HTTP_Connection, (void *)(new Socket::Connection(S)));
|
||||||
Connector_HTTP::active_threads.insert(T);
|
Connector_HTTP::active_threads.insert(T);
|
||||||
//clean up any threads that may have finished
|
//clean up any threads that may have finished
|
||||||
while (!Connector_HTTP::done_threads.empty()){
|
while ( !Connector_HTTP::done_threads.empty()){
|
||||||
T = *Connector_HTTP::done_threads.begin();
|
T = *Connector_HTTP::done_threads.begin();
|
||||||
T->join();
|
T->join();
|
||||||
Connector_HTTP::done_threads.erase(T);
|
Connector_HTTP::done_threads.erase(T);
|
||||||
|
@ -421,10 +444,28 @@ int main(int argc, char ** argv){
|
||||||
}
|
}
|
||||||
Connector_HTTP::thread_mutex.unlock();
|
Connector_HTTP::thread_mutex.unlock();
|
||||||
}else{
|
}else{
|
||||||
usleep(100000);//sleep 100ms
|
usleep(100000); //sleep 100ms
|
||||||
}
|
}
|
||||||
}//while connected and not requested to stop
|
} //while connected and not requested to stop
|
||||||
server_socket.close();
|
server_socket.close();
|
||||||
Util::Procs::StopAll();
|
|
||||||
|
//wait for existing connections to drop
|
||||||
|
bool repeat = true;
|
||||||
|
while (repeat){
|
||||||
|
Connector_HTTP::thread_mutex.lock();
|
||||||
|
repeat = !Connector_HTTP::active_threads.empty();
|
||||||
|
if (repeat){
|
||||||
|
usleep(100000); //sleep 100ms
|
||||||
|
}
|
||||||
|
//clean up any threads that may have finished
|
||||||
|
while ( !Connector_HTTP::done_threads.empty()){
|
||||||
|
tthread::thread * T = *Connector_HTTP::done_threads.begin();
|
||||||
|
T->join();
|
||||||
|
Connector_HTTP::done_threads.erase(T);
|
||||||
|
delete T;
|
||||||
|
}
|
||||||
|
Connector_HTTP::thread_mutex.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}//main
|
} //main
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
#include <mist/timing.h>
|
#include <mist/timing.h>
|
||||||
|
|
||||||
/// Holds everything unique to HTTP Dynamic Connector.
|
/// Holds everything unique to HTTP Dynamic Connector.
|
||||||
namespace Connector_HTTP{
|
namespace Connector_HTTP {
|
||||||
|
|
||||||
std::string GenerateBootstrap(std::string & MovieId, JSON::Value & metadata, int fragnum, int starttime, int endtime){
|
std::string GenerateBootstrap(std::string & MovieId, JSON::Value & metadata, int fragnum, int starttime, int endtime){
|
||||||
std::string empty;
|
std::string empty;
|
||||||
|
@ -37,13 +37,12 @@ namespace Connector_HTTP{
|
||||||
}
|
}
|
||||||
asrt.setVersion(1);
|
asrt.setVersion(1);
|
||||||
asrt.setQualityEntry(empty, 0);
|
asrt.setQualityEntry(empty, 0);
|
||||||
if (!metadata.isMember("keytime") || metadata["keytime"].size() == 0){
|
if ( !metadata.isMember("keytime") || metadata["keytime"].size() == 0){
|
||||||
asrt.setSegmentRun(1, 20000, 0);
|
asrt.setSegmentRun(1, 20000, 0);
|
||||||
}else{
|
}else{
|
||||||
asrt.setSegmentRun(1, metadata["keytime"].size(), 0);
|
asrt.setSegmentRun(1, metadata["keytime"].size(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
MP4::AFRT afrt;
|
MP4::AFRT afrt;
|
||||||
if (starttime == 0){
|
if (starttime == 0){
|
||||||
afrt.setUpdate(false);
|
afrt.setUpdate(false);
|
||||||
|
@ -54,10 +53,10 @@ namespace Connector_HTTP{
|
||||||
afrt.setTimeScale(1000);
|
afrt.setTimeScale(1000);
|
||||||
afrt.setQualityEntry(empty, 0);
|
afrt.setQualityEntry(empty, 0);
|
||||||
MP4::afrt_runtable afrtrun;
|
MP4::afrt_runtable afrtrun;
|
||||||
if (!metadata.isMember("keytime") || metadata["keytime"].size() == 0){
|
if ( !metadata.isMember("keytime") || metadata["keytime"].size() == 0){
|
||||||
afrtrun.firstFragment = 1;
|
afrtrun.firstFragment = 1;
|
||||||
afrtrun.firstTimestamp = 0;
|
afrtrun.firstTimestamp = 0;
|
||||||
if (!metadata.isMember("video") || !metadata["video"].isMember("keyms") || metadata["video"]["keyms"].asInt() == 0){
|
if ( !metadata.isMember("video") || !metadata["video"].isMember("keyms") || metadata["video"]["keyms"].asInt() == 0){
|
||||||
afrtrun.duration = 2000;
|
afrtrun.duration = 2000;
|
||||||
}else{
|
}else{
|
||||||
afrtrun.duration = metadata["video"]["keyms"].asInt();
|
afrtrun.duration = metadata["video"]["keyms"].asInt();
|
||||||
|
@ -65,15 +64,15 @@ namespace Connector_HTTP{
|
||||||
afrt.setFragmentRun(afrtrun, 0);
|
afrt.setFragmentRun(afrtrun, 0);
|
||||||
}else{
|
}else{
|
||||||
for (int i = 0; i < metadata["keytime"].size(); i++){
|
for (int i = 0; i < metadata["keytime"].size(); i++){
|
||||||
afrtrun.firstFragment = i+1;
|
afrtrun.firstFragment = i + 1;
|
||||||
afrtrun.firstTimestamp = metadata["keytime"][i].asInt();
|
afrtrun.firstTimestamp = metadata["keytime"][i].asInt();
|
||||||
if (i+1 < metadata["keytime"].size()){
|
if (i + 1 < metadata["keytime"].size()){
|
||||||
afrtrun.duration = metadata["keytime"][i+1].asInt() - metadata["keytime"][i].asInt();
|
afrtrun.duration = metadata["keytime"][i + 1].asInt() - metadata["keytime"][i].asInt();
|
||||||
}else{
|
}else{
|
||||||
if (metadata["lastms"].asInt()){
|
if (metadata["lastms"].asInt()){
|
||||||
afrtrun.duration = metadata["lastms"].asInt() - metadata["keytime"][i].asInt();
|
afrtrun.duration = metadata["lastms"].asInt() - metadata["keytime"][i].asInt();
|
||||||
}else{
|
}else{
|
||||||
afrtrun.duration = 3000;//guess 3 seconds if unknown
|
afrtrun.duration = 3000; //guess 3 seconds if unknown
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
afrt.setFragmentRun(afrtrun, i);
|
afrt.setFragmentRun(afrtrun, i);
|
||||||
|
@ -95,7 +94,7 @@ namespace Connector_HTTP{
|
||||||
if (metadata["lastms"].asInt()){
|
if (metadata["lastms"].asInt()){
|
||||||
abst.setCurrentMediaTime(metadata["lastms"].asInt());
|
abst.setCurrentMediaTime(metadata["lastms"].asInt());
|
||||||
}else{
|
}else{
|
||||||
abst.setCurrentMediaTime(1000*metadata["length"].asInt());
|
abst.setCurrentMediaTime(1000 * metadata["length"].asInt());
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
abst.setLive(true);
|
abst.setLive(true);
|
||||||
|
@ -110,18 +109,18 @@ namespace Connector_HTTP{
|
||||||
abst.setSegmentRunTable(asrt, 0);
|
abst.setSegmentRunTable(asrt, 0);
|
||||||
abst.setFragmentRunTable(afrt, 0);
|
abst.setFragmentRunTable(afrt, 0);
|
||||||
|
|
||||||
#if DEBUG >= 8
|
#if DEBUG >= 8
|
||||||
std::cout << "Sending bootstrap:" << std::endl << abst.toPrettyString(0) << std::endl;
|
std::cout << "Sending bootstrap:" << std::endl << abst.toPrettyString(0) << std::endl;
|
||||||
#endif
|
#endif
|
||||||
return std::string((char*)abst.asBox(), (int)abst.boxedSize());
|
return std::string((char*)abst.asBox(), (int)abst.boxedSize());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Returns a F4M-format manifest file
|
/// Returns a F4M-format manifest file
|
||||||
std::string BuildManifest(std::string & MovieId, JSON::Value & metadata){
|
std::string BuildManifest(std::string & MovieId, JSON::Value & metadata){
|
||||||
std::string Result;
|
std::string Result;
|
||||||
if (metadata.isMember("length") && metadata["length"].asInt() > 0){
|
if (metadata.isMember("length") && metadata["length"].asInt() > 0){
|
||||||
Result="<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
|
Result =
|
||||||
|
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
|
||||||
"<manifest xmlns=\"http://ns.adobe.com/f4m/1.0\">\n"
|
"<manifest xmlns=\"http://ns.adobe.com/f4m/1.0\">\n"
|
||||||
"<id>" + MovieId + "</id>\n"
|
"<id>" + MovieId + "</id>\n"
|
||||||
"<width>" + metadata["video"]["width"].asString() + "</width>\n"
|
"<width>" + metadata["video"]["width"].asString() + "</width>\n"
|
||||||
|
@ -130,13 +129,15 @@ namespace Connector_HTTP{
|
||||||
"<mimeType>video/mp4</mimeType>\n"
|
"<mimeType>video/mp4</mimeType>\n"
|
||||||
"<streamType>recorded</streamType>\n"
|
"<streamType>recorded</streamType>\n"
|
||||||
"<deliveryType>streaming</deliveryType>\n"
|
"<deliveryType>streaming</deliveryType>\n"
|
||||||
"<bootstrapInfo profile=\"named\" id=\"bootstrap1\">" + Base64::encode(GenerateBootstrap(MovieId, metadata, 1, 0, 0)) + "</bootstrapInfo>\n"
|
"<bootstrapInfo profile=\"named\" id=\"bootstrap1\">" + Base64::encode(GenerateBootstrap(MovieId, metadata, 1, 0, 0))
|
||||||
"<media streamId=\"1\" bootstrapInfoId=\"bootstrap1\" url=\"" + MovieId + "/\">\n"
|
+ "</bootstrapInfo>\n"
|
||||||
|
"<media streamId=\"1\" bootstrapInfoId=\"bootstrap1\" url=\"" + MovieId
|
||||||
|
+ "/\">\n"
|
||||||
"<metadata>AgAKb25NZXRhRGF0YQgAAAAAAAl0cmFja2luZm8KAAAAAgMACXRpbWVzY2FsZQBA+GoAAAAAAAAGbGVuZ3RoAEGMcHoQAAAAAAhsYW5ndWFnZQIAA2VuZwARc2FtcGxlZGVzY3JpcHRpb24KAAAAAQMACnNhbXBsZXR5cGUCAARhdmMxAAAJAAAJAwAJdGltZXNjYWxlAEDncAAAAAAAAAZsZW5ndGgAQXtNvTAAAAAACGxhbmd1YWdlAgADZW5nABFzYW1wbGVkZXNjcmlwdGlvbgoAAAABAwAKc2FtcGxldHlwZQIABG1wNGEAAAkAAAkADWF1ZGlvY2hhbm5lbHMAQAAAAAAAAAAAD2F1ZGlvc2FtcGxlcmF0ZQBA53AAAAAAAAAOdmlkZW9mcmFtZXJhdGUAQDf/gi5SciUABmFhY2FvdABAAAAAAAAAAAAIYXZjbGV2ZWwAQD8AAAAAAAAACmF2Y3Byb2ZpbGUAQFNAAAAAAAAADGF1ZGlvY29kZWNpZAIABG1wNGEADHZpZGVvY29kZWNpZAIABGF2YzEABXdpZHRoAECQ4AAAAAAAAAZoZWlnaHQAQIMAAAAAAAAACmZyYW1lV2lkdGgAQJDgAAAAAAAAC2ZyYW1lSGVpZ2h0AECDAAAAAAAAAAxkaXNwbGF5V2lkdGgAQJDgAAAAAAAADWRpc3BsYXlIZWlnaHQAQIMAAAAAAAAADG1vb3Zwb3NpdGlvbgBBmxq2uAAAAAAIZHVyYXRpb24AQIKjqW3oyhIAAAk=</metadata>\n"
|
"<metadata>AgAKb25NZXRhRGF0YQgAAAAAAAl0cmFja2luZm8KAAAAAgMACXRpbWVzY2FsZQBA+GoAAAAAAAAGbGVuZ3RoAEGMcHoQAAAAAAhsYW5ndWFnZQIAA2VuZwARc2FtcGxlZGVzY3JpcHRpb24KAAAAAQMACnNhbXBsZXR5cGUCAARhdmMxAAAJAAAJAwAJdGltZXNjYWxlAEDncAAAAAAAAAZsZW5ndGgAQXtNvTAAAAAACGxhbmd1YWdlAgADZW5nABFzYW1wbGVkZXNjcmlwdGlvbgoAAAABAwAKc2FtcGxldHlwZQIABG1wNGEAAAkAAAkADWF1ZGlvY2hhbm5lbHMAQAAAAAAAAAAAD2F1ZGlvc2FtcGxlcmF0ZQBA53AAAAAAAAAOdmlkZW9mcmFtZXJhdGUAQDf/gi5SciUABmFhY2FvdABAAAAAAAAAAAAIYXZjbGV2ZWwAQD8AAAAAAAAACmF2Y3Byb2ZpbGUAQFNAAAAAAAAADGF1ZGlvY29kZWNpZAIABG1wNGEADHZpZGVvY29kZWNpZAIABGF2YzEABXdpZHRoAECQ4AAAAAAAAAZoZWlnaHQAQIMAAAAAAAAACmZyYW1lV2lkdGgAQJDgAAAAAAAAC2ZyYW1lSGVpZ2h0AECDAAAAAAAAAAxkaXNwbGF5V2lkdGgAQJDgAAAAAAAADWRpc3BsYXlIZWlnaHQAQIMAAAAAAAAADG1vb3Zwb3NpdGlvbgBBmxq2uAAAAAAIZHVyYXRpb24AQIKjqW3oyhIAAAk=</metadata>\n"
|
||||||
"</media>\n"
|
"</media>\n"
|
||||||
"</manifest>\n";
|
"</manifest>\n";
|
||||||
}else{
|
}else{
|
||||||
Result="<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
|
Result = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
|
||||||
"<manifest xmlns=\"http://ns.adobe.com/f4m/1.0\">\n"
|
"<manifest xmlns=\"http://ns.adobe.com/f4m/1.0\">\n"
|
||||||
"<id>" + MovieId + "</id>\n"
|
"<id>" + MovieId + "</id>\n"
|
||||||
"<mimeType>video/mp4</mimeType>\n"
|
"<mimeType>video/mp4</mimeType>\n"
|
||||||
|
@ -146,27 +147,27 @@ namespace Connector_HTTP{
|
||||||
"<media streamId=\"1\" bootstrapInfoId=\"bootstrap1\" url=\"" + MovieId + "/\"></media>\n"
|
"<media streamId=\"1\" bootstrapInfoId=\"bootstrap1\" url=\"" + MovieId + "/\"></media>\n"
|
||||||
"</manifest>\n";
|
"</manifest>\n";
|
||||||
}
|
}
|
||||||
#if DEBUG >= 8
|
#if DEBUG >= 8
|
||||||
std::cerr << "Sending this manifest:" << std::endl << Result << std::endl;
|
std::cerr << "Sending this manifest:" << std::endl << Result << std::endl;
|
||||||
#endif
|
#endif
|
||||||
return Result;
|
return Result;
|
||||||
}//BuildManifest
|
} //BuildManifest
|
||||||
|
|
||||||
/// Main function for Connector_HTTP_Dynamic
|
/// Main function for Connector_HTTP_Dynamic
|
||||||
int Connector_HTTP_Dynamic(Socket::Connection conn){
|
int Connector_HTTP_Dynamic(Socket::Connection conn){
|
||||||
std::deque<std::string> FlashBuf;
|
std::deque<std::string> FlashBuf;
|
||||||
int FlashBufSize = 0;
|
int FlashBufSize = 0;
|
||||||
long long int FlashBufTime = 0;
|
long long int FlashBufTime = 0;
|
||||||
FLV::Tag tmp;//temporary tag
|
FLV::Tag tmp; //temporary tag
|
||||||
|
|
||||||
DTSC::Stream Strm;//Incoming stream buffer.
|
DTSC::Stream Strm; //Incoming stream buffer.
|
||||||
HTTP::Parser HTTP_R, HTTP_S;//HTTP Receiver en HTTP Sender.
|
HTTP::Parser HTTP_R, HTTP_S; //HTTP Receiver en HTTP Sender.
|
||||||
|
|
||||||
bool ready4data = false;//Set to true when streaming is to begin.
|
bool ready4data = false; //Set to true when streaming is to begin.
|
||||||
bool pending_manifest = false;
|
bool pending_manifest = false;
|
||||||
bool receive_marks = false;//when set to true, this stream will ignore keyframes and instead use pause marks
|
bool receive_marks = false; //when set to true, this stream will ignore keyframes and instead use pause marks
|
||||||
bool inited = false;
|
bool inited = false;
|
||||||
Socket::Connection ss(-1);
|
Socket::Connection ss( -1);
|
||||||
std::string streamname;
|
std::string streamname;
|
||||||
std::string recBuffer = "";
|
std::string recBuffer = "";
|
||||||
|
|
||||||
|
@ -176,12 +177,12 @@ namespace Connector_HTTP{
|
||||||
int temp;
|
int temp;
|
||||||
int Flash_RequestPending = 0;
|
int Flash_RequestPending = 0;
|
||||||
unsigned int lastStats = 0;
|
unsigned int lastStats = 0;
|
||||||
conn.setBlocking(false);//do not block on conn.spool() when no data is available
|
conn.setBlocking(false); //do not block on conn.spool() when no data is available
|
||||||
|
|
||||||
while (conn.connected()){
|
while (conn.connected()){
|
||||||
if (conn.spool() || conn.Received().size()){
|
if (conn.spool() || conn.Received().size()){
|
||||||
//make sure it ends in a \n
|
//make sure it ends in a \n
|
||||||
if (*(conn.Received().get().rbegin()) != '\n'){
|
if ( *(conn.Received().get().rbegin()) != '\n'){
|
||||||
std::string tmp = conn.Received().get();
|
std::string tmp = conn.Received().get();
|
||||||
conn.Received().get().clear();
|
conn.Received().get().clear();
|
||||||
if (conn.Received().size()){
|
if (conn.Received().size()){
|
||||||
|
@ -191,18 +192,18 @@ namespace Connector_HTTP{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (HTTP_R.Read(conn.Received().get())){
|
if (HTTP_R.Read(conn.Received().get())){
|
||||||
#if DEBUG >= 4
|
#if DEBUG >= 4
|
||||||
std::cout << "Received request: " << HTTP_R.getUrl() << std::endl;
|
std::cout << "Received request: " << HTTP_R.getUrl() << std::endl;
|
||||||
#endif
|
#endif
|
||||||
conn.setHost(HTTP_R.GetHeader("X-Origin"));
|
conn.setHost(HTTP_R.GetHeader("X-Origin"));
|
||||||
if (HTTP_R.url.find("f4m") == std::string::npos){
|
if (HTTP_R.url.find("f4m") == std::string::npos){
|
||||||
streamname = HTTP_R.url.substr(1,HTTP_R.url.find("/",1)-1);
|
streamname = HTTP_R.url.substr(1, HTTP_R.url.find("/", 1) - 1);
|
||||||
if (!ss){
|
if ( !ss){
|
||||||
ss = Util::Stream::getStream(streamname);
|
ss = Util::Stream::getStream(streamname);
|
||||||
if (!ss.connected()){
|
if ( !ss.connected()){
|
||||||
#if DEBUG >= 1
|
#if DEBUG >= 1
|
||||||
fprintf(stderr, "Could not connect to server!\n");
|
fprintf(stderr, "Could not connect to server!\n");
|
||||||
#endif
|
#endif
|
||||||
ss.close();
|
ss.close();
|
||||||
HTTP_S.Clean();
|
HTTP_S.Clean();
|
||||||
HTTP_S.SetBody("No such stream is available on the system. Please try again.\n");
|
HTTP_S.SetBody("No such stream is available on the system. Please try again.\n");
|
||||||
|
@ -213,32 +214,34 @@ namespace Connector_HTTP{
|
||||||
ss.setBlocking(false);
|
ss.setBlocking(false);
|
||||||
inited = true;
|
inited = true;
|
||||||
}
|
}
|
||||||
Quality = HTTP_R.url.substr( HTTP_R.url.find("/",1)+1 );
|
Quality = HTTP_R.url.substr(HTTP_R.url.find("/", 1) + 1);
|
||||||
Quality = Quality.substr(0, Quality.find("Seg"));
|
Quality = Quality.substr(0, Quality.find("Seg"));
|
||||||
temp = HTTP_R.url.find("Seg") + 3;
|
temp = HTTP_R.url.find("Seg") + 3;
|
||||||
Segment = atoi( HTTP_R.url.substr(temp,HTTP_R.url.find("-",temp)-temp).c_str());
|
Segment = atoi(HTTP_R.url.substr(temp, HTTP_R.url.find("-", temp) - temp).c_str());
|
||||||
temp = HTTP_R.url.find("Frag") + 4;
|
temp = HTTP_R.url.find("Frag") + 4;
|
||||||
ReqFragment = atoi( HTTP_R.url.substr(temp).c_str() );
|
ReqFragment = atoi(HTTP_R.url.substr(temp).c_str());
|
||||||
#if DEBUG >= 4
|
#if DEBUG >= 4
|
||||||
printf( "Quality: %s, Seg %d Frag %d\n", Quality.c_str(), Segment, ReqFragment);
|
printf("Quality: %s, Seg %d Frag %d\n", Quality.c_str(), Segment, ReqFragment);
|
||||||
#endif
|
#endif
|
||||||
std::stringstream sstream;
|
std::stringstream sstream;
|
||||||
sstream << "f " << ReqFragment << "\no \n";
|
sstream << "f " << ReqFragment << "\no \n";
|
||||||
ss.SendNow(sstream.str().c_str());
|
ss.SendNow(sstream.str().c_str());
|
||||||
Flash_RequestPending++;
|
Flash_RequestPending++;
|
||||||
}else{
|
}else{
|
||||||
streamname = HTTP_R.url.substr(1,HTTP_R.url.find("/",1)-1);
|
streamname = HTTP_R.url.substr(1, HTTP_R.url.find("/", 1) - 1);
|
||||||
if (!Strm.metadata.isNull()){
|
if ( !Strm.metadata.isNull()){
|
||||||
HTTP_S.Clean();
|
HTTP_S.Clean();
|
||||||
HTTP_S.SetHeader("Content-Type","text/xml");
|
HTTP_S.SetHeader("Content-Type", "text/xml");
|
||||||
HTTP_S.SetHeader("Cache-Control","no-cache");
|
HTTP_S.SetHeader("Cache-Control", "no-cache");
|
||||||
if (Strm.metadata.isMember("length")){receive_marks = true;}
|
if (Strm.metadata.isMember("length")){
|
||||||
|
receive_marks = true;
|
||||||
|
}
|
||||||
std::string manifest = BuildManifest(streamname, Strm.metadata);
|
std::string manifest = BuildManifest(streamname, Strm.metadata);
|
||||||
HTTP_S.SetBody(manifest);
|
HTTP_S.SetBody(manifest);
|
||||||
conn.SendNow(HTTP_S.BuildResponse("200", "OK"));
|
conn.SendNow(HTTP_S.BuildResponse("200", "OK"));
|
||||||
#if DEBUG >= 3
|
#if DEBUG >= 3
|
||||||
printf("Sent manifest\n");
|
printf("Sent manifest\n");
|
||||||
#endif
|
#endif
|
||||||
pending_manifest = false;
|
pending_manifest = false;
|
||||||
}else{
|
}else{
|
||||||
pending_manifest = true;
|
pending_manifest = true;
|
||||||
|
@ -249,19 +252,19 @@ namespace Connector_HTTP{
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
if (Flash_RequestPending){
|
if (Flash_RequestPending){
|
||||||
usleep(1000);//sleep 1ms
|
usleep(1000); //sleep 1ms
|
||||||
}else{
|
}else{
|
||||||
usleep(10000);//sleep 10ms
|
usleep(10000); //sleep 10ms
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ready4data){
|
if (ready4data){
|
||||||
if (!inited){
|
if ( !inited){
|
||||||
//we are ready, connect the socket!
|
//we are ready, connect the socket!
|
||||||
ss = Util::Stream::getStream(streamname);
|
ss = Util::Stream::getStream(streamname);
|
||||||
if (!ss.connected()){
|
if ( !ss.connected()){
|
||||||
#if DEBUG >= 1
|
#if DEBUG >= 1
|
||||||
fprintf(stderr, "Could not connect to server!\n");
|
fprintf(stderr, "Could not connect to server!\n");
|
||||||
#endif
|
#endif
|
||||||
ss.close();
|
ss.close();
|
||||||
HTTP_S.Clean();
|
HTTP_S.Clean();
|
||||||
HTTP_S.SetBody("No such stream is available on the system. Please try again.\n");
|
HTTP_S.SetBody("No such stream is available on the system. Please try again.\n");
|
||||||
|
@ -270,9 +273,9 @@ namespace Connector_HTTP{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
ss.setBlocking(false);
|
ss.setBlocking(false);
|
||||||
#if DEBUG >= 3
|
#if DEBUG >= 3
|
||||||
fprintf(stderr, "Everything connected, starting to send video data...\n");
|
fprintf(stderr, "Everything connected, starting to send video data...\n");
|
||||||
#endif
|
#endif
|
||||||
inited = true;
|
inited = true;
|
||||||
}
|
}
|
||||||
unsigned int now = Util::epoch();
|
unsigned int now = Util::epoch();
|
||||||
|
@ -283,10 +286,10 @@ namespace Connector_HTTP{
|
||||||
if (ss.spool()){
|
if (ss.spool()){
|
||||||
while (Strm.parsePacket(ss.Received())){
|
while (Strm.parsePacket(ss.Received())){
|
||||||
if (Strm.getPacket(0).isMember("time")){
|
if (Strm.getPacket(0).isMember("time")){
|
||||||
if (!Strm.metadata.isMember("firsttime")){
|
if ( !Strm.metadata.isMember("firsttime")){
|
||||||
Strm.metadata["firsttime"] = Strm.getPacket(0)["time"];
|
Strm.metadata["firsttime"] = Strm.getPacket(0)["time"];
|
||||||
}else{
|
}else{
|
||||||
if (!Strm.metadata.isMember("length") || Strm.metadata["length"].asInt() == 0){
|
if ( !Strm.metadata.isMember("length") || Strm.metadata["length"].asInt() == 0){
|
||||||
Strm.getPacket(0)["time"] = Strm.getPacket(0)["time"].asInt() - Strm.metadata["firsttime"].asInt();
|
Strm.getPacket(0)["time"] = Strm.getPacket(0)["time"].asInt() - Strm.metadata["firsttime"].asInt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -294,32 +297,36 @@ namespace Connector_HTTP{
|
||||||
}
|
}
|
||||||
if (pending_manifest){
|
if (pending_manifest){
|
||||||
HTTP_S.Clean();
|
HTTP_S.Clean();
|
||||||
HTTP_S.SetHeader("Content-Type","text/xml");
|
HTTP_S.SetHeader("Content-Type", "text/xml");
|
||||||
HTTP_S.SetHeader("Cache-Control","no-cache");
|
HTTP_S.SetHeader("Cache-Control", "no-cache");
|
||||||
if (Strm.metadata.isMember("length")){receive_marks = true;}
|
if (Strm.metadata.isMember("length")){
|
||||||
|
receive_marks = true;
|
||||||
|
}
|
||||||
std::string manifest = BuildManifest(streamname, Strm.metadata);
|
std::string manifest = BuildManifest(streamname, Strm.metadata);
|
||||||
HTTP_S.SetBody(manifest);
|
HTTP_S.SetBody(manifest);
|
||||||
conn.SendNow(HTTP_S.BuildResponse("200", "OK"));
|
conn.SendNow(HTTP_S.BuildResponse("200", "OK"));
|
||||||
#if DEBUG >= 3
|
#if DEBUG >= 3
|
||||||
printf("Sent manifest\n");
|
printf("Sent manifest\n");
|
||||||
#endif
|
#endif
|
||||||
pending_manifest = false;
|
pending_manifest = false;
|
||||||
}
|
}
|
||||||
if (!receive_marks && Strm.metadata.isMember("length")){receive_marks = true;}
|
if ( !receive_marks && Strm.metadata.isMember("length")){
|
||||||
|
receive_marks = true;
|
||||||
|
}
|
||||||
if ((Strm.getPacket(0).isMember("keyframe") && !receive_marks) || Strm.lastType() == DTSC::PAUSEMARK){
|
if ((Strm.getPacket(0).isMember("keyframe") && !receive_marks) || Strm.lastType() == DTSC::PAUSEMARK){
|
||||||
#if DEBUG >= 4
|
#if DEBUG >= 4
|
||||||
fprintf(stderr, "Received a %s fragment of %i bytes.\n", Strm.getPacket(0)["datatype"].asString().c_str(), FlashBufSize);
|
fprintf(stderr, "Received a %s fragment of %i bytes.\n", Strm.getPacket(0)["datatype"].asString().c_str(), FlashBufSize);
|
||||||
#endif
|
#endif
|
||||||
if (Flash_RequestPending > 0 && FlashBufSize){
|
if (Flash_RequestPending > 0 && FlashBufSize){
|
||||||
#if DEBUG >= 3
|
#if DEBUG >= 3
|
||||||
fprintf(stderr, "Sending a fragment...");
|
fprintf(stderr, "Sending a fragment...");
|
||||||
#endif
|
#endif
|
||||||
//static std::string btstrp;
|
//static std::string btstrp;
|
||||||
//btstrp = GenerateBootstrap(streamname, Strm.metadata, ReqFragment, FlashBufTime, Strm.getPacket(0)["time"]);
|
//btstrp = GenerateBootstrap(streamname, Strm.metadata, ReqFragment, FlashBufTime, Strm.getPacket(0)["time"]);
|
||||||
HTTP_S.Clean();
|
HTTP_S.Clean();
|
||||||
HTTP_S.SetHeader("Content-Type", "video/mp4");
|
HTTP_S.SetHeader("Content-Type", "video/mp4");
|
||||||
HTTP_S.SetBody("");
|
HTTP_S.SetBody("");
|
||||||
HTTP_S.SetHeader("Content-Length", FlashBufSize+8);//32+33+btstrp.size());
|
HTTP_S.SetHeader("Content-Length", FlashBufSize + 8); //32+33+btstrp.size());
|
||||||
conn.SendNow(HTTP_S.BuildResponse("200", "OK"));
|
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);
|
//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);
|
//unsigned long tmptime = htonl(FlashBufTime << 32);
|
||||||
|
@ -335,16 +342,16 @@ namespace Connector_HTTP{
|
||||||
//unsigned long fragno = htonl(ReqFragment);
|
//unsigned long fragno = htonl(ReqFragment);
|
||||||
//conn.SendNow((char*)&fragno, 4);
|
//conn.SendNow((char*)&fragno, 4);
|
||||||
unsigned long size = htonl(FlashBufSize+8);
|
unsigned long size = htonl(FlashBufSize+8);
|
||||||
conn.SendNow((char*)&size, 4);
|
conn.SendNow((char*) &size, 4);
|
||||||
conn.SendNow("mdat", 4);
|
conn.SendNow("mdat", 4);
|
||||||
while (FlashBuf.size() > 0){
|
while (FlashBuf.size() > 0){
|
||||||
conn.SendNow(FlashBuf.front());
|
conn.SendNow(FlashBuf.front());
|
||||||
FlashBuf.pop_front();
|
FlashBuf.pop_front();
|
||||||
}
|
}
|
||||||
Flash_RequestPending--;
|
Flash_RequestPending--;
|
||||||
#if DEBUG >= 3
|
#if DEBUG >= 3
|
||||||
fprintf(stderr, "Done\n");
|
fprintf(stderr, "Done\n");
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
FlashBuf.clear();
|
FlashBuf.clear();
|
||||||
FlashBufSize = 0;
|
FlashBufSize = 0;
|
||||||
|
@ -373,26 +380,32 @@ namespace Connector_HTTP{
|
||||||
}
|
}
|
||||||
if (pending_manifest && !Strm.metadata.isNull()){
|
if (pending_manifest && !Strm.metadata.isNull()){
|
||||||
HTTP_S.Clean();
|
HTTP_S.Clean();
|
||||||
HTTP_S.SetHeader("Content-Type","text/xml");
|
HTTP_S.SetHeader("Content-Type", "text/xml");
|
||||||
HTTP_S.SetHeader("Cache-Control","no-cache");
|
HTTP_S.SetHeader("Cache-Control", "no-cache");
|
||||||
if (Strm.metadata.isMember("length")){receive_marks = true;}
|
if (Strm.metadata.isMember("length")){
|
||||||
|
receive_marks = true;
|
||||||
|
}
|
||||||
std::string manifest = BuildManifest(streamname, Strm.metadata);
|
std::string manifest = BuildManifest(streamname, Strm.metadata);
|
||||||
HTTP_S.SetBody(manifest);
|
HTTP_S.SetBody(manifest);
|
||||||
conn.SendNow(HTTP_S.BuildResponse("200", "OK"));
|
conn.SendNow(HTTP_S.BuildResponse("200", "OK"));
|
||||||
#if DEBUG >= 3
|
#if DEBUG >= 3
|
||||||
printf("Sent manifest\n");
|
printf("Sent manifest\n");
|
||||||
#endif
|
#endif
|
||||||
pending_manifest = false;
|
pending_manifest = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!ss.connected()){break;}
|
if ( !ss.connected()){
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
conn.close();
|
conn.close();
|
||||||
ss.SendNow(conn.getStats("HTTP_Dynamic").c_str());
|
ss.SendNow(conn.getStats("HTTP_Dynamic").c_str());
|
||||||
ss.close();
|
ss.close();
|
||||||
#if DEBUG >= 1
|
#if DEBUG >= 1
|
||||||
if (FLV::Parse_Error){fprintf(stderr, "FLV Parser Error: %s\n", FLV::Error_Str.c_str());}
|
if (FLV::Parse_Error){
|
||||||
|
fprintf(stderr, "FLV Parser Error: %s\n", FLV::Error_Str.c_str());
|
||||||
|
}
|
||||||
fprintf(stderr, "User %i disconnected.\n", conn.getSocket());
|
fprintf(stderr, "User %i disconnected.\n", conn.getSocket());
|
||||||
if (inited){
|
if (inited){
|
||||||
fprintf(stderr, "Status was: inited\n");
|
fprintf(stderr, "Status was: inited\n");
|
||||||
|
@ -403,33 +416,35 @@ namespace Connector_HTTP{
|
||||||
fprintf(stderr, "Status was: connected\n");
|
fprintf(stderr, "Status was: connected\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
return 0;
|
return 0;
|
||||||
}//Connector_HTTP_Dynamic main function
|
} //Connector_HTTP_Dynamic main function
|
||||||
|
|
||||||
};//Connector_HTTP_Dynamic namespace
|
} //Connector_HTTP_Dynamic namespace
|
||||||
|
|
||||||
int main(int argc, char ** argv){
|
int main(int argc, char ** argv){
|
||||||
Util::Config conf(argv[0], PACKAGE_VERSION);
|
Util::Config conf(argv[0], PACKAGE_VERSION);
|
||||||
conf.addConnectorOptions(1935);
|
conf.addConnectorOptions(1935);
|
||||||
conf.parseArgs(argc, argv);
|
conf.parseArgs(argc, argv);
|
||||||
Socket::Server server_socket = Socket::Server("/tmp/mist/http_dynamic");
|
Socket::Server server_socket = Socket::Server("/tmp/mist/http_dynamic");
|
||||||
if (!server_socket.connected()){return 1;}
|
if ( !server_socket.connected()){
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
conf.activate();
|
conf.activate();
|
||||||
|
|
||||||
while (server_socket.connected() && conf.is_active){
|
while (server_socket.connected() && conf.is_active){
|
||||||
Socket::Connection S = server_socket.accept();
|
Socket::Connection S = server_socket.accept();
|
||||||
if (S.connected()){//check if the new connection is valid
|
if (S.connected()){ //check if the new connection is valid
|
||||||
pid_t myid = fork();
|
pid_t myid = fork();
|
||||||
if (myid == 0){//if new child, start MAINHANDLER
|
if (myid == 0){ //if new child, start MAINHANDLER
|
||||||
return Connector_HTTP::Connector_HTTP_Dynamic(S);
|
return Connector_HTTP::Connector_HTTP_Dynamic(S);
|
||||||
}else{//otherwise, do nothing or output debugging text
|
}else{ //otherwise, do nothing or output debugging text
|
||||||
#if DEBUG >= 3
|
#if DEBUG >= 3
|
||||||
fprintf(stderr, "Spawned new process %i for socket %i\n", (int)myid, S.getSocket());
|
fprintf(stderr, "Spawned new process %i for socket %i\n", (int)myid, S.getSocket());
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}//while connected
|
} //while connected
|
||||||
server_socket.close();
|
server_socket.close();
|
||||||
return 0;
|
return 0;
|
||||||
}//main
|
} //main
|
||||||
|
|
|
@ -21,29 +21,29 @@
|
||||||
#include <mist/timing.h>
|
#include <mist/timing.h>
|
||||||
|
|
||||||
/// Holds everything unique to HTTP Progressive Connector.
|
/// Holds everything unique to HTTP Progressive Connector.
|
||||||
namespace Connector_HTTP{
|
namespace Connector_HTTP {
|
||||||
|
|
||||||
/// Main function for Connector_HTTP_Progressive
|
/// Main function for Connector_HTTP_Progressive
|
||||||
int Connector_HTTP_Progressive(Socket::Connection conn){
|
int Connector_HTTP_Progressive(Socket::Connection conn){
|
||||||
bool progressive_has_sent_header = false;
|
bool progressive_has_sent_header = false;
|
||||||
bool ready4data = false;///< Set to true when streaming is to begin.
|
bool ready4data = false; ///< Set to true when streaming is to begin.
|
||||||
DTSC::Stream Strm;///< Incoming stream buffer.
|
DTSC::Stream Strm; ///< Incoming stream buffer.
|
||||||
HTTP::Parser HTTP_R, HTTP_S;///<HTTP Receiver en HTTP Sender.
|
HTTP::Parser HTTP_R, HTTP_S; ///<HTTP Receiver en HTTP Sender.
|
||||||
bool inited = false;
|
bool inited = false;
|
||||||
Socket::Connection ss(-1);
|
Socket::Connection ss( -1);
|
||||||
std::string streamname;
|
std::string streamname;
|
||||||
FLV::Tag tag;///< Temporary tag buffer.
|
FLV::Tag tag; ///< Temporary tag buffer.
|
||||||
|
|
||||||
unsigned int lastStats = 0;
|
unsigned int lastStats = 0;
|
||||||
unsigned int seek_sec = 0;//seek position in ms
|
unsigned int seek_sec = 0; //seek position in ms
|
||||||
unsigned int seek_byte = 0;//seek position in bytes
|
unsigned int seek_byte = 0; //seek position in bytes
|
||||||
|
|
||||||
while (conn.connected()){
|
while (conn.connected()){
|
||||||
//only parse input if available or not yet init'ed
|
//only parse input if available or not yet init'ed
|
||||||
if (!inited){
|
if ( !inited){
|
||||||
if (conn.Received().size() || conn.spool()){
|
if (conn.Received().size() || conn.spool()){
|
||||||
//make sure it ends in a \n
|
//make sure it ends in a \n
|
||||||
if (*(conn.Received().get().rbegin()) != '\n'){
|
if ( *(conn.Received().get().rbegin()) != '\n'){
|
||||||
std::string tmp = conn.Received().get();
|
std::string tmp = conn.Received().get();
|
||||||
conn.Received().get().clear();
|
conn.Received().get().clear();
|
||||||
if (conn.Received().size()){
|
if (conn.Received().size()){
|
||||||
|
@ -53,35 +53,37 @@ namespace Connector_HTTP{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (HTTP_R.Read(conn.Received().get())){
|
if (HTTP_R.Read(conn.Received().get())){
|
||||||
#if DEBUG >= 4
|
#if DEBUG >= 4
|
||||||
std::cout << "Received request: " << HTTP_R.getUrl() << std::endl;
|
std::cout << "Received request: " << HTTP_R.getUrl() << std::endl;
|
||||||
#endif
|
#endif
|
||||||
conn.setHost(HTTP_R.GetHeader("X-Origin"));
|
conn.setHost(HTTP_R.GetHeader("X-Origin"));
|
||||||
//we assume the URL is the stream name with a 3 letter extension
|
//we assume the URL is the stream name with a 3 letter extension
|
||||||
streamname = HTTP_R.getUrl().substr(1);
|
streamname = HTTP_R.getUrl().substr(1);
|
||||||
size_t extDot = streamname.rfind('.');
|
size_t extDot = streamname.rfind('.');
|
||||||
if (extDot != std::string::npos){streamname.resize(extDot);};//strip the extension
|
if (extDot != std::string::npos){
|
||||||
|
streamname.resize(extDot);
|
||||||
|
}; //strip the extension
|
||||||
int start = 0;
|
int start = 0;
|
||||||
if (!HTTP_R.GetVar("start").empty()){
|
if ( !HTTP_R.GetVar("start").empty()){
|
||||||
start = atoi(HTTP_R.GetVar("start").c_str());
|
start = atoi(HTTP_R.GetVar("start").c_str());
|
||||||
}
|
}
|
||||||
if (!HTTP_R.GetVar("starttime").empty()){
|
if ( !HTTP_R.GetVar("starttime").empty()){
|
||||||
start = atoi(HTTP_R.GetVar("starttime").c_str());
|
start = atoi(HTTP_R.GetVar("starttime").c_str());
|
||||||
}
|
}
|
||||||
if (!HTTP_R.GetVar("apstart").empty()){
|
if ( !HTTP_R.GetVar("apstart").empty()){
|
||||||
start = atoi(HTTP_R.GetVar("apstart").c_str());
|
start = atoi(HTTP_R.GetVar("apstart").c_str());
|
||||||
}
|
}
|
||||||
if (!HTTP_R.GetVar("ec_seek").empty()){
|
if ( !HTTP_R.GetVar("ec_seek").empty()){
|
||||||
start = atoi(HTTP_R.GetVar("ec_seek").c_str());
|
start = atoi(HTTP_R.GetVar("ec_seek").c_str());
|
||||||
}
|
}
|
||||||
if (!HTTP_R.GetVar("fs").empty()){
|
if ( !HTTP_R.GetVar("fs").empty()){
|
||||||
start = atoi(HTTP_R.GetVar("fs").c_str());
|
start = atoi(HTTP_R.GetVar("fs").c_str());
|
||||||
}
|
}
|
||||||
//under 3 hours we assume seconds, otherwise byte position
|
//under 3 hours we assume seconds, otherwise byte position
|
||||||
if (start < 10800){
|
if (start < 10800){
|
||||||
seek_sec = start*1000;//ms, not s
|
seek_sec = start * 1000; //ms, not s
|
||||||
}else{
|
}else{
|
||||||
seek_byte = start;//divide by 1mbit, then *1000 for ms.
|
seek_byte = start; //divide by 1mbit, then *1000 for ms.
|
||||||
}
|
}
|
||||||
ready4data = true;
|
ready4data = true;
|
||||||
HTTP_R.Clean(); //clean for any possible next requests
|
HTTP_R.Clean(); //clean for any possible next requests
|
||||||
|
@ -89,13 +91,13 @@ namespace Connector_HTTP{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ready4data){
|
if (ready4data){
|
||||||
if (!inited){
|
if ( !inited){
|
||||||
//we are ready, connect the socket!
|
//we are ready, connect the socket!
|
||||||
ss = Util::Stream::getStream(streamname);
|
ss = Util::Stream::getStream(streamname);
|
||||||
if (!ss.connected()){
|
if ( !ss.connected()){
|
||||||
#if DEBUG >= 1
|
#if DEBUG >= 1
|
||||||
fprintf(stderr, "Could not connect to server for %s!\n", streamname.c_str());
|
fprintf(stderr, "Could not connect to server for %s!\n", streamname.c_str());
|
||||||
#endif
|
#endif
|
||||||
ss.close();
|
ss.close();
|
||||||
HTTP_S.Clean();
|
HTTP_S.Clean();
|
||||||
HTTP_S.SetBody("No such stream is available on the system. Please try again.\n");
|
HTTP_S.SetBody("No such stream is available on the system. Please try again.\n");
|
||||||
|
@ -105,9 +107,9 @@ namespace Connector_HTTP{
|
||||||
}
|
}
|
||||||
if (seek_byte){
|
if (seek_byte){
|
||||||
//wait until we have a header
|
//wait until we have a header
|
||||||
while (!Strm.metadata){
|
while ( !Strm.metadata){
|
||||||
if (ss.spool()){
|
if (ss.spool()){
|
||||||
Strm.parsePacket(ss.Received());//read the metadata
|
Strm.parsePacket(ss.Received()); //read the metadata
|
||||||
}else{
|
}else{
|
||||||
Util::sleep(5);
|
Util::sleep(5);
|
||||||
}
|
}
|
||||||
|
@ -126,9 +128,9 @@ namespace Connector_HTTP{
|
||||||
cmd << "s " << seek_sec << "\n";
|
cmd << "s " << seek_sec << "\n";
|
||||||
ss.SendNow(cmd.str().c_str());
|
ss.SendNow(cmd.str().c_str());
|
||||||
}
|
}
|
||||||
#if DEBUG >= 3
|
#if DEBUG >= 3
|
||||||
fprintf(stderr, "Everything connected, starting to send video data...\n");
|
fprintf(stderr, "Everything connected, starting to send video data...\n");
|
||||||
#endif
|
#endif
|
||||||
ss.SendNow("p\n");
|
ss.SendNow("p\n");
|
||||||
inited = true;
|
inited = true;
|
||||||
}
|
}
|
||||||
|
@ -139,13 +141,13 @@ namespace Connector_HTTP{
|
||||||
}
|
}
|
||||||
if (ss.spool()){
|
if (ss.spool()){
|
||||||
while (Strm.parsePacket(ss.Received())){
|
while (Strm.parsePacket(ss.Received())){
|
||||||
if (!progressive_has_sent_header){
|
if ( !progressive_has_sent_header){
|
||||||
HTTP_S.Clean();//make sure no parts of old requests are left in any buffers
|
HTTP_S.Clean(); //make sure no parts of old requests are left in any buffers
|
||||||
HTTP_S.SetHeader("Content-Type", "video/x-flv");//Send the correct content-type for FLV files
|
HTTP_S.SetHeader("Content-Type", "video/x-flv"); //Send the correct content-type for FLV files
|
||||||
//HTTP_S.SetHeader("Transfer-Encoding", "chunked");
|
//HTTP_S.SetHeader("Transfer-Encoding", "chunked");
|
||||||
HTTP_S.protocol = "HTTP/1.0";
|
HTTP_S.protocol = "HTTP/1.0";
|
||||||
conn.SendNow(HTTP_S.BuildResponse("200", "OK"));//no SetBody = unknown length - this is intentional, we will stream the entire file
|
conn.SendNow(HTTP_S.BuildResponse("200", "OK")); //no SetBody = unknown length - this is intentional, we will stream the entire file
|
||||||
conn.SendNow(FLV::Header, 13);//write FLV header
|
conn.SendNow(FLV::Header, 13); //write FLV header
|
||||||
//write metadata
|
//write metadata
|
||||||
tag.DTSCMetaInit(Strm);
|
tag.DTSCMetaInit(Strm);
|
||||||
conn.SendNow(tag.data, tag.len);
|
conn.SendNow(tag.data, tag.len);
|
||||||
|
@ -160,24 +162,28 @@ namespace Connector_HTTP{
|
||||||
conn.SendNow(tag.data, tag.len);
|
conn.SendNow(tag.data, tag.len);
|
||||||
}
|
}
|
||||||
progressive_has_sent_header = true;
|
progressive_has_sent_header = true;
|
||||||
#if DEBUG >= 1
|
#if DEBUG >= 1
|
||||||
fprintf(stderr, "Sent progressive FLV header\n");
|
fprintf(stderr, "Sent progressive FLV header\n");
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
tag.DTSCLoader(Strm);
|
tag.DTSCLoader(Strm);
|
||||||
conn.SendNow(tag.data, tag.len);//write the tag contents
|
conn.SendNow(tag.data, tag.len); //write the tag contents
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
Util::sleep(1);
|
Util::sleep(1);
|
||||||
}
|
}
|
||||||
if (!ss.connected()){break;}
|
if ( !ss.connected()){
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
conn.close();
|
conn.close();
|
||||||
ss.SendNow(conn.getStats("HTTP_Dynamic").c_str());
|
ss.SendNow(conn.getStats("HTTP_Dynamic").c_str());
|
||||||
ss.close();
|
ss.close();
|
||||||
#if DEBUG >= 1
|
#if DEBUG >= 1
|
||||||
if (FLV::Parse_Error){fprintf(stderr, "FLV Parser Error: %s\n", FLV::Error_Str.c_str());}
|
if (FLV::Parse_Error){
|
||||||
|
fprintf(stderr, "FLV Parser Error: %s\n", FLV::Error_Str.c_str());
|
||||||
|
}
|
||||||
fprintf(stderr, "User %i disconnected.\n", conn.getSocket());
|
fprintf(stderr, "User %i disconnected.\n", conn.getSocket());
|
||||||
if (inited){
|
if (inited){
|
||||||
fprintf(stderr, "Status was: inited\n");
|
fprintf(stderr, "Status was: inited\n");
|
||||||
|
@ -188,33 +194,35 @@ namespace Connector_HTTP{
|
||||||
fprintf(stderr, "Status was: connected\n");
|
fprintf(stderr, "Status was: connected\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
return 0;
|
return 0;
|
||||||
}//Connector_HTTP main function
|
} //Connector_HTTP main function
|
||||||
|
|
||||||
};//Connector_HTTP namespace
|
} //Connector_HTTP namespace
|
||||||
|
|
||||||
int main(int argc, char ** argv){
|
int main(int argc, char ** argv){
|
||||||
Util::Config conf(argv[0], PACKAGE_VERSION);
|
Util::Config conf(argv[0], PACKAGE_VERSION);
|
||||||
conf.addConnectorOptions(1935);
|
conf.addConnectorOptions(1935);
|
||||||
conf.parseArgs(argc, argv);
|
conf.parseArgs(argc, argv);
|
||||||
Socket::Server server_socket = Socket::Server("/tmp/mist/http_progressive");
|
Socket::Server server_socket = Socket::Server("/tmp/mist/http_progressive");
|
||||||
if (!server_socket.connected()){return 1;}
|
if ( !server_socket.connected()){
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
conf.activate();
|
conf.activate();
|
||||||
|
|
||||||
while (server_socket.connected() && conf.is_active){
|
while (server_socket.connected() && conf.is_active){
|
||||||
Socket::Connection S = server_socket.accept();
|
Socket::Connection S = server_socket.accept();
|
||||||
if (S.connected()){//check if the new connection is valid
|
if (S.connected()){ //check if the new connection is valid
|
||||||
pid_t myid = fork();
|
pid_t myid = fork();
|
||||||
if (myid == 0){//if new child, start MAINHANDLER
|
if (myid == 0){ //if new child, start MAINHANDLER
|
||||||
return Connector_HTTP::Connector_HTTP_Progressive(S);
|
return Connector_HTTP::Connector_HTTP_Progressive(S);
|
||||||
}else{//otherwise, do nothing or output debugging text
|
}else{ //otherwise, do nothing or output debugging text
|
||||||
#if DEBUG >= 3
|
#if DEBUG >= 3
|
||||||
fprintf(stderr, "Spawned new process %i for socket %i\n", (int)myid, S.getSocket());
|
fprintf(stderr, "Spawned new process %i for socket %i\n", (int)myid, S.getSocket());
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}//while connected
|
} //while connected
|
||||||
server_socket.close();
|
server_socket.close();
|
||||||
return 0;
|
return 0;
|
||||||
}//main
|
} //main
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
#include <mist/http_parser.h>
|
#include <mist/http_parser.h>
|
||||||
#include <mist/json.h>
|
#include <mist/json.h>
|
||||||
#include <mist/dtsc.h>
|
#include <mist/dtsc.h>
|
||||||
#include <mist/flv_tag.h>
|
|
||||||
#include <mist/base64.h>
|
#include <mist/base64.h>
|
||||||
#include <mist/amf.h>
|
#include <mist/amf.h>
|
||||||
#include <mist/mp4.h>
|
#include <mist/mp4.h>
|
||||||
|
@ -25,56 +24,67 @@
|
||||||
#include <mist/timing.h>
|
#include <mist/timing.h>
|
||||||
|
|
||||||
/// Holds everything unique to HTTP Dynamic Connector.
|
/// Holds everything unique to HTTP Dynamic Connector.
|
||||||
namespace Connector_HTTP{
|
namespace Connector_HTTP {
|
||||||
/// Returns a Smooth-format manifest file
|
/// Returns a Smooth-format manifest file
|
||||||
std::string BuildManifest(std::string & MovieId, JSON::Value & metadata){
|
std::string BuildManifest(std::string & MovieId, JSON::Value & metadata){
|
||||||
std::stringstream Result;
|
std::stringstream Result;
|
||||||
Result << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
|
Result << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
|
||||||
Result << "<SmoothStreamingMedia MajorVersion=\"2\" MinorVersion=\"0\" TimeScale=\"10000000\" Duration=\"" << metadata["lastms"].asInt() << "\">\n";
|
Result << "<SmoothStreamingMedia MajorVersion=\"2\" MinorVersion=\"0\" TimeScale=\"10000000\" Duration=\"" << metadata["lastms"].asInt()
|
||||||
if( metadata.isMember( "audio" ) ) {
|
<< "\">\n";
|
||||||
Result << " <StreamIndex Type=\"audio\" QualityLevels=\"1\" Name=\"audio\" Chunks=\"" << metadata["keytime"].size() << "\" Url=\"Q({bitrate})/A({start time})\">\n";
|
if (metadata.isMember("audio")){
|
||||||
Result << " <QualityLevel Index=\"0\" Bitrate=\"" << metadata["audio"]["bps"].asInt()*8 << "\" CodecPrivateData=\"";
|
Result << " <StreamIndex Type=\"audio\" QualityLevels=\"1\" Name=\"audio\" Chunks=\"" << metadata["keytime"].size()
|
||||||
|
<< "\" Url=\"Q({bitrate})/A({start time})\">\n";
|
||||||
|
Result << " <QualityLevel Index=\"0\" Bitrate=\"" << metadata["audio"]["bps"].asInt() * 8 << "\" CodecPrivateData=\"";
|
||||||
Result << std::hex;
|
Result << std::hex;
|
||||||
for( int i = 0; i < metadata["audio"]["init"].asString().size(); i++ ) {
|
for (int i = 0; i < metadata["audio"]["init"].asString().size(); i++){
|
||||||
Result << std::setfill('0') << std::setw(2) << std::right << (int)metadata["audio"]["init"].asString()[i];
|
Result << std::setfill('0') << std::setw(2) << std::right << (int)metadata["audio"]["init"].asString()[i];
|
||||||
}
|
}
|
||||||
Result << std::dec;
|
Result << std::dec;
|
||||||
Result << "\" SamplingRate=\"" << metadata["audio"]["rate"].asInt() << "\" Channels=\"2\" BitsPerSample=\"16\" PacketSize=\"4\" AudioTag=\"255\" FourCC=\"AACL\" />\n";
|
Result << "\" SamplingRate=\"" << metadata["audio"]["rate"].asInt()
|
||||||
for( int i = 0; i < metadata["keytime"].size()-1; i++ ) {
|
<< "\" Channels=\"2\" BitsPerSample=\"16\" PacketSize=\"4\" AudioTag=\"255\" FourCC=\"AACL\" />\n";
|
||||||
|
for (int i = 0; i < metadata["keytime"].size() - 1; i++){
|
||||||
Result << " <c ";
|
Result << " <c ";
|
||||||
if( i == 0 ) { Result << "t=\"0\" "; }
|
if (i == 0){
|
||||||
Result << "d=\"" << 10000 * ( metadata["keytime"][i+1].asInt() - metadata["keytime"][i].asInt() ) << "\" />\n";
|
Result << "t=\"0\" ";
|
||||||
}
|
}
|
||||||
Result << " <c d=\"" << 10000 * ( metadata["lastms"].asInt() - metadata["keytime"][metadata["keytime"].size()-1].asInt() ) << "\" />\n";
|
Result << "d=\"" << 10000 * (metadata["keytime"][i + 1].asInt() - metadata["keytime"][i].asInt()) << "\" />\n";
|
||||||
|
}
|
||||||
|
Result << " <c d=\"" << 10000 * (metadata["lastms"].asInt() - metadata["keytime"][metadata["keytime"].size() - 1].asInt()) << "\" />\n";
|
||||||
Result << " </StreamIndex>\n";
|
Result << " </StreamIndex>\n";
|
||||||
}
|
}
|
||||||
if( metadata.isMember( "video" ) ) {
|
if (metadata.isMember("video")){
|
||||||
Result << " <StreamIndex Type=\"video\" QualityLevels=\"1\" Name=\"video\" Chunks=\"" << metadata["keytime"].size() << "\" Url=\"Q({bitrate})/V({start time})\" MaxWidth=\"" << metadata["video"]["width"].asInt() << "\" MaxHeight=\"" << metadata["video"]["height"].asInt() << "\" DisplayWidth=\"" << metadata["video"]["width"].asInt() << "\" DisplayHeight=\"" << metadata["video"]["height"].asInt() << "\">\n";
|
Result << " <StreamIndex Type=\"video\" QualityLevels=\"1\" Name=\"video\" Chunks=\"" << metadata["keytime"].size()
|
||||||
Result << " <QualityLevel Index=\"0\" Bitrate=\"" << metadata["video"]["bps"].asInt()*8 << "\" CodecPrivateData=\"";
|
<< "\" Url=\"Q({bitrate})/V({start time})\" MaxWidth=\"" << metadata["video"]["width"].asInt() << "\" MaxHeight=\""
|
||||||
|
<< metadata["video"]["height"].asInt() << "\" DisplayWidth=\"" << metadata["video"]["width"].asInt() << "\" DisplayHeight=\""
|
||||||
|
<< metadata["video"]["height"].asInt() << "\">\n";
|
||||||
|
Result << " <QualityLevel Index=\"0\" Bitrate=\"" << metadata["video"]["bps"].asInt() * 8 << "\" CodecPrivateData=\"";
|
||||||
MP4::AVCC avccbox;
|
MP4::AVCC avccbox;
|
||||||
avccbox.setPayload( metadata["video"]["init"].asString() );
|
avccbox.setPayload(metadata["video"]["init"].asString());
|
||||||
std::string tmpString = avccbox.asAnnexB( );
|
std::string tmpString = avccbox.asAnnexB();
|
||||||
Result << std::hex;
|
Result << std::hex;
|
||||||
for( int i = 0; i < tmpString.size(); i++ ) {
|
for (int i = 0; i < tmpString.size(); i++){
|
||||||
Result << std::setfill('0') << std::setw(2) << std::right << (int)tmpString[i];
|
Result << std::setfill('0') << std::setw(2) << std::right << (int)tmpString[i];
|
||||||
}
|
}
|
||||||
Result << std::dec;
|
Result << std::dec;
|
||||||
Result << "\" MaxWidth=\"" << metadata["video"]["width"].asInt() << "\" MaxHeight=\"" << metadata["video"]["height"].asInt() << "\" FourCC=\"AVC1\" />\n";
|
Result << "\" MaxWidth=\"" << metadata["video"]["width"].asInt() << "\" MaxHeight=\"" << metadata["video"]["height"].asInt()
|
||||||
for( int i = 0; i < metadata["keytime"].size()-1; i++ ) {
|
<< "\" FourCC=\"AVC1\" />\n";
|
||||||
|
for (int i = 0; i < metadata["keytime"].size() - 1; i++){
|
||||||
Result << " <c ";
|
Result << " <c ";
|
||||||
if( i == 0 ) { Result << "t=\"0\" "; }
|
if (i == 0){
|
||||||
Result << "d=\"" << 10000 * ( metadata["keytime"][i+1].asInt() - metadata["keytime"][i].asInt() ) << "\" />\n";
|
Result << "t=\"0\" ";
|
||||||
}
|
}
|
||||||
Result << " <c d=\"" << 10000 * ( metadata["lastms"].asInt() - metadata["keytime"][metadata["keytime"].size()-1].asInt() ) << "\" />\n";
|
Result << "d=\"" << 10000 * (metadata["keytime"][i + 1].asInt() - metadata["keytime"][i].asInt()) << "\" />\n";
|
||||||
|
}
|
||||||
|
Result << " <c d=\"" << 10000 * (metadata["lastms"].asInt() - metadata["keytime"][metadata["keytime"].size() - 1].asInt()) << "\" />\n";
|
||||||
Result << " </StreamIndex>\n";
|
Result << " </StreamIndex>\n";
|
||||||
}
|
}
|
||||||
Result << "</SmoothStreamingMedia>\n";
|
Result << "</SmoothStreamingMedia>\n";
|
||||||
|
|
||||||
#if DEBUG >= 8
|
#if DEBUG >= 8
|
||||||
std::cerr << "Sending this manifest:" << std::endl << Result << std::endl;
|
std::cerr << "Sending this manifest:" << std::endl << Result << std::endl;
|
||||||
#endif
|
#endif
|
||||||
return Result.str();
|
return Result.str();
|
||||||
}//BuildManifest
|
} //BuildManifest
|
||||||
|
|
||||||
/// Main function for Connector_HTTP_Dynamic
|
/// Main function for Connector_HTTP_Dynamic
|
||||||
int Connector_HTTP_Dynamic(Socket::Connection conn){
|
int Connector_HTTP_Dynamic(Socket::Connection conn){
|
||||||
|
@ -82,16 +92,15 @@ namespace Connector_HTTP{
|
||||||
std::vector<int> Timestamps;
|
std::vector<int> Timestamps;
|
||||||
int FlashBufSize = 0;
|
int FlashBufSize = 0;
|
||||||
long long int FlashBufTime = 0;
|
long long int FlashBufTime = 0;
|
||||||
FLV::Tag tmp;//temporary tag
|
|
||||||
|
|
||||||
DTSC::Stream Strm;//Incoming stream buffer.
|
DTSC::Stream Strm; //Incoming stream buffer.
|
||||||
HTTP::Parser HTTP_R, HTTP_S;//HTTP Receiver en HTTP Sender.
|
HTTP::Parser HTTP_R, HTTP_S; //HTTP Receiver en HTTP Sender.
|
||||||
|
|
||||||
bool ready4data = false;//Set to true when streaming is to begin.
|
bool ready4data = false; //Set to true when streaming is to begin.
|
||||||
bool pending_manifest = false;
|
bool pending_manifest = false;
|
||||||
bool receive_marks = false;//when set to true, this stream will ignore keyframes and instead use pause marks
|
bool receive_marks = false; //when set to true, this stream will ignore keyframes and instead use pause marks
|
||||||
bool inited = false;
|
bool inited = false;
|
||||||
Socket::Connection ss(-1);
|
Socket::Connection ss( -1);
|
||||||
std::string streamname;
|
std::string streamname;
|
||||||
std::string recBuffer = "";
|
std::string recBuffer = "";
|
||||||
|
|
||||||
|
@ -105,12 +114,12 @@ namespace Connector_HTTP{
|
||||||
std::string tempStr;
|
std::string tempStr;
|
||||||
int Flash_RequestPending = 0;
|
int Flash_RequestPending = 0;
|
||||||
unsigned int lastStats = 0;
|
unsigned int lastStats = 0;
|
||||||
conn.setBlocking(false);//do not block on conn.spool() when no data is available
|
conn.setBlocking(false); //do not block on conn.spool() when no data is available
|
||||||
|
|
||||||
while (conn.connected()){
|
while (conn.connected()){
|
||||||
if (conn.spool() || conn.Received().size()){
|
if (conn.spool() || conn.Received().size()){
|
||||||
//make sure it ends in a \n
|
//make sure it ends in a \n
|
||||||
if (*(conn.Received().get().rbegin()) != '\n'){
|
if ( *(conn.Received().get().rbegin()) != '\n'){
|
||||||
std::string tmp = conn.Received().get();
|
std::string tmp = conn.Received().get();
|
||||||
conn.Received().get().clear();
|
conn.Received().get().clear();
|
||||||
if (conn.Received().size()){
|
if (conn.Received().size()){
|
||||||
|
@ -120,18 +129,18 @@ namespace Connector_HTTP{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (HTTP_R.Read(conn.Received().get())){
|
if (HTTP_R.Read(conn.Received().get())){
|
||||||
#if DEBUG >= 4
|
#if DEBUG >= 4
|
||||||
std::cout << "Received request: " << HTTP_R.getUrl() << std::endl;
|
std::cout << "Received request: " << HTTP_R.getUrl() << std::endl;
|
||||||
#endif
|
#endif
|
||||||
conn.setHost(HTTP_R.GetHeader("X-Origin"));
|
conn.setHost(HTTP_R.GetHeader("X-Origin"));
|
||||||
if (HTTP_R.url.find("Manifest") == std::string::npos){
|
if (HTTP_R.url.find("Manifest") == std::string::npos){
|
||||||
streamname = HTTP_R.url.substr(8,HTTP_R.url.find("/",8)-12);
|
streamname = HTTP_R.url.substr(8, HTTP_R.url.find("/", 8) - 12);
|
||||||
if (!ss){
|
if ( !ss){
|
||||||
ss = Util::Stream::getStream(streamname);
|
ss = Util::Stream::getStream(streamname);
|
||||||
if (!ss.connected()){
|
if ( !ss.connected()){
|
||||||
#if DEBUG >= 1
|
#if DEBUG >= 1
|
||||||
fprintf(stderr, "Could not connect to server!\n");
|
fprintf(stderr, "Could not connect to server!\n");
|
||||||
#endif
|
#endif
|
||||||
ss.close();
|
ss.close();
|
||||||
HTTP_S.Clean();
|
HTTP_S.Clean();
|
||||||
HTTP_S.SetBody("No such stream " + streamname + " 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");
|
||||||
|
@ -142,35 +151,41 @@ namespace Connector_HTTP{
|
||||||
ss.setBlocking(false);
|
ss.setBlocking(false);
|
||||||
inited = true;
|
inited = true;
|
||||||
}
|
}
|
||||||
Quality = HTTP_R.url.substr( HTTP_R.url.find("/Q(",8)+3 );
|
Quality = HTTP_R.url.substr(HTTP_R.url.find("/Q(", 8) + 3);
|
||||||
Quality = Quality.substr(0, Quality.find(")"));
|
Quality = Quality.substr(0, Quality.find(")"));
|
||||||
tempStr = HTTP_R.url.substr( HTTP_R.url.find(")/") + 2 );
|
tempStr = HTTP_R.url.substr(HTTP_R.url.find(")/") + 2);
|
||||||
wantsAudio = false;
|
wantsAudio = false;
|
||||||
wantsVideo = false;
|
wantsVideo = false;
|
||||||
if( tempStr[0] == 'A' ) { wantsAudio = true; }
|
if (tempStr[0] == 'A'){
|
||||||
if( tempStr[0] == 'V' ) { wantsVideo = true; }
|
wantsAudio = true;
|
||||||
tempStr = tempStr.substr( tempStr.find("(") + 1 );
|
}
|
||||||
ReqFragment = atoll( tempStr.substr(0,tempStr.find(")")).c_str() );
|
if (tempStr[0] == 'V'){
|
||||||
#if DEBUG >= 4
|
wantsVideo = true;
|
||||||
printf( "Quality: %s, Frag %d\n", Quality.c_str(), ( ReqFragment / 10000 ) );
|
}
|
||||||
#endif
|
tempStr = tempStr.substr(tempStr.find("(") + 1);
|
||||||
|
ReqFragment = atoll(tempStr.substr(0, tempStr.find(")")).c_str());
|
||||||
|
#if DEBUG >= 4
|
||||||
|
printf("Quality: %s, Frag %d\n", Quality.c_str(), (ReqFragment / 10000));
|
||||||
|
#endif
|
||||||
std::stringstream sstream;
|
std::stringstream sstream;
|
||||||
sstream << "s " << ( ReqFragment / 10000 ) << "\no \n";
|
sstream << "s " << (ReqFragment / 10000) << "\no \n";
|
||||||
ss.SendNow(sstream.str().c_str());
|
ss.SendNow(sstream.str().c_str());
|
||||||
Flash_RequestPending++;
|
Flash_RequestPending++;
|
||||||
}else{
|
}else{
|
||||||
streamname = HTTP_R.url.substr(8,HTTP_R.url.find("/",8)-12);
|
streamname = HTTP_R.url.substr(8, HTTP_R.url.find("/", 8) - 12);
|
||||||
if (!Strm.metadata.isNull()){
|
if ( !Strm.metadata.isNull()){
|
||||||
HTTP_S.Clean();
|
HTTP_S.Clean();
|
||||||
HTTP_S.SetHeader("Content-Type","text/xml");
|
HTTP_S.SetHeader("Content-Type", "text/xml");
|
||||||
HTTP_S.SetHeader("Cache-Control","no-cache");
|
HTTP_S.SetHeader("Cache-Control", "no-cache");
|
||||||
if (Strm.metadata.isMember("length")){receive_marks = true;}
|
if (Strm.metadata.isMember("length")){
|
||||||
|
receive_marks = true;
|
||||||
|
}
|
||||||
std::string manifest = BuildManifest(streamname, Strm.metadata);
|
std::string manifest = BuildManifest(streamname, Strm.metadata);
|
||||||
HTTP_S.SetBody(manifest);
|
HTTP_S.SetBody(manifest);
|
||||||
conn.SendNow(HTTP_S.BuildResponse("200", "OK"));
|
conn.SendNow(HTTP_S.BuildResponse("200", "OK"));
|
||||||
#if DEBUG >= 3
|
#if DEBUG >= 3
|
||||||
printf("Sent manifest\n");
|
printf("Sent manifest\n");
|
||||||
#endif
|
#endif
|
||||||
pending_manifest = false;
|
pending_manifest = false;
|
||||||
}else{
|
}else{
|
||||||
pending_manifest = true;
|
pending_manifest = true;
|
||||||
|
@ -181,19 +196,19 @@ namespace Connector_HTTP{
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
if (Flash_RequestPending){
|
if (Flash_RequestPending){
|
||||||
usleep(1000);//sleep 1ms
|
usleep(1000); //sleep 1ms
|
||||||
}else{
|
}else{
|
||||||
usleep(10000);//sleep 10ms
|
usleep(10000); //sleep 10ms
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ready4data){
|
if (ready4data){
|
||||||
if (!inited){
|
if ( !inited){
|
||||||
//we are ready, connect the socket!
|
//we are ready, connect the socket!
|
||||||
ss = Util::Stream::getStream(streamname);
|
ss = Util::Stream::getStream(streamname);
|
||||||
if (!ss.connected()){
|
if ( !ss.connected()){
|
||||||
#if DEBUG >= 1
|
#if DEBUG >= 1
|
||||||
fprintf(stderr, "Could not connect to server!\n");
|
fprintf(stderr, "Could not connect to server!\n");
|
||||||
#endif
|
#endif
|
||||||
ss.close();
|
ss.close();
|
||||||
HTTP_S.Clean();
|
HTTP_S.Clean();
|
||||||
HTTP_S.SetBody("No such stream " + streamname + " 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");
|
||||||
|
@ -202,9 +217,9 @@ namespace Connector_HTTP{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
ss.setBlocking(false);
|
ss.setBlocking(false);
|
||||||
#if DEBUG >= 3
|
#if DEBUG >= 3
|
||||||
fprintf(stderr, "Everything connected, starting to send video data...\n");
|
fprintf(stderr, "Everything connected, starting to send video data...\n");
|
||||||
#endif
|
#endif
|
||||||
inited = true;
|
inited = true;
|
||||||
}
|
}
|
||||||
unsigned int now = Util::epoch();
|
unsigned int now = Util::epoch();
|
||||||
|
@ -215,10 +230,10 @@ namespace Connector_HTTP{
|
||||||
if (ss.spool()){
|
if (ss.spool()){
|
||||||
while (Strm.parsePacket(ss.Received())){
|
while (Strm.parsePacket(ss.Received())){
|
||||||
if (Strm.getPacket(0).isMember("time")){
|
if (Strm.getPacket(0).isMember("time")){
|
||||||
if (!Strm.metadata.isMember("firsttime")){
|
if ( !Strm.metadata.isMember("firsttime")){
|
||||||
Strm.metadata["firsttime"] = Strm.getPacket(0)["time"];
|
Strm.metadata["firsttime"] = Strm.getPacket(0)["time"];
|
||||||
}else{
|
}else{
|
||||||
if (!Strm.metadata.isMember("length") || Strm.metadata["length"].asInt() == 0){
|
if ( !Strm.metadata.isMember("length") || Strm.metadata["length"].asInt() == 0){
|
||||||
Strm.getPacket(0)["time"] = Strm.getPacket(0)["time"].asInt() - Strm.metadata["firsttime"].asInt();
|
Strm.getPacket(0)["time"] = Strm.getPacket(0)["time"].asInt() - Strm.metadata["firsttime"].asInt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -226,29 +241,33 @@ namespace Connector_HTTP{
|
||||||
}
|
}
|
||||||
if (pending_manifest){
|
if (pending_manifest){
|
||||||
HTTP_S.Clean();
|
HTTP_S.Clean();
|
||||||
HTTP_S.SetHeader("Content-Type","text/xml");
|
HTTP_S.SetHeader("Content-Type", "text/xml");
|
||||||
HTTP_S.SetHeader("Cache-Control","no-cache");
|
HTTP_S.SetHeader("Cache-Control", "no-cache");
|
||||||
if (Strm.metadata.isMember("length")){receive_marks = true;}
|
if (Strm.metadata.isMember("length")){
|
||||||
|
receive_marks = true;
|
||||||
|
}
|
||||||
std::string manifest = BuildManifest(streamname, Strm.metadata);
|
std::string manifest = BuildManifest(streamname, Strm.metadata);
|
||||||
HTTP_S.SetBody(manifest);
|
HTTP_S.SetBody(manifest);
|
||||||
conn.SendNow(HTTP_S.BuildResponse("200", "OK"));
|
conn.SendNow(HTTP_S.BuildResponse("200", "OK"));
|
||||||
#if DEBUG >= 3
|
#if DEBUG >= 3
|
||||||
printf("Sent manifest\n");
|
printf("Sent manifest\n");
|
||||||
#endif
|
#endif
|
||||||
pending_manifest = false;
|
pending_manifest = false;
|
||||||
}
|
}
|
||||||
if (!receive_marks && Strm.metadata.isMember("length")){receive_marks = true;}
|
if ( !receive_marks && Strm.metadata.isMember("length")){
|
||||||
if ( Strm.lastType() == DTSC::PAUSEMARK ) {
|
receive_marks = true;
|
||||||
Timestamps.push_back( Strm.getPacket(0)["time"].asInt() );
|
}
|
||||||
|
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 ((Strm.getPacket(0).isMember("keyframe") && !receive_marks) || Strm.lastType() == DTSC::PAUSEMARK){
|
||||||
#if DEBUG >= 4
|
#if DEBUG >= 4
|
||||||
fprintf(stderr, "Received a %s fragment of %i bytes.\n", Strm.getPacket(0)["datatype"].asString().c_str(), FlashBufSize);
|
fprintf(stderr, "Received a %s fragment of %i bytes.\n", Strm.getPacket(0)["datatype"].asString().c_str(), FlashBufSize);
|
||||||
#endif
|
#endif
|
||||||
if (Flash_RequestPending > 0 && FlashBufSize){
|
if (Flash_RequestPending > 0 && FlashBufSize){
|
||||||
#if DEBUG >= 3
|
#if DEBUG >= 3
|
||||||
fprintf(stderr, "Sending a fragment...");
|
fprintf(stderr, "Sending a fragment...");
|
||||||
#endif
|
#endif
|
||||||
//static std::string btstrp;
|
//static std::string btstrp;
|
||||||
//btstrp = GenerateBootstrap(streamname, Strm.metadata, ReqFragment, FlashBufTime, Strm.getPacket(0)["time"]);
|
//btstrp = GenerateBootstrap(streamname, Strm.metadata, ReqFragment, FlashBufTime, Strm.getPacket(0)["time"]);
|
||||||
HTTP_S.Clean();
|
HTTP_S.Clean();
|
||||||
|
@ -258,13 +277,13 @@ namespace Connector_HTTP{
|
||||||
int myDuration;
|
int myDuration;
|
||||||
|
|
||||||
MP4::MFHD mfhd_box;
|
MP4::MFHD mfhd_box;
|
||||||
for( int i = 0; i < Strm.metadata["keytime"].size(); i++ ) {
|
for (int i = 0; i < Strm.metadata["keytime"].size(); i++){
|
||||||
if( Strm.metadata["keytime"][i].asInt() >= ( ReqFragment / 10000 ) ) {
|
if (Strm.metadata["keytime"][i].asInt() >= (ReqFragment / 10000)){
|
||||||
std::cerr << "Sequence Number: " << i+1 << std::endl;
|
std::cerr << "Sequence Number: " << i + 1 << std::endl;
|
||||||
mfhd_box.setSequenceNumber( i+1 );
|
mfhd_box.setSequenceNumber(i + 1);
|
||||||
if( i != Strm.metadata["keytime"].size() ) {
|
if (i != Strm.metadata["keytime"].size()){
|
||||||
myDuration = Strm.metadata["keytime"][i+1].asInt() - Strm.metadata["keytime"][i].asInt();
|
myDuration = Strm.metadata["keytime"][i + 1].asInt() - Strm.metadata["keytime"][i].asInt();
|
||||||
} else {
|
}else{
|
||||||
myDuration = Strm.metadata["lastms"].asInt() - Strm.metadata["keytime"][i].asInt();
|
myDuration = Strm.metadata["lastms"].asInt() - Strm.metadata["keytime"][i].asInt();
|
||||||
}
|
}
|
||||||
myDuration = myDuration * 10000;
|
myDuration = myDuration * 10000;
|
||||||
|
@ -273,97 +292,98 @@ namespace Connector_HTTP{
|
||||||
}
|
}
|
||||||
|
|
||||||
MP4::TFHD tfhd_box;
|
MP4::TFHD tfhd_box;
|
||||||
tfhd_box.setFlags( MP4::tfhdSampleFlag );
|
tfhd_box.setFlags(MP4::tfhdSampleFlag);
|
||||||
tfhd_box.setTrackID( 1 );
|
tfhd_box.setTrackID(1);
|
||||||
tfhd_box.setDefaultSampleFlags( 0x000000C0 | MP4::noIPicture | MP4::noDisposable | MP4::noKeySample );
|
tfhd_box.setDefaultSampleFlags(0x000000C0 | MP4::noIPicture | MP4::noDisposable | MP4::noKeySample);
|
||||||
|
|
||||||
MP4::TRUN trun_box;
|
MP4::TRUN trun_box;
|
||||||
//maybe reinsert dataOffset
|
//maybe reinsert dataOffset
|
||||||
std::cerr << "Setting Flags: " << (MP4::trundataOffset | MP4::trunfirstSampleFlags | MP4::trunsampleDuration | MP4::trunsampleSize) << std::endl;
|
std::cerr << "Setting Flags: " << (MP4::trundataOffset | MP4::trunfirstSampleFlags | MP4::trunsampleDuration | MP4::trunsampleSize)
|
||||||
trun_box.setFlags( MP4::trundataOffset | MP4::trunfirstSampleFlags | MP4::trunsampleDuration | MP4::trunsampleSize );
|
<< std::endl;
|
||||||
trun_box.setDataOffset( 42 );
|
trun_box.setFlags(MP4::trundataOffset | MP4::trunfirstSampleFlags | MP4::trunsampleDuration | MP4::trunsampleSize);
|
||||||
trun_box.setFirstSampleFlags( 0x00000040 | MP4::isIPicture | MP4::noDisposable | MP4::isKeySample );
|
trun_box.setDataOffset(42);
|
||||||
for( int i = 0; i < FlashBuf.size(); i++ ) {
|
trun_box.setFirstSampleFlags(0x00000040 | MP4::isIPicture | MP4::noDisposable | MP4::isKeySample);
|
||||||
|
for (int i = 0; i < FlashBuf.size(); i++){
|
||||||
MP4::trunSampleInformation trunSample;
|
MP4::trunSampleInformation trunSample;
|
||||||
trunSample.sampleSize = FlashBuf[i].size();
|
trunSample.sampleSize = FlashBuf[i].size();
|
||||||
//trunSample.sampleDuration = (Timestamps[i+1]-Timestamps[i]) * 10000;
|
//trunSample.sampleDuration = (Timestamps[i+1]-Timestamps[i]) * 10000;
|
||||||
trunSample.sampleDuration = (((double)myDuration / FlashBuf.size()) * i) - (((double)myDuration / FlashBuf.size()) * (i-1));
|
trunSample.sampleDuration = (((double)myDuration / FlashBuf.size()) * i) - (((double)myDuration / FlashBuf.size()) * (i - 1));
|
||||||
trun_box.setSampleInformation( trunSample, i );
|
trun_box.setSampleInformation(trunSample, i);
|
||||||
}
|
}
|
||||||
MP4::SDTP sdtp_box;
|
MP4::SDTP sdtp_box;
|
||||||
sdtp_box.setVersion( 0 );
|
sdtp_box.setVersion(0);
|
||||||
sdtp_box.setValue( 0x24, 4 );
|
sdtp_box.setValue(0x24, 4);
|
||||||
for( int i = 1; i < FlashBuf.size(); i++ ) {
|
for (int i = 1; i < FlashBuf.size(); i++){
|
||||||
sdtp_box.setValue( 0x14, 4+i );
|
sdtp_box.setValue(0x14, 4 + i);
|
||||||
}
|
}
|
||||||
|
|
||||||
MP4::TRAF traf_box;
|
MP4::TRAF traf_box;
|
||||||
traf_box.setContent( tfhd_box, 0 );
|
traf_box.setContent(tfhd_box, 0);
|
||||||
traf_box.setContent( trun_box, 1 );
|
traf_box.setContent(trun_box, 1);
|
||||||
traf_box.setContent( sdtp_box, 2 );
|
traf_box.setContent(sdtp_box, 2);
|
||||||
|
|
||||||
MP4::MOOF moof_box;
|
MP4::MOOF moof_box;
|
||||||
moof_box.setContent( mfhd_box, 0 );
|
moof_box.setContent(mfhd_box, 0);
|
||||||
moof_box.setContent( traf_box, 1 );
|
moof_box.setContent(traf_box, 1);
|
||||||
|
|
||||||
//setting tha offsets!
|
//setting tha offsets!
|
||||||
trun_box.setDataOffset(moof_box.boxedSize() + 8);
|
trun_box.setDataOffset(moof_box.boxedSize() + 8);
|
||||||
traf_box.setContent( trun_box, 1 );
|
traf_box.setContent(trun_box, 1);
|
||||||
moof_box.setContent( traf_box, 1 );
|
moof_box.setContent(traf_box, 1);
|
||||||
|
|
||||||
|
|
||||||
//std::cerr << "\t[encoded] = " << ((MP4::TRUN&)(((MP4::TRAF&)(moof_box.getContent(1))).getContent(1))).getDataOffset() << std::endl;
|
//std::cerr << "\t[encoded] = " << ((MP4::TRUN&)(((MP4::TRAF&)(moof_box.getContent(1))).getContent(1))).getDataOffset() << std::endl;
|
||||||
|
|
||||||
|
HTTP_S.SetHeader("Content-Length", FlashBufSize + 8 + moof_box.boxedSize()); //32+33+btstrp.size());
|
||||||
HTTP_S.SetHeader("Content-Length", FlashBufSize+8+moof_box.boxedSize());//32+33+btstrp.size());
|
|
||||||
conn.SendNow(HTTP_S.BuildResponse("200", "OK"));
|
conn.SendNow(HTTP_S.BuildResponse("200", "OK"));
|
||||||
|
|
||||||
|
conn.SendNow(moof_box.asBox(), moof_box.boxedSize());
|
||||||
conn.SendNow( moof_box.asBox(), moof_box.boxedSize() );
|
|
||||||
|
|
||||||
unsigned long size = htonl(FlashBufSize+8);
|
unsigned long size = htonl(FlashBufSize+8);
|
||||||
conn.SendNow((char*)&size, 4);
|
conn.SendNow((char*) &size, 4);
|
||||||
conn.SendNow("mdat", 4);
|
conn.SendNow("mdat", 4);
|
||||||
while (FlashBuf.size() > 0){
|
while (FlashBuf.size() > 0){
|
||||||
conn.SendNow(FlashBuf.front());
|
conn.SendNow(FlashBuf.front());
|
||||||
FlashBuf.pop_front();
|
FlashBuf.pop_front();
|
||||||
}
|
}
|
||||||
Flash_RequestPending--;
|
Flash_RequestPending--;
|
||||||
#if DEBUG >= 3
|
#if DEBUG >= 3
|
||||||
fprintf(stderr, "Done\n");
|
fprintf(stderr, "Done\n");
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
FlashBuf.clear();
|
FlashBuf.clear();
|
||||||
FlashBufSize = 0;
|
FlashBufSize = 0;
|
||||||
}
|
}
|
||||||
if ( ( wantsAudio && Strm.lastType() == DTSC::AUDIO ) || ( wantsVideo && Strm.lastType() == DTSC::VIDEO ) ) {
|
if ((wantsAudio && Strm.lastType() == DTSC::AUDIO) || (wantsVideo && Strm.lastType() == DTSC::VIDEO)){
|
||||||
FlashBuf.push_back( Strm.lastData() );
|
FlashBuf.push_back(Strm.lastData());
|
||||||
FlashBufSize += Strm.lastData().size();
|
FlashBufSize += Strm.lastData().size();
|
||||||
Timestamps.push_back( Strm.getPacket(0)["time"].asInt() );
|
Timestamps.push_back(Strm.getPacket(0)["time"].asInt());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (pending_manifest && !Strm.metadata.isNull()){
|
if (pending_manifest && !Strm.metadata.isNull()){
|
||||||
HTTP_S.Clean();
|
HTTP_S.Clean();
|
||||||
HTTP_S.SetHeader("Content-Type","text/xml");
|
HTTP_S.SetHeader("Content-Type", "text/xml");
|
||||||
HTTP_S.SetHeader("Cache-Control","no-cache");
|
HTTP_S.SetHeader("Cache-Control", "no-cache");
|
||||||
if (Strm.metadata.isMember("length")){receive_marks = true;}
|
if (Strm.metadata.isMember("length")){
|
||||||
|
receive_marks = true;
|
||||||
|
}
|
||||||
std::string manifest = BuildManifest(streamname, Strm.metadata);
|
std::string manifest = BuildManifest(streamname, Strm.metadata);
|
||||||
HTTP_S.SetBody(manifest);
|
HTTP_S.SetBody(manifest);
|
||||||
conn.SendNow(HTTP_S.BuildResponse("200", "OK"));
|
conn.SendNow(HTTP_S.BuildResponse("200", "OK"));
|
||||||
#if DEBUG >= 3
|
#if DEBUG >= 3
|
||||||
printf("Sent manifest\n");
|
printf("Sent manifest\n");
|
||||||
#endif
|
#endif
|
||||||
pending_manifest = false;
|
pending_manifest = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!ss.connected()){break;}
|
if ( !ss.connected()){
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
conn.close();
|
conn.close();
|
||||||
ss.SendNow(conn.getStats("HTTP_Smooth").c_str());
|
ss.SendNow(conn.getStats("HTTP_Smooth").c_str());
|
||||||
ss.close();
|
ss.close();
|
||||||
#if DEBUG >= 1
|
#if DEBUG >= 1
|
||||||
if (FLV::Parse_Error){fprintf(stderr, "FLV Parser Error: %s\n", FLV::Error_Str.c_str());}
|
|
||||||
fprintf(stderr, "User %i disconnected.\n", conn.getSocket());
|
fprintf(stderr, "User %i disconnected.\n", conn.getSocket());
|
||||||
if (inited){
|
if (inited){
|
||||||
fprintf(stderr, "Status was: inited\n");
|
fprintf(stderr, "Status was: inited\n");
|
||||||
|
@ -374,33 +394,35 @@ namespace Connector_HTTP{
|
||||||
fprintf(stderr, "Status was: connected\n");
|
fprintf(stderr, "Status was: connected\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
return 0;
|
return 0;
|
||||||
}//Connector_HTTP_Smooth main function
|
} //Connector_HTTP_Smooth main function
|
||||||
|
|
||||||
};//Connector_HTTP_Smooth namespace
|
} //Connector_HTTP_Smooth namespace
|
||||||
|
|
||||||
int main(int argc, char ** argv){
|
int main(int argc, char ** argv){
|
||||||
Util::Config conf(argv[0], PACKAGE_VERSION);
|
Util::Config conf(argv[0], PACKAGE_VERSION);
|
||||||
conf.addConnectorOptions(1935);
|
conf.addConnectorOptions(1935);
|
||||||
conf.parseArgs(argc, argv);
|
conf.parseArgs(argc, argv);
|
||||||
Socket::Server server_socket = Socket::Server("/tmp/mist/http_smooth");
|
Socket::Server server_socket = Socket::Server("/tmp/mist/http_smooth");
|
||||||
if (!server_socket.connected()){return 1;}
|
if ( !server_socket.connected()){
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
conf.activate();
|
conf.activate();
|
||||||
|
|
||||||
while (server_socket.connected() && conf.is_active){
|
while (server_socket.connected() && conf.is_active){
|
||||||
Socket::Connection S = server_socket.accept();
|
Socket::Connection S = server_socket.accept();
|
||||||
if (S.connected()){//check if the new connection is valid
|
if (S.connected()){ //check if the new connection is valid
|
||||||
pid_t myid = fork();
|
pid_t myid = fork();
|
||||||
if (myid == 0){//if new child, start MAINHANDLER
|
if (myid == 0){ //if new child, start MAINHANDLER
|
||||||
return Connector_HTTP::Connector_HTTP_Dynamic(S);
|
return Connector_HTTP::Connector_HTTP_Dynamic(S);
|
||||||
}else{//otherwise, do nothing or output debugging text
|
}else{ //otherwise, do nothing or output debugging text
|
||||||
#if DEBUG >= 3
|
#if DEBUG >= 3
|
||||||
fprintf(stderr, "Spawned new process %i for socket %i\n", (int)myid, S.getSocket());
|
fprintf(stderr, "Spawned new process %i for socket %i\n", (int)myid, S.getSocket());
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}//while connected
|
} //while connected
|
||||||
server_socket.close();
|
server_socket.close();
|
||||||
return 0;
|
return 0;
|
||||||
}//main
|
} //main
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
/// Contains the main code for the RAW connector.
|
/// Contains the main code for the RAW connector.
|
||||||
/// Expects a single commandline argument telling it which stream to connect to,
|
/// Expects a single commandline argument telling it which stream to connect to,
|
||||||
/// then outputs the raw stream to stdout.
|
/// then outputs the raw stream to stdout.
|
||||||
int main(int argc, char ** argv) {
|
int main(int argc, char ** argv){
|
||||||
Util::Config conf(argv[0], PACKAGE_VERSION);
|
Util::Config conf(argv[0], PACKAGE_VERSION);
|
||||||
conf.addOption("stream_name", JSON::fromString("{\"arg_num\":1, \"help\":\"Name of the stream to write to stdout.\"}"));
|
conf.addOption("stream_name", JSON::fromString("{\"arg_num\":1, \"help\":\"Name of the stream to write to stdout.\"}"));
|
||||||
conf.parseArgs(argc, argv);
|
conf.parseArgs(argc, argv);
|
||||||
|
@ -19,20 +19,20 @@ int main(int argc, char ** argv) {
|
||||||
//connect to the proper stream
|
//connect to the proper stream
|
||||||
Socket::Connection S = Util::Stream::getStream(conf.getString("stream_name"));
|
Socket::Connection S = Util::Stream::getStream(conf.getString("stream_name"));
|
||||||
S.setBlocking(false);
|
S.setBlocking(false);
|
||||||
if (!S.connected()){
|
if ( !S.connected()){
|
||||||
std::cout << "Could not open stream " << conf.getString("stream_name") << std::endl;
|
std::cout << "Could not open stream " << conf.getString("stream_name") << std::endl;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
long long int lastStats = 0;
|
long long int lastStats = 0;
|
||||||
long long int started = Util::epoch();
|
long long int started = Util::epoch();
|
||||||
while(std::cout.good()){
|
while (std::cout.good()){
|
||||||
if (S.spool()){
|
if (S.spool()){
|
||||||
while (S.Received().size()){
|
while (S.Received().size()){
|
||||||
std::cout.write(S.Received().get().c_str(),S.Received().get().size());
|
std::cout.write(S.Received().get().c_str(), S.Received().get().size());
|
||||||
S.Received().get().clear();
|
S.Received().get().clear();
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
Util::sleep(10);//sleep 10ms if no data
|
Util::sleep(10); //sleep 10ms if no data
|
||||||
}
|
}
|
||||||
unsigned int now = Util::epoch();
|
unsigned int now = Util::epoch();
|
||||||
if (now != lastStats){
|
if (now != lastStats){
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
#include <mist/timing.h>
|
#include <mist/timing.h>
|
||||||
|
|
||||||
/// Holds all functions and data unique to the RTMP Connector
|
/// Holds all functions and data unique to the RTMP Connector
|
||||||
namespace Connector_RTMP{
|
namespace Connector_RTMP {
|
||||||
|
|
||||||
//for connection to server
|
//for connection to server
|
||||||
bool ready4data = false; ///< Set to true when streaming starts.
|
bool ready4data = false; ///< Set to true when streaming starts.
|
||||||
|
@ -34,17 +34,16 @@ namespace Connector_RTMP{
|
||||||
int play_msgtype = -1;
|
int play_msgtype = -1;
|
||||||
|
|
||||||
//generic state keeping
|
//generic state keeping
|
||||||
bool stream_inited = false;///true if init data for audio/video was sent
|
bool stream_inited = false; ///true if init data for audio/video was sent
|
||||||
|
|
||||||
Socket::Connection Socket; ///< Socket connected to user
|
Socket::Connection Socket; ///< Socket connected to user
|
||||||
Socket::Connection SS; ///< Socket connected to server
|
Socket::Connection SS; ///< Socket connected to server
|
||||||
std::string streamname; ///< Stream that will be opened
|
std::string streamname; ///< Stream that will be opened
|
||||||
void parseChunk(Socket::Buffer & buffer);///< Parses a single RTMP chunk.
|
void parseChunk(Socket::Buffer & buffer); ///< Parses a single RTMP chunk.
|
||||||
void sendCommand(AMF::Object & amfreply, int messagetype, int stream_id);///< Sends a RTMP command either in AMF or AMF3 mode.
|
void sendCommand(AMF::Object & amfreply, int messagetype, int stream_id); ///< Sends a RTMP command either in AMF or AMF3 mode.
|
||||||
void parseAMFCommand(AMF::Object & amfdata, int messagetype, int stream_id);///< Parses a single AMF command message.
|
void parseAMFCommand(AMF::Object & amfdata, int messagetype, int stream_id); ///< Parses a single AMF command message.
|
||||||
int Connector_RTMP(Socket::Connection conn);
|
int Connector_RTMP(Socket::Connection conn);
|
||||||
};//Connector_RTMP namespace;
|
} //Connector_RTMP namespace;
|
||||||
|
|
||||||
|
|
||||||
/// Main Connector_RTMP function
|
/// Main Connector_RTMP function
|
||||||
int Connector_RTMP::Connector_RTMP(Socket::Connection conn){
|
int Connector_RTMP::Connector_RTMP(Socket::Connection conn){
|
||||||
|
@ -53,22 +52,28 @@ int Connector_RTMP::Connector_RTMP(Socket::Connection conn){
|
||||||
FLV::Tag tag, init_tag;
|
FLV::Tag tag, init_tag;
|
||||||
DTSC::Stream Strm;
|
DTSC::Stream Strm;
|
||||||
|
|
||||||
while (!Socket.Received().available(1537) && Socket.connected()){Socket.spool(); Util::sleep(5);}
|
while ( !Socket.Received().available(1537) && Socket.connected()){
|
||||||
|
Socket.spool();
|
||||||
|
Util::sleep(5);
|
||||||
|
}
|
||||||
RTMPStream::handshake_in = Socket.Received().remove(1537);
|
RTMPStream::handshake_in = Socket.Received().remove(1537);
|
||||||
RTMPStream::rec_cnt += 1537;
|
RTMPStream::rec_cnt += 1537;
|
||||||
|
|
||||||
if (RTMPStream::doHandshake()){
|
if (RTMPStream::doHandshake()){
|
||||||
Socket.SendNow(RTMPStream::handshake_out);
|
Socket.SendNow(RTMPStream::handshake_out);
|
||||||
while (!Socket.Received().available(1536) && Socket.connected()){Socket.spool(); Util::sleep(5);}
|
while ( !Socket.Received().available(1536) && Socket.connected()){
|
||||||
|
Socket.spool();
|
||||||
|
Util::sleep(5);
|
||||||
|
}
|
||||||
Socket.Received().remove(1536);
|
Socket.Received().remove(1536);
|
||||||
RTMPStream::rec_cnt += 1536;
|
RTMPStream::rec_cnt += 1536;
|
||||||
#if DEBUG >= 4
|
#if DEBUG >= 4
|
||||||
fprintf(stderr, "Handshake succcess!\n");
|
fprintf(stderr, "Handshake succcess!\n");
|
||||||
#endif
|
#endif
|
||||||
}else{
|
}else{
|
||||||
#if DEBUG >= 1
|
#if DEBUG >= 1
|
||||||
fprintf(stderr, "Handshake fail!\n");
|
fprintf(stderr, "Handshake fail!\n");
|
||||||
#endif
|
#endif
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,23 +85,23 @@ int Connector_RTMP::Connector_RTMP(Socket::Connection conn){
|
||||||
parseChunk(Socket.Received());
|
parseChunk(Socket.Received());
|
||||||
firsttime = false;
|
firsttime = false;
|
||||||
}else{
|
}else{
|
||||||
Util::sleep(1);//sleep 1ms to prevent high CPU usage
|
Util::sleep(1); //sleep 1ms to prevent high CPU usage
|
||||||
}
|
}
|
||||||
if (ready4data){
|
if (ready4data){
|
||||||
if (!inited){
|
if ( !inited){
|
||||||
//we are ready, connect the socket!
|
//we are ready, connect the socket!
|
||||||
SS = Util::Stream::getStream(streamname);
|
SS = Util::Stream::getStream(streamname);
|
||||||
if (!SS.connected()){
|
if ( !SS.connected()){
|
||||||
#if DEBUG >= 1
|
#if DEBUG >= 1
|
||||||
fprintf(stderr, "Could not connect to server!\n");
|
fprintf(stderr, "Could not connect to server!\n");
|
||||||
#endif
|
#endif
|
||||||
Socket.close();//disconnect user
|
Socket.close(); //disconnect user
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
SS.setBlocking(false);
|
SS.setBlocking(false);
|
||||||
#if DEBUG >= 3
|
#if DEBUG >= 3
|
||||||
fprintf(stderr, "Everything connected, starting to send video data...\n");
|
fprintf(stderr, "Everything connected, starting to send video data...\n");
|
||||||
#endif
|
#endif
|
||||||
SS.SendNow("p\n");
|
SS.SendNow("p\n");
|
||||||
inited = true;
|
inited = true;
|
||||||
}
|
}
|
||||||
|
@ -112,10 +117,10 @@ int Connector_RTMP::Connector_RTMP(Socket::Connection conn){
|
||||||
if (play_trans != -1){
|
if (play_trans != -1){
|
||||||
//send a status reply
|
//send a status reply
|
||||||
AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER);
|
AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER);
|
||||||
amfreply.addContent(AMF::Object("", "onStatus"));//status reply
|
amfreply.addContent(AMF::Object("", "onStatus")); //status reply
|
||||||
amfreply.addContent(AMF::Object("", (double)play_trans));//same transaction ID
|
amfreply.addContent(AMF::Object("", (double)play_trans)); //same transaction ID
|
||||||
amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null - command info
|
amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL)); //null - command info
|
||||||
amfreply.addContent(AMF::Object(""));//info
|
amfreply.addContent(AMF::Object("")); //info
|
||||||
amfreply.getContentP(3)->addContent(AMF::Object("level", "status"));
|
amfreply.getContentP(3)->addContent(AMF::Object("level", "status"));
|
||||||
amfreply.getContentP(3)->addContent(AMF::Object("code", "NetStream.Play.Reset"));
|
amfreply.getContentP(3)->addContent(AMF::Object("code", "NetStream.Play.Reset"));
|
||||||
amfreply.getContentP(3)->addContent(AMF::Object("description", "Playing and resetting..."));
|
amfreply.getContentP(3)->addContent(AMF::Object("description", "Playing and resetting..."));
|
||||||
|
@ -124,31 +129,31 @@ int Connector_RTMP::Connector_RTMP(Socket::Connection conn){
|
||||||
sendCommand(amfreply, play_msgtype, play_streamid);
|
sendCommand(amfreply, play_msgtype, play_streamid);
|
||||||
//send streamisrecorded if stream, well, is recorded.
|
//send streamisrecorded if stream, well, is recorded.
|
||||||
if (Strm.metadata.isMember("length") && Strm.metadata["length"].asInt() > 0){
|
if (Strm.metadata.isMember("length") && Strm.metadata["length"].asInt() > 0){
|
||||||
Socket.Send(RTMPStream::SendUSR(4, 1));//send UCM StreamIsRecorded (4), stream 1
|
Socket.Send(RTMPStream::SendUSR(4, 1)); //send UCM StreamIsRecorded (4), stream 1
|
||||||
}
|
}
|
||||||
//send streambegin
|
//send streambegin
|
||||||
Socket.Send(RTMPStream::SendUSR(0, 1));//send UCM StreamBegin (0), stream 1
|
Socket.Send(RTMPStream::SendUSR(0, 1)); //send UCM StreamBegin (0), stream 1
|
||||||
//and more reply
|
//and more reply
|
||||||
amfreply = AMF::Object("container", AMF::AMF0_DDV_CONTAINER);
|
amfreply = AMF::Object("container", AMF::AMF0_DDV_CONTAINER);
|
||||||
amfreply.addContent(AMF::Object("", "onStatus"));//status reply
|
amfreply.addContent(AMF::Object("", "onStatus")); //status reply
|
||||||
amfreply.addContent(AMF::Object("", (double)play_trans));//same transaction ID
|
amfreply.addContent(AMF::Object("", (double)play_trans)); //same transaction ID
|
||||||
amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null - command info
|
amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL)); //null - command info
|
||||||
amfreply.addContent(AMF::Object(""));//info
|
amfreply.addContent(AMF::Object("")); //info
|
||||||
amfreply.getContentP(3)->addContent(AMF::Object("level", "status"));
|
amfreply.getContentP(3)->addContent(AMF::Object("level", "status"));
|
||||||
amfreply.getContentP(3)->addContent(AMF::Object("code", "NetStream.Play.Start"));
|
amfreply.getContentP(3)->addContent(AMF::Object("code", "NetStream.Play.Start"));
|
||||||
amfreply.getContentP(3)->addContent(AMF::Object("description", "Playing!"));
|
amfreply.getContentP(3)->addContent(AMF::Object("description", "Playing!"));
|
||||||
amfreply.getContentP(3)->addContent(AMF::Object("details", "DDV"));
|
amfreply.getContentP(3)->addContent(AMF::Object("details", "DDV"));
|
||||||
amfreply.getContentP(3)->addContent(AMF::Object("clientid", (double)1337));
|
amfreply.getContentP(3)->addContent(AMF::Object("clientid", (double)1337));
|
||||||
sendCommand(amfreply, play_msgtype, play_streamid);
|
sendCommand(amfreply, play_msgtype, play_streamid);
|
||||||
RTMPStream::chunk_snd_max = 102400;//100KiB
|
RTMPStream::chunk_snd_max = 102400; //100KiB
|
||||||
Socket.Send(RTMPStream::SendCTL(1, RTMPStream::chunk_snd_max));//send chunk size max (msg 1)
|
Socket.Send(RTMPStream::SendCTL(1, RTMPStream::chunk_snd_max)); //send chunk size max (msg 1)
|
||||||
//send dunno?
|
//send dunno?
|
||||||
Socket.Send(RTMPStream::SendUSR(32, 1));//send UCM no clue?, stream 1
|
Socket.Send(RTMPStream::SendUSR(32, 1)); //send UCM no clue?, stream 1
|
||||||
play_trans = -1;
|
play_trans = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
//sent init data if needed
|
//sent init data if needed
|
||||||
if (!stream_inited){
|
if ( !stream_inited){
|
||||||
init_tag.DTSCMetaInit(Strm);
|
init_tag.DTSCMetaInit(Strm);
|
||||||
Socket.SendNow(RTMPStream::SendMedia(init_tag));
|
Socket.SendNow(RTMPStream::SendMedia(init_tag));
|
||||||
if (Strm.metadata.isMember("audio") && Strm.metadata["audio"].isMember("init")){
|
if (Strm.metadata.isMember("audio") && Strm.metadata["audio"].isMember("init")){
|
||||||
|
@ -164,9 +169,9 @@ int Connector_RTMP::Connector_RTMP(Socket::Connection conn){
|
||||||
//sent a tag
|
//sent a tag
|
||||||
tag.DTSCLoader(Strm);
|
tag.DTSCLoader(Strm);
|
||||||
Socket.SendNow(RTMPStream::SendMedia(tag));
|
Socket.SendNow(RTMPStream::SendMedia(tag));
|
||||||
#if DEBUG >= 8
|
#if DEBUG >= 8
|
||||||
fprintf(stderr, "Sent tag to %i: [%u] %s\n", Socket.getSocket(), tag.tagTime(), tag.tagType().c_str());
|
fprintf(stderr, "Sent tag to %i: [%u] %s\n", Socket.getSocket(), tag.tagTime(), tag.tagType().c_str());
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -174,8 +179,10 @@ int Connector_RTMP::Connector_RTMP(Socket::Connection conn){
|
||||||
Socket.close();
|
Socket.close();
|
||||||
SS.SendNow(Socket.getStats("RTMP").c_str());
|
SS.SendNow(Socket.getStats("RTMP").c_str());
|
||||||
SS.close();
|
SS.close();
|
||||||
#if DEBUG >= 1
|
#if DEBUG >= 1
|
||||||
if (FLV::Parse_Error){fprintf(stderr, "FLV Parse Error: %s\n", FLV::Error_Str.c_str());}
|
if (FLV::Parse_Error){
|
||||||
|
fprintf(stderr, "FLV Parse Error: %s\n", FLV::Error_Str.c_str());
|
||||||
|
}
|
||||||
fprintf(stderr, "User %i disconnected.\n", conn.getSocket());
|
fprintf(stderr, "User %i disconnected.\n", conn.getSocket());
|
||||||
if (inited){
|
if (inited){
|
||||||
fprintf(stderr, "Status was: inited\n");
|
fprintf(stderr, "Status was: inited\n");
|
||||||
|
@ -186,9 +193,9 @@ int Connector_RTMP::Connector_RTMP(Socket::Connection conn){
|
||||||
fprintf(stderr, "Status was: connected\n");
|
fprintf(stderr, "Status was: connected\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
return 0;
|
return 0;
|
||||||
}//Connector_RTMP
|
} //Connector_RTMP
|
||||||
|
|
||||||
/// Tries to get and parse one RTMP chunk at a time.
|
/// Tries to get and parse one RTMP chunk at a time.
|
||||||
void Connector_RTMP::parseChunk(Socket::Buffer & inbuffer){
|
void Connector_RTMP::parseChunk(Socket::Buffer & inbuffer){
|
||||||
|
@ -210,35 +217,35 @@ void Connector_RTMP::parseChunk(Socket::Buffer & inbuffer){
|
||||||
//send ACK if we received a whole window
|
//send ACK if we received a whole window
|
||||||
if ((RTMPStream::rec_cnt - RTMPStream::rec_window_at > RTMPStream::rec_window_size)){
|
if ((RTMPStream::rec_cnt - RTMPStream::rec_window_at > RTMPStream::rec_window_size)){
|
||||||
RTMPStream::rec_window_at = RTMPStream::rec_cnt;
|
RTMPStream::rec_window_at = RTMPStream::rec_cnt;
|
||||||
Socket.Send(RTMPStream::SendCTL(3, RTMPStream::rec_cnt));//send ack (msg 3)
|
Socket.Send(RTMPStream::SendCTL(3, RTMPStream::rec_cnt)); //send ack (msg 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (next.msg_type_id){
|
switch (next.msg_type_id){
|
||||||
case 0://does not exist
|
case 0: //does not exist
|
||||||
#if DEBUG >= 2
|
#if DEBUG >= 2
|
||||||
fprintf(stderr, "UNKN: Received a zero-type message. This is an error.\n");
|
fprintf(stderr, "UNKN: Received a zero-type message. This is an error.\n");
|
||||||
#endif
|
#endif
|
||||||
break;//happens when connection breaks unexpectedly
|
break; //happens when connection breaks unexpectedly
|
||||||
case 1://set chunk size
|
case 1: //set chunk size
|
||||||
RTMPStream::chunk_rec_max = ntohl(*(int*)next.data.c_str());
|
RTMPStream::chunk_rec_max = ntohl(*(int*)next.data.c_str());
|
||||||
#if DEBUG >= 4
|
#if DEBUG >= 4
|
||||||
fprintf(stderr, "CTRL: Set chunk size: %i\n", RTMPStream::chunk_rec_max);
|
fprintf(stderr, "CTRL: Set chunk size: %i\n", RTMPStream::chunk_rec_max);
|
||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
case 2://abort message - we ignore this one
|
case 2: //abort message - we ignore this one
|
||||||
#if DEBUG >= 4
|
#if DEBUG >= 4
|
||||||
fprintf(stderr, "CTRL: Abort message\n");
|
fprintf(stderr, "CTRL: Abort message\n");
|
||||||
#endif
|
#endif
|
||||||
//4 bytes of stream id to drop
|
//4 bytes of stream id to drop
|
||||||
break;
|
break;
|
||||||
case 3://ack
|
case 3: //ack
|
||||||
#if DEBUG >= 4
|
#if DEBUG >= 4
|
||||||
fprintf(stderr, "CTRL: Acknowledgement\n");
|
fprintf(stderr, "CTRL: Acknowledgement\n");
|
||||||
#endif
|
#endif
|
||||||
RTMPStream::snd_window_at = ntohl(*(int*)next.data.c_str());
|
RTMPStream::snd_window_at = ntohl(*(int*)next.data.c_str());
|
||||||
RTMPStream::snd_window_at = RTMPStream::snd_cnt;
|
RTMPStream::snd_window_at = RTMPStream::snd_cnt;
|
||||||
break;
|
break;
|
||||||
case 4:{
|
case 4: {
|
||||||
//2 bytes event type, rest = event data
|
//2 bytes event type, rest = event data
|
||||||
//types:
|
//types:
|
||||||
//0 = stream begin, 4 bytes ID
|
//0 = stream begin, 4 bytes ID
|
||||||
|
@ -249,50 +256,67 @@ void Connector_RTMP::parseChunk(Socket::Buffer & inbuffer){
|
||||||
//6 = pingrequest, 4 bytes data
|
//6 = pingrequest, 4 bytes data
|
||||||
//7 = pingresponse, 4 bytes data
|
//7 = pingresponse, 4 bytes data
|
||||||
//we don't need to process this
|
//we don't need to process this
|
||||||
#if DEBUG >= 4
|
#if DEBUG >= 4
|
||||||
short int ucmtype = ntohs(*(short int*)next.data.c_str());
|
short int ucmtype = ntohs(*(short int*)next.data.c_str());
|
||||||
switch (ucmtype){
|
switch (ucmtype){
|
||||||
case 0: fprintf(stderr, "CTRL: UCM StreamBegin %i\n", ntohl(*((int*)(next.data.c_str()+2)))); break;
|
case 0:
|
||||||
case 1: fprintf(stderr, "CTRL: UCM StreamEOF %i\n", ntohl(*((int*)(next.data.c_str()+2)))); break;
|
fprintf(stderr, "CTRL: UCM StreamBegin %i\n", ntohl(*((int*)(next.data.c_str()+2))));
|
||||||
case 2: fprintf(stderr, "CTRL: UCM StreamDry %i\n", ntohl(*((int*)(next.data.c_str()+2)))); break;
|
break;
|
||||||
case 3: fprintf(stderr, "CTRL: UCM SetBufferLength %i %i\n", ntohl(*((int*)(next.data.c_str()+2))), ntohl(*((int*)(next.data.c_str()+6)))); break;
|
case 1:
|
||||||
case 4: fprintf(stderr, "CTRL: UCM StreamIsRecorded %i\n", ntohl(*((int*)(next.data.c_str()+2)))); break;
|
fprintf(stderr, "CTRL: UCM StreamEOF %i\n", ntohl(*((int*)(next.data.c_str()+2))));
|
||||||
case 6: fprintf(stderr, "CTRL: UCM PingRequest %i\n", ntohl(*((int*)(next.data.c_str()+2)))); break;
|
break;
|
||||||
case 7: fprintf(stderr, "CTRL: UCM PingResponse %i\n", ntohl(*((int*)(next.data.c_str()+2)))); break;
|
case 2:
|
||||||
default: fprintf(stderr, "CTRL: UCM Unknown (%hi)\n", ucmtype); break;
|
fprintf(stderr, "CTRL: UCM StreamDry %i\n", ntohl(*((int*)(next.data.c_str()+2))));
|
||||||
}
|
break;
|
||||||
#endif
|
case 3:
|
||||||
} break;
|
fprintf(stderr, "CTRL: UCM SetBufferLength %i %i\n", ntohl(*((int*)(next.data.c_str()+2))), ntohl(*((int*)(next.data.c_str()+6))));
|
||||||
case 5://window size of other end
|
break;
|
||||||
#if DEBUG >= 4
|
case 4:
|
||||||
fprintf(stderr, "CTRL: Window size\n");
|
fprintf(stderr, "CTRL: UCM StreamIsRecorded %i\n", ntohl(*((int*)(next.data.c_str()+2))));
|
||||||
#endif
|
|
||||||
RTMPStream::rec_window_size = ntohl(*(int*)next.data.c_str());
|
|
||||||
RTMPStream::rec_window_at = RTMPStream::rec_cnt;
|
|
||||||
Socket.Send(RTMPStream::SendCTL(3, RTMPStream::rec_cnt));//send ack (msg 3)
|
|
||||||
break;
|
break;
|
||||||
case 6:
|
case 6:
|
||||||
#if DEBUG >= 4
|
fprintf(stderr, "CTRL: UCM PingRequest %i\n", ntohl(*((int*)(next.data.c_str()+2))));
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
fprintf(stderr, "CTRL: UCM PingResponse %i\n", ntohl(*((int*)(next.data.c_str()+2))));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
fprintf(stderr, "CTRL: UCM Unknown (%hi)\n", ucmtype);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 5: //window size of other end
|
||||||
|
#if DEBUG >= 4
|
||||||
|
fprintf(stderr, "CTRL: Window size\n");
|
||||||
|
#endif
|
||||||
|
RTMPStream::rec_window_size = ntohl(*(int*)next.data.c_str());
|
||||||
|
RTMPStream::rec_window_at = RTMPStream::rec_cnt;
|
||||||
|
Socket.Send(RTMPStream::SendCTL(3, RTMPStream::rec_cnt)); //send ack (msg 3)
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
#if DEBUG >= 4
|
||||||
fprintf(stderr, "CTRL: Set peer bandwidth\n");
|
fprintf(stderr, "CTRL: Set peer bandwidth\n");
|
||||||
#endif
|
#endif
|
||||||
//4 bytes window size, 1 byte limit type (ignored)
|
//4 bytes window size, 1 byte limit type (ignored)
|
||||||
RTMPStream::snd_window_size = ntohl(*(int*)next.data.c_str());
|
RTMPStream::snd_window_size = ntohl(*(int*)next.data.c_str());
|
||||||
Socket.Send(RTMPStream::SendCTL(5, RTMPStream::snd_window_size));//send window acknowledgement size (msg 5)
|
Socket.Send(RTMPStream::SendCTL(5, RTMPStream::snd_window_size)); //send window acknowledgement size (msg 5)
|
||||||
break;
|
break;
|
||||||
case 8://audio data
|
case 8: //audio data
|
||||||
case 9://video data
|
case 9: //video data
|
||||||
case 18://meta data
|
case 18: //meta data
|
||||||
if (SS.connected()){
|
if (SS.connected()){
|
||||||
F.ChunkLoader(next);
|
F.ChunkLoader(next);
|
||||||
JSON::Value pack_out = F.toJSON(meta_out);
|
JSON::Value pack_out = F.toJSON(meta_out);
|
||||||
if (!pack_out.isNull()){
|
if ( !pack_out.isNull()){
|
||||||
if (!sending){
|
if ( !sending){
|
||||||
counter++;
|
counter++;
|
||||||
if (counter > 8){
|
if (counter > 8){
|
||||||
sending = true;
|
sending = true;
|
||||||
SS.SendNow(meta_out.toNetPacked());
|
SS.SendNow(meta_out.toNetPacked());
|
||||||
SS.SendNow(prebuffer.str().c_str(), prebuffer.str().size());//write buffer
|
SS.SendNow(prebuffer.str().c_str(), prebuffer.str().size()); //write buffer
|
||||||
prebuffer.str("");//clear buffer
|
prebuffer.str(""); //clear buffer
|
||||||
SS.SendNow(pack_out.toNetPacked());
|
SS.SendNow(pack_out.toNetPacked());
|
||||||
}else{
|
}else{
|
||||||
prebuffer << pack_out.toNetPacked();
|
prebuffer << pack_out.toNetPacked();
|
||||||
|
@ -302,115 +326,125 @@ void Connector_RTMP::parseChunk(Socket::Buffer & inbuffer){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
#if DEBUG >= 4
|
#if DEBUG >= 4
|
||||||
fprintf(stderr, "Received useless media data\n");
|
fprintf(stderr, "Received useless media data\n");
|
||||||
#endif
|
#endif
|
||||||
Socket.close();
|
Socket.close();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 15:
|
case 15:
|
||||||
#if DEBUG >= 4
|
#if DEBUG >= 4
|
||||||
fprintf(stderr, "Received AFM3 data message\n");
|
fprintf(stderr, "Received AFM3 data message\n");
|
||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
case 16:
|
case 16:
|
||||||
#if DEBUG >= 4
|
#if DEBUG >= 4
|
||||||
fprintf(stderr, "Received AFM3 shared object\n");
|
fprintf(stderr, "Received AFM3 shared object\n");
|
||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
case 17:{
|
case 17: {
|
||||||
#if DEBUG >= 4
|
#if DEBUG >= 4
|
||||||
fprintf(stderr, "Received AFM3 command message\n");
|
fprintf(stderr, "Received AFM3 command message\n");
|
||||||
#endif
|
#endif
|
||||||
if (next.data[0] != 0){
|
if (next.data[0] != 0){
|
||||||
next.data = next.data.substr(1);
|
next.data = next.data.substr(1);
|
||||||
amf3data = AMF::parse3(next.data);
|
amf3data = AMF::parse3(next.data);
|
||||||
#if DEBUG >= 4
|
#if DEBUG >= 4
|
||||||
amf3data.Print();
|
amf3data.Print();
|
||||||
#endif
|
#endif
|
||||||
}else{
|
}else{
|
||||||
#if DEBUG >= 4
|
#if DEBUG >= 4
|
||||||
fprintf(stderr, "Received AFM3-0 command message\n");
|
fprintf(stderr, "Received AFM3-0 command message\n");
|
||||||
#endif
|
#endif
|
||||||
next.data = next.data.substr(1);
|
next.data = next.data.substr(1);
|
||||||
amfdata = AMF::parse(next.data);
|
amfdata = AMF::parse(next.data);
|
||||||
parseAMFCommand(amfdata, 17, next.msg_stream_id);
|
parseAMFCommand(amfdata, 17, next.msg_stream_id);
|
||||||
}//parsing AMF0-style
|
} //parsing AMF0-style
|
||||||
} break;
|
}
|
||||||
case 19:
|
|
||||||
#if DEBUG >= 4
|
|
||||||
fprintf(stderr, "Received AFM0 shared object\n");
|
|
||||||
#endif
|
|
||||||
break;
|
break;
|
||||||
case 20:{//AMF0 command message
|
case 19:
|
||||||
|
#if DEBUG >= 4
|
||||||
|
fprintf(stderr, "Received AFM0 shared object\n");
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
case 20: { //AMF0 command message
|
||||||
amfdata = AMF::parse(next.data);
|
amfdata = AMF::parse(next.data);
|
||||||
parseAMFCommand(amfdata, 20, next.msg_stream_id);
|
parseAMFCommand(amfdata, 20, next.msg_stream_id);
|
||||||
} break;
|
}
|
||||||
|
break;
|
||||||
case 22:
|
case 22:
|
||||||
#if DEBUG >= 4
|
#if DEBUG >= 4
|
||||||
fprintf(stderr, "Received aggregate message\n");
|
fprintf(stderr, "Received aggregate message\n");
|
||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
#if DEBUG >= 1
|
#if DEBUG >= 1
|
||||||
fprintf(stderr, "Unknown chunk received! Probably protocol corruption, stopping parsing of incoming data.\n");
|
fprintf(stderr, "Unknown chunk received! Probably protocol corruption, stopping parsing of incoming data.\n");
|
||||||
#endif
|
#endif
|
||||||
Connector_RTMP::stopparsing = true;
|
Connector_RTMP::stopparsing = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}//parseChunk
|
} //parseChunk
|
||||||
|
|
||||||
void Connector_RTMP::sendCommand(AMF::Object & amfreply, int messagetype, int stream_id){
|
void Connector_RTMP::sendCommand(AMF::Object & amfreply, int messagetype, int stream_id){
|
||||||
#if DEBUG >= 4
|
#if DEBUG >= 4
|
||||||
std::cerr << amfreply.Print() << std::endl;
|
std::cerr << amfreply.Print() << std::endl;
|
||||||
#endif
|
#endif
|
||||||
if (messagetype == 17){
|
if (messagetype == 17){
|
||||||
Socket.SendNow(RTMPStream::SendChunk(3, messagetype, stream_id, (char)0+amfreply.Pack()));
|
Socket.SendNow(RTMPStream::SendChunk(3, messagetype, stream_id, (char)0 + amfreply.Pack()));
|
||||||
}else{
|
}else{
|
||||||
Socket.SendNow(RTMPStream::SendChunk(3, messagetype, stream_id, amfreply.Pack()));
|
Socket.SendNow(RTMPStream::SendChunk(3, messagetype, stream_id, amfreply.Pack()));
|
||||||
}
|
}
|
||||||
}//sendCommand
|
} //sendCommand
|
||||||
|
|
||||||
void Connector_RTMP::parseAMFCommand(AMF::Object & amfdata, int messagetype, int stream_id){
|
void Connector_RTMP::parseAMFCommand(AMF::Object & amfdata, int messagetype, int stream_id){
|
||||||
#if DEBUG >= 4
|
#if DEBUG >= 4
|
||||||
fprintf(stderr, "Received command: %s\n", amfdata.Print().c_str());
|
fprintf(stderr, "Received command: %s\n", amfdata.Print().c_str());
|
||||||
#endif
|
#endif
|
||||||
#if DEBUG >= 3
|
#if DEBUG >= 3
|
||||||
fprintf(stderr, "AMF0 command: %s\n", amfdata.getContentP(0)->StrValue().c_str());
|
fprintf(stderr, "AMF0 command: %s\n", amfdata.getContentP(0)->StrValue().c_str());
|
||||||
#endif
|
#endif
|
||||||
if (amfdata.getContentP(0)->StrValue() == "connect"){
|
if (amfdata.getContentP(0)->StrValue() == "connect"){
|
||||||
double objencoding = 0;
|
double objencoding = 0;
|
||||||
if (amfdata.getContentP(2)->getContentP("objectEncoding")){
|
if (amfdata.getContentP(2)->getContentP("objectEncoding")){
|
||||||
objencoding = amfdata.getContentP(2)->getContentP("objectEncoding")->NumValue();
|
objencoding = amfdata.getContentP(2)->getContentP("objectEncoding")->NumValue();
|
||||||
}
|
}
|
||||||
#if DEBUG >= 4
|
#if DEBUG >= 4
|
||||||
int tmpint;
|
int tmpint;
|
||||||
if (amfdata.getContentP(2)->getContentP("videoCodecs")){
|
if (amfdata.getContentP(2)->getContentP("videoCodecs")){
|
||||||
tmpint = (int)amfdata.getContentP(2)->getContentP("videoCodecs")->NumValue();
|
tmpint = (int)amfdata.getContentP(2)->getContentP("videoCodecs")->NumValue();
|
||||||
if (tmpint & 0x04){fprintf(stderr, "Sorensen video support detected\n");}
|
if (tmpint & 0x04){
|
||||||
if (tmpint & 0x80){fprintf(stderr, "H264 video support detected\n");}
|
fprintf(stderr, "Sorensen video support detected\n");
|
||||||
|
}
|
||||||
|
if (tmpint & 0x80){
|
||||||
|
fprintf(stderr, "H264 video support detected\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (amfdata.getContentP(2)->getContentP("audioCodecs")){
|
if (amfdata.getContentP(2)->getContentP("audioCodecs")){
|
||||||
tmpint = (int)amfdata.getContentP(2)->getContentP("audioCodecs")->NumValue();
|
tmpint = (int)amfdata.getContentP(2)->getContentP("audioCodecs")->NumValue();
|
||||||
if (tmpint & 0x04){fprintf(stderr, "MP3 audio support detected\n");}
|
if (tmpint & 0x04){
|
||||||
if (tmpint & 0x400){fprintf(stderr, "AAC audio support detected\n");}
|
fprintf(stderr, "MP3 audio support detected\n");
|
||||||
}
|
}
|
||||||
#endif
|
if (tmpint & 0x400){
|
||||||
|
fprintf(stderr, "AAC audio support detected\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
RTMPStream::chunk_snd_max = 4096;
|
RTMPStream::chunk_snd_max = 4096;
|
||||||
Socket.Send(RTMPStream::SendCTL(1, RTMPStream::chunk_snd_max));//send chunk size max (msg 1)
|
Socket.Send(RTMPStream::SendCTL(1, RTMPStream::chunk_snd_max)); //send chunk size max (msg 1)
|
||||||
Socket.Send(RTMPStream::SendCTL(5, RTMPStream::snd_window_size));//send window acknowledgement size (msg 5)
|
Socket.Send(RTMPStream::SendCTL(5, RTMPStream::snd_window_size)); //send window acknowledgement size (msg 5)
|
||||||
Socket.Send(RTMPStream::SendCTL(6, RTMPStream::rec_window_size));//send rec window acknowledgement size (msg 6)
|
Socket.Send(RTMPStream::SendCTL(6, RTMPStream::rec_window_size)); //send rec window acknowledgement size (msg 6)
|
||||||
Socket.Send(RTMPStream::SendUSR(0, 1));//send UCM StreamBegin (0), stream 1
|
Socket.Send(RTMPStream::SendUSR(0, 1)); //send UCM StreamBegin (0), stream 1
|
||||||
//send a _result reply
|
//send a _result reply
|
||||||
AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER);
|
AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER);
|
||||||
amfreply.addContent(AMF::Object("", "_result"));//result success
|
amfreply.addContent(AMF::Object("", "_result")); //result success
|
||||||
amfreply.addContent(amfdata.getContent(1));//same transaction ID
|
amfreply.addContent(amfdata.getContent(1)); //same transaction ID
|
||||||
amfreply.addContent(AMF::Object(""));//server properties
|
amfreply.addContent(AMF::Object("")); //server properties
|
||||||
amfreply.getContentP(2)->addContent(AMF::Object("fmsVer", "FMS/3,5,5,2004"));
|
amfreply.getContentP(2)->addContent(AMF::Object("fmsVer", "FMS/3,5,5,2004"));
|
||||||
amfreply.getContentP(2)->addContent(AMF::Object("capabilities", (double)31));
|
amfreply.getContentP(2)->addContent(AMF::Object("capabilities", (double)31));
|
||||||
amfreply.getContentP(2)->addContent(AMF::Object("mode", (double)1));
|
amfreply.getContentP(2)->addContent(AMF::Object("mode", (double)1));
|
||||||
amfreply.addContent(AMF::Object(""));//info
|
amfreply.addContent(AMF::Object("")); //info
|
||||||
amfreply.getContentP(3)->addContent(AMF::Object("level", "status"));
|
amfreply.getContentP(3)->addContent(AMF::Object("level", "status"));
|
||||||
amfreply.getContentP(3)->addContent(AMF::Object("code", "NetConnection.Connect.Success"));
|
amfreply.getContentP(3)->addContent(AMF::Object("code", "NetConnection.Connect.Success"));
|
||||||
amfreply.getContentP(3)->addContent(AMF::Object("description", "Connection succeeded."));
|
amfreply.getContentP(3)->addContent(AMF::Object("description", "Connection succeeded."));
|
||||||
|
@ -426,92 +460,94 @@ void Connector_RTMP::parseAMFCommand(AMF::Object & amfdata, int messagetype, int
|
||||||
//amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null
|
//amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null
|
||||||
//sendCommand(amfreply, messagetype, stream_id);
|
//sendCommand(amfreply, messagetype, stream_id);
|
||||||
return;
|
return;
|
||||||
}//connect
|
} //connect
|
||||||
if (amfdata.getContentP(0)->StrValue() == "createStream"){
|
if (amfdata.getContentP(0)->StrValue() == "createStream"){
|
||||||
//send a _result reply
|
//send a _result reply
|
||||||
AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER);
|
AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER);
|
||||||
amfreply.addContent(AMF::Object("", "_result"));//result success
|
amfreply.addContent(AMF::Object("", "_result")); //result success
|
||||||
amfreply.addContent(amfdata.getContent(1));//same transaction ID
|
amfreply.addContent(amfdata.getContent(1)); //same transaction ID
|
||||||
amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null - command info
|
amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL)); //null - command info
|
||||||
amfreply.addContent(AMF::Object("", (double)1));//stream ID - we use 1
|
amfreply.addContent(AMF::Object("", (double)1)); //stream ID - we use 1
|
||||||
sendCommand(amfreply, messagetype, stream_id);
|
sendCommand(amfreply, messagetype, stream_id);
|
||||||
Socket.Send(RTMPStream::SendUSR(0, 1));//send UCM StreamBegin (0), stream 1
|
Socket.Send(RTMPStream::SendUSR(0, 1)); //send UCM StreamBegin (0), stream 1
|
||||||
return;
|
return;
|
||||||
}//createStream
|
} //createStream
|
||||||
if ((amfdata.getContentP(0)->StrValue() == "closeStream") || (amfdata.getContentP(0)->StrValue() == "deleteStream")){
|
if ((amfdata.getContentP(0)->StrValue() == "closeStream") || (amfdata.getContentP(0)->StrValue() == "deleteStream")){
|
||||||
if (SS.connected()){SS.close();}
|
if (SS.connected()){
|
||||||
|
SS.close();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ((amfdata.getContentP(0)->StrValue() == "getStreamLength") || (amfdata.getContentP(0)->StrValue() == "getMovLen")){
|
if ((amfdata.getContentP(0)->StrValue() == "getStreamLength") || (amfdata.getContentP(0)->StrValue() == "getMovLen")){
|
||||||
//send a _result reply
|
//send a _result reply
|
||||||
AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER);
|
AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER);
|
||||||
amfreply.addContent(AMF::Object("", "_result"));//result success
|
amfreply.addContent(AMF::Object("", "_result")); //result success
|
||||||
amfreply.addContent(amfdata.getContent(1));//same transaction ID
|
amfreply.addContent(amfdata.getContent(1)); //same transaction ID
|
||||||
amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null - command info
|
amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL)); //null - command info
|
||||||
amfreply.addContent(AMF::Object("", (double)0));//zero length
|
amfreply.addContent(AMF::Object("", (double)0)); //zero length
|
||||||
sendCommand(amfreply, messagetype, stream_id);
|
sendCommand(amfreply, messagetype, stream_id);
|
||||||
return;
|
return;
|
||||||
}//getStreamLength
|
} //getStreamLength
|
||||||
if ((amfdata.getContentP(0)->StrValue() == "publish")){
|
if ((amfdata.getContentP(0)->StrValue() == "publish")){
|
||||||
if (amfdata.getContentP(3)){
|
if (amfdata.getContentP(3)){
|
||||||
streamname = amfdata.getContentP(3)->StrValue();
|
streamname = amfdata.getContentP(3)->StrValue();
|
||||||
/// \todo implement push for MistPlayer or restrict and change to getLive
|
/// \todo implement push for MistPlayer or restrict and change to getLive
|
||||||
SS = Util::Stream::getStream(streamname);
|
SS = Util::Stream::getStream(streamname);
|
||||||
if (!SS.connected()){
|
if ( !SS.connected()){
|
||||||
#if DEBUG >= 1
|
#if DEBUG >= 1
|
||||||
fprintf(stderr, "Could not connect to server!\n");
|
fprintf(stderr, "Could not connect to server!\n");
|
||||||
#endif
|
#endif
|
||||||
Socket.close();//disconnect user
|
Socket.close(); //disconnect user
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
SS.Send("P ");
|
SS.Send("P ");
|
||||||
SS.Send(Socket.getHost().c_str());
|
SS.Send(Socket.getHost().c_str());
|
||||||
SS.Send("\n");
|
SS.Send("\n");
|
||||||
nostats = true;
|
nostats = true;
|
||||||
#if DEBUG >= 4
|
#if DEBUG >= 4
|
||||||
fprintf(stderr, "Connected to buffer, starting to send data...\n");
|
fprintf(stderr, "Connected to buffer, starting to send data...\n");
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
//send a _result reply
|
//send a _result reply
|
||||||
AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER);
|
AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER);
|
||||||
amfreply.addContent(AMF::Object("", "_result"));//result success
|
amfreply.addContent(AMF::Object("", "_result")); //result success
|
||||||
amfreply.addContent(amfdata.getContent(1));//same transaction ID
|
amfreply.addContent(amfdata.getContent(1)); //same transaction ID
|
||||||
amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null - command info
|
amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL)); //null - command info
|
||||||
amfreply.addContent(AMF::Object("", 1, AMF::AMF0_BOOL));//publish success?
|
amfreply.addContent(AMF::Object("", 1, AMF::AMF0_BOOL)); //publish success?
|
||||||
sendCommand(amfreply, messagetype, stream_id);
|
sendCommand(amfreply, messagetype, stream_id);
|
||||||
Socket.Send(RTMPStream::SendUSR(0, 1));//send UCM StreamBegin (0), stream 1
|
Socket.Send(RTMPStream::SendUSR(0, 1)); //send UCM StreamBegin (0), stream 1
|
||||||
//send a status reply
|
//send a status reply
|
||||||
amfreply = AMF::Object("container", AMF::AMF0_DDV_CONTAINER);
|
amfreply = AMF::Object("container", AMF::AMF0_DDV_CONTAINER);
|
||||||
amfreply.addContent(AMF::Object("", "onStatus"));//status reply
|
amfreply.addContent(AMF::Object("", "onStatus")); //status reply
|
||||||
amfreply.addContent(AMF::Object("", 0, AMF::AMF0_NUMBER));//same transaction ID
|
amfreply.addContent(AMF::Object("", 0, AMF::AMF0_NUMBER)); //same transaction ID
|
||||||
amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null - command info
|
amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL)); //null - command info
|
||||||
amfreply.addContent(AMF::Object(""));//info
|
amfreply.addContent(AMF::Object("")); //info
|
||||||
amfreply.getContentP(3)->addContent(AMF::Object("level", "status"));
|
amfreply.getContentP(3)->addContent(AMF::Object("level", "status"));
|
||||||
amfreply.getContentP(3)->addContent(AMF::Object("code", "NetStream.Publish.Start"));
|
amfreply.getContentP(3)->addContent(AMF::Object("code", "NetStream.Publish.Start"));
|
||||||
amfreply.getContentP(3)->addContent(AMF::Object("description", "Stream is now published!"));
|
amfreply.getContentP(3)->addContent(AMF::Object("description", "Stream is now published!"));
|
||||||
amfreply.getContentP(3)->addContent(AMF::Object("clientid", (double)1337));
|
amfreply.getContentP(3)->addContent(AMF::Object("clientid", (double)1337));
|
||||||
sendCommand(amfreply, messagetype, stream_id);
|
sendCommand(amfreply, messagetype, stream_id);
|
||||||
return;
|
return;
|
||||||
}//getStreamLength
|
} //getStreamLength
|
||||||
if (amfdata.getContentP(0)->StrValue() == "checkBandwidth"){
|
if (amfdata.getContentP(0)->StrValue() == "checkBandwidth"){
|
||||||
//send a _result reply
|
//send a _result reply
|
||||||
AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER);
|
AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER);
|
||||||
amfreply.addContent(AMF::Object("", "_result"));//result success
|
amfreply.addContent(AMF::Object("", "_result")); //result success
|
||||||
amfreply.addContent(amfdata.getContent(1));//same transaction ID
|
amfreply.addContent(amfdata.getContent(1)); //same transaction ID
|
||||||
amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null - command info
|
amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL)); //null - command info
|
||||||
amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null - command info
|
amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL)); //null - command info
|
||||||
sendCommand(amfreply, messagetype, stream_id);
|
sendCommand(amfreply, messagetype, stream_id);
|
||||||
return;
|
return;
|
||||||
}//checkBandwidth
|
} //checkBandwidth
|
||||||
if ((amfdata.getContentP(0)->StrValue() == "play") || (amfdata.getContentP(0)->StrValue() == "play2")){
|
if ((amfdata.getContentP(0)->StrValue() == "play") || (amfdata.getContentP(0)->StrValue() == "play2")){
|
||||||
//set reply number and stream name, actual reply is sent up in the SS.spool() handler
|
//set reply number and stream name, actual reply is sent up in the SS.spool() handler
|
||||||
play_trans = amfdata.getContentP(1)->NumValue();
|
play_trans = amfdata.getContentP(1)->NumValue();
|
||||||
play_msgtype = messagetype;
|
play_msgtype = messagetype;
|
||||||
play_streamid = stream_id;
|
play_streamid = stream_id;
|
||||||
streamname = amfdata.getContentP(3)->StrValue();
|
streamname = amfdata.getContentP(3)->StrValue();
|
||||||
Connector_RTMP::ready4data = true;//start sending video data!
|
Connector_RTMP::ready4data = true; //start sending video data!
|
||||||
return;
|
return;
|
||||||
}//play
|
} //play
|
||||||
if ((amfdata.getContentP(0)->StrValue() == "seek")){
|
if ((amfdata.getContentP(0)->StrValue() == "seek")){
|
||||||
//set reply number and stream name, actual reply is sent up in the SS.spool() handler
|
//set reply number and stream name, actual reply is sent up in the SS.spool() handler
|
||||||
play_trans = amfdata.getContentP(1)->NumValue();
|
play_trans = amfdata.getContentP(1)->NumValue();
|
||||||
|
@ -520,10 +556,10 @@ void Connector_RTMP::parseAMFCommand(AMF::Object & amfdata, int messagetype, int
|
||||||
stream_inited = false;
|
stream_inited = false;
|
||||||
|
|
||||||
AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER);
|
AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER);
|
||||||
amfreply.addContent(AMF::Object("", "onStatus"));//status reply
|
amfreply.addContent(AMF::Object("", "onStatus")); //status reply
|
||||||
amfreply.addContent(amfdata.getContent(1));//same transaction ID
|
amfreply.addContent(amfdata.getContent(1)); //same transaction ID
|
||||||
amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null - command info
|
amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL)); //null - command info
|
||||||
amfreply.addContent(AMF::Object(""));//info
|
amfreply.addContent(AMF::Object("")); //info
|
||||||
amfreply.getContentP(3)->addContent(AMF::Object("level", "status"));
|
amfreply.getContentP(3)->addContent(AMF::Object("level", "status"));
|
||||||
amfreply.getContentP(3)->addContent(AMF::Object("code", "NetStream.Seek.Notify"));
|
amfreply.getContentP(3)->addContent(AMF::Object("code", "NetStream.Seek.Notify"));
|
||||||
amfreply.getContentP(3)->addContent(AMF::Object("description", "Seeking to the specified time"));
|
amfreply.getContentP(3)->addContent(AMF::Object("description", "Seeking to the specified time"));
|
||||||
|
@ -534,16 +570,16 @@ void Connector_RTMP::parseAMFCommand(AMF::Object & amfdata, int messagetype, int
|
||||||
SS.Send(JSON::Value((long long int)amfdata.getContentP(3)->NumValue()).asString().c_str());
|
SS.Send(JSON::Value((long long int)amfdata.getContentP(3)->NumValue()).asString().c_str());
|
||||||
SS.Send("\n");
|
SS.Send("\n");
|
||||||
return;
|
return;
|
||||||
}//seek
|
} //seek
|
||||||
if ((amfdata.getContentP(0)->StrValue() == "pauseRaw") || (amfdata.getContentP(0)->StrValue() == "pause")){
|
if ((amfdata.getContentP(0)->StrValue() == "pauseRaw") || (amfdata.getContentP(0)->StrValue() == "pause")){
|
||||||
if (amfdata.getContentP(3)->NumValue()){
|
if (amfdata.getContentP(3)->NumValue()){
|
||||||
SS.Send("q\n");//quit playing
|
SS.Send("q\n"); //quit playing
|
||||||
//send a status reply
|
//send a status reply
|
||||||
AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER);
|
AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER);
|
||||||
amfreply.addContent(AMF::Object("", "onStatus"));//status reply
|
amfreply.addContent(AMF::Object("", "onStatus")); //status reply
|
||||||
amfreply.addContent(amfdata.getContent(1));//same transaction ID
|
amfreply.addContent(amfdata.getContent(1)); //same transaction ID
|
||||||
amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null - command info
|
amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL)); //null - command info
|
||||||
amfreply.addContent(AMF::Object(""));//info
|
amfreply.addContent(AMF::Object("")); //info
|
||||||
amfreply.getContentP(3)->addContent(AMF::Object("level", "status"));
|
amfreply.getContentP(3)->addContent(AMF::Object("level", "status"));
|
||||||
amfreply.getContentP(3)->addContent(AMF::Object("code", "NetStream.Pause.Notify"));
|
amfreply.getContentP(3)->addContent(AMF::Object("code", "NetStream.Pause.Notify"));
|
||||||
amfreply.getContentP(3)->addContent(AMF::Object("description", "Pausing playback"));
|
amfreply.getContentP(3)->addContent(AMF::Object("description", "Pausing playback"));
|
||||||
|
@ -551,13 +587,13 @@ void Connector_RTMP::parseAMFCommand(AMF::Object & amfdata, int messagetype, int
|
||||||
amfreply.getContentP(3)->addContent(AMF::Object("clientid", (double)1337));
|
amfreply.getContentP(3)->addContent(AMF::Object("clientid", (double)1337));
|
||||||
sendCommand(amfreply, play_msgtype, play_streamid);
|
sendCommand(amfreply, play_msgtype, play_streamid);
|
||||||
}else{
|
}else{
|
||||||
SS.Send("p\n");//start playing
|
SS.Send("p\n"); //start playing
|
||||||
//send a status reply
|
//send a status reply
|
||||||
AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER);
|
AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER);
|
||||||
amfreply.addContent(AMF::Object("", "onStatus"));//status reply
|
amfreply.addContent(AMF::Object("", "onStatus")); //status reply
|
||||||
amfreply.addContent(amfdata.getContent(1));//same transaction ID
|
amfreply.addContent(amfdata.getContent(1)); //same transaction ID
|
||||||
amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null - command info
|
amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL)); //null - command info
|
||||||
amfreply.addContent(AMF::Object(""));//info
|
amfreply.addContent(AMF::Object("")); //info
|
||||||
amfreply.getContentP(3)->addContent(AMF::Object("level", "status"));
|
amfreply.getContentP(3)->addContent(AMF::Object("level", "status"));
|
||||||
amfreply.getContentP(3)->addContent(AMF::Object("code", "NetStream.Unpause.Notify"));
|
amfreply.getContentP(3)->addContent(AMF::Object("code", "NetStream.Unpause.Notify"));
|
||||||
amfreply.getContentP(3)->addContent(AMF::Object("description", "Resuming playback"));
|
amfreply.getContentP(3)->addContent(AMF::Object("description", "Resuming playback"));
|
||||||
|
@ -566,34 +602,36 @@ void Connector_RTMP::parseAMFCommand(AMF::Object & amfdata, int messagetype, int
|
||||||
sendCommand(amfreply, play_msgtype, play_streamid);
|
sendCommand(amfreply, play_msgtype, play_streamid);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}//seek
|
} //seek
|
||||||
|
|
||||||
#if DEBUG >= 2
|
#if DEBUG >= 2
|
||||||
fprintf(stderr, "AMF0 command not processed! :(\n");
|
fprintf(stderr, "AMF0 command not processed! :(\n");
|
||||||
#endif
|
#endif
|
||||||
}//parseAMFCommand
|
} //parseAMFCommand
|
||||||
|
|
||||||
int main(int argc, char ** argv){
|
int main(int argc, char ** argv){
|
||||||
Util::Config conf(argv[0], PACKAGE_VERSION);
|
Util::Config conf(argv[0], PACKAGE_VERSION);
|
||||||
conf.addConnectorOptions(1935);
|
conf.addConnectorOptions(1935);
|
||||||
conf.parseArgs(argc, argv);
|
conf.parseArgs(argc, argv);
|
||||||
Socket::Server server_socket = Socket::Server(conf.getInteger("listen_port"), conf.getString("listen_interface"));
|
Socket::Server server_socket = Socket::Server(conf.getInteger("listen_port"), conf.getString("listen_interface"));
|
||||||
if (!server_socket.connected()){return 1;}
|
if ( !server_socket.connected()){
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
conf.activate();
|
conf.activate();
|
||||||
|
|
||||||
while (server_socket.connected() && conf.is_active){
|
while (server_socket.connected() && conf.is_active){
|
||||||
Socket::Connection S = server_socket.accept();
|
Socket::Connection S = server_socket.accept();
|
||||||
if (S.connected()){//check if the new connection is valid
|
if (S.connected()){ //check if the new connection is valid
|
||||||
pid_t myid = fork();
|
pid_t myid = fork();
|
||||||
if (myid == 0){//if new child, start MAINHANDLER
|
if (myid == 0){ //if new child, start MAINHANDLER
|
||||||
return Connector_RTMP::Connector_RTMP(S);
|
return Connector_RTMP::Connector_RTMP(S);
|
||||||
}else{//otherwise, do nothing or output debugging text
|
}else{ //otherwise, do nothing or output debugging text
|
||||||
#if DEBUG >= 3
|
#if DEBUG >= 3
|
||||||
fprintf(stderr, "Spawned new process %i for socket %i\n", (int)myid, S.getSocket());
|
fprintf(stderr, "Spawned new process %i for socket %i\n", (int)myid, S.getSocket());
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}//while connected
|
} //while connected
|
||||||
server_socket.close();
|
server_socket.close();
|
||||||
return 0;
|
return 0;
|
||||||
}//main
|
} //main
|
||||||
|
|
|
@ -2,26 +2,8 @@
|
||||||
/// Contains all code for the controller executable.
|
/// Contains all code for the controller executable.
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <fstream>
|
|
||||||
#include <string>
|
|
||||||
#include <sstream>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <map>
|
|
||||||
#include <set>
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <queue>
|
|
||||||
#include <cmath>
|
|
||||||
#include <cstdio>
|
|
||||||
#include <climits>
|
|
||||||
#include <cstring>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <getopt.h>
|
|
||||||
#include <set>
|
|
||||||
#include <sys/wait.h>
|
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <sys/types.h>
|
|
||||||
#include <signal.h>
|
|
||||||
#include <sstream>
|
|
||||||
#include <mist/config.h>
|
#include <mist/config.h>
|
||||||
#include <mist/socket.h>
|
#include <mist/socket.h>
|
||||||
#include <mist/http_parser.h>
|
#include <mist/http_parser.h>
|
||||||
|
@ -30,26 +12,19 @@
|
||||||
#include <mist/timing.h>
|
#include <mist/timing.h>
|
||||||
#include "controller_storage.h"
|
#include "controller_storage.h"
|
||||||
#include "controller_connectors.h"
|
#include "controller_connectors.h"
|
||||||
|
#include "controller_streams.h"
|
||||||
|
#include "controller_capabilities.h"
|
||||||
#include "server.html.h"
|
#include "server.html.h"
|
||||||
|
|
||||||
#define UPLINK_INTERVAL 30
|
#define UPLINK_INTERVAL 30
|
||||||
#define COMPILED_USERNAME ""
|
#define COMPILED_USERNAME ""
|
||||||
#define COMPILED_PASSWORD ""
|
#define COMPILED_PASSWORD ""
|
||||||
|
|
||||||
namespace Controller{
|
namespace Controller {
|
||||||
|
|
||||||
std::map<std::string, int> lastBuffer; ///< Last moment of contact with all buffers.
|
Secure::Auth keychecker; ///< Checks key authorization.
|
||||||
Secure::Auth keychecker; ///< Checks key authorization.
|
|
||||||
|
|
||||||
|
class ConnectedUser{
|
||||||
void WriteFile( std::string Filename, std::string contents ) {
|
|
||||||
std::ofstream File;
|
|
||||||
File.open( Filename.c_str( ) );
|
|
||||||
File << contents << std::endl;
|
|
||||||
File.close( );
|
|
||||||
}
|
|
||||||
|
|
||||||
class ConnectedUser{
|
|
||||||
public:
|
public:
|
||||||
Socket::Connection C;
|
Socket::Connection C;
|
||||||
HTTP::Parser H;
|
HTTP::Parser H;
|
||||||
|
@ -64,16 +39,16 @@ class ConnectedUser{
|
||||||
Authorized = false;
|
Authorized = false;
|
||||||
clientMode = false;
|
clientMode = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
void Authorize( JSON::Value & Request, JSON::Value & Response, ConnectedUser & conn ) {
|
void Authorize(JSON::Value & Request, JSON::Value & Response, ConnectedUser & conn){
|
||||||
time_t Time = time(0);
|
time_t Time = time(0);
|
||||||
tm * TimeInfo = localtime(&Time);
|
tm * TimeInfo = localtime( &Time);
|
||||||
std::stringstream Date;
|
std::stringstream Date;
|
||||||
std::string retval;
|
std::string retval;
|
||||||
Date << TimeInfo->tm_mday << "-" << TimeInfo->tm_mon << "-" << TimeInfo->tm_year + 1900;
|
Date << TimeInfo->tm_mday << "-" << TimeInfo->tm_mon << "-" << TimeInfo->tm_year + 1900;
|
||||||
std::string Challenge = Secure::md5( Date.str().c_str() + conn.C.getHost() );
|
std::string Challenge = Secure::md5(Date.str().c_str() + conn.C.getHost());
|
||||||
if( Request.isMember( "authorize" ) ) {
|
if (Request.isMember("authorize")){
|
||||||
std::string UserID = Request["authorize"]["username"];
|
std::string UserID = Request["authorize"]["username"];
|
||||||
if (Storage["account"].isMember(UserID)){
|
if (Storage["account"].isMember(UserID)){
|
||||||
if (Secure::md5(Storage["account"][UserID]["password"].asString() + Challenge) == Request["authorize"]["password"].asString()){
|
if (Secure::md5(Storage["account"][UserID]["password"].asString() + Challenge) == Request["authorize"]["password"].asString()){
|
||||||
|
@ -84,8 +59,9 @@ void Authorize( JSON::Value & Request, JSON::Value & Response, ConnectedUser & c
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (UserID != ""){
|
if (UserID != ""){
|
||||||
if (Request["authorize"]["password"].asString() != "" && Secure::md5(Storage["account"][UserID]["password"].asString()) != Request["authorize"]["password"].asString()){
|
if (Request["authorize"]["password"].asString() != ""
|
||||||
Log("AUTH", "Failed login attempt "+UserID+" @ "+conn.C.getHost());
|
&& Secure::md5(Storage["account"][UserID]["password"].asString()) != Request["authorize"]["password"].asString()){
|
||||||
|
Log("AUTH", "Failed login attempt " + UserID + " @ " + conn.C.getHost());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
conn.logins++;
|
conn.logins++;
|
||||||
|
@ -95,70 +71,29 @@ void Authorize( JSON::Value & Request, JSON::Value & Response, ConnectedUser & c
|
||||||
Response["authorize"]["status"] = "CHALL";
|
Response["authorize"]["status"] = "CHALL";
|
||||||
Response["authorize"]["challenge"] = Challenge;
|
Response["authorize"]["challenge"] = Challenge;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CheckConfig(JSON::Value & in, JSON::Value & out){
|
void CheckConfig(JSON::Value & in, JSON::Value & out){
|
||||||
for (JSON::ObjIter jit = in.ObjBegin(); jit != in.ObjEnd(); jit++){
|
for (JSON::ObjIter jit = in.ObjBegin(); jit != in.ObjEnd(); jit++){
|
||||||
if (out.isMember(jit->first)){
|
if (out.isMember(jit->first)){
|
||||||
if (jit->second != out[jit->first]){
|
if (jit->second != out[jit->first]){
|
||||||
if (jit->first != "time"){
|
if (jit->first != "time"){
|
||||||
Log("CONF", std::string("Updated configuration value ")+jit->first);
|
Log("CONF", std::string("Updated configuration value ") + jit->first);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
Log("CONF", std::string("New configuration value ")+jit->first);
|
Log("CONF", std::string("New configuration value ") + jit->first);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (JSON::ObjIter jit = out.ObjBegin(); jit != out.ObjEnd(); jit++){
|
for (JSON::ObjIter jit = out.ObjBegin(); jit != out.ObjEnd(); jit++){
|
||||||
if (!in.isMember(jit->first)){
|
if ( !in.isMember(jit->first)){
|
||||||
Log("CONF", std::string("Deleted configuration value ")+jit->first);
|
Log("CONF", std::string("Deleted configuration value ") + jit->first);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
out = in;
|
out = in;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool streamsEqual(JSON::Value & one, JSON::Value & two){
|
void CheckStats(JSON::Value & stats){
|
||||||
if (one["channel"]["URL"] != two["channel"]["URL"]){return false;}
|
|
||||||
if (one["preset"]["cmd"] != two["preset"]["cmd"]){return false;}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void startStream(std::string name, JSON::Value & data){
|
|
||||||
std::string URL = data["channel"]["URL"];
|
|
||||||
std::string preset = data["preset"]["cmd"];
|
|
||||||
std::string cmd1, cmd2, cmd3;
|
|
||||||
if (URL.substr(0, 4) == "push"){
|
|
||||||
std::string pusher = URL.substr(7);
|
|
||||||
cmd2 = "MistBuffer -s "+name+" "+pusher;
|
|
||||||
Util::Procs::Start(name, Util::getMyPath() + cmd2);
|
|
||||||
Log("BUFF", "(re)starting stream buffer "+name+" for push data from "+pusher);
|
|
||||||
}else{
|
|
||||||
if (URL.substr(0, 1) == "/"){
|
|
||||||
struct stat fileinfo;
|
|
||||||
if (stat(URL.c_str(), &fileinfo) != 0 || S_ISDIR(fileinfo.st_mode)){
|
|
||||||
Log("BUFF", "Warning for VoD stream "+name+"! File not found: "+URL);
|
|
||||||
data["error"] = "Not found: "+URL;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cmd1 = "cat "+URL;
|
|
||||||
data["error"] = "Available";
|
|
||||||
return; //MistPlayer handles VoD
|
|
||||||
}else{
|
|
||||||
cmd1 = "ffmpeg -re -async 2 -i "+URL+" "+preset+" -f flv -";
|
|
||||||
cmd2 = "MistFLV2DTSC";
|
|
||||||
}
|
|
||||||
cmd3 = "MistBuffer -s "+name;
|
|
||||||
if (cmd2 != ""){
|
|
||||||
Util::Procs::Start(name, cmd1, Util::getMyPath() + cmd2, Util::getMyPath() + cmd3);
|
|
||||||
Log("BUFF", "(re)starting stream buffer "+name+" for ffmpeg data: "+cmd1);
|
|
||||||
}else{
|
|
||||||
Util::Procs::Start(name, cmd1, Util::getMyPath() + cmd3);
|
|
||||||
Log("BUFF", "(re)starting stream buffer "+name+" using input file "+URL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void CheckStats(JSON::Value & stats){
|
|
||||||
long long int currTime = Util::epoch();
|
long long int currTime = Util::epoch();
|
||||||
for (JSON::ObjIter jit = stats.ObjBegin(); jit != stats.ObjEnd(); jit++){
|
for (JSON::ObjIter jit = stats.ObjBegin(); jit != stats.ObjEnd(); jit++){
|
||||||
if (currTime - lastBuffer[jit->first] > 120){
|
if (currTime - lastBuffer[jit->first] > 120){
|
||||||
|
@ -170,203 +105,49 @@ void CheckStats(JSON::Value & stats){
|
||||||
if (u_it->second.isMember("now") && u_it->second["now"].asInt() < currTime - 3){
|
if (u_it->second.isMember("now") && u_it->second["now"].asInt() < currTime - 3){
|
||||||
jit->second["log"].append(u_it->second);
|
jit->second["log"].append(u_it->second);
|
||||||
jit->second["curr"].removeMember(u_it->first);
|
jit->second["curr"].removeMember(u_it->first);
|
||||||
if (!jit->second["curr"].size()){break;}
|
if ( !jit->second["curr"].size()){
|
||||||
|
break;
|
||||||
|
}
|
||||||
u_it = jit->second["curr"].ObjBegin();
|
u_it = jit->second["curr"].ObjBegin();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class cpudata {
|
} //Controller namespace
|
||||||
public:
|
|
||||||
std::string model;
|
|
||||||
int cores;
|
|
||||||
int threads;
|
|
||||||
int mhz;
|
|
||||||
int id;
|
|
||||||
cpudata(){
|
|
||||||
model = "Unknown";
|
|
||||||
cores = 1;
|
|
||||||
threads = 1;
|
|
||||||
mhz = 0;
|
|
||||||
id = 0;
|
|
||||||
};
|
|
||||||
void fill(char * data){
|
|
||||||
int i;
|
|
||||||
i = 0;
|
|
||||||
if (sscanf(data, "model name : %n", &i) != EOF && i > 0){model = (data+i);}
|
|
||||||
if (sscanf(data, "cpu cores : %d", &i) == 1){cores = i;}
|
|
||||||
if (sscanf(data, "siblings : %d", &i) == 1){threads = i;}
|
|
||||||
if (sscanf(data, "physical id : %d", &i) == 1){id = i;}
|
|
||||||
if (sscanf(data, "cpu MHz : %d", &i) == 1){mhz = i;}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
void checkCapable(JSON::Value & capa){
|
|
||||||
capa.null();
|
|
||||||
std::ifstream cpuinfo("/proc/cpuinfo");
|
|
||||||
if (cpuinfo){
|
|
||||||
std::map<int, cpudata> cpus;
|
|
||||||
char line[300];
|
|
||||||
int proccount = -1;
|
|
||||||
while (cpuinfo.good()){
|
|
||||||
cpuinfo.getline(line, 300);
|
|
||||||
if (cpuinfo.fail()){
|
|
||||||
//empty lines? ignore them, clear flags, continue
|
|
||||||
if (!cpuinfo.eof()){
|
|
||||||
cpuinfo.ignore();
|
|
||||||
cpuinfo.clear();
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (memcmp(line, "processor", 9) == 0){proccount++;}
|
|
||||||
cpus[proccount].fill(line);
|
|
||||||
}
|
|
||||||
//fix wrong core counts
|
|
||||||
std::map<int,int> corecounts;
|
|
||||||
for (int i = 0; i <= proccount; ++i){
|
|
||||||
corecounts[cpus[i].id]++;
|
|
||||||
}
|
|
||||||
//remove double physical IDs - we only want real CPUs.
|
|
||||||
std::set<int> used_physids;
|
|
||||||
int total_speed = 0;
|
|
||||||
int total_threads = 0;
|
|
||||||
for (int i = 0; i <= proccount; ++i){
|
|
||||||
if (!used_physids.count(cpus[i].id)){
|
|
||||||
used_physids.insert(cpus[i].id);
|
|
||||||
JSON::Value thiscpu;
|
|
||||||
thiscpu["model"] = cpus[i].model;
|
|
||||||
thiscpu["cores"] = cpus[i].cores;
|
|
||||||
if (cpus[i].cores < 2 && corecounts[cpus[i].id] > cpus[i].cores){
|
|
||||||
thiscpu["cores"] = corecounts[cpus[i].id];
|
|
||||||
}
|
|
||||||
thiscpu["threads"] = cpus[i].threads;
|
|
||||||
if (thiscpu["cores"].asInt() > thiscpu["threads"].asInt()){
|
|
||||||
thiscpu["threads"] = thiscpu["cores"];
|
|
||||||
}
|
|
||||||
thiscpu["mhz"] = cpus[i].mhz;
|
|
||||||
capa["cpu"].append(thiscpu);
|
|
||||||
total_speed += cpus[i].cores * cpus[i].mhz;
|
|
||||||
total_threads += cpus[i].threads;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
capa["speed"] = total_speed;
|
|
||||||
capa["threads"] = total_threads;
|
|
||||||
}
|
|
||||||
std::ifstream meminfo("/proc/meminfo");
|
|
||||||
if (meminfo){
|
|
||||||
char line[300];
|
|
||||||
int bufcache = 0;
|
|
||||||
while (meminfo.good()){
|
|
||||||
meminfo.getline(line, 300);
|
|
||||||
if (meminfo.fail()){
|
|
||||||
//empty lines? ignore them, clear flags, continue
|
|
||||||
if (!meminfo.eof()){
|
|
||||||
meminfo.ignore();
|
|
||||||
meminfo.clear();
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
long long int i;
|
|
||||||
if (sscanf(line, "MemTotal : %Li kB", &i) == 1){capa["mem"]["total"] = i/1024;}
|
|
||||||
if (sscanf(line, "MemFree : %Li kB", &i) == 1){capa["mem"]["free"] = i/1024;}
|
|
||||||
if (sscanf(line, "SwapTotal : %Li kB", &i) == 1){capa["mem"]["swaptotal"] = i/1024;}
|
|
||||||
if (sscanf(line, "SwapFree : %Li kB", &i) == 1){capa["mem"]["swapfree"] = i/1024;}
|
|
||||||
if (sscanf(line, "Buffers : %Li kB", &i) == 1){bufcache += i/1024;}
|
|
||||||
if (sscanf(line, "Cached : %Li kB", &i) == 1){bufcache += i/1024;}
|
|
||||||
}
|
|
||||||
capa["mem"]["used"] = capa["mem"]["total"].asInt() - capa["mem"]["free"].asInt() - bufcache;
|
|
||||||
capa["mem"]["cached"] = bufcache;
|
|
||||||
capa["load"]["memory"] = ((capa["mem"]["used"].asInt() + (capa["mem"]["swaptotal"].asInt() - capa["mem"]["swapfree"].asInt())) * 100) / capa["mem"]["total"].asInt();
|
|
||||||
}
|
|
||||||
std::ifstream loadavg("/proc/loadavg");
|
|
||||||
if (loadavg){
|
|
||||||
char line[300];
|
|
||||||
int bufcache = 0;
|
|
||||||
loadavg.getline(line, 300);
|
|
||||||
//parse lines here
|
|
||||||
float onemin, fivemin, fifteenmin;
|
|
||||||
if (sscanf(line, "%f %f %f", &onemin, &fivemin, &fifteenmin) == 3){
|
|
||||||
capa["load"]["one"] = (long long int)(onemin * 100);
|
|
||||||
capa["load"]["five"] = (long long int)(onemin * 100);
|
|
||||||
capa["load"]["fifteen"] = (long long int)(onemin * 100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void CheckAllStreams(JSON::Value & data){
|
|
||||||
long long int currTime = Util::epoch();
|
|
||||||
for (JSON::ObjIter jit = data.ObjBegin(); jit != data.ObjEnd(); jit++){
|
|
||||||
if (!Util::Procs::isActive(jit->first)){
|
|
||||||
startStream(jit->first, jit->second);
|
|
||||||
}
|
|
||||||
if (currTime - lastBuffer[jit->first] > 5){
|
|
||||||
if (jit->second.isMember("error") && jit->second["error"].asString() != ""){
|
|
||||||
jit->second["online"] = jit->second["error"];
|
|
||||||
}else{
|
|
||||||
jit->second["online"] = 0;
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
jit->second["online"] = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
static JSON::Value strlist;
|
|
||||||
bool changed = false;
|
|
||||||
if (strlist["config"] != Storage["config"]){
|
|
||||||
strlist["config"] = Storage["config"];
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
if (strlist["streams"] != Storage["streams"]){
|
|
||||||
strlist["streams"] = Storage["streams"];
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
if (changed){WriteFile("/tmp/mist/streamlist", strlist.toString());}
|
|
||||||
}
|
|
||||||
|
|
||||||
void CheckStreams(JSON::Value & in, JSON::Value & out){
|
|
||||||
bool changed = false;
|
|
||||||
for (JSON::ObjIter jit = in.ObjBegin(); jit != in.ObjEnd(); jit++){
|
|
||||||
if (out.isMember(jit->first)){
|
|
||||||
if (!streamsEqual(jit->second, out[jit->first])){
|
|
||||||
Log("STRM", std::string("Updated stream ")+jit->first);
|
|
||||||
Util::Procs::Stop(jit->first);
|
|
||||||
startStream(jit->first, jit->second);
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
Log("STRM", std::string("New stream ")+jit->first);
|
|
||||||
startStream(jit->first, jit->second);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (JSON::ObjIter jit = out.ObjBegin(); jit != out.ObjEnd(); jit++){
|
|
||||||
if (!in.isMember(jit->first)){
|
|
||||||
Log("STRM", std::string("Deleted stream ")+jit->first);
|
|
||||||
Util::Procs::Stop(jit->first);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out = in;
|
|
||||||
}
|
|
||||||
|
|
||||||
}; //Connector namespace
|
|
||||||
|
|
||||||
int main(int argc, char ** argv){
|
int main(int argc, char ** argv){
|
||||||
Controller::Storage = JSON::fromFile("config.json");
|
Controller::Storage = JSON::fromFile("config.json");
|
||||||
JSON::Value stored_port = JSON::fromString("{\"long\":\"port\", \"short\":\"p\", \"arg\":\"integer\", \"help\":\"TCP port to listen on.\"}");
|
JSON::Value stored_port = JSON::fromString("{\"long\":\"port\", \"short\":\"p\", \"arg\":\"integer\", \"help\":\"TCP port to listen on.\"}");
|
||||||
stored_port["default"] = Controller::Storage["config"]["controller"]["port"];
|
stored_port["default"] = Controller::Storage["config"]["controller"]["port"];
|
||||||
if (!stored_port["default"]){stored_port["default"] = 4242;}
|
if ( !stored_port["default"]){
|
||||||
JSON::Value stored_interface = JSON::fromString("{\"long\":\"interface\", \"short\":\"i\", \"arg\":\"string\", \"help\":\"Interface address to listen on, or 0.0.0.0 for all available interfaces.\"}");
|
stored_port["default"] = 4242;
|
||||||
|
}
|
||||||
|
JSON::Value stored_interface =
|
||||||
|
JSON::fromString(
|
||||||
|
"{\"long\":\"interface\", \"short\":\"i\", \"arg\":\"string\", \"help\":\"Interface address to listen on, or 0.0.0.0 for all available interfaces.\"}");
|
||||||
stored_interface["default"] = Controller::Storage["config"]["controller"]["interface"];
|
stored_interface["default"] = Controller::Storage["config"]["controller"]["interface"];
|
||||||
if (!stored_interface["default"]){stored_interface["default"] = "0.0.0.0";}
|
if ( !stored_interface["default"]){
|
||||||
JSON::Value stored_user = JSON::fromString("{\"long\":\"username\", \"short\":\"u\", \"arg\":\"string\", \"help\":\"Username to drop privileges to, or root to not drop provileges.\"}");
|
stored_interface["default"] = "0.0.0.0";
|
||||||
|
}
|
||||||
|
JSON::Value stored_user = JSON::fromString(
|
||||||
|
"{\"long\":\"username\", \"short\":\"u\", \"arg\":\"string\", \"help\":\"Username to drop privileges to, or root to not drop provileges.\"}");
|
||||||
stored_user["default"] = Controller::Storage["config"]["controller"]["username"];
|
stored_user["default"] = Controller::Storage["config"]["controller"]["username"];
|
||||||
if (!stored_user["default"]){stored_user["default"] = "root";}
|
if ( !stored_user["default"]){
|
||||||
Util::Config conf = Util::Config(argv[0], PACKAGE_VERSION);
|
stored_user["default"] = "root";
|
||||||
|
}
|
||||||
|
Util::Config conf = Util::Config(argv[0], PACKAGE_VERSION " / " RELEASE);
|
||||||
conf.addOption("listen_port", stored_port);
|
conf.addOption("listen_port", stored_port);
|
||||||
conf.addOption("listen_interface", stored_interface);
|
conf.addOption("listen_interface", stored_interface);
|
||||||
conf.addOption("username", stored_user);
|
conf.addOption("username", stored_user);
|
||||||
conf.addOption("daemonize", JSON::fromString("{\"long\":\"daemon\", \"short\":\"d\", \"default\":1, \"long_off\":\"nodaemon\", \"short_off\":\"n\", \"help\":\"Whether or not to daemonize the process after starting.\"}"));
|
conf.addOption("daemonize",
|
||||||
conf.addOption("account", JSON::fromString("{\"long\":\"account\", \"short\":\"a\", \"arg\":\"string\" \"default\":\"\", \"help\":\"A username:password string to create a new account with.\"}"));
|
JSON::fromString(
|
||||||
|
"{\"long\":\"daemon\", \"short\":\"d\", \"default\":1, \"long_off\":\"nodaemon\", \"short_off\":\"n\", \"help\":\"Whether or not to daemonize the process after starting.\"}"));
|
||||||
|
conf.addOption("account",
|
||||||
|
JSON::fromString(
|
||||||
|
"{\"long\":\"account\", \"short\":\"a\", \"arg\":\"string\" \"default\":\"\", \"help\":\"A username:password string to create a new account with.\"}"));
|
||||||
conf.addOption("uplink", JSON::fromString("{\"default\":0, \"help\":\"Enable MistSteward uplink.\", \"short\":\"U\", \"long\":\"uplink\"}"));
|
conf.addOption("uplink", JSON::fromString("{\"default\":0, \"help\":\"Enable MistSteward uplink.\", \"short\":\"U\", \"long\":\"uplink\"}"));
|
||||||
conf.parseArgs(argc, argv);
|
conf.parseArgs(argc, argv);
|
||||||
|
|
||||||
|
@ -376,18 +157,18 @@ int main(int argc, char ** argv){
|
||||||
if (colon != std::string::npos && colon != 0 && colon != account.size()){
|
if (colon != std::string::npos && colon != 0 && colon != account.size()){
|
||||||
std::string uname = account.substr(0, colon);
|
std::string uname = account.substr(0, colon);
|
||||||
std::string pword = account.substr(colon + 1, std::string::npos);
|
std::string pword = account.substr(colon + 1, std::string::npos);
|
||||||
Controller::Log("CONF", "Created account "+uname+" through commandline option");
|
Controller::Log("CONF", "Created account " + uname + " through commandline option");
|
||||||
Controller::Storage["account"][uname]["password"] = Secure::md5(pword);
|
Controller::Storage["account"][uname]["password"] = Secure::md5(pword);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
time_t lastuplink = 0;
|
time_t lastuplink = 0;
|
||||||
time_t processchecker = 0;
|
time_t processchecker = 0;
|
||||||
Socket::Server API_Socket = Socket::Server(conf.getInteger("listen_port"), conf.getString("listen_interface"), true);
|
Socket::Server API_Socket = Socket::Server(conf.getInteger("listen_port"), conf.getString("listen_interface"), true);
|
||||||
mkdir("/tmp/mist", S_IRWXU | S_IRWXG | S_IRWXO);//attempt to create /tmp/mist/ - ignore failures
|
mkdir("/tmp/mist", S_IRWXU | S_IRWXG | S_IRWXO); //attempt to create /tmp/mist/ - ignore failures
|
||||||
Socket::Server Stats_Socket = Socket::Server("/tmp/mist/statistics", true);
|
Socket::Server Stats_Socket = Socket::Server("/tmp/mist/statistics", true);
|
||||||
conf.activate();
|
conf.activate();
|
||||||
Socket::Connection Incoming;
|
Socket::Connection Incoming;
|
||||||
std::vector< Controller::ConnectedUser > users;
|
std::vector<Controller::ConnectedUser> users;
|
||||||
std::vector<Socket::Connection> buffers;
|
std::vector<Socket::Connection> buffers;
|
||||||
JSON::Value Request;
|
JSON::Value Request;
|
||||||
JSON::Value Response;
|
JSON::Value Response;
|
||||||
|
@ -408,19 +189,22 @@ int main(int argc, char ** argv){
|
||||||
lastuplink = Util::epoch();
|
lastuplink = Util::epoch();
|
||||||
bool gotUplink = false;
|
bool gotUplink = false;
|
||||||
if (users.size() > 0){
|
if (users.size() > 0){
|
||||||
for( std::vector< Controller::ConnectedUser >::iterator it = users.end() - 1; it >= users.begin(); it--) {
|
for (std::vector<Controller::ConnectedUser>::iterator it = users.end() - 1; it >= users.begin(); it--){
|
||||||
if (!it->C.connected()){
|
if ( !it->C.connected()){
|
||||||
it->C.close();
|
it->C.close();
|
||||||
users.erase(it);
|
users.erase(it);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (it->clientMode){uplink = &*it; gotUplink = true;}
|
if (it->clientMode){
|
||||||
|
uplink = & *it;
|
||||||
|
gotUplink = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!gotUplink){
|
}
|
||||||
|
if ( !gotUplink){
|
||||||
Incoming = Socket::Connection("gearbox.ddvtech.com", 4242, true);
|
Incoming = Socket::Connection("gearbox.ddvtech.com", 4242, true);
|
||||||
if (Incoming.connected()){
|
if (Incoming.connected()){
|
||||||
users.push_back(Incoming);
|
users.push_back((Controller::ConnectedUser)Incoming);
|
||||||
users.back().clientMode = true;
|
users.back().clientMode = true;
|
||||||
uplink = &users.back();
|
uplink = &users.back();
|
||||||
gotUplink = true;
|
gotUplink = true;
|
||||||
|
@ -434,7 +218,7 @@ int main(int argc, char ** argv){
|
||||||
Response["statistics"] = Controller::Storage["statistics"];
|
Response["statistics"] = Controller::Storage["statistics"];
|
||||||
Response["now"] = (unsigned int)lastuplink;
|
Response["now"] = (unsigned int)lastuplink;
|
||||||
uplink->H.Clean();
|
uplink->H.Clean();
|
||||||
uplink->H.SetBody("command="+HTTP::Parser::urlencode(Response.toString()));
|
uplink->H.SetBody("command=" + HTTP::Parser::urlencode(Response.toString()));
|
||||||
uplink->H.BuildRequest();
|
uplink->H.BuildRequest();
|
||||||
uplink->C.Send(uplink->H.BuildResponse("200", "OK"));
|
uplink->C.Send(uplink->H.BuildResponse("200", "OK"));
|
||||||
uplink->H.Clean();
|
uplink->H.Clean();
|
||||||
|
@ -445,12 +229,16 @@ int main(int argc, char ** argv){
|
||||||
}
|
}
|
||||||
|
|
||||||
Incoming = API_Socket.accept(true);
|
Incoming = API_Socket.accept(true);
|
||||||
if (Incoming.connected()){users.push_back(Incoming);}
|
if (Incoming.connected()){
|
||||||
|
users.push_back((Controller::ConnectedUser)Incoming);
|
||||||
|
}
|
||||||
Incoming = Stats_Socket.accept(true);
|
Incoming = Stats_Socket.accept(true);
|
||||||
if (Incoming.connected()){buffers.push_back(Incoming);}
|
if (Incoming.connected()){
|
||||||
|
buffers.push_back(Incoming);
|
||||||
|
}
|
||||||
if (buffers.size() > 0){
|
if (buffers.size() > 0){
|
||||||
for( std::vector< Socket::Connection >::iterator it = buffers.begin(); it != buffers.end(); it++) {
|
for (std::vector<Socket::Connection>::iterator it = buffers.begin(); it != buffers.end(); it++){
|
||||||
if (!it->connected()){
|
if ( !it->connected()){
|
||||||
it->close();
|
it->close();
|
||||||
buffers.erase(it);
|
buffers.erase(it);
|
||||||
break;
|
break;
|
||||||
|
@ -472,10 +260,10 @@ int main(int argc, char ** argv){
|
||||||
std::string nowstr = Request["totals"]["now"].asString();
|
std::string nowstr = Request["totals"]["now"].asString();
|
||||||
Controller::Storage["statistics"][thisbuffer]["totals"][nowstr] = Request["totals"];
|
Controller::Storage["statistics"][thisbuffer]["totals"][nowstr] = Request["totals"];
|
||||||
Controller::Storage["statistics"][thisbuffer]["totals"][nowstr].removeMember("now");
|
Controller::Storage["statistics"][thisbuffer]["totals"][nowstr].removeMember("now");
|
||||||
Controller::Storage["statistics"][thisbuffer]["totals"].shrink(600);//limit to 10 minutes of data
|
Controller::Storage["statistics"][thisbuffer]["totals"].shrink(600); //limit to 10 minutes of data
|
||||||
for (JSON::ObjIter jit = Request["log"].ObjBegin(); jit != Request["log"].ObjEnd(); jit++){
|
for (JSON::ObjIter jit = Request["log"].ObjBegin(); jit != Request["log"].ObjEnd(); jit++){
|
||||||
Controller::Storage["statistics"][thisbuffer]["log"].append(jit->second);
|
Controller::Storage["statistics"][thisbuffer]["log"].append(jit->second);
|
||||||
Controller::Storage["statistics"][thisbuffer]["log"].shrink(1000);//limit to 1000 users per buffer
|
Controller::Storage["statistics"][thisbuffer]["log"].shrink(1000); //limit to 1000 users per buffer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -492,7 +280,8 @@ int main(int argc, char ** argv){
|
||||||
Controller::Storage["statistics"][oit->first]["curr"][sockit.asString()] = Request["vod"];
|
Controller::Storage["statistics"][oit->first]["curr"][sockit.asString()] = Request["vod"];
|
||||||
Controller::Storage["statistics"][oit->first]["curr"][sockit.asString()].removeMember("meta");
|
Controller::Storage["statistics"][oit->first]["curr"][sockit.asString()].removeMember("meta");
|
||||||
JSON::Value nowtotal;
|
JSON::Value nowtotal;
|
||||||
for (JSON::ObjIter u_it = Controller::Storage["statistics"][oit->first]["curr"].ObjBegin(); u_it != Controller::Storage["statistics"][oit->first]["curr"].ObjEnd(); ++u_it){
|
for (JSON::ObjIter u_it = Controller::Storage["statistics"][oit->first]["curr"].ObjBegin();
|
||||||
|
u_it != Controller::Storage["statistics"][oit->first]["curr"].ObjEnd(); ++u_it){
|
||||||
nowtotal["up"] = nowtotal["up"].asInt() + u_it->second["up"].asInt();
|
nowtotal["up"] = nowtotal["up"].asInt() + u_it->second["up"].asInt();
|
||||||
nowtotal["down"] = nowtotal["down"].asInt() + u_it->second["down"].asInt();
|
nowtotal["down"] = nowtotal["down"].asInt() + u_it->second["down"].asInt();
|
||||||
nowtotal["count"] = nowtotal["count"].asInt() + 1;
|
nowtotal["count"] = nowtotal["count"].asInt() + 1;
|
||||||
|
@ -507,14 +296,14 @@ int main(int argc, char ** argv){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (users.size() > 0){
|
if (users.size() > 0){
|
||||||
for( std::vector< Controller::ConnectedUser >::iterator it = users.begin(); it != users.end(); it++) {
|
for (std::vector<Controller::ConnectedUser>::iterator it = users.begin(); it != users.end(); it++){
|
||||||
if (!it->C.connected() || it->logins > 3){
|
if ( !it->C.connected() || it->logins > 3){
|
||||||
it->C.close();
|
it->C.close();
|
||||||
users.erase(it);
|
users.erase(it);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (it->C.spool() || it->C.Received().size()){
|
if (it->C.spool() || it->C.Received().size()){
|
||||||
if (*(it->C.Received().get().rbegin()) != '\n'){
|
if ( *(it->C.Received().get().rbegin()) != '\n'){
|
||||||
std::string tmp = it->C.Received().get();
|
std::string tmp = it->C.Received().get();
|
||||||
it->C.Received().get().clear();
|
it->C.Received().get().clear();
|
||||||
if (it->C.Received().size()){
|
if (it->C.Received().size()){
|
||||||
|
@ -547,7 +336,7 @@ int main(int argc, char ** argv){
|
||||||
Controller::Log("UPLK", "Responding to login challenge: " + Request["authorize"]["challenge"].asString());
|
Controller::Log("UPLK", "Responding to login challenge: " + Request["authorize"]["challenge"].asString());
|
||||||
Response["authorize"]["password"] = Secure::md5(COMPILED_PASSWORD + Request["authorize"]["challenge"].asString());
|
Response["authorize"]["password"] = Secure::md5(COMPILED_PASSWORD + Request["authorize"]["challenge"].asString());
|
||||||
it->H.Clean();
|
it->H.Clean();
|
||||||
it->H.SetBody("command="+HTTP::Parser::urlencode(Response.toString()));
|
it->H.SetBody("command=" + HTTP::Parser::urlencode(Response.toString()));
|
||||||
it->H.BuildRequest();
|
it->H.BuildRequest();
|
||||||
it->C.Send(it->H.BuildResponse("200", "OK"));
|
it->C.Send(it->H.BuildResponse("200", "OK"));
|
||||||
it->H.Clean();
|
it->H.Clean();
|
||||||
|
@ -555,8 +344,12 @@ int main(int argc, char ** argv){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
if (Request.isMember("config")){Controller::CheckConfig(Request["config"], Controller::Storage["config"]);}
|
if (Request.isMember("config")){
|
||||||
if (Request.isMember("streams")){Controller::CheckStreams(Request["streams"], Controller::Storage["streams"]);}
|
Controller::CheckConfig(Request["config"], Controller::Storage["config"]);
|
||||||
|
}
|
||||||
|
if (Request.isMember("streams")){
|
||||||
|
Controller::CheckStreams(Request["streams"], Controller::Storage["streams"]);
|
||||||
|
}
|
||||||
if (Request.isMember("clearstatlogs")){
|
if (Request.isMember("clearstatlogs")){
|
||||||
Controller::Storage["log"].null();
|
Controller::Storage["log"].null();
|
||||||
Controller::Storage["statistics"].null();
|
Controller::Storage["statistics"].null();
|
||||||
|
@ -564,7 +357,7 @@ int main(int argc, char ** argv){
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
Request = JSON::fromString(it->H.GetVar("command"));
|
Request = JSON::fromString(it->H.GetVar("command"));
|
||||||
if (!Request.isObject() && it->H.url != "/api"){
|
if ( !Request.isObject() && it->H.url != "/api"){
|
||||||
it->H.Clean();
|
it->H.Clean();
|
||||||
it->H.SetHeader("Content-Type", "text/html");
|
it->H.SetHeader("Content-Type", "text/html");
|
||||||
it->H.SetHeader("X-Info", "To force an API response, request the file /api");
|
it->H.SetHeader("X-Info", "To force an API response, request the file /api");
|
||||||
|
@ -573,11 +366,15 @@ int main(int argc, char ** argv){
|
||||||
it->C.Send(it->H.BuildResponse("200", "OK"));
|
it->C.Send(it->H.BuildResponse("200", "OK"));
|
||||||
it->H.Clean();
|
it->H.Clean();
|
||||||
}else{
|
}else{
|
||||||
Authorize(Request, Response, (*it));
|
Authorize(Request, Response, ( *it));
|
||||||
if (it->Authorized){
|
if (it->Authorized){
|
||||||
//Parse config and streams from the request.
|
//Parse config and streams from the request.
|
||||||
if (Request.isMember("config")){Controller::CheckConfig(Request["config"], Controller::Storage["config"]);}
|
if (Request.isMember("config")){
|
||||||
if (Request.isMember("streams")){Controller::CheckStreams(Request["streams"], Controller::Storage["streams"]);}
|
Controller::CheckConfig(Request["config"], Controller::Storage["config"]);
|
||||||
|
}
|
||||||
|
if (Request.isMember("streams")){
|
||||||
|
Controller::CheckStreams(Request["streams"], Controller::Storage["streams"]);
|
||||||
|
}
|
||||||
if (Request.isMember("save")){
|
if (Request.isMember("save")){
|
||||||
Controller::WriteFile("config.json", Controller::Storage.toString());
|
Controller::WriteFile("config.json", Controller::Storage.toString());
|
||||||
Controller::Log("CONF", "Config written to file on request through API");
|
Controller::Log("CONF", "Config written to file on request through API");
|
||||||
|
@ -589,7 +386,9 @@ int main(int argc, char ** argv){
|
||||||
Response["streams"] = Controller::Storage["streams"];
|
Response["streams"] = Controller::Storage["streams"];
|
||||||
//add required data to the current unix time to the config, for syncing reasons
|
//add required data to the current unix time to the config, for syncing reasons
|
||||||
Response["config"]["time"] = Util::epoch();
|
Response["config"]["time"] = Util::epoch();
|
||||||
if (!Response["config"].isMember("serverid")){Response["config"]["serverid"] = "";}
|
if ( !Response["config"].isMember("serverid")){
|
||||||
|
Response["config"]["serverid"] = "";
|
||||||
|
}
|
||||||
//sent any available logs and statistics
|
//sent any available logs and statistics
|
||||||
Response["log"] = Controller::Storage["log"];
|
Response["log"] = Controller::Storage["log"];
|
||||||
Response["statistics"] = Controller::Storage["statistics"];
|
Response["statistics"] = Controller::Storage["statistics"];
|
||||||
|
@ -600,14 +399,18 @@ int main(int argc, char ** argv){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jsonp = "";
|
jsonp = "";
|
||||||
if (it->H.GetVar("callback") != ""){jsonp = it->H.GetVar("callback");}
|
if (it->H.GetVar("callback") != ""){
|
||||||
if (it->H.GetVar("jsonp") != ""){jsonp = it->H.GetVar("jsonp");}
|
jsonp = it->H.GetVar("callback");
|
||||||
|
}
|
||||||
|
if (it->H.GetVar("jsonp") != ""){
|
||||||
|
jsonp = it->H.GetVar("jsonp");
|
||||||
|
}
|
||||||
it->H.Clean();
|
it->H.Clean();
|
||||||
it->H.SetHeader("Content-Type", "text/javascript");
|
it->H.SetHeader("Content-Type", "text/javascript");
|
||||||
if (jsonp == ""){
|
if (jsonp == ""){
|
||||||
it->H.SetBody(Response.toString()+"\n\n");
|
it->H.SetBody(Response.toString() + "\n\n");
|
||||||
}else{
|
}else{
|
||||||
it->H.SetBody(jsonp+"("+Response.toString()+");\n\n");
|
it->H.SetBody(jsonp + "(" + Response.toString() + ");\n\n");
|
||||||
}
|
}
|
||||||
it->C.Send(it->H.BuildResponse("200", "OK"));
|
it->C.Send(it->H.BuildResponse("200", "OK"));
|
||||||
it->H.Clean();
|
it->H.Clean();
|
||||||
|
|
154
src/controller_capabilities.cpp
Normal file
154
src/controller_capabilities.cpp
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <fstream>
|
||||||
|
#include <set>
|
||||||
|
#include "controller_capabilities.h"
|
||||||
|
|
||||||
|
namespace Controller {
|
||||||
|
|
||||||
|
class cpudata{
|
||||||
|
public:
|
||||||
|
std::string model;
|
||||||
|
int cores;
|
||||||
|
int threads;
|
||||||
|
int mhz;
|
||||||
|
int id;
|
||||||
|
cpudata(){
|
||||||
|
model = "Unknown";
|
||||||
|
cores = 1;
|
||||||
|
threads = 1;
|
||||||
|
mhz = 0;
|
||||||
|
id = 0;
|
||||||
|
}
|
||||||
|
;
|
||||||
|
void fill(char * data){
|
||||||
|
int i;
|
||||||
|
i = 0;
|
||||||
|
if (sscanf(data, "model name : %n", &i) != EOF && i > 0){
|
||||||
|
model = (data + i);
|
||||||
|
}
|
||||||
|
if (sscanf(data, "cpu cores : %d", &i) == 1){
|
||||||
|
cores = i;
|
||||||
|
}
|
||||||
|
if (sscanf(data, "siblings : %d", &i) == 1){
|
||||||
|
threads = i;
|
||||||
|
}
|
||||||
|
if (sscanf(data, "physical id : %d", &i) == 1){
|
||||||
|
id = i;
|
||||||
|
}
|
||||||
|
if (sscanf(data, "cpu MHz : %d", &i) == 1){
|
||||||
|
mhz = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
;
|
||||||
|
};
|
||||||
|
|
||||||
|
void checkCapable(JSON::Value & capa){
|
||||||
|
capa.null();
|
||||||
|
std::ifstream cpuinfo("/proc/cpuinfo");
|
||||||
|
if (cpuinfo){
|
||||||
|
std::map<int, cpudata> cpus;
|
||||||
|
char line[300];
|
||||||
|
int proccount = -1;
|
||||||
|
while (cpuinfo.good()){
|
||||||
|
cpuinfo.getline(line, 300);
|
||||||
|
if (cpuinfo.fail()){
|
||||||
|
//empty lines? ignore them, clear flags, continue
|
||||||
|
if ( !cpuinfo.eof()){
|
||||||
|
cpuinfo.ignore();
|
||||||
|
cpuinfo.clear();
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (memcmp(line, "processor", 9) == 0){
|
||||||
|
proccount++;
|
||||||
|
}
|
||||||
|
cpus[proccount].fill(line);
|
||||||
|
}
|
||||||
|
//fix wrong core counts
|
||||||
|
std::map<int, int> corecounts;
|
||||||
|
for (int i = 0; i <= proccount; ++i){
|
||||||
|
corecounts[cpus[i].id]++;
|
||||||
|
}
|
||||||
|
//remove double physical IDs - we only want real CPUs.
|
||||||
|
std::set<int> used_physids;
|
||||||
|
int total_speed = 0;
|
||||||
|
int total_threads = 0;
|
||||||
|
for (int i = 0; i <= proccount; ++i){
|
||||||
|
if ( !used_physids.count(cpus[i].id)){
|
||||||
|
used_physids.insert(cpus[i].id);
|
||||||
|
JSON::Value thiscpu;
|
||||||
|
thiscpu["model"] = cpus[i].model;
|
||||||
|
thiscpu["cores"] = cpus[i].cores;
|
||||||
|
if (cpus[i].cores < 2 && corecounts[cpus[i].id] > cpus[i].cores){
|
||||||
|
thiscpu["cores"] = corecounts[cpus[i].id];
|
||||||
|
}
|
||||||
|
thiscpu["threads"] = cpus[i].threads;
|
||||||
|
if (thiscpu["cores"].asInt() > thiscpu["threads"].asInt()){
|
||||||
|
thiscpu["threads"] = thiscpu["cores"];
|
||||||
|
}
|
||||||
|
thiscpu["mhz"] = cpus[i].mhz;
|
||||||
|
capa["cpu"].append(thiscpu);
|
||||||
|
total_speed += cpus[i].cores * cpus[i].mhz;
|
||||||
|
total_threads += cpus[i].threads;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
capa["speed"] = total_speed;
|
||||||
|
capa["threads"] = total_threads;
|
||||||
|
}
|
||||||
|
std::ifstream meminfo("/proc/meminfo");
|
||||||
|
if (meminfo){
|
||||||
|
char line[300];
|
||||||
|
int bufcache = 0;
|
||||||
|
while (meminfo.good()){
|
||||||
|
meminfo.getline(line, 300);
|
||||||
|
if (meminfo.fail()){
|
||||||
|
//empty lines? ignore them, clear flags, continue
|
||||||
|
if ( !meminfo.eof()){
|
||||||
|
meminfo.ignore();
|
||||||
|
meminfo.clear();
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
long long int i;
|
||||||
|
if (sscanf(line, "MemTotal : %Li kB", &i) == 1){
|
||||||
|
capa["mem"]["total"] = i / 1024;
|
||||||
|
}
|
||||||
|
if (sscanf(line, "MemFree : %Li kB", &i) == 1){
|
||||||
|
capa["mem"]["free"] = i / 1024;
|
||||||
|
}
|
||||||
|
if (sscanf(line, "SwapTotal : %Li kB", &i) == 1){
|
||||||
|
capa["mem"]["swaptotal"] = i / 1024;
|
||||||
|
}
|
||||||
|
if (sscanf(line, "SwapFree : %Li kB", &i) == 1){
|
||||||
|
capa["mem"]["swapfree"] = i / 1024;
|
||||||
|
}
|
||||||
|
if (sscanf(line, "Buffers : %Li kB", &i) == 1){
|
||||||
|
bufcache += i / 1024;
|
||||||
|
}
|
||||||
|
if (sscanf(line, "Cached : %Li kB", &i) == 1){
|
||||||
|
bufcache += i / 1024;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
capa["mem"]["used"] = capa["mem"]["total"].asInt() - capa["mem"]["free"].asInt() - bufcache;
|
||||||
|
capa["mem"]["cached"] = bufcache;
|
||||||
|
capa["load"]["memory"] = ((capa["mem"]["used"].asInt() + (capa["mem"]["swaptotal"].asInt() - capa["mem"]["swapfree"].asInt())) * 100)
|
||||||
|
/ capa["mem"]["total"].asInt();
|
||||||
|
}
|
||||||
|
std::ifstream loadavg("/proc/loadavg");
|
||||||
|
if (loadavg){
|
||||||
|
char line[300];
|
||||||
|
int bufcache = 0;
|
||||||
|
loadavg.getline(line, 300);
|
||||||
|
//parse lines here
|
||||||
|
float onemin, fivemin, fifteenmin;
|
||||||
|
if (sscanf(line, "%f %f %f", &onemin, &fivemin, &fifteenmin) == 3){
|
||||||
|
capa["load"]["one"] = (long long int)(onemin * 100);
|
||||||
|
capa["load"]["five"] = (long long int)(onemin * 100);
|
||||||
|
capa["load"]["fifteen"] = (long long int)(onemin * 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
5
src/controller_capabilities.h
Normal file
5
src/controller_capabilities.h
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
#include <mist/json.h>
|
||||||
|
|
||||||
|
namespace Controller {
|
||||||
|
void checkCapable(JSON::Value & capa);
|
||||||
|
}
|
|
@ -2,11 +2,35 @@
|
||||||
#include <mist/config.h>
|
#include <mist/config.h>
|
||||||
#include <mist/procs.h>
|
#include <mist/procs.h>
|
||||||
#include "controller_storage.h"
|
#include "controller_storage.h"
|
||||||
|
#include "controller_connectors.h"
|
||||||
|
|
||||||
namespace Controller{
|
namespace Controller {
|
||||||
|
|
||||||
void CheckProtocols(JSON::Value & p){
|
|
||||||
static std::map<std::string, std::string> current_connectors;
|
static std::map<std::string, std::string> current_connectors;
|
||||||
|
|
||||||
|
/// Checks if the binary mentioned in the protocol argument is currently active, if so, restarts it.
|
||||||
|
void UpdateProtocol(std::string protocol){
|
||||||
|
std::map<std::string, std::string>::iterator iter;
|
||||||
|
for (iter = current_connectors.begin(); iter != current_connectors.end(); iter++){
|
||||||
|
if (iter->second.substr(0, protocol.size()) == protocol){
|
||||||
|
Log("CONF", "Restarting connector for update: " + iter->second);
|
||||||
|
Util::Procs::Stop(iter->first);
|
||||||
|
int i = 0;
|
||||||
|
while (Util::Procs::isActive(iter->first) && i < 30){
|
||||||
|
Util::sleep(100);
|
||||||
|
}
|
||||||
|
if (i >= 30){
|
||||||
|
Log("WARN", "Connector still active 3 seconds after shutdown - delaying restart.");
|
||||||
|
}else{
|
||||||
|
Util::Procs::Start(iter->first, Util::getMyPath() + iter->second);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks current protocol configuration, updates state of enabled connectors if neccesary.
|
||||||
|
void CheckProtocols(JSON::Value & p){
|
||||||
std::map<std::string, std::string> new_connectors;
|
std::map<std::string, std::string> new_connectors;
|
||||||
std::map<std::string, std::string>::iterator iter;
|
std::map<std::string, std::string>::iterator iter;
|
||||||
bool haveHTTPgeneric = false;
|
bool haveHTTPgeneric = false;
|
||||||
|
@ -16,35 +40,40 @@ namespace Controller{
|
||||||
JSON::Value counter = (long long int)0;
|
JSON::Value counter = (long long int)0;
|
||||||
|
|
||||||
for (JSON::ArrIter ait = p.ArrBegin(); ait != p.ArrEnd(); ait++){
|
for (JSON::ArrIter ait = p.ArrBegin(); ait != p.ArrEnd(); ait++){
|
||||||
if (!(*ait).isMember("connector") || (*ait)["connector"].asString() == ""){continue;}
|
if ( !( *ait).isMember("connector") || ( *ait)["connector"].asString() == ""){
|
||||||
|
continue;
|
||||||
tmp = std::string("MistConn") + (*ait)["connector"].asString() + std::string(" -n");
|
|
||||||
if ((*ait)["connector"].asString() == "HTTP"){haveHTTPgeneric = true;}
|
|
||||||
if ((*ait)["connector"].asString() != "HTTP" && (*ait)["connector"].asString().substr(0, 4) == "HTTP"){haveHTTPspecific = true;}
|
|
||||||
|
|
||||||
if ((*ait).isMember("port") && (*ait)["port"].asInt() != 0){
|
|
||||||
tmp += std::string(" -p ") + (*ait)["port"].asString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((*ait).isMember("interface") && (*ait)["interface"].asString() != "" && (*ait)["interface"].asString() != "0.0.0.0"){
|
tmp = std::string("MistConn") + ( *ait)["connector"].asString() + std::string(" -n");
|
||||||
tmp += std::string(" -i ") + (*ait)["interface"].asString();
|
if (( *ait)["connector"].asString() == "HTTP"){
|
||||||
|
haveHTTPgeneric = true;
|
||||||
|
}
|
||||||
|
if (( *ait)["connector"].asString() != "HTTP" && ( *ait)["connector"].asString().substr(0, 4) == "HTTP"){
|
||||||
|
haveHTTPspecific = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((*ait).isMember("username") && (*ait)["username"].asString() != "" && (*ait)["username"].asString() != "root"){
|
if (( *ait).isMember("port") && ( *ait)["port"].asInt() != 0){
|
||||||
tmp += std::string(" -u ") + (*ait)["username"].asString();
|
tmp += std::string(" -p ") + ( *ait)["port"].asString();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((*ait).isMember("args") && (*ait)["args"].asString() != ""){
|
if (( *ait).isMember("interface") && ( *ait)["interface"].asString() != "" && ( *ait)["interface"].asString() != "0.0.0.0"){
|
||||||
tmp += std::string(" ") + (*ait)["args"].asString();
|
tmp += std::string(" -i ") + ( *ait)["interface"].asString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (( *ait).isMember("username") && ( *ait)["username"].asString() != "" && ( *ait)["username"].asString() != "root"){
|
||||||
|
tmp += std::string(" -u ") + ( *ait)["username"].asString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (( *ait).isMember("args") && ( *ait)["args"].asString() != ""){
|
||||||
|
tmp += std::string(" ") + ( *ait)["args"].asString();
|
||||||
|
}
|
||||||
|
|
||||||
counter = counter.asInt() + 1;
|
counter = counter.asInt() + 1;
|
||||||
new_connectors[std::string("Conn")+counter.asString()] = tmp;
|
new_connectors[std::string("Conn") + counter.asString()] = tmp;
|
||||||
if (Util::Procs::isActive(std::string("Conn")+counter.asString())){
|
if (Util::Procs::isActive(std::string("Conn") + counter.asString())){
|
||||||
(*ait)["online"] = 1;
|
( *ait)["online"] = 1;
|
||||||
}else{
|
}else{
|
||||||
(*ait)["online"] = 0;
|
( *ait)["online"] = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,7 +96,7 @@ namespace Controller{
|
||||||
if (haveHTTPgeneric && !haveHTTPspecific){
|
if (haveHTTPgeneric && !haveHTTPspecific){
|
||||||
Log("WARN", "HTTP Connector is enabled but no HTTP-based protocols are active!");
|
Log("WARN", "HTTP Connector is enabled but no HTTP-based protocols are active!");
|
||||||
}
|
}
|
||||||
if (!haveHTTPgeneric && haveHTTPspecific){
|
if ( !haveHTTPgeneric && haveHTTPspecific){
|
||||||
Log("WARN", "HTTP-based protocols will not work without the generic HTTP connector!");
|
Log("WARN", "HTTP-based protocols will not work without the generic HTTP connector!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,5 +104,4 @@ namespace Controller{
|
||||||
current_connectors = new_connectors;
|
current_connectors = new_connectors;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
|
#include <mist/json.h>
|
||||||
|
|
||||||
|
namespace Controller {
|
||||||
|
|
||||||
namespace Controller{
|
/// Checks if the binary mentioned in the protocol argument is currently active, if so, restarts it.
|
||||||
|
void UpdateProtocol(std::string protocol);
|
||||||
|
|
||||||
|
/// Checks current protocol configuration, updates state of enabled connectors if neccesary.
|
||||||
void CheckProtocols(JSON::Value & p);
|
void CheckProtocols(JSON::Value & p);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
#include <mist/timing.h>
|
#include <mist/timing.h>
|
||||||
#include "controller_storage.h"
|
#include "controller_storage.h"
|
||||||
|
|
||||||
namespace Controller{
|
namespace Controller {
|
||||||
|
|
||||||
JSON::Value Storage; ///< Global storage of data.
|
JSON::Value Storage; ///< Global storage of data.
|
||||||
|
|
||||||
|
@ -11,15 +12,25 @@ namespace Controller{
|
||||||
//if last log message equals this one, do not log.
|
//if last log message equals this one, do not log.
|
||||||
if (Storage["log"].size() > 0){
|
if (Storage["log"].size() > 0){
|
||||||
JSON::ArrIter it = Storage["log"].ArrEnd() - 1;
|
JSON::ArrIter it = Storage["log"].ArrEnd() - 1;
|
||||||
if ((*it)[2] == message){return;}
|
if (( *it)[2] == message){
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
JSON::Value m;
|
JSON::Value m;
|
||||||
m.append(Util::epoch());
|
m.append(Util::epoch());
|
||||||
m.append(kind);
|
m.append(kind);
|
||||||
m.append(message);
|
m.append(message);
|
||||||
Storage["log"].append(m);
|
Storage["log"].append(m);
|
||||||
Storage["log"].shrink(100);//limit to 100 log messages
|
Storage["log"].shrink(100); //limit to 100 log messages
|
||||||
std::cout << "[" << kind << "] " << message << std::endl;
|
std::cout << "[" << kind << "] " << message << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Write contents to Filename
|
||||||
|
void WriteFile(std::string Filename, std::string contents){
|
||||||
|
std::ofstream File;
|
||||||
|
File.open(Filename.c_str());
|
||||||
|
File << contents << std::endl;
|
||||||
|
File.close();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <mist/json.h>
|
#include <mist/json.h>
|
||||||
|
|
||||||
namespace Controller{
|
namespace Controller {
|
||||||
|
|
||||||
extern JSON::Value Storage; ///< Global storage of data.
|
extern JSON::Value Storage; ///< Global storage of data.
|
||||||
|
|
||||||
/// Store and print a log message.
|
/// Store and print a log message.
|
||||||
void Log(std::string kind, std::string message);
|
void Log(std::string kind, std::string message);
|
||||||
|
|
||||||
|
/// Write contents to Filename.
|
||||||
|
void WriteFile(std::string Filename, std::string contents);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
111
src/controller_streams.cpp
Normal file
111
src/controller_streams.cpp
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
#include <mist/procs.h>
|
||||||
|
#include <mist/config.h>
|
||||||
|
#include <mist/timing.h>
|
||||||
|
#include "controller_streams.h"
|
||||||
|
#include "controller_storage.h"
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
namespace Controller {
|
||||||
|
|
||||||
|
std::map<std::string, int> lastBuffer; ///< Last moment of contact with all buffers.
|
||||||
|
|
||||||
|
bool streamsEqual(JSON::Value & one, JSON::Value & two){
|
||||||
|
if (one["channel"]["URL"] != two["channel"]["URL"]){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (one["preset"]["cmd"] != two["preset"]["cmd"]){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void startStream(std::string name, JSON::Value & data){
|
||||||
|
std::string URL = data["channel"]["URL"];
|
||||||
|
std::string preset = data["preset"]["cmd"];
|
||||||
|
std::string cmd1, cmd2, cmd3;
|
||||||
|
if (URL.substr(0, 4) == "push"){
|
||||||
|
std::string pusher = URL.substr(7);
|
||||||
|
cmd2 = "MistBuffer -s " + name + " " + pusher;
|
||||||
|
Util::Procs::Start(name, Util::getMyPath() + cmd2);
|
||||||
|
Log("BUFF", "(re)starting stream buffer " + name + " for push data from " + pusher);
|
||||||
|
}else{
|
||||||
|
if (URL.substr(0, 1) == "/"){
|
||||||
|
struct stat fileinfo;
|
||||||
|
if (stat(URL.c_str(), &fileinfo) != 0 || S_ISDIR(fileinfo.st_mode)){
|
||||||
|
Log("BUFF", "Warning for VoD stream " + name + "! File not found: " + URL);
|
||||||
|
data["error"] = "Not found: " + URL;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cmd1 = "cat " + URL;
|
||||||
|
data["error"] = "Available";
|
||||||
|
return; //MistPlayer handles VoD
|
||||||
|
}else{
|
||||||
|
cmd1 = "ffmpeg -re -async 2 -i " + URL + " " + preset + " -f flv -";
|
||||||
|
cmd2 = "MistFLV2DTSC";
|
||||||
|
}
|
||||||
|
cmd3 = "MistBuffer -s " + name;
|
||||||
|
if (cmd2 != ""){
|
||||||
|
Util::Procs::Start(name, cmd1, Util::getMyPath() + cmd2, Util::getMyPath() + cmd3);
|
||||||
|
Log("BUFF", "(re)starting stream buffer " + name + " for ffmpeg data: " + cmd1);
|
||||||
|
}else{
|
||||||
|
Util::Procs::Start(name, cmd1, Util::getMyPath() + cmd3);
|
||||||
|
Log("BUFF", "(re)starting stream buffer " + name + " using input file " + URL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CheckAllStreams(JSON::Value & data){
|
||||||
|
long long int currTime = Util::epoch();
|
||||||
|
for (JSON::ObjIter jit = data.ObjBegin(); jit != data.ObjEnd(); jit++){
|
||||||
|
if ( !Util::Procs::isActive(jit->first)){
|
||||||
|
startStream(jit->first, jit->second);
|
||||||
|
}
|
||||||
|
if (currTime - lastBuffer[jit->first] > 5){
|
||||||
|
if (jit->second.isMember("error") && jit->second["error"].asString() != ""){
|
||||||
|
jit->second["online"] = jit->second["error"];
|
||||||
|
}else{
|
||||||
|
jit->second["online"] = 0;
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
jit->second["online"] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static JSON::Value strlist;
|
||||||
|
bool changed = false;
|
||||||
|
if (strlist["config"] != Storage["config"]){
|
||||||
|
strlist["config"] = Storage["config"];
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if (strlist["streams"] != Storage["streams"]){
|
||||||
|
strlist["streams"] = Storage["streams"];
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if (changed){
|
||||||
|
WriteFile("/tmp/mist/streamlist", strlist.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CheckStreams(JSON::Value & in, JSON::Value & out){
|
||||||
|
bool changed = false;
|
||||||
|
for (JSON::ObjIter jit = in.ObjBegin(); jit != in.ObjEnd(); jit++){
|
||||||
|
if (out.isMember(jit->first)){
|
||||||
|
if ( !streamsEqual(jit->second, out[jit->first])){
|
||||||
|
Log("STRM", std::string("Updated stream ") + jit->first);
|
||||||
|
Util::Procs::Stop(jit->first);
|
||||||
|
startStream(jit->first, jit->second);
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
Log("STRM", std::string("New stream ") + jit->first);
|
||||||
|
startStream(jit->first, jit->second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (JSON::ObjIter jit = out.ObjBegin(); jit != out.ObjEnd(); jit++){
|
||||||
|
if ( !in.isMember(jit->first)){
|
||||||
|
Log("STRM", std::string("Deleted stream ") + jit->first);
|
||||||
|
Util::Procs::Stop(jit->first);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out = in;
|
||||||
|
}
|
||||||
|
|
||||||
|
} //Controller namespace
|
10
src/controller_streams.h
Normal file
10
src/controller_streams.h
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
#include <mist/json.h>
|
||||||
|
|
||||||
|
namespace Controller {
|
||||||
|
extern std::map<std::string, int> lastBuffer; ///< Last moment of contact with all buffers.
|
||||||
|
|
||||||
|
bool streamsEqual(JSON::Value & one, JSON::Value & two);
|
||||||
|
void startStream(std::string name, JSON::Value & data);
|
||||||
|
void CheckAllStreams(JSON::Value & data);
|
||||||
|
void CheckStreams(JSON::Value & in, JSON::Value & out);
|
||||||
|
} //Controller namespace
|
|
@ -16,20 +16,20 @@
|
||||||
#include <mist/config.h>
|
#include <mist/config.h>
|
||||||
|
|
||||||
/// Holds all code that converts filetypes to/from DTSC.
|
/// Holds all code that converts filetypes to/from DTSC.
|
||||||
namespace Converters{
|
namespace Converters {
|
||||||
|
|
||||||
/// Reads DTSC from STDIN, outputs FLV to STDOUT.
|
/// Reads DTSC from STDIN, outputs FLV to STDOUT.
|
||||||
int DTSC2FLV() {
|
int DTSC2FLV(){
|
||||||
FLV::Tag FLV_out; // Temporary storage for outgoing FLV data.
|
FLV::Tag FLV_out; // Temporary storage for outgoing FLV data.
|
||||||
DTSC::Stream Strm;
|
DTSC::Stream Strm;
|
||||||
std::string inBuffer;
|
std::string inBuffer;
|
||||||
char charBuffer[1024*10];
|
char charBuffer[1024 * 10];
|
||||||
unsigned int charCount;
|
unsigned int charCount;
|
||||||
bool doneheader = false;
|
bool doneheader = false;
|
||||||
|
|
||||||
while (std::cin.good()){
|
while (std::cin.good()){
|
||||||
if (Strm.parsePacket(inBuffer)){
|
if (Strm.parsePacket(inBuffer)){
|
||||||
if (!doneheader){
|
if ( !doneheader){
|
||||||
doneheader = true;
|
doneheader = true;
|
||||||
std::cout.write(FLV::Header, 13);
|
std::cout.write(FLV::Header, 13);
|
||||||
FLV_out.DTSCMetaInit(Strm);
|
FLV_out.DTSCMetaInit(Strm);
|
||||||
|
@ -47,7 +47,7 @@ namespace Converters{
|
||||||
std::cout.write(FLV_out.data, FLV_out.len);
|
std::cout.write(FLV_out.data, FLV_out.len);
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
std::cin.read(charBuffer, 1024*10);
|
std::cin.read(charBuffer, 1024 * 10);
|
||||||
charCount = std::cin.gcount();
|
charCount = std::cin.gcount();
|
||||||
inBuffer.append(charBuffer, charCount);
|
inBuffer.append(charBuffer, charCount);
|
||||||
}
|
}
|
||||||
|
@ -56,13 +56,13 @@ namespace Converters{
|
||||||
std::cerr << "Done!" << std::endl;
|
std::cerr << "Done!" << std::endl;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}//FLV2DTSC
|
} //FLV2DTSC
|
||||||
|
|
||||||
};//Converter namespace
|
} //Converter namespace
|
||||||
|
|
||||||
/// Entry point for DTSC2FLV, simply calls Converters::DTSC2FLV().
|
/// Entry point for DTSC2FLV, simply calls Converters::DTSC2FLV().
|
||||||
int main(int argc, char ** argv){
|
int main(int argc, char ** argv){
|
||||||
Util::Config conf = Util::Config(argv[0], PACKAGE_VERSION);
|
Util::Config conf = Util::Config(argv[0], PACKAGE_VERSION);
|
||||||
conf.parseArgs(argc, argv);
|
conf.parseArgs(argc, argv);
|
||||||
return Converters::DTSC2FLV();
|
return Converters::DTSC2FLV();
|
||||||
}//main
|
} //main
|
||||||
|
|
|
@ -7,16 +7,16 @@
|
||||||
#include <mist/config.h>
|
#include <mist/config.h>
|
||||||
|
|
||||||
/// Holds all code that converts filetypes to/from to DTSC.
|
/// Holds all code that converts filetypes to/from to DTSC.
|
||||||
namespace Converters{
|
namespace Converters {
|
||||||
|
|
||||||
/// Reads an DTSC file and attempts to fix the metadata in it.
|
/// Reads an DTSC file and attempts to fix the metadata in it.
|
||||||
int DTSCFix(Util::Config & conf) {
|
int DTSCFix(Util::Config & conf){
|
||||||
DTSC::File F(conf.getString("filename"));
|
DTSC::File F(conf.getString("filename"));
|
||||||
JSON::Value oriheader = F.getMeta();
|
JSON::Value oriheader = F.getMeta();
|
||||||
JSON::Value meta = oriheader;
|
JSON::Value meta = oriheader;
|
||||||
JSON::Value pack;
|
JSON::Value pack;
|
||||||
|
|
||||||
if (!oriheader.isMember("moreheader")){
|
if ( !oriheader.isMember("moreheader")){
|
||||||
std::cerr << "This file is not DTSCFix'able. Please re-convert and try again." << std::endl;
|
std::cerr << "This file is not DTSCFix'able. Please re-convert and try again." << std::endl;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
@ -46,14 +46,20 @@ namespace Converters{
|
||||||
long long unsigned int bps = 0;
|
long long unsigned int bps = 0;
|
||||||
|
|
||||||
F.seekNext();
|
F.seekNext();
|
||||||
while (!F.getJSON().isNull()){
|
while ( !F.getJSON().isNull()){
|
||||||
nowpack = F.getJSON()["time"].asInt();
|
nowpack = F.getJSON()["time"].asInt();
|
||||||
if (firstpack == 0){firstpack = nowpack;}
|
if (firstpack == 0){
|
||||||
|
firstpack = nowpack;
|
||||||
|
}
|
||||||
if (F.getJSON()["datatype"].asString() == "audio"){
|
if (F.getJSON()["datatype"].asString() == "audio"){
|
||||||
if (lastaudio != 0 && (nowpack - lastaudio) != 0){
|
if (lastaudio != 0 && (nowpack - lastaudio) != 0){
|
||||||
bps = F.getJSON()["data"].asString().size() / (nowpack - lastaudio);
|
bps = F.getJSON()["data"].asString().size() / (nowpack - lastaudio);
|
||||||
if (bps < aud_min){aud_min = bps;}
|
if (bps < aud_min){
|
||||||
if (bps > aud_max){aud_max = bps;}
|
aud_min = bps;
|
||||||
|
}
|
||||||
|
if (bps > aud_max){
|
||||||
|
aud_max = bps;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
totalaudio += F.getJSON()["data"].asString().size();
|
totalaudio += F.getJSON()["data"].asString().size();
|
||||||
lastaudio = nowpack;
|
lastaudio = nowpack;
|
||||||
|
@ -61,24 +67,36 @@ namespace Converters{
|
||||||
if (F.getJSON()["datatype"].asString() == "video"){
|
if (F.getJSON()["datatype"].asString() == "video"){
|
||||||
if (lastvideo != 0 && (nowpack - lastvideo) != 0){
|
if (lastvideo != 0 && (nowpack - lastvideo) != 0){
|
||||||
bps = F.getJSON()["data"].asString().size() / (nowpack - lastvideo);
|
bps = F.getJSON()["data"].asString().size() / (nowpack - lastvideo);
|
||||||
if (bps < vid_min){vid_min = bps;}
|
if (bps < vid_min){
|
||||||
if (bps > vid_max){vid_max = bps;}
|
vid_min = bps;
|
||||||
|
}
|
||||||
|
if (bps > vid_max){
|
||||||
|
vid_max = bps;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (F.getJSON()["keyframe"].asInt() != 0){
|
if (F.getJSON()["keyframe"].asInt() != 0){
|
||||||
meta["keytime"].append(F.getJSON()["time"]);
|
meta["keytime"].append(F.getJSON()["time"]);
|
||||||
meta["keybpos"].append(F.getLastReadPos());
|
meta["keybpos"].append(F.getLastReadPos());
|
||||||
if (lastkey != 0){
|
if (lastkey != 0){
|
||||||
bps = nowpack - lastkey;
|
bps = nowpack - lastkey;
|
||||||
if (bps < key_min){key_min = bps;}
|
if (bps < key_min){
|
||||||
if (bps > key_max){key_max = bps;}
|
key_min = bps;
|
||||||
|
}
|
||||||
|
if (bps > key_max){
|
||||||
|
key_max = bps;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
keyframes++;
|
keyframes++;
|
||||||
lastkey = nowpack;
|
lastkey = nowpack;
|
||||||
}
|
}
|
||||||
if (F.getJSON()["offset"].asInt() != 0){
|
if (F.getJSON()["offset"].asInt() != 0){
|
||||||
bps = F.getJSON()["offset"].asInt();
|
bps = F.getJSON()["offset"].asInt();
|
||||||
if (bps < bfrm_min){bfrm_min = bps;}
|
if (bps < bfrm_min){
|
||||||
if (bps > bfrm_max){bfrm_max = bps;}
|
bfrm_min = bps;
|
||||||
|
}
|
||||||
|
if (bps > bfrm_max){
|
||||||
|
bfrm_max = bps;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
totalvideo += F.getJSON()["data"].asString().size();
|
totalvideo += F.getJSON()["data"].asString().size();
|
||||||
lastvideo = nowpack;
|
lastvideo = nowpack;
|
||||||
|
@ -86,7 +104,7 @@ namespace Converters{
|
||||||
F.seekNext();
|
F.seekNext();
|
||||||
}
|
}
|
||||||
|
|
||||||
meta["length"] = (long long int)((nowpack - firstpack)/1000);
|
meta["length"] = (long long int)((nowpack - firstpack) / 1000);
|
||||||
meta["lastms"] = (long long int)nowpack;
|
meta["lastms"] = (long long int)nowpack;
|
||||||
if (meta.isMember("audio")){
|
if (meta.isMember("audio")){
|
||||||
meta["audio"]["bps"] = (long long int)(totalaudio / ((lastaudio - firstpack) / 1000));
|
meta["audio"]["bps"] = (long long int)(totalaudio / ((lastaudio - firstpack) / 1000));
|
||||||
|
@ -104,7 +122,7 @@ namespace Converters{
|
||||||
std::cerr << "Appending new header..." << std::endl;
|
std::cerr << "Appending new header..." << std::endl;
|
||||||
std::string loader = meta.toPacked();
|
std::string loader = meta.toPacked();
|
||||||
long long int newHPos = F.addHeader(loader);
|
long long int newHPos = F.addHeader(loader);
|
||||||
if (!newHPos){
|
if ( !newHPos){
|
||||||
std::cerr << "Failure appending new header. Cancelling." << std::endl;
|
std::cerr << "Failure appending new header. Cancelling." << std::endl;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
@ -117,9 +135,9 @@ namespace Converters{
|
||||||
}else{
|
}else{
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}//DTSCFix
|
} //DTSCFix
|
||||||
|
|
||||||
};
|
}
|
||||||
|
|
||||||
/// Entry point for FLV2DTSC, simply calls Converters::FLV2DTSC().
|
/// Entry point for FLV2DTSC, simply calls Converters::FLV2DTSC().
|
||||||
int main(int argc, char ** argv){
|
int main(int argc, char ** argv){
|
||||||
|
@ -127,4 +145,4 @@ int main(int argc, char ** argv){
|
||||||
conf.addOption("filename", JSON::fromString("{\"arg_num\":1, \"arg\":\"string\", \"help\":\"Filename of the file to attempt to fix.\"}"));
|
conf.addOption("filename", JSON::fromString("{\"arg_num\":1, \"arg\":\"string\", \"help\":\"Filename of the file to attempt to fix.\"}"));
|
||||||
conf.parseArgs(argc, argv);
|
conf.parseArgs(argc, argv);
|
||||||
return Converters::DTSCFix(conf);
|
return Converters::DTSCFix(conf);
|
||||||
}//main
|
} //main
|
||||||
|
|
|
@ -17,10 +17,10 @@
|
||||||
#include <mist/config.h>
|
#include <mist/config.h>
|
||||||
|
|
||||||
/// Holds all code that converts filetypes to/from to DTSC.
|
/// Holds all code that converts filetypes to/from to DTSC.
|
||||||
namespace Converters{
|
namespace Converters {
|
||||||
|
|
||||||
/// Reads FLV from STDIN, outputs DTSC to STDOUT.
|
/// Reads FLV from STDIN, outputs DTSC to STDOUT.
|
||||||
int FLV2DTSC() {
|
int FLV2DTSC(){
|
||||||
FLV::Tag FLV_in; // Temporary storage for incoming FLV data.
|
FLV::Tag FLV_in; // Temporary storage for incoming FLV data.
|
||||||
JSON::Value meta_out; // Storage for outgoing header data.
|
JSON::Value meta_out; // Storage for outgoing header data.
|
||||||
JSON::Value pack_out; // Storage for outgoing data.
|
JSON::Value pack_out; // Storage for outgoing data.
|
||||||
|
@ -28,54 +28,56 @@ namespace Converters{
|
||||||
bool sending = false;
|
bool sending = false;
|
||||||
unsigned int counter = 0;
|
unsigned int counter = 0;
|
||||||
|
|
||||||
while (!feof(stdin)){
|
while ( !feof(stdin)){
|
||||||
if (FLV_in.FileLoader(stdin)){
|
if (FLV_in.FileLoader(stdin)){
|
||||||
pack_out = FLV_in.toJSON(meta_out);
|
pack_out = FLV_in.toJSON(meta_out);
|
||||||
if (pack_out.isNull()){continue;}
|
if (pack_out.isNull()){
|
||||||
if (!sending){
|
continue;
|
||||||
|
}
|
||||||
|
if ( !sending){
|
||||||
counter++;
|
counter++;
|
||||||
if (counter > 8){
|
if (counter > 8){
|
||||||
sending = true;
|
sending = true;
|
||||||
meta_out["moreheader"] = 0LL;
|
meta_out["moreheader"] = 0LL;
|
||||||
std::string packed_header = meta_out.toPacked();
|
std::string packed_header = meta_out.toPacked();
|
||||||
unsigned int size = htonl(packed_header.size());
|
unsigned int size = htonl(packed_header.size());
|
||||||
std::cout << std::string(DTSC::Magic_Header, 4) << std::string((char*)&size, 4) << packed_header;
|
std::cout << std::string(DTSC::Magic_Header, 4) << std::string((char*) &size, 4) << packed_header;
|
||||||
std::cout << prebuffer.rdbuf();
|
std::cout << prebuffer.rdbuf();
|
||||||
prebuffer.str("");
|
prebuffer.str("");
|
||||||
std::cerr << "Buffer done, starting real-time output..." << std::endl;
|
std::cerr << "Buffer done, starting real-time output..." << std::endl;
|
||||||
}else{
|
}else{
|
||||||
std::string packed_out = pack_out.toPacked();
|
std::string packed_out = pack_out.toPacked();
|
||||||
unsigned int size = htonl(packed_out.size());
|
unsigned int size = htonl(packed_out.size());
|
||||||
prebuffer << std::string(DTSC::Magic_Packet, 4) << std::string((char*)&size, 4) << packed_out;
|
prebuffer << std::string(DTSC::Magic_Packet, 4) << std::string((char*) &size, 4) << packed_out;
|
||||||
continue;//don't also write
|
continue; //don't also write
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//simply write
|
//simply write
|
||||||
std::string packed_out = pack_out.toPacked();
|
std::string packed_out = pack_out.toPacked();
|
||||||
unsigned int size = htonl(packed_out.size());
|
unsigned int size = htonl(packed_out.size());
|
||||||
std::cout << std::string(DTSC::Magic_Packet, 4) << std::string((char*)&size, 4) << packed_out;
|
std::cout << std::string(DTSC::Magic_Packet, 4) << std::string((char*) &size, 4) << packed_out;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the FLV input is very short, do output it correctly...
|
// if the FLV input is very short, do output it correctly...
|
||||||
if (!sending){
|
if ( !sending){
|
||||||
std::cerr << "EOF - outputting buffer..." << std::endl;
|
std::cerr << "EOF - outputting buffer..." << std::endl;
|
||||||
meta_out["moreheader"] = 0LL;
|
meta_out["moreheader"] = 0LL;
|
||||||
std::string packed_header = meta_out.toPacked();
|
std::string packed_header = meta_out.toPacked();
|
||||||
unsigned int size = htonl(packed_header.size());
|
unsigned int size = htonl(packed_header.size());
|
||||||
std::cout << std::string(DTSC::Magic_Header, 4) << std::string((char*)&size, 4) << packed_header;
|
std::cout << std::string(DTSC::Magic_Header, 4) << std::string((char*) &size, 4) << packed_header;
|
||||||
std::cout << prebuffer.rdbuf();
|
std::cout << prebuffer.rdbuf();
|
||||||
}
|
}
|
||||||
std::cerr << "Done! If you output this data to a file, don't forget to run MistDTSCFix next." << std::endl;
|
std::cerr << "Done! If you output this data to a file, don't forget to run MistDTSCFix next." << std::endl;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}//FLV2DTSC
|
} //FLV2DTSC
|
||||||
|
|
||||||
};//Buffer namespace
|
}
|
||||||
|
|
||||||
/// Entry point for FLV2DTSC, simply calls Converters::FLV2DTSC().
|
/// Entry point for FLV2DTSC, simply calls Converters::FLV2DTSC().
|
||||||
int main(int argc, char ** argv){
|
int main(int argc, char ** argv){
|
||||||
Util::Config conf = Util::Config(argv[0], PACKAGE_VERSION);
|
Util::Config conf = Util::Config(argv[0], PACKAGE_VERSION);
|
||||||
conf.parseArgs(argc, argv);
|
conf.parseArgs(argc, argv);
|
||||||
return Converters::FLV2DTSC();
|
return Converters::FLV2DTSC();
|
||||||
}//main
|
} //main
|
||||||
|
|
102
src/player.cpp
102
src/player.cpp
|
@ -32,32 +32,35 @@ class Stats{
|
||||||
std::string connector;
|
std::string connector;
|
||||||
unsigned int conntime;
|
unsigned int conntime;
|
||||||
Stats(){
|
Stats(){
|
||||||
up = 0; down = 0; conntime = 0;
|
up = 0;
|
||||||
};
|
down = 0;
|
||||||
|
conntime = 0;
|
||||||
|
}
|
||||||
|
;
|
||||||
/// Reads a stats string and parses it to the internal representation.
|
/// Reads a stats string and parses it to the internal representation.
|
||||||
Stats(std::string s){
|
Stats(std::string s){
|
||||||
size_t f = s.find(' ');
|
size_t f = s.find(' ');
|
||||||
if (f != std::string::npos){
|
if (f != std::string::npos){
|
||||||
host = s.substr(0, f);
|
host = s.substr(0, f);
|
||||||
s.erase(0, f+1);
|
s.erase(0, f + 1);
|
||||||
}
|
}
|
||||||
f = s.find(' ');
|
f = s.find(' ');
|
||||||
if (f != std::string::npos){
|
if (f != std::string::npos){
|
||||||
connector = s.substr(0, f);
|
connector = s.substr(0, f);
|
||||||
s.erase(0, f+1);
|
s.erase(0, f + 1);
|
||||||
}
|
}
|
||||||
f = s.find(' ');
|
f = s.find(' ');
|
||||||
if (f != std::string::npos){
|
if (f != std::string::npos){
|
||||||
conntime = atoi(s.substr(0, f).c_str());
|
conntime = atoi(s.substr(0, f).c_str());
|
||||||
s.erase(0, f+1);
|
s.erase(0, f + 1);
|
||||||
}
|
}
|
||||||
f = s.find(' ');
|
f = s.find(' ');
|
||||||
if (f != std::string::npos){
|
if (f != std::string::npos){
|
||||||
up = atoi(s.substr(0, f).c_str());
|
up = atoi(s.substr(0, f).c_str());
|
||||||
s.erase(0, f+1);
|
s.erase(0, f + 1);
|
||||||
down = atoi(s.c_str());
|
down = atoi(s.c_str());
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
int main(int argc, char** argv){
|
int main(int argc, char** argv){
|
||||||
|
@ -75,7 +78,7 @@ int main(int argc, char** argv){
|
||||||
pausemark["time"] = (long long int)0;
|
pausemark["time"] = (long long int)0;
|
||||||
|
|
||||||
Socket::Connection StatsSocket = Socket::Connection("/tmp/mist/statistics", true);
|
Socket::Connection StatsSocket = Socket::Connection("/tmp/mist/statistics", true);
|
||||||
int lasttime = Util::epoch();//time last packet was sent
|
int lasttime = Util::epoch(); //time last packet was sent
|
||||||
|
|
||||||
//send the header
|
//send the header
|
||||||
std::string meta_str = meta.toNetPacked();
|
std::string meta_str = meta.toNetPacked();
|
||||||
|
@ -87,8 +90,8 @@ int main(int argc, char** argv){
|
||||||
JSON::Value last_pack;
|
JSON::Value last_pack;
|
||||||
|
|
||||||
bool meta_sent = false;
|
bool meta_sent = false;
|
||||||
long long now, lastTime = 0;//for timing of sending packets
|
long long now, lastTime = 0; //for timing of sending packets
|
||||||
long long bench = 0;//for benchmarking
|
long long bench = 0; //for benchmarking
|
||||||
Stats sts;
|
Stats sts;
|
||||||
CYG_DEFI
|
CYG_DEFI
|
||||||
|
|
||||||
|
@ -97,21 +100,22 @@ int main(int argc, char** argv){
|
||||||
if (CYG_LOOP in_out.spool()){
|
if (CYG_LOOP in_out.spool()){
|
||||||
while (in_out.Received().size()){
|
while (in_out.Received().size()){
|
||||||
//delete anything that doesn't end with a newline
|
//delete anything that doesn't end with a newline
|
||||||
if (*(in_out.Received().get().rbegin()) != '\n'){
|
if ( *(in_out.Received().get().rbegin()) != '\n'){
|
||||||
in_out.Received().get().clear();
|
in_out.Received().get().clear();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
in_out.Received().get().resize(in_out.Received().get().size() - 1);
|
in_out.Received().get().resize(in_out.Received().get().size() - 1);
|
||||||
if (!in_out.Received().get().empty()){
|
if ( !in_out.Received().get().empty()){
|
||||||
switch (in_out.Received().get()[0]){
|
switch (in_out.Received().get()[0]){
|
||||||
case 'P':{ //Push
|
case 'P': { //Push
|
||||||
#if DEBUG >= 4
|
#if DEBUG >= 4
|
||||||
std::cerr << "Received push - ignoring (" << in_out.Received().get() << ")" << std::endl;
|
std::cerr << "Received push - ignoring (" << in_out.Received().get() << ")" << std::endl;
|
||||||
#endif
|
#endif
|
||||||
in_out.close();//pushing to VoD makes no sense
|
in_out.close(); //pushing to VoD makes no sense
|
||||||
} break;
|
}
|
||||||
case 'S':{ //Stats
|
break;
|
||||||
if (!StatsSocket.connected()){
|
case 'S': { //Stats
|
||||||
|
if ( !StatsSocket.connected()){
|
||||||
StatsSocket = Socket::Connection("/tmp/mist/statistics", true);
|
StatsSocket = Socket::Connection("/tmp/mist/statistics", true);
|
||||||
}
|
}
|
||||||
if (StatsSocket.connected()){
|
if (StatsSocket.connected()){
|
||||||
|
@ -125,7 +129,7 @@ int main(int argc, char** argv){
|
||||||
json_sts["vod"]["filename"] = conf.getString("filename");
|
json_sts["vod"]["filename"] = conf.getString("filename");
|
||||||
json_sts["vod"]["now"] = Util::epoch();
|
json_sts["vod"]["now"] = Util::epoch();
|
||||||
json_sts["vod"]["start"] = Util::epoch() - sts.conntime;
|
json_sts["vod"]["start"] = Util::epoch() - sts.conntime;
|
||||||
if (!meta_sent){
|
if ( !meta_sent){
|
||||||
json_sts["vod"]["meta"] = meta;
|
json_sts["vod"]["meta"] = meta;
|
||||||
json_sts["vod"]["meta"]["audio"].removeMember("init");
|
json_sts["vod"]["meta"]["audio"].removeMember("init");
|
||||||
json_sts["vod"]["meta"]["video"].removeMember("init");
|
json_sts["vod"]["meta"]["video"].removeMember("init");
|
||||||
|
@ -137,34 +141,42 @@ int main(int argc, char** argv){
|
||||||
StatsSocket.Send("\n\n");
|
StatsSocket.Send("\n\n");
|
||||||
StatsSocket.flush();
|
StatsSocket.flush();
|
||||||
}
|
}
|
||||||
} break;
|
}
|
||||||
case 's':{ //second-seek
|
break;
|
||||||
|
case 's': { //second-seek
|
||||||
int ms = JSON::Value(in_out.Received().get().substr(2)).asInt();
|
int ms = JSON::Value(in_out.Received().get().substr(2)).asInt();
|
||||||
bool ret = source.seek_time(ms);
|
bool ret = source.seek_time(ms);
|
||||||
lastTime = 0;
|
lastTime = 0;
|
||||||
} break;
|
}
|
||||||
case 'f':{ //frame-seek
|
break;
|
||||||
|
case 'f': { //frame-seek
|
||||||
bool ret = source.seek_frame(JSON::Value(in_out.Received().get().substr(2)).asInt());
|
bool ret = source.seek_frame(JSON::Value(in_out.Received().get().substr(2)).asInt());
|
||||||
lastTime = 0;
|
lastTime = 0;
|
||||||
} break;
|
}
|
||||||
case 'p':{ //play
|
break;
|
||||||
|
case 'p': { //play
|
||||||
playing = -1;
|
playing = -1;
|
||||||
lastTime = 0;
|
lastTime = 0;
|
||||||
in_out.setBlocking(false);
|
in_out.setBlocking(false);
|
||||||
} break;
|
}
|
||||||
case 'o':{ //once-play
|
break;
|
||||||
if (playing <= 0){playing = 1;}
|
case 'o': { //once-play
|
||||||
|
if (playing <= 0){
|
||||||
|
playing = 1;
|
||||||
|
}
|
||||||
++playing;
|
++playing;
|
||||||
in_out.setBlocking(false);
|
in_out.setBlocking(false);
|
||||||
#if DEBUG >= 4
|
#if DEBUG >= 4
|
||||||
std::cerr << "Playing one keyframe" << std::endl;
|
std::cerr << "Playing one keyframe" << std::endl;
|
||||||
#endif
|
#endif
|
||||||
bench = Util::getMS();
|
bench = Util::getMS();
|
||||||
} break;
|
}
|
||||||
case 'q':{ //quit-playing
|
break;
|
||||||
|
case 'q': { //quit-playing
|
||||||
playing = 0;
|
playing = 0;
|
||||||
in_out.setBlocking(true);
|
in_out.setBlocking(true);
|
||||||
} break;
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
in_out.Received().get().clear();
|
in_out.Received().get().clear();
|
||||||
}
|
}
|
||||||
|
@ -173,18 +185,22 @@ int main(int argc, char** argv){
|
||||||
if (playing != 0){
|
if (playing != 0){
|
||||||
now = Util::getMS();
|
now = Util::getMS();
|
||||||
source.seekNext();
|
source.seekNext();
|
||||||
if (!source.getJSON()){playing = 0;}
|
if ( !source.getJSON()){
|
||||||
|
playing = 0;
|
||||||
|
}
|
||||||
if (source.getJSON().isMember("keyframe")){
|
if (source.getJSON().isMember("keyframe")){
|
||||||
if (playing == -1 && meta["video"]["keyms"].asInt() > now-lastTime) {
|
if (playing == -1 && meta["video"]["keyms"].asInt() > now - lastTime){
|
||||||
Util::sleep(meta["video"]["keyms"].asInt()-(now-lastTime));
|
Util::sleep(meta["video"]["keyms"].asInt() - (now - lastTime));
|
||||||
}
|
}
|
||||||
lastTime = now;
|
lastTime = now;
|
||||||
if (playing > 0){--playing;}
|
if (playing > 0){
|
||||||
|
--playing;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (playing == 0){
|
if (playing == 0){
|
||||||
#if DEBUG >= 4
|
#if DEBUG >= 4
|
||||||
std::cerr << "Sending pause_marker (" << (Util::getMS() - bench) << "ms)" << std::endl;
|
std::cerr << "Sending pause_marker (" << (Util::getMS() - bench) << "ms)" << std::endl;
|
||||||
#endif
|
#endif
|
||||||
pausemark["time"] = source.getJSON()["time"];
|
pausemark["time"] = source.getJSON()["time"];
|
||||||
pausemark.toPacked();
|
pausemark.toPacked();
|
||||||
in_out.SendNow(pausemark.toNetPacked());
|
in_out.SendNow(pausemark.toNetPacked());
|
||||||
|
@ -195,7 +211,7 @@ int main(int argc, char** argv){
|
||||||
in_out.Send("DTPD");
|
in_out.Send("DTPD");
|
||||||
//insert the packet length
|
//insert the packet length
|
||||||
unsigned int size = htonl(source.getPacket().size());
|
unsigned int size = htonl(source.getPacket().size());
|
||||||
in_out.Send((char*)&size, 4);
|
in_out.Send((char*) &size, 4);
|
||||||
in_out.SendNow(source.getPacket());
|
in_out.SendNow(source.getPacket());
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
|
@ -204,12 +220,12 @@ int main(int argc, char** argv){
|
||||||
}
|
}
|
||||||
StatsSocket.close();
|
StatsSocket.close();
|
||||||
in_out.close();
|
in_out.close();
|
||||||
#if DEBUG >= 4
|
#if DEBUG >= 4
|
||||||
if (Util::epoch() - lasttime < 60){
|
if (Util::epoch() - lasttime < 60){
|
||||||
std::cerr << "Player exited (disconnect)." << std::endl;
|
std::cerr << "Player exited (disconnect)." << std::endl;
|
||||||
}else{
|
}else{
|
||||||
std::cerr << "Player exited (timeout)." << std::endl;
|
std::cerr << "Player exited (timeout)." << std::endl;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue