Refactor and doxygen of all connectors finished. Internal documentation follows later.
This commit is contained in:
parent
570aa95315
commit
9b41a07c2f
7 changed files with 809 additions and 763 deletions
|
@ -30,18 +30,18 @@ namespace Connector_HTTP {
|
||||||
class ConnConn{
|
class ConnConn{
|
||||||
public:
|
public:
|
||||||
Socket::Connection * conn; ///< The socket of this connection
|
Socket::Connection * conn; ///< The socket of this connection
|
||||||
unsigned int lastuse; ///< Seconds since last use of this connection.
|
unsigned int lastUse; ///< Seconds since last use of this connection.
|
||||||
tthread::mutex in_use; ///< Mutex for this connection.
|
tthread::mutex inUse; ///< Mutex for this connection.
|
||||||
/// Constructor that sets the socket and lastuse to 0.
|
/// Constructor that sets the socket and lastUse to 0.
|
||||||
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.
|
||||||
|
@ -55,44 +55,51 @@ namespace Connector_HTTP {
|
||||||
;
|
;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::map<std::string, ConnConn *> connconn; ///< Connections to connectors
|
std::map<std::string, ConnConn *> connectorConnections; ///< Connections to connectors
|
||||||
std::set<tthread::thread *> active_threads; ///< Holds currently active threads
|
std::set<tthread::thread *> activeThreads; ///< Holds currently active threads
|
||||||
std::set<tthread::thread *> done_threads; ///< Holds threads that are done and ready to be joined.
|
std::set<tthread::thread *> doneThreads; ///< Holds threads that are done and ready to be joined.
|
||||||
tthread::mutex thread_mutex; ///< Mutex for adding/removing threads.
|
tthread::mutex threadMutex; ///< Mutex for adding/removing threads.
|
||||||
tthread::mutex conn_mutex; ///< Mutex for adding/removing connector connections.
|
tthread::mutex connMutex; ///< Mutex for adding/removing connector connections.
|
||||||
tthread::mutex timeout_mutex; ///< Mutex for timeout thread.
|
tthread::mutex timeoutMutex; ///< Mutex for timeout thread.
|
||||||
tthread::thread * timeouter = 0; ///< Thread that times out connections to connectors.
|
tthread::thread * timeouter = 0; ///< Thread that times out connections to connectors.
|
||||||
|
|
||||||
|
///\brief Function run as a thread to timeout requests on the proxy.
|
||||||
|
///\param n A NULL-pointer
|
||||||
void proxyTimeoutThread(void * n){
|
void proxyTimeoutThread(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(timeoutMutex);
|
||||||
while (true){
|
while (true){
|
||||||
{
|
{
|
||||||
tthread::lock_guard<tthread::mutex> guard(conn_mutex);
|
tthread::lock_guard<tthread::mutex> guard(connMutex);
|
||||||
if (connconn.empty()){
|
if (connectorConnections.empty()){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
std::map<std::string, ConnConn*>::iterator it;
|
std::map<std::string, ConnConn*>::iterator it;
|
||||||
for (it = connconn.begin(); it != connconn.end(); it++){
|
for (it = connectorConnections.begin(); it != connectorConnections.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->inUse.try_lock()){
|
||||||
it->second->in_use.unlock();
|
it->second->inUse.unlock();
|
||||||
delete it->second;
|
delete it->second;
|
||||||
connconn.erase(it);
|
connectorConnections.erase(it);
|
||||||
it = connconn.begin(); //get a valid iterator
|
it = connectorConnections.begin(); //get a valid iterator
|
||||||
if (it == connconn.end()){
|
if (it == connectorConnections.end()){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
conn_mutex.unlock();
|
connMutex.unlock();
|
||||||
}
|
}
|
||||||
usleep(1000000); //sleep 1 second and re-check
|
usleep(1000000); //sleep 1 second and re-check
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handles requests without associated handler, displaying a nice friendly error message.
|
///\brief Handles requests without associated handler.
|
||||||
|
///
|
||||||
|
///Displays a friendly error message.
|
||||||
|
///\param H The request to be handled.
|
||||||
|
///\param conn The connection to the client that issued the request.
|
||||||
|
///\return A timestamp indicating when the request was parsed.
|
||||||
long long int proxyHandleUnsupported(HTTP::Parser & H, Socket::Connection * conn){
|
long long int proxyHandleUnsupported(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);
|
||||||
|
@ -103,6 +110,12 @@ namespace Connector_HTTP {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///\brief Handles requests that have timed out.
|
||||||
|
///
|
||||||
|
///Displays a friendly error message.
|
||||||
|
///\param H The request that was being handled upon timeout.
|
||||||
|
///\param conn The connection to the client that issued the request.
|
||||||
|
///\return A timestamp indicating when the request was parsed.
|
||||||
long long int proxyHandleTimeout(HTTP::Parser & H, Socket::Connection * conn){
|
long long int proxyHandleTimeout(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);
|
||||||
|
@ -113,7 +126,19 @@ namespace Connector_HTTP {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handles internal requests.
|
///\brief Handles requests within the proxy.
|
||||||
|
///
|
||||||
|
///Currently supported urls:
|
||||||
|
/// - /crossdomain.xml
|
||||||
|
/// - /clientaccesspolicy.xml
|
||||||
|
/// - *.ico (for favicon)
|
||||||
|
/// - /info_[streamname].js (stream info)
|
||||||
|
/// - /embed_[streamname].js (embed info)
|
||||||
|
///
|
||||||
|
///Unsupported urls default to proxyHandleUnsupported( ).
|
||||||
|
///\param H The request to be handled.
|
||||||
|
///\param conn The connection to the client that issued the request.
|
||||||
|
///\return A timestamp indicating when the request was parsed.
|
||||||
long long int proxyHandleInternal(HTTP::Parser & H, Socket::Connection * conn){
|
long long int proxyHandleInternal(HTTP::Parser & H, Socket::Connection * conn){
|
||||||
|
|
||||||
std::string url = H.getUrl();
|
std::string url = H.getUrl();
|
||||||
|
@ -263,7 +288,11 @@ namespace Connector_HTTP {
|
||||||
return proxyHandleUnsupported(H, conn); //anything else doesn't get handled
|
return proxyHandleUnsupported(H, conn); //anything else doesn't get handled
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handles requests without associated handler, displaying a nice friendly error message.
|
///\brief Handles requests by dispatching them to the corresponding connector.
|
||||||
|
///\param H The request to be handled.
|
||||||
|
///\param conn The connection to the client that issued the request.
|
||||||
|
///\param connector The type of connector to be invoked.
|
||||||
|
///\return A timestamp indicating when the request was parsed.
|
||||||
long long int proxyHandleThroughConnector(HTTP::Parser & H, Socket::Connection * conn, std::string & connector){
|
long long int proxyHandleThroughConnector(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;
|
||||||
|
@ -275,13 +304,13 @@ namespace Connector_HTTP {
|
||||||
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();
|
connMutex.lock();
|
||||||
if ( !connconn.count(uid) || !connconn[uid]->conn->connected()){
|
if ( !connectorConnections.count(uid) || !connectorConnections[uid]->conn->connected()){
|
||||||
if (connconn.count(uid)){
|
if (connectorConnections.count(uid)){
|
||||||
connconn.erase(uid);
|
connectorConnections.erase(uid);
|
||||||
}
|
}
|
||||||
connconn[uid] = new ConnConn(new Socket::Connection("/tmp/mist/http_" + connector));
|
connectorConnections[uid] = new ConnConn(new Socket::Connection("/tmp/mist/http_" + connector));
|
||||||
connconn[uid]->conn->setBlocking(false); //do not block on spool() with no data
|
connectorConnections[uid]->conn->setBlocking(false); //do not block on spool() with no data
|
||||||
#if DEBUG >= 4
|
#if DEBUG >= 4
|
||||||
std::cout << "Created new connection " << uid << std::endl;
|
std::cout << "Created new connection " << uid << std::endl;
|
||||||
#endif
|
#endif
|
||||||
|
@ -291,56 +320,56 @@ namespace Connector_HTTP {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
//start a new timeout thread, if neccesary
|
//start a new timeout thread, if neccesary
|
||||||
if (timeout_mutex.try_lock()){
|
if (timeoutMutex.try_lock()){
|
||||||
if (timeouter){
|
if (timeouter){
|
||||||
timeouter->join();
|
timeouter->join();
|
||||||
delete timeouter;
|
delete timeouter;
|
||||||
}
|
}
|
||||||
timeouter = new tthread::thread(Connector_HTTP::proxyTimeoutThread, 0);
|
timeouter = new tthread::thread(Connector_HTTP::proxyTimeoutThread, 0);
|
||||||
timeout_mutex.unlock();
|
timeoutMutex.unlock();
|
||||||
}
|
}
|
||||||
conn_mutex.unlock();
|
connMutex.unlock();
|
||||||
|
|
||||||
//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(connectorConnections[uid]->inUse);
|
||||||
//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 ( !connectorConnections.count(uid) || !connectorConnections[uid]->conn->connected()){
|
||||||
connconn[uid]->conn->close();
|
connectorConnections[uid]->conn->close();
|
||||||
return proxyHandleTimeout(H, conn);
|
return proxyHandleTimeout(H, conn);
|
||||||
}
|
}
|
||||||
//forward the original request
|
//forward the original request
|
||||||
connconn[uid]->conn->SendNow(request);
|
connectorConnections[uid]->conn->SendNow(request);
|
||||||
connconn[uid]->lastuse = 0;
|
connectorConnections[uid]->lastUse = 0;
|
||||||
unsigned int timeout = 0;
|
unsigned int timeout = 0;
|
||||||
unsigned int retries = 0;
|
unsigned int retries = 0;
|
||||||
//wait for a response
|
//wait for a response
|
||||||
while (connconn.count(uid) && connconn[uid]->conn->connected() && conn->connected()){
|
while (connectorConnections.count(uid) && connectorConnections[uid]->conn->connected() && conn->connected()){
|
||||||
conn->spool();
|
conn->spool();
|
||||||
if (connconn[uid]->conn->Received().size() || connconn[uid]->conn->spool()){
|
if (connectorConnections[uid]->conn->Received().size() || connectorConnections[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 ( *(connectorConnections[uid]->conn->Received().get().rbegin()) != '\n'){
|
||||||
std::string tmp = connconn[uid]->conn->Received().get();
|
std::string tmp = connectorConnections[uid]->conn->Received().get();
|
||||||
connconn[uid]->conn->Received().get().clear();
|
connectorConnections[uid]->conn->Received().get().clear();
|
||||||
if (connconn[uid]->conn->Received().size()){
|
if (connectorConnections[uid]->conn->Received().size()){
|
||||||
connconn[uid]->conn->Received().get().insert(0, tmp);
|
connectorConnections[uid]->conn->Received().get().insert(0, tmp);
|
||||||
}else{
|
}else{
|
||||||
connconn[uid]->conn->Received().append(tmp);
|
connectorConnections[uid]->conn->Received().append(tmp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//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(connectorConnections[uid]->conn->Received().get())){
|
||||||
//208 means the fragment is too new, retry in 3s
|
//208 means the fragment is too new, retry in 3s
|
||||||
if (H.url == "208"){
|
if (H.url == "208"){
|
||||||
retries++;
|
retries++;
|
||||||
if (retries >= 5){
|
if (retries >= 5){
|
||||||
std::cout << "[5 retry-laters, cancelled]" << std::endl;
|
std::cout << "[5 retry-laters, cancelled]" << std::endl;
|
||||||
connconn[uid]->conn->close();
|
connectorConnections[uid]->conn->close();
|
||||||
return proxyHandleTimeout(H, conn);
|
return proxyHandleTimeout(H, conn);
|
||||||
}
|
}
|
||||||
connconn[uid]->lastuse = 0;
|
connectorConnections[uid]->lastUse = 0;
|
||||||
timeout = 0;
|
timeout = 0;
|
||||||
Util::sleep(3000);
|
Util::sleep(3000);
|
||||||
connconn[uid]->conn->SendNow(request);
|
connectorConnections[uid]->conn->SendNow(request);
|
||||||
H.Clean();
|
H.Clean();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -350,16 +379,16 @@ namespace Connector_HTTP {
|
||||||
//keep trying unless the timeout triggers
|
//keep trying unless the timeout triggers
|
||||||
if (timeout++ > 4000){
|
if (timeout++ > 4000){
|
||||||
std::cout << "[20s timeout triggered]" << std::endl;
|
std::cout << "[20s timeout triggered]" << std::endl;
|
||||||
connconn[uid]->conn->close();
|
connectorConnections[uid]->conn->close();
|
||||||
return proxyHandleTimeout(H, conn);
|
return proxyHandleTimeout(H, conn);
|
||||||
}else{
|
}else{
|
||||||
Util::sleep(5);
|
Util::sleep(5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ( !connconn.count(uid) || !connconn[uid]->conn->connected() || !conn->connected()){
|
if ( !connectorConnections.count(uid) || !connectorConnections[uid]->conn->connected() || !conn->connected()){
|
||||||
//failure, disconnect and sent error to user
|
//failure, disconnect and sent error to user
|
||||||
connconn[uid]->conn->close();
|
connectorConnections[uid]->conn->close();
|
||||||
return proxyHandleTimeout(H, conn);
|
return proxyHandleTimeout(H, conn);
|
||||||
}else{
|
}else{
|
||||||
long long int ret = Util::getMS();
|
long long int ret = Util::getMS();
|
||||||
|
@ -375,9 +404,9 @@ namespace Connector_HTTP {
|
||||||
H.SetHeader("Server", "mistserver/" PACKAGE_VERSION "/" + Util::Config::libver);
|
H.SetHeader("Server", "mistserver/" PACKAGE_VERSION "/" + Util::Config::libver);
|
||||||
conn->SendNow(H.BuildResponse("200", "OK"));
|
conn->SendNow(H.BuildResponse("200", "OK"));
|
||||||
//switch out the connection for an empty one - it makes no sense to keep these globally
|
//switch out the connection for an empty one - it makes no sense to keep these globally
|
||||||
Socket::Connection * myConn = connconn[uid]->conn;
|
Socket::Connection * myConn = connectorConnections[uid]->conn;
|
||||||
connconn[uid]->conn = new Socket::Connection();
|
connectorConnections[uid]->conn = new Socket::Connection();
|
||||||
connconn[uid]->in_use.unlock();
|
connectorConnections[uid]->inUse.unlock();
|
||||||
//continue sending data from this socket and keep it permanently in use
|
//continue sending data from this socket and keep it permanently in use
|
||||||
while (myConn->connected() && conn->connected()){
|
while (myConn->connected() && conn->connected()){
|
||||||
if (myConn->Received().size() || myConn->spool()){
|
if (myConn->Received().size() || myConn->spool()){
|
||||||
|
@ -396,12 +425,16 @@ namespace Connector_HTTP {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the name of the HTTP connector the given request should be served by.
|
///\brief Determines the type of connector to be used for handling a request.
|
||||||
/// Can currently return:
|
///\param H The request to be handled..
|
||||||
/// - none (request not supported)
|
///\return A string indicating the type of connector.
|
||||||
/// - internal (request fed from information internal to this connector)
|
///Possible values are:
|
||||||
/// - dynamic (request fed from http_dynamic connector)
|
/// - "none" The request is not supported.
|
||||||
/// - progressive (request fed from http_progressive connector)
|
/// - "internal" The request should be handled by the proxy itself.
|
||||||
|
/// - "dynamic" The request should be dispatched to the HTTP Dynamic Connector
|
||||||
|
/// - "progressive" The request should be dispatched to the HTTP Progressive Connector
|
||||||
|
/// - "smooth" The request should be dispatched to the HTTP Smooth Connector
|
||||||
|
/// - "live" The request should be dispatched to the HTTP Live Connector
|
||||||
std::string proxyGetHandleType(HTTP::Parser & H){
|
std::string proxyGetHandleType(HTTP::Parser & H){
|
||||||
std::string url = H.getUrl();
|
std::string url = H.getUrl();
|
||||||
if (url.find("/dynamic/") != std::string::npos){
|
if (url.find("/dynamic/") != std::string::npos){
|
||||||
|
@ -449,7 +482,8 @@ namespace Connector_HTTP {
|
||||||
return "none";
|
return "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Thread for handling a single HTTP connection
|
///\brief Function run as a thread to handle a single HTTP connection.
|
||||||
|
///\param pointer A Socket::Connection* indicating the connection to th client.
|
||||||
void proxyHandleHTTPConnection(void * pointer){
|
void proxyHandleHTTPConnection(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
|
||||||
|
@ -504,17 +538,17 @@ namespace Connector_HTTP {
|
||||||
//close and remove the connection
|
//close and remove the connection
|
||||||
conn->close();
|
conn->close();
|
||||||
delete conn;
|
delete conn;
|
||||||
//remove this thread from active_threads and add it to done_threads.
|
//remove this thread from activeThreads and add it to doneThreads.
|
||||||
thread_mutex.lock();
|
threadMutex.lock();
|
||||||
for (std::set<tthread::thread *>::iterator it = active_threads.begin(); it != active_threads.end(); it++){
|
for (std::set<tthread::thread *>::iterator it = activeThreads.begin(); it != activeThreads.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);
|
activeThreads.erase(T);
|
||||||
done_threads.insert(T);
|
doneThreads.insert(T);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
thread_mutex.unlock();
|
threadMutex.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
} //Connector_HTTP namespace
|
} //Connector_HTTP namespace
|
||||||
|
@ -533,17 +567,17 @@ int main(int argc, char ** argv){
|
||||||
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::threadMutex.lock();
|
||||||
tthread::thread * T = new tthread::thread(Connector_HTTP::proxyHandleHTTPConnection, (void *)(new Socket::Connection(S)));
|
tthread::thread * T = new tthread::thread(Connector_HTTP::proxyHandleHTTPConnection, (void *)(new Socket::Connection(S)));
|
||||||
Connector_HTTP::active_threads.insert(T);
|
Connector_HTTP::activeThreads.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::doneThreads.empty()){
|
||||||
T = *Connector_HTTP::done_threads.begin();
|
T = *Connector_HTTP::doneThreads.begin();
|
||||||
T->join();
|
T->join();
|
||||||
Connector_HTTP::done_threads.erase(T);
|
Connector_HTTP::doneThreads.erase(T);
|
||||||
delete T;
|
delete T;
|
||||||
}
|
}
|
||||||
Connector_HTTP::thread_mutex.unlock();
|
Connector_HTTP::threadMutex.unlock();
|
||||||
}else{
|
}else{
|
||||||
Util::sleep(10); //sleep 10ms
|
Util::sleep(10); //sleep 10ms
|
||||||
}
|
}
|
||||||
|
@ -553,16 +587,16 @@ int main(int argc, char ** argv){
|
||||||
//wait for existing connections to drop
|
//wait for existing connections to drop
|
||||||
bool repeat = true;
|
bool repeat = true;
|
||||||
while (repeat){
|
while (repeat){
|
||||||
Connector_HTTP::thread_mutex.lock();
|
Connector_HTTP::threadMutex.lock();
|
||||||
repeat = !Connector_HTTP::active_threads.empty();
|
repeat = !Connector_HTTP::activeThreads.empty();
|
||||||
//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::doneThreads.empty()){
|
||||||
tthread::thread * T = *Connector_HTTP::done_threads.begin();
|
tthread::thread * T = *Connector_HTTP::doneThreads.begin();
|
||||||
T->join();
|
T->join();
|
||||||
Connector_HTTP::done_threads.erase(T);
|
Connector_HTTP::doneThreads.erase(T);
|
||||||
delete T;
|
delete T;
|
||||||
}
|
}
|
||||||
Connector_HTTP::thread_mutex.unlock();
|
Connector_HTTP::threadMutex.unlock();
|
||||||
if (repeat){
|
if (repeat){
|
||||||
Util::sleep(100); //sleep 100ms
|
Util::sleep(100); //sleep 100ms
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,11 +26,11 @@
|
||||||
/// Holds everything unique to HTTP Connectors.
|
/// Holds everything unique to HTTP Connectors.
|
||||||
namespace Connector_HTTP {
|
namespace Connector_HTTP {
|
||||||
///\brief Builds a bootstrap for use in HTTP Dynamic streaming.
|
///\brief Builds a bootstrap for use in HTTP Dynamic streaming.
|
||||||
///\param MovieId The name of the movie.
|
///\param streamName The name of the stream.
|
||||||
///\param metadata The current metadata, used to generate the index.
|
///\param metadata The current metadata, used to generate the index.
|
||||||
///\param fragnum The index of the current fragment
|
///\param fragnum The index of the current fragment
|
||||||
///\return The generated bootstrap.
|
///\return The generated bootstrap.
|
||||||
std::string dynamicBootstrap(std::string & MovieId, JSON::Value & metadata, int fragnum = 0){
|
std::string dynamicBootstrap(std::string & streamName, JSON::Value & metadata, int fragnum = 0){
|
||||||
std::string empty;
|
std::string empty;
|
||||||
|
|
||||||
MP4::ASRT asrt;
|
MP4::ASRT asrt;
|
||||||
|
@ -80,7 +80,7 @@ namespace Connector_HTTP {
|
||||||
abst.setLive(false);
|
abst.setLive(false);
|
||||||
abst.setCurrentMediaTime(metadata["lastms"].asInt());
|
abst.setCurrentMediaTime(metadata["lastms"].asInt());
|
||||||
abst.setSmpteTimeCodeOffset(0);
|
abst.setSmpteTimeCodeOffset(0);
|
||||||
abst.setMovieIdentifier(MovieId);
|
abst.setMovieIdentifier(streamName);
|
||||||
abst.setSegmentRunTable(asrt, 0);
|
abst.setSegmentRunTable(asrt, 0);
|
||||||
abst.setFragmentRunTable(afrt, 0);
|
abst.setFragmentRunTable(afrt, 0);
|
||||||
|
|
||||||
|
@ -91,24 +91,24 @@ namespace Connector_HTTP {
|
||||||
}
|
}
|
||||||
|
|
||||||
///\brief Builds an index file for HTTP Dynamic streaming.
|
///\brief Builds an index file for HTTP Dynamic streaming.
|
||||||
///\param MovieId The name of the movie.
|
///\param streamName The name of the stream.
|
||||||
///\param metadata The current metadata, used to generate the index.
|
///\param metadata The current metadata, used to generate the index.
|
||||||
///\return The index file for HTTP Dynamic Streaming.
|
///\return The index file for HTTP Dynamic Streaming.
|
||||||
std::string dynamicIndex(std::string & MovieId, JSON::Value & metadata){
|
std::string dynamicIndex(std::string & streamName, JSON::Value & metadata){
|
||||||
std::string Result;
|
std::string Result;
|
||||||
if (metadata.isMember("vod")){
|
if (metadata.isMember("vod")){
|
||||||
Result =
|
Result =
|
||||||
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
|
"<?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>" + streamName + "</id>\n"
|
||||||
"<width>" + metadata["video"]["width"].asString() + "</width>\n"
|
"<width>" + metadata["video"]["width"].asString() + "</width>\n"
|
||||||
"<height>" + metadata["video"]["height"].asString() + "</height>\n"
|
"<height>" + metadata["video"]["height"].asString() + "</height>\n"
|
||||||
"<duration>" + metadata["length"].asString() + ".000</duration>\n"
|
"<duration>" + metadata["length"].asString() + ".000</duration>\n"
|
||||||
"<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(dynamicBootstrap(MovieId, metadata)) + "</bootstrapInfo>\n"
|
"<bootstrapInfo profile=\"named\" id=\"bootstrap1\">" + Base64::encode(dynamicBootstrap(streamName, metadata)) + "</bootstrapInfo>\n"
|
||||||
"<media streamId=\"1\" bootstrapInfoId=\"bootstrap1\" url=\"" + MovieId + "/\">\n"
|
"<media streamId=\"1\" bootstrapInfoId=\"bootstrap1\" url=\"" + streamName + "/\">\n"
|
||||||
"<metadata>AgAKb25NZXRhRGF0YQMAAAk=</metadata>\n"
|
"<metadata>AgAKb25NZXRhRGF0YQMAAAk=</metadata>\n"
|
||||||
"</media>\n"
|
"</media>\n"
|
||||||
"</manifest>\n";
|
"</manifest>\n";
|
||||||
|
@ -116,15 +116,15 @@ namespace Connector_HTTP {
|
||||||
Result =
|
Result =
|
||||||
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
|
"<?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>" + streamName + "</id>\n"
|
||||||
"<dvrInfo windowDuration=\"" + metadata["buffer_window"].asString().substr(0, metadata["buffer_window"].asString().size() - 3) + "\"></dvrInfo>"
|
"<dvrInfo windowDuration=\"" + metadata["buffer_window"].asString().substr(0, metadata["buffer_window"].asString().size() - 3) + "\"></dvrInfo>"
|
||||||
"<mimeType>video/mp4</mimeType>\n"
|
"<mimeType>video/mp4</mimeType>\n"
|
||||||
"<streamType>live</streamType>\n"
|
"<streamType>live</streamType>\n"
|
||||||
"<deliveryType>streaming</deliveryType>\n"
|
"<deliveryType>streaming</deliveryType>\n"
|
||||||
"<media url=\"" + MovieId + "/\">\n"
|
"<media url=\"" + streamName + "/\">\n"
|
||||||
"<metadata>AgAKb25NZXRhRGF0YQMAAAk=</metadata>\n"
|
"<metadata>AgAKb25NZXRhRGF0YQMAAAk=</metadata>\n"
|
||||||
"</media>\n"
|
"</media>\n"
|
||||||
"<bootstrapInfo profile=\"named\" url=\"" + MovieId + ".abst\" />\n"
|
"<bootstrapInfo profile=\"named\" url=\"" + streamName + ".abst\" />\n"
|
||||||
"</manifest>\n";
|
"</manifest>\n";
|
||||||
}
|
}
|
||||||
#if DEBUG >= 8
|
#if DEBUG >= 8
|
||||||
|
|
|
@ -26,10 +26,9 @@
|
||||||
/// Holds everything unique to HTTP Connectors.
|
/// Holds everything unique to HTTP Connectors.
|
||||||
namespace Connector_HTTP {
|
namespace Connector_HTTP {
|
||||||
///\brief Builds an index file for HTTP Live streaming.
|
///\brief Builds an index file for HTTP Live streaming.
|
||||||
///\param MovieId The name of the movie.
|
|
||||||
///\param metadata The current metadata, used to generate the index.
|
///\param metadata The current metadata, used to generate the index.
|
||||||
///\return The index file for HTTP Live Streaming.
|
///\return The index file for HTTP Live Streaming.
|
||||||
std::string liveIndex(std::string & MovieId, JSON::Value & metadata){
|
std::string liveIndex(JSON::Value & metadata){
|
||||||
std::stringstream Result;
|
std::stringstream Result;
|
||||||
if ( !metadata.isMember("live")){
|
if ( !metadata.isMember("live")){
|
||||||
int longestFragment = 0;
|
int longestFragment = 0;
|
||||||
|
@ -62,7 +61,7 @@ namespace Connector_HTTP {
|
||||||
return Result.str();
|
return Result.str();
|
||||||
} //liveIndex
|
} //liveIndex
|
||||||
|
|
||||||
///\brief Main function for the HTTP HLS Connector
|
///\brief Main function for the HTTP Live Connector
|
||||||
///\param conn A socket describing the connection the client.
|
///\param conn A socket describing the connection the client.
|
||||||
///\return The exit code of the connector.
|
///\return The exit code of the connector.
|
||||||
int liveConnector(Socket::Connection conn){
|
int liveConnector(Socket::Connection conn){
|
||||||
|
@ -175,7 +174,7 @@ namespace Connector_HTTP {
|
||||||
HTTP_S.Clean();
|
HTTP_S.Clean();
|
||||||
HTTP_S.SetHeader("Content-Type", manifestType);
|
HTTP_S.SetHeader("Content-Type", manifestType);
|
||||||
HTTP_S.SetHeader("Cache-Control", "no-cache");
|
HTTP_S.SetHeader("Cache-Control", "no-cache");
|
||||||
std::string manifest = liveIndex(streamname, Strm.metadata);
|
std::string manifest = liveIndex(Strm.metadata);
|
||||||
HTTP_S.SetBody(manifest);
|
HTTP_S.SetBody(manifest);
|
||||||
conn.SendNow(HTTP_S.BuildResponse("200", "OK"));
|
conn.SendNow(HTTP_S.BuildResponse("200", "OK"));
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,10 +28,9 @@
|
||||||
///\brief Holds everything unique to HTTP Connectors.
|
///\brief Holds everything unique to HTTP Connectors.
|
||||||
namespace Connector_HTTP {
|
namespace Connector_HTTP {
|
||||||
///\brief Builds an index file for HTTP Smooth streaming.
|
///\brief Builds an index file for HTTP Smooth streaming.
|
||||||
///\param MovieId The name of the movie.
|
|
||||||
///\param metadata The current metadata, used to generate the index.
|
///\param metadata The current metadata, used to generate the index.
|
||||||
///\return The index file for HTTP Smooth Streaming.
|
///\return The index file for HTTP Smooth Streaming.
|
||||||
std::string smoothIndex(std::string & MovieId, JSON::Value & metadata){
|
std::string smoothIndex(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 "
|
Result << "<SmoothStreamingMedia "
|
||||||
|
@ -245,7 +244,7 @@ namespace Connector_HTTP {
|
||||||
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");
|
||||||
std::string manifest = smoothIndex(streamname, Strm.metadata);
|
std::string manifest = smoothIndex(Strm.metadata);
|
||||||
HTTP_S.SetBody(manifest);
|
HTTP_S.SetBody(manifest);
|
||||||
conn.SendNow(HTTP_S.BuildResponse("200", "OK"));
|
conn.SendNow(HTTP_S.BuildResponse("200", "OK"));
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,8 @@
|
||||||
#include <mist/stream.h>
|
#include <mist/stream.h>
|
||||||
#include <mist/timing.h>
|
#include <mist/timing.h>
|
||||||
|
|
||||||
/// Contains the main code for the RAW connector.
|
///\brief 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){
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
/// Contains the main code for the RTMP Connector
|
/// Contains the main code for the RTMP Connector
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
@ -10,7 +12,7 @@
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
#include <getopt.h>
|
#include <getopt.h>
|
||||||
#include <sstream>
|
|
||||||
#include <mist/socket.h>
|
#include <mist/socket.h>
|
||||||
#include <mist/config.h>
|
#include <mist/config.h>
|
||||||
#include <mist/flv_tag.h>
|
#include <mist/flv_tag.h>
|
||||||
|
@ -19,171 +21,261 @@
|
||||||
#include <mist/stream.h>
|
#include <mist/stream.h>
|
||||||
#include <mist/timing.h>
|
#include <mist/timing.h>
|
||||||
|
|
||||||
/// Holds all functions and data unique to the RTMP Connector
|
///\brief Holds everything 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; ///< Indicates whether streaming can start.
|
||||||
bool inited = false; ///< Set to true when ready to connect to Buffer.
|
bool inited = false; ///< Indicates whether we are ready to connect to the Buffer.
|
||||||
bool nostats = false; ///< Set to true if no stats should be sent anymore (push mode).
|
bool noStats = false; ///< Indicates when no stats should be sent anymore. Used in push mode.
|
||||||
bool stopparsing = false; ///< Set to true when all parsing needs to be cancelled.
|
bool stopParsing = false; ///< Indicates when to stop all parsing.
|
||||||
|
|
||||||
//for reply to play command
|
//for reply to play command
|
||||||
int play_trans = -1;
|
int playTransaction = -1;///<The transaction number of the reply.
|
||||||
int play_streamid = -1;
|
int playStreamId = -1;///<The stream id of the reply.
|
||||||
int play_msgtype = -1;
|
int playMessageType = -1;///<The message type of the reply.
|
||||||
|
|
||||||
//generic state keeping
|
//generic state keeping
|
||||||
bool stream_inited = false; ///true if init data for audio/video was sent
|
bool streamInited = false;///<Indicates whether init data for audio/video was sent.
|
||||||
|
|
||||||
Socket::Connection Socket; ///< Socket connected to user
|
Socket::Connection Socket; ///< A copy of the user socket to allow helper functions to directly send data.
|
||||||
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 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.
|
|
||||||
int Connector_RTMP(Socket::Connection conn);
|
|
||||||
} //Connector_RTMP namespace;
|
|
||||||
|
|
||||||
///\brief Main Connector_RTMP function
|
///\brief Sends a RTMP command either in AMF or AMF3 mode.
|
||||||
///\param conn A socket describing the connection the client.
|
///\param amfReply The data to be sent over RTMP.
|
||||||
///\return The exit code of the connector.
|
///\param messageType The type of message.
|
||||||
int Connector_RTMP::Connector_RTMP(Socket::Connection conn){
|
///\param streamId The ID of the AMF stream.
|
||||||
Socket = conn;
|
void sendCommand(AMF::Object & amfReply, int messageType, int streamId){
|
||||||
Socket.setBlocking(false);
|
#if DEBUG >= 8
|
||||||
FLV::Tag tag, init_tag;
|
std::cerr << amfReply.Print() << std::endl;
|
||||||
DTSC::Stream Strm;
|
|
||||||
|
|
||||||
while ( !Socket.Received().available(1537) && Socket.connected()){
|
|
||||||
Socket.spool();
|
|
||||||
Util::sleep(5);
|
|
||||||
}
|
|
||||||
RTMPStream::handshake_in = Socket.Received().remove(1537);
|
|
||||||
RTMPStream::rec_cnt += 1537;
|
|
||||||
|
|
||||||
if (RTMPStream::doHandshake()){
|
|
||||||
Socket.SendNow(RTMPStream::handshake_out);
|
|
||||||
while ( !Socket.Received().available(1536) && Socket.connected()){
|
|
||||||
Socket.spool();
|
|
||||||
Util::sleep(5);
|
|
||||||
}
|
|
||||||
Socket.Received().remove(1536);
|
|
||||||
RTMPStream::rec_cnt += 1536;
|
|
||||||
#if DEBUG >= 5
|
|
||||||
fprintf(stderr, "Handshake succcess!\n");
|
|
||||||
#endif
|
#endif
|
||||||
|
if (messageType == 17){
|
||||||
|
Socket.SendNow(RTMPStream::SendChunk(3, messageType, streamId, (char)0 + amfReply.Pack()));
|
||||||
}else{
|
}else{
|
||||||
|
Socket.SendNow(RTMPStream::SendChunk(3, messageType, streamId, amfReply.Pack()));
|
||||||
|
}
|
||||||
|
} //sendCommand
|
||||||
|
|
||||||
|
///\brief Parses a single AMF command message, and sends a direct response through sendCommand().
|
||||||
|
///\param amfData The received request.
|
||||||
|
///\param messageType The type of message.
|
||||||
|
///\param streamId The ID of the AMF stream.
|
||||||
|
void parseAMFCommand(AMF::Object & amfData, int messageType, int streamId){
|
||||||
#if DEBUG >= 5
|
#if DEBUG >= 5
|
||||||
fprintf(stderr, "Handshake fail!\n");
|
fprintf(stderr, "Received command: %s\n", amfData.Print().c_str());
|
||||||
#endif
|
#endif
|
||||||
return 0;
|
#if DEBUG >= 8
|
||||||
|
fprintf(stderr, "AMF0 command: %s\n", amfData.getContentP(0)->StrValue().c_str());
|
||||||
|
#endif
|
||||||
|
if (amfData.getContentP(0)->StrValue() == "connect"){
|
||||||
|
double objencoding = 0;
|
||||||
|
if (amfData.getContentP(2)->getContentP("objectEncoding")){
|
||||||
|
objencoding = amfData.getContentP(2)->getContentP("objectEncoding")->NumValue();
|
||||||
}
|
}
|
||||||
|
#if DEBUG >= 6
|
||||||
unsigned int lastStats = 0;
|
int tmpint;
|
||||||
bool firsttime = true;
|
if (amfData.getContentP(2)->getContentP("videoCodecs")){
|
||||||
|
tmpint = (int)amfData.getContentP(2)->getContentP("videoCodecs")->NumValue();
|
||||||
while (Socket.connected()){
|
if (tmpint & 0x04){
|
||||||
if (Socket.spool() || firsttime){
|
fprintf(stderr, "Sorensen video support detected\n");
|
||||||
parseChunk(Socket.Received());
|
|
||||||
firsttime = false;
|
|
||||||
}else{
|
|
||||||
Util::sleep(1); //sleep 1ms to prevent high CPU usage
|
|
||||||
}
|
}
|
||||||
if (ready4data){
|
if (tmpint & 0x80){
|
||||||
if ( !inited){
|
fprintf(stderr, "H264 video support detected\n");
|
||||||
//we are ready, connect the socket!
|
}
|
||||||
SS = Util::Stream::getStream(streamname);
|
}
|
||||||
if ( !SS.connected()){
|
if (amfData.getContentP(2)->getContentP("audioCodecs")){
|
||||||
|
tmpint = (int)amfData.getContentP(2)->getContentP("audioCodecs")->NumValue();
|
||||||
|
if (tmpint & 0x04){
|
||||||
|
fprintf(stderr, "MP3 audio support detected\n");
|
||||||
|
}
|
||||||
|
if (tmpint & 0x400){
|
||||||
|
fprintf(stderr, "AAC audio support detected\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
RTMPStream::chunk_snd_max = 4096;
|
||||||
|
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(6, RTMPStream::rec_window_size)); //send rec window acknowledgement size (msg 6)
|
||||||
|
Socket.Send(RTMPStream::SendUSR(0, 1)); //send UCM StreamBegin (0), stream 1
|
||||||
|
//send a _result reply
|
||||||
|
AMF::Object amfReply("container", AMF::AMF0_DDV_CONTAINER);
|
||||||
|
amfReply.addContent(AMF::Object("", "_result")); //result success
|
||||||
|
amfReply.addContent(amfData.getContent(1)); //same transaction ID
|
||||||
|
amfReply.addContent(AMF::Object("")); //server properties
|
||||||
|
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("mode", (double)1));
|
||||||
|
amfReply.addContent(AMF::Object("")); //info
|
||||||
|
amfReply.getContentP(3)->addContent(AMF::Object("level", "status"));
|
||||||
|
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("clientid", 1337));
|
||||||
|
amfReply.getContentP(3)->addContent(AMF::Object("objectEncoding", objencoding));
|
||||||
|
//amfReply.getContentP(3)->addContent(AMF::Object("data", AMF::AMF0_ECMA_ARRAY));
|
||||||
|
//amfReply.getContentP(3)->getContentP(4)->addContent(AMF::Object("version", "3,5,4,1004"));
|
||||||
|
sendCommand(amfReply, messageType, streamId);
|
||||||
|
//send onBWDone packet - no clue what it is, but real server sends it...
|
||||||
|
//amfReply = AMF::Object("container", AMF::AMF0_DDV_CONTAINER);
|
||||||
|
//amfReply.addContent(AMF::Object("", "onBWDone"));//result
|
||||||
|
//amfReply.addContent(amfData.getContent(1));//same transaction ID
|
||||||
|
//amfReply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null
|
||||||
|
//sendCommand(amfReply, messageType, streamId);
|
||||||
|
return;
|
||||||
|
} //connect
|
||||||
|
if (amfData.getContentP(0)->StrValue() == "createStream"){
|
||||||
|
//send a _result reply
|
||||||
|
AMF::Object amfReply("container", AMF::AMF0_DDV_CONTAINER);
|
||||||
|
amfReply.addContent(AMF::Object("", "_result")); //result success
|
||||||
|
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)1)); //stream ID - we use 1
|
||||||
|
sendCommand(amfReply, messageType, streamId);
|
||||||
|
Socket.Send(RTMPStream::SendUSR(0, 1)); //send UCM StreamBegin (0), stream 1
|
||||||
|
return;
|
||||||
|
} //createStream
|
||||||
|
if ((amfData.getContentP(0)->StrValue() == "closeStream") || (amfData.getContentP(0)->StrValue() == "deleteStream")){
|
||||||
|
if (ss.connected()){
|
||||||
|
ss.close();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ((amfData.getContentP(0)->StrValue() == "FCPublish") || (amfData.getContentP(0)->StrValue() == "FCUnpublish") || (amfData.getContentP(0)->StrValue() == "releaseStream")){
|
||||||
|
// ignored
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ((amfData.getContentP(0)->StrValue() == "getStreamLength") || (amfData.getContentP(0)->StrValue() == "getMovLen")){
|
||||||
|
//send a _result reply
|
||||||
|
AMF::Object amfReply("container", AMF::AMF0_DDV_CONTAINER);
|
||||||
|
amfReply.addContent(AMF::Object("", "_result")); //result success
|
||||||
|
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)); //zero length
|
||||||
|
sendCommand(amfReply, messageType, streamId);
|
||||||
|
return;
|
||||||
|
} //getStreamLength
|
||||||
|
if ((amfData.getContentP(0)->StrValue() == "publish")){
|
||||||
|
if (amfData.getContentP(3)){
|
||||||
|
streamName = amfData.getContentP(3)->StrValue();
|
||||||
|
/// \todo implement push for MistPlayer or restrict and change to getLive
|
||||||
|
ss = Util::Stream::getStream(streamName);
|
||||||
|
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;
|
return;
|
||||||
}
|
}
|
||||||
SS.setBlocking(false);
|
ss.Send("P ");
|
||||||
SS.SendNow("p\n");
|
ss.Send(Socket.getHost().c_str());
|
||||||
inited = true;
|
ss.Send("\n");
|
||||||
|
noStats = true;
|
||||||
}
|
}
|
||||||
if (inited && !nostats){
|
//send a _result reply
|
||||||
long long int now = Util::epoch();
|
AMF::Object amfReply("container", AMF::AMF0_DDV_CONTAINER);
|
||||||
if (now != lastStats){
|
amfReply.addContent(AMF::Object("", "_result")); //result success
|
||||||
lastStats = now;
|
amfReply.addContent(amfData.getContent(1)); //same transaction ID
|
||||||
SS.SendNow(Socket.getStats("RTMP").c_str());
|
amfReply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL)); //null - command info
|
||||||
}
|
amfReply.addContent(AMF::Object("", 1, AMF::AMF0_BOOL)); //publish success?
|
||||||
}
|
sendCommand(amfReply, messageType, streamId);
|
||||||
if (SS.spool()){
|
|
||||||
while (Strm.parsePacket(SS.Received())){
|
|
||||||
if (play_trans != -1){
|
|
||||||
//send a status reply
|
|
||||||
AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER);
|
|
||||||
amfreply.addContent(AMF::Object("", "onStatus")); //status reply
|
|
||||||
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("")); //info
|
|
||||||
amfreply.getContentP(3)->addContent(AMF::Object("level", "status"));
|
|
||||||
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("details", "DDV"));
|
|
||||||
amfreply.getContentP(3)->addContent(AMF::Object("clientid", (double)1337));
|
|
||||||
sendCommand(amfreply, play_msgtype, play_streamid);
|
|
||||||
//send streamisrecorded if stream, well, is recorded.
|
|
||||||
if (Strm.metadata.isMember("length") && Strm.metadata["length"].asInt() > 0){
|
|
||||||
Socket.Send(RTMPStream::SendUSR(4, 1)); //send UCM StreamIsRecorded (4), stream 1
|
|
||||||
}
|
|
||||||
//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
|
//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("", (double)play_trans)); //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.Play.Start"));
|
amfReply.getContentP(3)->addContent(AMF::Object("code", "NetStream.Publish.Start"));
|
||||||
amfreply.getContentP(3)->addContent(AMF::Object("description", "Playing!"));
|
amfReply.getContentP(3)->addContent(AMF::Object("description", "Stream is now published!"));
|
||||||
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, messageType, streamId);
|
||||||
sendCommand(amfreply, play_msgtype, play_streamid);
|
return;
|
||||||
RTMPStream::chunk_snd_max = 102400; //100KiB
|
} //getStreamLength
|
||||||
Socket.Send(RTMPStream::SendCTL(1, RTMPStream::chunk_snd_max)); //send chunk size max (msg 1)
|
if (amfData.getContentP(0)->StrValue() == "checkBandwidth"){
|
||||||
//send dunno?
|
//send a _result reply
|
||||||
Socket.Send(RTMPStream::SendUSR(32, 1)); //send UCM no clue?, stream 1
|
AMF::Object amfReply("container", AMF::AMF0_DDV_CONTAINER);
|
||||||
play_trans = -1;
|
amfReply.addContent(AMF::Object("", "_result")); //result success
|
||||||
}
|
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
|
||||||
|
sendCommand(amfReply, messageType, streamId);
|
||||||
|
return;
|
||||||
|
} //checkBandwidth
|
||||||
|
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
|
||||||
|
playTransaction = amfData.getContentP(1)->NumValue();
|
||||||
|
playMessageType = messageType;
|
||||||
|
playStreamId = streamId;
|
||||||
|
streamName = amfData.getContentP(3)->StrValue();
|
||||||
|
Connector_RTMP::ready4data = true; //start sending video data!
|
||||||
|
return;
|
||||||
|
} //play
|
||||||
|
if ((amfData.getContentP(0)->StrValue() == "seek")){
|
||||||
|
//set reply number and stream name, actual reply is sent up in the ss.spool() handler
|
||||||
|
playTransaction = amfData.getContentP(1)->NumValue();
|
||||||
|
playMessageType = messageType;
|
||||||
|
playStreamId = streamId;
|
||||||
|
streamInited = false;
|
||||||
|
|
||||||
//sent init data if needed
|
AMF::Object amfReply("container", AMF::AMF0_DDV_CONTAINER);
|
||||||
if ( !stream_inited){
|
amfReply.addContent(AMF::Object("", "onStatus")); //status reply
|
||||||
init_tag.DTSCMetaInit(Strm);
|
amfReply.addContent(amfData.getContent(1)); //same transaction ID
|
||||||
Socket.SendNow(RTMPStream::SendMedia(init_tag));
|
amfReply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL)); //null - command info
|
||||||
if (Strm.metadata.isMember("audio") && Strm.metadata["audio"].isMember("init")){
|
amfReply.addContent(AMF::Object("")); //info
|
||||||
init_tag.DTSCAudioInit(Strm);
|
amfReply.getContentP(3)->addContent(AMF::Object("level", "status"));
|
||||||
Socket.SendNow(RTMPStream::SendMedia(init_tag));
|
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("details", "DDV"));
|
||||||
|
amfReply.getContentP(3)->addContent(AMF::Object("clientid", (double)1337));
|
||||||
|
sendCommand(amfReply, playMessageType, playStreamId);
|
||||||
|
ss.Send("s ");
|
||||||
|
ss.Send(JSON::Value((long long int)amfData.getContentP(3)->NumValue()).asString().c_str());
|
||||||
|
ss.Send("\n");
|
||||||
|
return;
|
||||||
|
} //seek
|
||||||
|
if ((amfData.getContentP(0)->StrValue() == "pauseRaw") || (amfData.getContentP(0)->StrValue() == "pause")){
|
||||||
|
if (amfData.getContentP(3)->NumValue()){
|
||||||
|
ss.Send("q\n"); //quit playing
|
||||||
|
//send a status reply
|
||||||
|
AMF::Object amfReply("container", AMF::AMF0_DDV_CONTAINER);
|
||||||
|
amfReply.addContent(AMF::Object("", "onStatus")); //status reply
|
||||||
|
amfReply.addContent(amfData.getContent(1)); //same transaction ID
|
||||||
|
amfReply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL)); //null - command info
|
||||||
|
amfReply.addContent(AMF::Object("")); //info
|
||||||
|
amfReply.getContentP(3)->addContent(AMF::Object("level", "status"));
|
||||||
|
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("details", "DDV"));
|
||||||
|
amfReply.getContentP(3)->addContent(AMF::Object("clientid", (double)1337));
|
||||||
|
sendCommand(amfReply, playMessageType, playStreamId);
|
||||||
|
}else{
|
||||||
|
ss.Send("p\n"); //start playing
|
||||||
|
//send a status reply
|
||||||
|
AMF::Object amfReply("container", AMF::AMF0_DDV_CONTAINER);
|
||||||
|
amfReply.addContent(AMF::Object("", "onStatus")); //status reply
|
||||||
|
amfReply.addContent(amfData.getContent(1)); //same transaction ID
|
||||||
|
amfReply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL)); //null - command info
|
||||||
|
amfReply.addContent(AMF::Object("")); //info
|
||||||
|
amfReply.getContentP(3)->addContent(AMF::Object("level", "status"));
|
||||||
|
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("details", "DDV"));
|
||||||
|
amfReply.getContentP(3)->addContent(AMF::Object("clientid", (double)1337));
|
||||||
|
sendCommand(amfReply, playMessageType, playStreamId);
|
||||||
}
|
}
|
||||||
if (Strm.metadata.isMember("video") && Strm.metadata["video"].isMember("init")){
|
return;
|
||||||
init_tag.DTSCVideoInit(Strm);
|
} //seek
|
||||||
Socket.SendNow(RTMPStream::SendMedia(init_tag));
|
|
||||||
}
|
#if DEBUG >= 2
|
||||||
stream_inited = true;
|
fprintf(stderr, "AMF0 command not processed!\n%s\n", amfData.Print().c_str());
|
||||||
}
|
|
||||||
//sent a tag
|
|
||||||
tag.DTSCLoader(Strm);
|
|
||||||
Socket.SendNow(RTMPStream::SendMedia(tag));
|
|
||||||
#if DEBUG >= 8
|
|
||||||
fprintf(stderr, "Sent tag to %i: [%u] %s\n", Socket.getSocket(), tag.tagTime(), tag.tagType().c_str());
|
|
||||||
#endif
|
#endif
|
||||||
}
|
} //parseAMFCommand
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Socket.close();
|
|
||||||
SS.SendNow(Socket.getStats("RTMP").c_str());
|
|
||||||
SS.close();
|
|
||||||
return 0;
|
|
||||||
} //Connector_RTMP
|
|
||||||
|
|
||||||
///\brief Tries to get and parse one RTMP chunk at a time.
|
///\brief Gets and parses one RTMP chunk at a time.
|
||||||
///\param inbuffer A buffer filled with chunk data.
|
///\param inputBuffer A buffer filled with chunk data.
|
||||||
void Connector_RTMP::parseChunk(Socket::Buffer & inbuffer){
|
void parseChunk(Socket::Buffer & inputBuffer){
|
||||||
//for DTSC conversion
|
//for DTSC conversion
|
||||||
static JSON::Value meta_out;
|
static JSON::Value meta_out;
|
||||||
static std::stringstream prebuffer; // Temporary buffer before sending real data
|
static std::stringstream prebuffer; // Temporary buffer before sending real data
|
||||||
|
@ -197,7 +289,7 @@ void Connector_RTMP::parseChunk(Socket::Buffer & inbuffer){
|
||||||
static AMF::Object3 amf3data("empty", AMF::AMF3_DDV_CONTAINER);
|
static AMF::Object3 amf3data("empty", AMF::AMF3_DDV_CONTAINER);
|
||||||
static AMF::Object3 amf3elem("empty", AMF::AMF3_DDV_CONTAINER);
|
static AMF::Object3 amf3elem("empty", AMF::AMF3_DDV_CONTAINER);
|
||||||
|
|
||||||
while (next.Parse(inbuffer)){
|
while (next.Parse(inputBuffer)){
|
||||||
|
|
||||||
//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)){
|
||||||
|
@ -210,10 +302,10 @@ void Connector_RTMP::parseChunk(Socket::Buffer & inbuffer){
|
||||||
#if DEBUG >= 2
|
#if DEBUG >= 2
|
||||||
fprintf(stderr, "UNKN: Received a zero-type message. Possible data corruption? Aborting!\n");
|
fprintf(stderr, "UNKN: Received a zero-type message. Possible data corruption? Aborting!\n");
|
||||||
#endif
|
#endif
|
||||||
while (inbuffer.size()){
|
while (inputBuffer.size()){
|
||||||
inbuffer.get().clear();
|
inputBuffer.get().clear();
|
||||||
}
|
}
|
||||||
SS.close();
|
ss.close();
|
||||||
Socket.close();
|
Socket.close();
|
||||||
break; //happens when connection breaks unexpectedly
|
break; //happens when connection breaks unexpectedly
|
||||||
case 1: //set chunk size
|
case 1: //set chunk size
|
||||||
|
@ -296,7 +388,7 @@ void Connector_RTMP::parseChunk(Socket::Buffer & inbuffer){
|
||||||
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()){
|
||||||
|
@ -304,15 +396,15 @@ void Connector_RTMP::parseChunk(Socket::Buffer & inbuffer){
|
||||||
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();
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
SS.SendNow(pack_out.toNetPacked());
|
ss.SendNow(pack_out.toNetPacked());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
|
@ -371,234 +463,148 @@ void Connector_RTMP::parseChunk(Socket::Buffer & inbuffer){
|
||||||
#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;
|
stopParsing = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} //parseChunk
|
} //parseChunk
|
||||||
|
|
||||||
void Connector_RTMP::sendCommand(AMF::Object & amfreply, int messagetype, int stream_id){
|
///\brief Main function for the RTMP Connector
|
||||||
#if DEBUG >= 8
|
///\param conn A socket describing the connection the client.
|
||||||
std::cerr << amfreply.Print() << std::endl;
|
///\return The exit code of the connector.
|
||||||
#endif
|
int rtmpConnector(Socket::Connection conn){
|
||||||
if (messagetype == 17){
|
Socket = conn;
|
||||||
Socket.SendNow(RTMPStream::SendChunk(3, messagetype, stream_id, (char)0 + amfreply.Pack()));
|
Socket.setBlocking(false);
|
||||||
}else{
|
FLV::Tag tag, init_tag;
|
||||||
Socket.SendNow(RTMPStream::SendChunk(3, messagetype, stream_id, amfreply.Pack()));
|
DTSC::Stream Strm;
|
||||||
}
|
|
||||||
} //sendCommand
|
|
||||||
|
|
||||||
void Connector_RTMP::parseAMFCommand(AMF::Object & amfdata, int messagetype, int stream_id){
|
while ( !Socket.Received().available(1537) && Socket.connected()){
|
||||||
|
Socket.spool();
|
||||||
|
Util::sleep(5);
|
||||||
|
}
|
||||||
|
RTMPStream::handshake_in = Socket.Received().remove(1537);
|
||||||
|
RTMPStream::rec_cnt += 1537;
|
||||||
|
|
||||||
|
if (RTMPStream::doHandshake()){
|
||||||
|
Socket.SendNow(RTMPStream::handshake_out);
|
||||||
|
while ( !Socket.Received().available(1536) && Socket.connected()){
|
||||||
|
Socket.spool();
|
||||||
|
Util::sleep(5);
|
||||||
|
}
|
||||||
|
Socket.Received().remove(1536);
|
||||||
|
RTMPStream::rec_cnt += 1536;
|
||||||
#if DEBUG >= 5
|
#if DEBUG >= 5
|
||||||
fprintf(stderr, "Received command: %s\n", amfdata.Print().c_str());
|
fprintf(stderr, "Handshake succcess!\n");
|
||||||
#endif
|
#endif
|
||||||
#if DEBUG >= 8
|
}else{
|
||||||
fprintf(stderr, "AMF0 command: %s\n", amfdata.getContentP(0)->StrValue().c_str());
|
#if DEBUG >= 5
|
||||||
|
fprintf(stderr, "Handshake fail!\n");
|
||||||
#endif
|
#endif
|
||||||
if (amfdata.getContentP(0)->StrValue() == "connect"){
|
return 0;
|
||||||
double objencoding = 0;
|
|
||||||
if (amfdata.getContentP(2)->getContentP("objectEncoding")){
|
|
||||||
objencoding = amfdata.getContentP(2)->getContentP("objectEncoding")->NumValue();
|
|
||||||
}
|
}
|
||||||
#if DEBUG >= 6
|
|
||||||
int tmpint;
|
unsigned int lastStats = 0;
|
||||||
if (amfdata.getContentP(2)->getContentP("videoCodecs")){
|
bool firsttime = true;
|
||||||
tmpint = (int)amfdata.getContentP(2)->getContentP("videoCodecs")->NumValue();
|
|
||||||
if (tmpint & 0x04){
|
while (Socket.connected()){
|
||||||
fprintf(stderr, "Sorensen video support detected\n");
|
if (Socket.spool() || firsttime){
|
||||||
|
parseChunk(Socket.Received());
|
||||||
|
firsttime = false;
|
||||||
|
}else{
|
||||||
|
Util::sleep(1); //sleep 1ms to prevent high CPU usage
|
||||||
}
|
}
|
||||||
if (tmpint & 0x80){
|
if (ready4data){
|
||||||
fprintf(stderr, "H264 video support detected\n");
|
if ( !inited){
|
||||||
}
|
//we are ready, connect the socket!
|
||||||
}
|
ss = Util::Stream::getStream(streamName);
|
||||||
if (amfdata.getContentP(2)->getContentP("audioCodecs")){
|
if ( !ss.connected()){
|
||||||
tmpint = (int)amfdata.getContentP(2)->getContentP("audioCodecs")->NumValue();
|
|
||||||
if (tmpint & 0x04){
|
|
||||||
fprintf(stderr, "MP3 audio support detected\n");
|
|
||||||
}
|
|
||||||
if (tmpint & 0x400){
|
|
||||||
fprintf(stderr, "AAC audio support detected\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
RTMPStream::chunk_snd_max = 4096;
|
|
||||||
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(6, RTMPStream::rec_window_size)); //send rec window acknowledgement size (msg 6)
|
|
||||||
Socket.Send(RTMPStream::SendUSR(0, 1)); //send UCM StreamBegin (0), stream 1
|
|
||||||
//send a _result reply
|
|
||||||
AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER);
|
|
||||||
amfreply.addContent(AMF::Object("", "_result")); //result success
|
|
||||||
amfreply.addContent(amfdata.getContent(1)); //same transaction ID
|
|
||||||
amfreply.addContent(AMF::Object("")); //server properties
|
|
||||||
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("mode", (double)1));
|
|
||||||
amfreply.addContent(AMF::Object("")); //info
|
|
||||||
amfreply.getContentP(3)->addContent(AMF::Object("level", "status"));
|
|
||||||
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("clientid", 1337));
|
|
||||||
amfreply.getContentP(3)->addContent(AMF::Object("objectEncoding", objencoding));
|
|
||||||
//amfreply.getContentP(3)->addContent(AMF::Object("data", AMF::AMF0_ECMA_ARRAY));
|
|
||||||
//amfreply.getContentP(3)->getContentP(4)->addContent(AMF::Object("version", "3,5,4,1004"));
|
|
||||||
sendCommand(amfreply, messagetype, stream_id);
|
|
||||||
//send onBWDone packet - no clue what it is, but real server sends it...
|
|
||||||
//amfreply = AMF::Object("container", AMF::AMF0_DDV_CONTAINER);
|
|
||||||
//amfreply.addContent(AMF::Object("", "onBWDone"));//result
|
|
||||||
//amfreply.addContent(amfdata.getContent(1));//same transaction ID
|
|
||||||
//amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL));//null
|
|
||||||
//sendCommand(amfreply, messagetype, stream_id);
|
|
||||||
return;
|
|
||||||
} //connect
|
|
||||||
if (amfdata.getContentP(0)->StrValue() == "createStream"){
|
|
||||||
//send a _result reply
|
|
||||||
AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER);
|
|
||||||
amfreply.addContent(AMF::Object("", "_result")); //result success
|
|
||||||
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)1)); //stream ID - we use 1
|
|
||||||
sendCommand(amfreply, messagetype, stream_id);
|
|
||||||
Socket.Send(RTMPStream::SendUSR(0, 1)); //send UCM StreamBegin (0), stream 1
|
|
||||||
return;
|
|
||||||
} //createStream
|
|
||||||
if ((amfdata.getContentP(0)->StrValue() == "closeStream") || (amfdata.getContentP(0)->StrValue() == "deleteStream")){
|
|
||||||
if (SS.connected()){
|
|
||||||
SS.close();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ((amfdata.getContentP(0)->StrValue() == "FCPublish") || (amfdata.getContentP(0)->StrValue() == "FCUnpublish") || (amfdata.getContentP(0)->StrValue() == "releaseStream")){
|
|
||||||
// ignored
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ((amfdata.getContentP(0)->StrValue() == "getStreamLength") || (amfdata.getContentP(0)->StrValue() == "getMovLen")){
|
|
||||||
//send a _result reply
|
|
||||||
AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER);
|
|
||||||
amfreply.addContent(AMF::Object("", "_result")); //result success
|
|
||||||
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)); //zero length
|
|
||||||
sendCommand(amfreply, messagetype, stream_id);
|
|
||||||
return;
|
|
||||||
} //getStreamLength
|
|
||||||
if ((amfdata.getContentP(0)->StrValue() == "publish")){
|
|
||||||
if (amfdata.getContentP(3)){
|
|
||||||
streamname = amfdata.getContentP(3)->StrValue();
|
|
||||||
/// \todo implement push for MistPlayer or restrict and change to getLive
|
|
||||||
SS = Util::Stream::getStream(streamname);
|
|
||||||
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;
|
break;
|
||||||
}
|
}
|
||||||
SS.Send("P ");
|
ss.setBlocking(false);
|
||||||
SS.Send(Socket.getHost().c_str());
|
ss.SendNow("p\n");
|
||||||
SS.Send("\n");
|
inited = true;
|
||||||
nostats = true;
|
|
||||||
}
|
}
|
||||||
//send a _result reply
|
if (inited && !noStats){
|
||||||
AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER);
|
long long int now = Util::epoch();
|
||||||
amfreply.addContent(AMF::Object("", "_result")); //result success
|
if (now != lastStats){
|
||||||
amfreply.addContent(amfdata.getContent(1)); //same transaction ID
|
lastStats = now;
|
||||||
amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL)); //null - command info
|
ss.SendNow(Socket.getStats("RTMP").c_str());
|
||||||
amfreply.addContent(AMF::Object("", 1, AMF::AMF0_BOOL)); //publish success?
|
}
|
||||||
sendCommand(amfreply, messagetype, stream_id);
|
}
|
||||||
Socket.Send(RTMPStream::SendUSR(0, 1)); //send UCM StreamBegin (0), stream 1
|
if (ss.spool()){
|
||||||
|
while (Strm.parsePacket(ss.Received())){
|
||||||
|
if (playTransaction != -1){
|
||||||
//send a status reply
|
//send a status reply
|
||||||
|
AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER);
|
||||||
|
amfreply.addContent(AMF::Object("", "onStatus")); //status reply
|
||||||
|
amfreply.addContent(AMF::Object("", (double)playTransaction)); //same transaction ID
|
||||||
|
amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL)); //null - command info
|
||||||
|
amfreply.addContent(AMF::Object("")); //info
|
||||||
|
amfreply.getContentP(3)->addContent(AMF::Object("level", "status"));
|
||||||
|
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("details", "DDV"));
|
||||||
|
amfreply.getContentP(3)->addContent(AMF::Object("clientid", (double)1337));
|
||||||
|
sendCommand(amfreply, playMessageType, playStreamId);
|
||||||
|
//send streamisrecorded if stream, well, is recorded.
|
||||||
|
if (Strm.metadata.isMember("length") && Strm.metadata["length"].asInt() > 0){
|
||||||
|
Socket.Send(RTMPStream::SendUSR(4, 1)); //send UCM StreamIsRecorded (4), stream 1
|
||||||
|
}
|
||||||
|
//send streambegin
|
||||||
|
Socket.Send(RTMPStream::SendUSR(0, 1)); //send UCM StreamBegin (0), stream 1
|
||||||
|
//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("", 0, AMF::AMF0_NUMBER)); //same transaction ID
|
amfreply.addContent(AMF::Object("", (double)playTransaction)); //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.Play.Start"));
|
||||||
amfreply.getContentP(3)->addContent(AMF::Object("description", "Stream is now published!"));
|
amfreply.getContentP(3)->addContent(AMF::Object("description", "Playing!"));
|
||||||
amfreply.getContentP(3)->addContent(AMF::Object("clientid", (double)1337));
|
|
||||||
sendCommand(amfreply, messagetype, stream_id);
|
|
||||||
return;
|
|
||||||
} //getStreamLength
|
|
||||||
if (amfdata.getContentP(0)->StrValue() == "checkBandwidth"){
|
|
||||||
//send a _result reply
|
|
||||||
AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER);
|
|
||||||
amfreply.addContent(AMF::Object("", "_result")); //result success
|
|
||||||
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
|
|
||||||
sendCommand(amfreply, messagetype, stream_id);
|
|
||||||
return;
|
|
||||||
} //checkBandwidth
|
|
||||||
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
|
|
||||||
play_trans = amfdata.getContentP(1)->NumValue();
|
|
||||||
play_msgtype = messagetype;
|
|
||||||
play_streamid = stream_id;
|
|
||||||
streamname = amfdata.getContentP(3)->StrValue();
|
|
||||||
Connector_RTMP::ready4data = true; //start sending video data!
|
|
||||||
return;
|
|
||||||
} //play
|
|
||||||
if ((amfdata.getContentP(0)->StrValue() == "seek")){
|
|
||||||
//set reply number and stream name, actual reply is sent up in the SS.spool() handler
|
|
||||||
play_trans = amfdata.getContentP(1)->NumValue();
|
|
||||||
play_msgtype = messagetype;
|
|
||||||
play_streamid = stream_id;
|
|
||||||
stream_inited = false;
|
|
||||||
|
|
||||||
AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER);
|
|
||||||
amfreply.addContent(AMF::Object("", "onStatus")); //status reply
|
|
||||||
amfreply.addContent(amfdata.getContent(1)); //same transaction ID
|
|
||||||
amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL)); //null - command info
|
|
||||||
amfreply.addContent(AMF::Object("")); //info
|
|
||||||
amfreply.getContentP(3)->addContent(AMF::Object("level", "status"));
|
|
||||||
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("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, playMessageType, playStreamId);
|
||||||
SS.Send("s ");
|
RTMPStream::chunk_snd_max = 102400; //100KiB
|
||||||
SS.Send(JSON::Value((long long int)amfdata.getContentP(3)->NumValue()).asString().c_str());
|
Socket.Send(RTMPStream::SendCTL(1, RTMPStream::chunk_snd_max)); //send chunk size max (msg 1)
|
||||||
SS.Send("\n");
|
//send dunno?
|
||||||
return;
|
Socket.Send(RTMPStream::SendUSR(32, 1)); //send UCM no clue?, stream 1
|
||||||
} //seek
|
playTransaction = -1;
|
||||||
if ((amfdata.getContentP(0)->StrValue() == "pauseRaw") || (amfdata.getContentP(0)->StrValue() == "pause")){
|
|
||||||
if (amfdata.getContentP(3)->NumValue()){
|
|
||||||
SS.Send("q\n"); //quit playing
|
|
||||||
//send a status reply
|
|
||||||
AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER);
|
|
||||||
amfreply.addContent(AMF::Object("", "onStatus")); //status reply
|
|
||||||
amfreply.addContent(amfdata.getContent(1)); //same transaction ID
|
|
||||||
amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL)); //null - command info
|
|
||||||
amfreply.addContent(AMF::Object("")); //info
|
|
||||||
amfreply.getContentP(3)->addContent(AMF::Object("level", "status"));
|
|
||||||
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("details", "DDV"));
|
|
||||||
amfreply.getContentP(3)->addContent(AMF::Object("clientid", (double)1337));
|
|
||||||
sendCommand(amfreply, play_msgtype, play_streamid);
|
|
||||||
}else{
|
|
||||||
SS.Send("p\n"); //start playing
|
|
||||||
//send a status reply
|
|
||||||
AMF::Object amfreply("container", AMF::AMF0_DDV_CONTAINER);
|
|
||||||
amfreply.addContent(AMF::Object("", "onStatus")); //status reply
|
|
||||||
amfreply.addContent(amfdata.getContent(1)); //same transaction ID
|
|
||||||
amfreply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL)); //null - command info
|
|
||||||
amfreply.addContent(AMF::Object("")); //info
|
|
||||||
amfreply.getContentP(3)->addContent(AMF::Object("level", "status"));
|
|
||||||
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("details", "DDV"));
|
|
||||||
amfreply.getContentP(3)->addContent(AMF::Object("clientid", (double)1337));
|
|
||||||
sendCommand(amfreply, play_msgtype, play_streamid);
|
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
} //seek
|
|
||||||
|
|
||||||
#if DEBUG >= 2
|
//sent init data if needed
|
||||||
fprintf(stderr, "AMF0 command not processed!\n%s\n", amfdata.Print().c_str());
|
if ( !streamInited){
|
||||||
|
init_tag.DTSCMetaInit(Strm);
|
||||||
|
Socket.SendNow(RTMPStream::SendMedia(init_tag));
|
||||||
|
if (Strm.metadata.isMember("audio") && Strm.metadata["audio"].isMember("init")){
|
||||||
|
init_tag.DTSCAudioInit(Strm);
|
||||||
|
Socket.SendNow(RTMPStream::SendMedia(init_tag));
|
||||||
|
}
|
||||||
|
if (Strm.metadata.isMember("video") && Strm.metadata["video"].isMember("init")){
|
||||||
|
init_tag.DTSCVideoInit(Strm);
|
||||||
|
Socket.SendNow(RTMPStream::SendMedia(init_tag));
|
||||||
|
}
|
||||||
|
streamInited = true;
|
||||||
|
}
|
||||||
|
//sent a tag
|
||||||
|
tag.DTSCLoader(Strm);
|
||||||
|
Socket.SendNow(RTMPStream::SendMedia(tag));
|
||||||
|
#if DEBUG >= 8
|
||||||
|
fprintf(stderr, "Sent tag to %i: [%u] %s\n", Socket.getSocket(), tag.tagTime(), tag.tagType().c_str());
|
||||||
#endif
|
#endif
|
||||||
} //parseAMFCommand
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Socket.close();
|
||||||
|
ss.SendNow(Socket.getStats("RTMP").c_str());
|
||||||
|
ss.close();
|
||||||
|
return 0;
|
||||||
|
} //Connector_RTMP
|
||||||
|
}
|
||||||
|
|
||||||
///\brief The standard process-spawning main function.
|
///\brief The standard process-spawning main function.
|
||||||
int main(int argc, char ** argv){
|
int main(int argc, char ** argv){
|
||||||
|
@ -616,7 +622,7 @@ int main(int argc, char ** argv){
|
||||||
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::rtmpConnector(S);
|
||||||
}else{ //otherwise, do nothing or output debugging text
|
}else{ //otherwise, do nothing or output debugging text
|
||||||
#if DEBUG >= 5
|
#if DEBUG >= 5
|
||||||
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());
|
||||||
|
|
|
@ -2,28 +2,34 @@
|
||||||
/// Contains the main code for the TS Connector
|
/// Contains the main code for the TS Connector
|
||||||
|
|
||||||
#include <queue>
|
#include <queue>
|
||||||
|
#include <string>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <string>
|
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <getopt.h>
|
#include <getopt.h>
|
||||||
#include <iostream>
|
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
|
||||||
#include <mist/socket.h>
|
#include <mist/socket.h>
|
||||||
#include <mist/config.h>
|
#include <mist/config.h>
|
||||||
#include <mist/stream.h>
|
#include <mist/stream.h>
|
||||||
#include <mist/ts_packet.h> //TS support
|
#include <mist/ts_packet.h> //TS support
|
||||||
#include <mist/dtsc.h> //DTSC support
|
#include <mist/dtsc.h> //DTSC support
|
||||||
#include <mist/mp4.h> //For initdata conversion
|
#include <mist/mp4.h> //For initdata conversion
|
||||||
/// The main function of the connector
|
|
||||||
/// \param conn A connection with the client
|
///\brief Holds everything unique to the TS Connector
|
||||||
/// \param streamname The name of the stream
|
namespace Connector_TS {
|
||||||
int TS_Handler(Socket::Connection conn, std::string streamname){
|
///\brief Main function for the TS Connector
|
||||||
|
///\param conn A socket describing the connection the client.
|
||||||
|
///\param streamName The stream to connect to.
|
||||||
|
///\return The exit code of the connector.
|
||||||
|
int tsConnector(Socket::Connection conn, std::string streamName){
|
||||||
std::string ToPack;
|
std::string ToPack;
|
||||||
TS::Packet PackData;
|
TS::Packet PackData;
|
||||||
std::string DTMIData;
|
std::string DTMIData;
|
||||||
|
@ -45,7 +51,7 @@ int TS_Handler(Socket::Connection conn, std::string streamname){
|
||||||
|
|
||||||
while (conn.connected()){
|
while (conn.connected()){
|
||||||
if ( !inited){
|
if ( !inited){
|
||||||
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");
|
||||||
|
@ -142,6 +148,7 @@ int TS_Handler(Socket::Connection conn, std::string streamname){
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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);
|
||||||
|
@ -160,7 +167,7 @@ int main(int argc, char ** argv){
|
||||||
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 TS_Handler(S, conf.getString("streamname"));
|
return Connector_TS::tsConnector(S, conf.getString("streamname"));
|
||||||
}else{ //otherwise, do nothing or output debugging text
|
}else{ //otherwise, do nothing or output debugging text
|
||||||
#if DEBUG >= 5
|
#if DEBUG >= 5
|
||||||
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());
|
||||||
|
|
Loading…
Add table
Reference in a new issue