diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2075856..ba64e42 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -22,7 +22,9 @@ set(DEPLIBS
set(OCTONET_SOURCES
src/OctonetData.cpp
- src/client.cpp)
+ src/client.cpp
+ src/Socket.cpp
+ src/rtsp_client.cpp)
build_addon(pvr.octonet OCTONET DEPLIBS)
diff --git a/src/OctonetData.cpp b/src/OctonetData.cpp
index 612f153..b102f7d 100644
--- a/src/OctonetData.cpp
+++ b/src/OctonetData.cpp
@@ -226,7 +226,6 @@ PVR_ERROR OctonetData::getChannels(ADDON_HANDLE handle, bool bRadio)
chan.bIsRadio = channel.radio;
chan.iChannelNumber = i;
strncpy(chan.strChannelName, channel.name.c_str(), strlen(channel.name.c_str()));
- strncpy(chan.strStreamURL, channel.url.c_str(), strlen(channel.url.c_str()));
strcpy(chan.strInputFormat, "video/x-mpegts");
chan.bIsHidden = false;
diff --git a/src/Socket.cpp b/src/Socket.cpp
new file mode 100644
index 0000000..bcc8980
--- /dev/null
+++ b/src/Socket.cpp
@@ -0,0 +1,722 @@
+/*
+ * Copyright (C) 2005-2011 Team XBMC
+ * http://www.xbmc.org
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+#include "libXBMC_addon.h"
+#include
+#include "p8-platform/os.h"
+#include "client.h"
+#include "Socket.h"
+#include
+
+using namespace std;
+using namespace ADDON;
+
+namespace OCTO
+{
+
+/* Master defines for client control */
+#define RECEIVE_TIMEOUT 6 //sec
+
+Socket::Socket(const enum SocketFamily family, const enum SocketDomain domain, const enum SocketType type, const enum SocketProtocol protocol)
+{
+ _sd = INVALID_SOCKET;
+ _family = family;
+ _domain = domain;
+ _type = type;
+ _protocol = protocol;
+ _port = 0;
+ memset (&_sockaddr, 0, sizeof( _sockaddr ) );
+}
+
+
+Socket::Socket()
+{
+ // Default constructor, default settings
+ _sd = INVALID_SOCKET;
+ _family = af_inet;
+ _domain = pf_inet;
+ _type = sock_stream;
+ _protocol = tcp;
+ _port = 0;
+ memset (&_sockaddr, 0, sizeof( _sockaddr ) );
+}
+
+
+Socket::~Socket()
+{
+ close();
+ osCleanup();
+}
+
+bool Socket::setHostname(const std::string& host)
+{
+ _hostname = host;
+ return true;
+}
+
+bool Socket::close()
+{
+ if (is_valid())
+ {
+ if (_sd != SOCKET_ERROR)
+ closesocket(_sd);
+ _sd = INVALID_SOCKET;
+ return true;
+ }
+ return false;
+}
+
+bool Socket::create()
+{
+ close();
+
+ if(!osInit())
+ {
+ return false;
+ }
+
+ return true;
+}
+
+
+bool Socket::bind ( const unsigned short port )
+{
+
+ if (is_valid())
+ {
+ close();
+ }
+
+ _sd = socket(_family, _type, _protocol);
+ _port = port;
+ _sockaddr.sin_family = (sa_family_t) _family;
+ _sockaddr.sin_addr.s_addr = INADDR_ANY; //listen to all
+ _sockaddr.sin_port = htons( _port );
+
+ int bind_return = ::bind(_sd, (sockaddr*)(&_sockaddr), sizeof(_sockaddr));
+
+ if ( bind_return == -1 )
+ {
+ errormessage( getLastError(), "Socket::bind" );
+ return false;
+ }
+
+ return true;
+}
+
+
+bool Socket::listen() const
+{
+
+ if (!is_valid())
+ {
+ return false;
+ }
+
+ int listen_return = ::listen (_sd, SOMAXCONN);
+ //This is defined as 5 in winsock.h, and 0x7FFFFFFF in winsock2.h.
+ //linux 128//MAXCONNECTIONS =1
+
+ if (listen_return == -1)
+ {
+ errormessage( getLastError(), "Socket::listen" );
+ return false;
+ }
+
+ return true;
+}
+
+
+bool Socket::accept ( Socket& new_socket ) const
+{
+ if (!is_valid())
+ {
+ return false;
+ }
+
+ socklen_t addr_length = sizeof( _sockaddr );
+ new_socket._sd = ::accept(_sd, const_cast( (const sockaddr*) &_sockaddr), &addr_length );
+
+#ifdef TARGET_WINDOWS
+ if (new_socket._sd == INVALID_SOCKET)
+#else
+ if (new_socket._sd <= 0)
+#endif
+ {
+ errormessage( getLastError(), "Socket::accept" );
+ return false;
+ }
+
+ return true;
+}
+
+
+int Socket::send ( const std::string& data )
+{
+ return Socket::send( (const char*) data.c_str(), (const unsigned int) data.size());
+}
+
+
+int Socket::send ( const char* data, const unsigned int len )
+{
+ fd_set set_w, set_e;
+ struct timeval tv;
+ int result;
+
+ if (!is_valid())
+ {
+ return 0;
+ }
+
+ // fill with new data
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+
+ FD_ZERO(&set_w);
+ FD_ZERO(&set_e);
+ FD_SET(_sd, &set_w);
+ FD_SET(_sd, &set_e);
+
+ result = select(FD_SETSIZE, &set_w, NULL, &set_e, &tv);
+
+ if (result < 0)
+ {
+ kodi->Log(LOG_ERROR, "Socket::send - select failed");
+ close();
+ return 0;
+ }
+ if (FD_ISSET(_sd, &set_w))
+ {
+ kodi->Log(LOG_ERROR, "Socket::send - failed to send data");
+ close();
+ return 0;
+ }
+
+ int status = ::send(_sd, data, len, 0 );
+
+ if (status == -1)
+ {
+ errormessage( getLastError(), "Socket::send");
+ kodi->Log(LOG_ERROR, "Socket::send - failed to send data");
+ close();
+ return 0;
+ }
+ return status;
+}
+
+
+int Socket::sendto ( const char* data, unsigned int size, bool sendcompletebuffer)
+{
+ int sentbytes = 0;
+ int i;
+
+ do
+ {
+ i = ::sendto(_sd, data, size, 0, (const struct sockaddr*) &_sockaddr, sizeof( _sockaddr ) );
+
+ if (i <= 0)
+ {
+ errormessage( getLastError(), "Socket::sendto");
+ osCleanup();
+ return i;
+ }
+ sentbytes += i;
+ } while ( (sentbytes < (int) size) && (sendcompletebuffer == true));
+
+ return i;
+}
+
+
+int Socket::receive ( std::string& data, unsigned int minpacketsize ) const
+{
+ char * buf = NULL;
+ int status = 0;
+
+ if (!is_valid())
+ {
+ return 0;
+ }
+
+ buf = new char [ minpacketsize + 1 ];
+ memset ( buf, 0, minpacketsize + 1 );
+
+ status = receive( buf, minpacketsize, minpacketsize );
+
+ data = buf;
+
+ delete[] buf;
+ return status;
+}
+
+
+//Receive until error or \n
+bool Socket::ReadLine (string& line)
+{
+ fd_set set_r, set_e;
+ timeval timeout;
+ int retries = 6;
+ char buffer[2048];
+
+ if (!is_valid())
+ return false;
+
+ while (true)
+ {
+ size_t pos1 = line.find("\r\n", 0);
+ if (pos1 != std::string::npos)
+ {
+ line.erase(pos1, string::npos);
+ return true;
+ }
+
+ timeout.tv_sec = RECEIVE_TIMEOUT;
+ timeout.tv_usec = 0;
+
+ // fill with new data
+ FD_ZERO(&set_r);
+ FD_ZERO(&set_e);
+ FD_SET(_sd, &set_r);
+ FD_SET(_sd, &set_e);
+ int result = select(FD_SETSIZE, &set_r, NULL, &set_e, &timeout);
+
+ if (result < 0)
+ {
+ kodi->Log(LOG_DEBUG, "%s: select failed", __FUNCTION__);
+ errormessage(getLastError(), __FUNCTION__);
+ close();
+ return false;
+ }
+
+ if (result == 0)
+ {
+ if (retries != 0)
+ {
+ kodi->Log(LOG_DEBUG, "%s: timeout waiting for response, retrying... (%i)", __FUNCTION__, retries);
+ retries--;
+ continue;
+ } else {
+ kodi->Log(LOG_DEBUG, "%s: timeout waiting for response. Aborting after 10 retries.", __FUNCTION__);
+ return false;
+ }
+ }
+
+ result = recv(_sd, buffer, sizeof(buffer) - 1, 0);
+ if (result < 0)
+ {
+ kodi->Log(LOG_DEBUG, "%s: recv failed", __FUNCTION__);
+ errormessage(getLastError(), __FUNCTION__);
+ close();
+ return false;
+ }
+ buffer[result] = 0;
+
+ line.append(buffer);
+ }
+
+ return true;
+}
+
+
+int Socket::receive ( std::string& data) const
+{
+ char buf[MAXRECV + 1];
+ int status = 0;
+
+ if ( !is_valid() )
+ {
+ return 0;
+ }
+
+ memset ( buf, 0, MAXRECV + 1 );
+ status = receive( buf, MAXRECV, 0 );
+ data = buf;
+
+ return status;
+}
+
+int Socket::receive ( char* data, const unsigned int buffersize, const unsigned int minpacketsize ) const
+{
+ unsigned int receivedsize = 0;
+
+ if ( !is_valid() )
+ {
+ return 0;
+ }
+
+ while ( (receivedsize <= minpacketsize) && (receivedsize < buffersize) )
+ {
+ int status = ::recv(_sd, data+receivedsize, (buffersize - receivedsize), 0 );
+
+ if ( status == SOCKET_ERROR )
+ {
+ errormessage( getLastError(), "Socket::receive" );
+ return status;
+ }
+
+ receivedsize += status;
+ }
+
+ return receivedsize;
+}
+
+
+int Socket::recvfrom ( char* data, const int buffersize, struct sockaddr* from, socklen_t* fromlen) const
+{
+ int status = ::recvfrom(_sd, data, buffersize, 0, from, fromlen);
+
+ return status;
+}
+
+
+bool Socket::connect ( const std::string& host, const unsigned short port )
+{
+ close();
+
+ if ( !setHostname( host ) )
+ {
+ kodi->Log(LOG_ERROR, "Socket::setHostname(%s) failed.\n", host.c_str());
+ return false;
+ }
+ _port = port;
+
+ char strPort[15];
+ snprintf(strPort, 15, "%hu", port);
+
+ struct addrinfo hints;
+ struct addrinfo* result = NULL;
+ struct addrinfo *address = NULL;
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = _family;
+ hints.ai_socktype = _type;
+ hints.ai_protocol = _protocol;
+
+ int retval = getaddrinfo(host.c_str(), strPort, &hints, &result);
+ if (retval != 0)
+ {
+ errormessage(getLastError(), "Socket::connect");
+ return false;
+ }
+
+ for (address = result; address != NULL; address = address->ai_next)
+ {
+ // Create the socket
+ _sd = socket(address->ai_family, address->ai_socktype, address->ai_protocol);
+
+ if (_sd == INVALID_SOCKET)
+ {
+ errormessage(getLastError(), "Socket::create");
+ continue;
+ }
+
+ int status = ::connect(_sd, address->ai_addr, address->ai_addrlen);
+ if (status == SOCKET_ERROR)
+ {
+ close();
+ continue;
+ }
+
+ // We have a conection
+ break;
+ }
+
+ freeaddrinfo(result);
+
+ if (address == NULL)
+ {
+ kodi->Log(LOG_ERROR, "Socket::connect %s:%u\n", host.c_str(), port);
+ errormessage(getLastError(), "Socket::connect");
+ close();
+ return false;
+ }
+
+ return true;
+}
+
+bool Socket::reconnect()
+{
+ if ( is_valid() )
+ {
+ return true;
+ }
+
+ return connect(_hostname, _port);
+}
+
+bool Socket::is_valid() const
+{
+ return (_sd != INVALID_SOCKET);
+}
+
+#if defined(TARGET_WINDOWS)
+bool Socket::set_non_blocking ( const bool b )
+{
+ u_long iMode;
+
+ if ( b )
+ iMode = 1; // enable non_blocking
+ else
+ iMode = 0; // disable non_blocking
+
+ if (ioctlsocket(_sd, FIONBIO, &iMode) == -1)
+ {
+ kodi->Log(LOG_ERROR, "Socket::set_non_blocking - Can't set socket condition to: %i", iMode);
+ return false;
+ }
+
+ return true;
+}
+
+void Socket::errormessage( int errnum, const char* functionname) const
+{
+ const char* errmsg = NULL;
+
+ switch (errnum)
+ {
+ case WSANOTINITIALISED:
+ errmsg = "A successful WSAStartup call must occur before using this function.";
+ break;
+ case WSAENETDOWN:
+ errmsg = "The network subsystem or the associated service provider has failed";
+ break;
+ case WSA_NOT_ENOUGH_MEMORY:
+ errmsg = "Insufficient memory available";
+ break;
+ case WSA_INVALID_PARAMETER:
+ errmsg = "One or more parameters are invalid";
+ break;
+ case WSA_OPERATION_ABORTED:
+ errmsg = "Overlapped operation aborted";
+ break;
+ case WSAEINTR:
+ errmsg = "Interrupted function call";
+ break;
+ case WSAEBADF:
+ errmsg = "File handle is not valid";
+ break;
+ case WSAEACCES:
+ errmsg = "Permission denied";
+ break;
+ case WSAEFAULT:
+ errmsg = "Bad address";
+ break;
+ case WSAEINVAL:
+ errmsg = "Invalid argument";
+ break;
+ case WSAENOTSOCK:
+ errmsg = "Socket operation on nonsocket";
+ break;
+ case WSAEDESTADDRREQ:
+ errmsg = "Destination address required";
+ break;
+ case WSAEMSGSIZE:
+ errmsg = "Message too long";
+ break;
+ case WSAEPROTOTYPE:
+ errmsg = "Protocol wrong type for socket";
+ break;
+ case WSAENOPROTOOPT:
+ errmsg = "Bad protocol option";
+ break;
+ case WSAEPFNOSUPPORT:
+ errmsg = "Protocol family not supported";
+ break;
+ case WSAEAFNOSUPPORT:
+ errmsg = "Address family not supported by protocol family";
+ break;
+ case WSAEADDRINUSE:
+ errmsg = "Address already in use";
+ break;
+ case WSAECONNRESET:
+ errmsg = "Connection reset by peer";
+ break;
+ case WSAHOST_NOT_FOUND:
+ errmsg = "Authoritative answer host not found";
+ break;
+ case WSATRY_AGAIN:
+ errmsg = "Nonauthoritative host not found, or server failure";
+ break;
+ case WSAEISCONN:
+ errmsg = "Socket is already connected";
+ break;
+ case WSAETIMEDOUT:
+ errmsg = "Connection timed out";
+ break;
+ case WSAECONNREFUSED:
+ errmsg = "Connection refused";
+ break;
+ case WSANO_DATA:
+ errmsg = "Valid name, no data record of requested type";
+ break;
+ default:
+ errmsg = "WSA Error";
+ }
+ kodi->Log(LOG_ERROR, "%s: (Winsock error=%i) %s\n", functionname, errnum, errmsg);
+}
+
+int Socket::getLastError() const
+{
+ return WSAGetLastError();
+}
+
+int Socket::win_usage_count = 0; //Declared static in Socket class
+
+bool Socket::osInit()
+{
+ win_usage_count++;
+ // initialize winsock:
+ if (WSAStartup(MAKEWORD(2,2),&_wsaData) != 0)
+ {
+ return false;
+ }
+
+ WORD wVersionRequested = MAKEWORD(2,2);
+
+ // check version
+ if (_wsaData.wVersion != wVersionRequested)
+ {
+ return false;
+ }
+
+ return true;
+}
+
+void Socket::osCleanup()
+{
+ win_usage_count--;
+ if(win_usage_count == 0)
+ {
+ WSACleanup();
+ }
+}
+
+#elif defined TARGET_LINUX || defined TARGET_DARWIN || defined TARGET_FREEBSD
+bool Socket::set_non_blocking ( const bool b )
+{
+ int opts;
+
+ opts = fcntl(_sd, F_GETFL);
+
+ if ( opts < 0 )
+ {
+ return false;
+ }
+
+ if ( b )
+ opts = ( opts | O_NONBLOCK );
+ else
+ opts = ( opts & ~O_NONBLOCK );
+
+ if(fcntl (_sd , F_SETFL, opts) == -1)
+ {
+ kodi->Log(LOG_ERROR, "Socket::set_non_blocking - Can't set socket flags to: %i", opts);
+ return false;
+ }
+ return true;
+}
+
+void Socket::errormessage( int errnum, const char* functionname) const
+{
+ const char* errmsg = NULL;
+
+ switch ( errnum )
+ {
+ case EAGAIN: //same as EWOULDBLOCK
+ errmsg = "EAGAIN: The socket is marked non-blocking and the requested operation would block";
+ break;
+ case EBADF:
+ errmsg = "EBADF: An invalid descriptor was specified";
+ break;
+ case ECONNRESET:
+ errmsg = "ECONNRESET: Connection reset by peer";
+ break;
+ case EDESTADDRREQ:
+ errmsg = "EDESTADDRREQ: The socket is not in connection mode and no peer address is set";
+ break;
+ case EFAULT:
+ errmsg = "EFAULT: An invalid userspace address was specified for a parameter";
+ break;
+ case EINTR:
+ errmsg = "EINTR: A signal occurred before data was transmitted";
+ break;
+ case EINVAL:
+ errmsg = "EINVAL: Invalid argument passed";
+ break;
+ case ENOTSOCK:
+ errmsg = "ENOTSOCK: The argument is not a valid socket";
+ break;
+ case EMSGSIZE:
+ errmsg = "EMSGSIZE: The socket requires that message be sent atomically, and the size of the message to be sent made this impossible";
+ break;
+ case ENOBUFS:
+ errmsg = "ENOBUFS: The output queue for a network interface was full";
+ break;
+ case ENOMEM:
+ errmsg = "ENOMEM: No memory available";
+ break;
+ case EPIPE:
+ errmsg = "EPIPE: The local end has been shut down on a connection oriented socket";
+ break;
+ case EPROTONOSUPPORT:
+ errmsg = "EPROTONOSUPPORT: The protocol type or the specified protocol is not supported within this domain";
+ break;
+ case EAFNOSUPPORT:
+ errmsg = "EAFNOSUPPORT: The implementation does not support the specified address family";
+ break;
+ case ENFILE:
+ errmsg = "ENFILE: Not enough kernel memory to allocate a new socket structure";
+ break;
+ case EMFILE:
+ errmsg = "EMFILE: Process file table overflow";
+ break;
+ case EACCES:
+ errmsg = "EACCES: Permission to create a socket of the specified type and/or protocol is denied";
+ break;
+ case ECONNREFUSED:
+ errmsg = "ECONNREFUSED: A remote host refused to allow the network connection (typically because it is not running the requested service)";
+ break;
+ case ENOTCONN:
+ errmsg = "ENOTCONN: The socket is associated with a connection-oriented protocol and has not been connected";
+ break;
+ //case E:
+ // errmsg = "";
+ // break;
+ default:
+ break;
+ }
+
+ kodi->Log(LOG_ERROR, "%s: (errno=%i) %s\n", functionname, errnum, errmsg);
+}
+
+int Socket::getLastError() const
+{
+ return errno;
+}
+
+bool Socket::osInit()
+{
+ // Not needed for Linux
+ return true;
+}
+
+void Socket::osCleanup()
+{
+ // Not needed for Linux
+}
+#endif //TARGET_WINDOWS || TARGET_LINUX || TARGET_DARWIN || TARGET_FREEBSD
+
+} //namespace OCTO
diff --git a/src/Socket.h b/src/Socket.h
new file mode 100644
index 0000000..4d19e40
--- /dev/null
+++ b/src/Socket.h
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2005-2011 Team XBMC
+ * http://www.xbmc.org
+ *
+ * This Program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This Program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+#pragma once
+
+//Include platform specific datatypes, header files, defines and constants:
+#if defined TARGET_WINDOWS
+ #define WIN32_LEAN_AND_MEAN // Enable LEAN_AND_MEAN support
+ #pragma warning(disable:4005) // Disable "warning C4005: '_WINSOCKAPI_' : macro redefinition"
+ #include
+ #include
+ #pragma warning(default:4005)
+ #include
+
+ #ifndef NI_MAXHOST
+ #define NI_MAXHOST 1025
+ #endif
+
+ #ifndef socklen_t
+ typedef int socklen_t;
+ #endif
+ #ifndef ipaddr_t
+ typedef unsigned long ipaddr_t;
+ #endif
+ #ifndef port_t
+ typedef unsigned short port_t;
+ #endif
+ #ifndef sa_family_t
+ #define sa_family_t ADDRESS_FAMILY
+ #endif
+#elif defined TARGET_LINUX || defined TARGET_DARWIN || defined TARGET_FREEBSD
+#ifdef SOCKADDR_IN
+#undef SOCKADDR_IN
+#endif
+ #include /* for socket,connect */
+ #include /* for socket,connect */
+ #include /* for Unix socket */
+ #include /* for inet_pton */
+ #include /* for gethostbyname */
+ #include /* for htons */
+ #include /* for read, write, close */
+ #include
+ #include
+
+ typedef int SOCKET;
+ typedef sockaddr SOCKADDR;
+ typedef sockaddr_in SOCKADDR_IN;
+ #ifndef INVALID_SOCKET
+ #define INVALID_SOCKET (-1)
+ #endif
+ #define SOCKET_ERROR (-1)
+
+ #define closesocket(sd) ::close(sd)
+#else
+ #error Platform specific socket support is not yet available on this platform!
+#endif
+
+
+#include
+
+namespace OCTO
+{
+
+#define MAXCONNECTIONS 1 ///< Maximum number of pending connections before "Connection refused"
+#define MAXRECV 1500 ///< Maximum packet size
+
+enum SocketFamily
+{
+ af_unspec = AF_UNSPEC,
+ af_inet = AF_INET,
+ af_inet6 = AF_INET6
+};
+
+enum SocketDomain
+{
+ #if defined TARGET_LINUX || defined TARGET_DARWIN || defined TARGET_FREEBSD
+ pf_unix = PF_UNIX,
+ pf_local = PF_LOCAL,
+ #endif
+ pf_inet = PF_INET
+};
+
+enum SocketType
+{
+ sock_stream = SOCK_STREAM,
+ sock_dgram = SOCK_DGRAM
+};
+
+enum SocketProtocol
+{
+ tcp = IPPROTO_TCP,
+ udp = IPPROTO_UDP
+};
+
+class Socket
+{
+ public:
+
+ /*!
+ * An unconnected socket may be created directly on the local
+ * machine. The socket type (SOCK_STREAM, SOCK_DGRAM) and
+ * protocol may also be specified.
+ * If the socket cannot be created, an exception is thrown.
+ *
+ * \param family Socket family (IPv4 or IPv6)
+ * \param domain The domain parameter specifies a communications domain within which communication will take place;
+ * this selects the protocol family which should be used.
+ * \param type base type and protocol family of the socket.
+ * \param protocol specific protocol to apply.
+ */
+ Socket(const enum SocketFamily family, const enum SocketDomain domain, const enum SocketType type, const enum SocketProtocol protocol = tcp);
+ Socket(void);
+ virtual ~Socket();
+
+ //Socket settings
+
+ /*!
+ * Socket setFamily
+ * \param family Can be af_inet or af_inet6. Default: af_inet
+ */
+ void setFamily(const enum SocketFamily family)
+ {
+ _family = family;
+ };
+
+ /*!
+ * Socket setDomain
+ * \param domain Can be pf_unix, pf_local, pf_inet or pf_inet6. Default: pf_inet
+ */
+ void setDomain(const enum SocketDomain domain)
+ {
+ _domain = domain;
+ };
+
+ /*!
+ * Socket setType
+ * \param type Can be sock_stream or sock_dgram. Default: sock_stream.
+ */
+ void setType(const enum SocketType type)
+ {
+ _type = type;
+ };
+
+ /*!
+ * Socket setProtocol
+ * \param protocol Can be tcp or udp. Default: tcp.
+ */
+ void setProtocol(const enum SocketProtocol protocol)
+ {
+ _protocol = protocol;
+ };
+
+ /*!
+ * Socket setPort
+ * \param port port number for socket communication
+ */
+ void setPort (const unsigned short port)
+ {
+ _sockaddr.sin_port = htons ( port );
+ };
+
+ bool setHostname ( const std::string& host );
+
+ // Server initialization
+
+ /*!
+ * Socket create
+ * Create a new socket
+ * \return True if succesful
+ */
+ bool create();
+
+ /*!
+ * Socket close
+ * Close the socket
+ * \return True if succesful
+ */
+ bool close();
+
+ /*!
+ * Socket bind
+ */
+ bool bind ( const unsigned short port );
+ bool listen() const;
+ bool accept ( Socket& socket ) const;
+
+ // Client initialization
+ bool connect ( const std::string& host, const unsigned short port );
+
+ bool reconnect();
+
+ // Data Transmission
+
+ /*!
+ * Socket send function
+ *
+ * \param data Reference to a std::string with the data to transmit
+ * \return Number of bytes send or -1 in case of an error
+ */
+ int send ( const std::string& data );
+
+ /*!
+ * Socket send function
+ *
+ * \param data Pointer to a character array of size 'size' with the data to transmit
+ * \param size Length of the data to transmit
+ * \return Number of bytes send or -1 in case of an error
+ */
+ int send ( const char* data, const unsigned int size );
+
+ /*!
+ * Socket sendto function
+ *
+ * \param data Reference to a std::string with the data to transmit
+ * \param size Length of the data to transmit
+ * \param sendcompletebuffer If 'true': do not return until the complete buffer is transmitted
+ * \return Number of bytes send or -1 in case of an error
+ */
+ int sendto ( const char* data, unsigned int size, bool sendcompletebuffer = false);
+ // Data Receive
+
+ /*!
+ * Socket receive function
+ *
+ * \param data Reference to a std::string for storage of the received data.
+ * \param minpacketsize The minimum number of bytes that should be received before returning from this function
+ * \return Number of bytes received or SOCKET_ERROR
+ */
+ int receive ( std::string& data, unsigned int minpacketsize ) const;
+
+ /*!
+ * Socket receive function
+ *
+ * \param data Reference to a std::string for storage of the received data.
+ * \return Number of bytes received or SOCKET_ERROR
+ */
+ int receive ( std::string& data ) const;
+
+ /*!
+ * Socket receive function
+ *
+ * \param data Pointer to a character array of size buffersize. Used to store the received data.
+ * \param buffersize Size of the 'data' buffer
+ * \param minpacketsize Specifies the minimum number of bytes that need to be received before returning
+ * \return Number of bytes received or SOCKET_ERROR
+ */
+ int receive ( char* data, const unsigned int buffersize, const unsigned int minpacketsize ) const;
+
+ /*!
+ * Socket recvfrom function
+ *
+ * \param data Pointer to a character array of size buffersize. Used to store the received data.
+ * \param buffersize Size of the 'data' buffer
+ * \param from Optional: pointer to a sockaddr struct that will get the address from which the data is received
+ * \param fromlen Optional, only required if 'from' is given: length of from struct
+ * \return Number of bytes received or SOCKET_ERROR
+ */
+ int recvfrom ( char* data, const int buffersize, struct sockaddr* from = NULL, socklen_t* fromlen = NULL) const;
+
+ bool set_non_blocking ( const bool );
+
+ bool ReadLine (std::string& line);
+
+ bool is_valid() const;
+
+ private:
+
+ SOCKET _sd; ///< Socket Descriptor
+ SOCKADDR_IN _sockaddr; ///< Socket Address
+ //struct addrinfo* _addrinfo; ///< Socket address info
+ std::string _hostname; ///< Hostname
+ unsigned short _port; ///< Port number
+
+ enum SocketFamily _family; ///< Socket Address Family
+ enum SocketProtocol _protocol; ///< Socket Protocol
+ enum SocketType _type; ///< Socket Type
+ enum SocketDomain _domain; ///< Socket domain
+
+ #ifdef TARGET_WINDOWS
+ WSADATA _wsaData; ///< Windows Socket data
+ static int win_usage_count; ///< Internal Windows usage counter used to prevent a global WSACleanup when more than one Socket object is used
+ #endif
+
+ void errormessage( int errornum, const char* functionname = NULL) const;
+ int getLastError(void) const;
+ bool osInit();
+ void osCleanup();
+};
+
+} //namespace OCTO
diff --git a/src/client.cpp b/src/client.cpp
index d5f006b..783d515 100644
--- a/src/client.cpp
+++ b/src/client.cpp
@@ -27,6 +27,7 @@
#include
#include "OctonetData.h"
+#include "rtsp_client.hpp"
using namespace ADDON;
@@ -247,14 +248,28 @@ PVR_ERROR UpdateTimer(const PVR_TIMER& timer) { return PVR_ERROR_NOT_IMPLEMENTED
/* PVR stream handling */
/* entirely unused, as we use standard RTSP+TS mux, which can be handlded by
* Kodi core */
-bool OpenLiveStream(const PVR_CHANNEL& channel) { return false; }
-void CloseLiveStream(void) {}
-int ReadLiveStream(unsigned char* pBuffer, unsigned int iBufferSize) { return -1; }
+bool OpenLiveStream(const PVR_CHANNEL& channel) {
+ return rtsp_open(data->getUrl(channel.iUniqueId));
+}
+
+int ReadLiveStream(unsigned char* pBuffer, unsigned int iBufferSize) {
+ return rtsp_read(pBuffer, iBufferSize);
+}
+
+void CloseLiveStream(void) {
+ rtsp_close();
+}
+
long long SeekLiveStream(long long iPosition, int iWhence) { return -1; }
long long PositionLiveStream(void) { return -1; }
long long LengthLiveStream(void) { return -1; }
bool IsRealTimeStream(void) { return true; }
-bool SwitchChannel(const PVR_CHANNEL& channel) { return false; }
+
+bool SwitchChannel(const PVR_CHANNEL& channel) {
+ CloseLiveStream();
+ return OpenLiveStream(channel);
+}
+
PVR_ERROR SignalStatus(PVR_SIGNAL_STATUS& signalStatus) { return PVR_ERROR_NOT_IMPLEMENTED; }
const char* GetLiveStreamURL(const PVR_CHANNEL& channel) { return NULL; }
PVR_ERROR GetStreamProperties(PVR_STREAM_PROPERTIES* pProperties) { return PVR_ERROR_NOT_IMPLEMENTED; }
diff --git a/src/rtsp_client.cpp b/src/rtsp_client.cpp
new file mode 100644
index 0000000..a975233
--- /dev/null
+++ b/src/rtsp_client.cpp
@@ -0,0 +1,374 @@
+#include "rtsp_client.hpp"
+#include
+#include
+#include
+#include "Socket.h"
+#include "client.h"
+#include
+#include
+// #include
+#include
+
+#define RTSP_DEFAULT_PORT 554
+#define RTSP_RECEIVE_BUFFER 2048
+#define RTP_HEADER_SIZE 12
+#define VLEN 100
+#define KEEPALIVE_INTERVAL 60
+#define KEEPALIVE_MARGIN 5
+#define UDP_ADDRESS_LEN 16
+
+using namespace std;
+using namespace ADDON;
+using namespace OCTO;
+
+enum rtsp_state {
+ RTSP_IDLE,
+ RTSP_DESCRIBE,
+ RTSP_SETUP,
+ RTSP_PLAY,
+ RTSP_RUNNING
+};
+
+enum rtsp_result {
+ RTSP_RESULT_OK = 200,
+};
+
+struct rtsp_client {
+ char *content_base;
+ char *control;
+ char session_id[64];
+ uint16_t stream_id;
+ int keepalive_interval;
+
+ char udp_address[UDP_ADDRESS_LEN];
+ uint16_t udp_port;
+
+ Socket tcp_sock;
+ Socket udp_sock;
+
+ enum rtsp_state state;
+ int cseq;
+
+ size_t fifo_size;
+ uint16_t last_seq_nr;
+};
+
+struct url {
+ string protocol;
+ string host;
+ int port;
+ string path;
+};
+
+static rtsp_client *rtsp = NULL;
+
+static url parse_url(const std::string& str) {
+ static const string prot_end = "://";
+ static const string host_end = "/";
+ url result;
+
+ string::const_iterator begin = str.begin();
+ string::const_iterator end = search(begin, str.end(), prot_end.begin(), prot_end.end());
+ result.protocol.reserve(distance(begin, end));
+ transform(begin, end, back_inserter(result.protocol), ::tolower);
+ advance(end, prot_end.size());
+ begin = end;
+
+ end = search(begin, str.end(), host_end.begin(), host_end.end());
+ result.host.reserve(distance(begin, end));
+ transform(begin, end, back_inserter(result.host), ::tolower);
+ advance(end, host_end.size());
+ begin = end;
+
+ result.port = RTSP_DEFAULT_PORT;
+
+ result.path.reserve(distance(begin, str.end()));
+ transform(begin, str.end(), back_inserter(result.path), ::tolower);
+
+ return result;
+}
+
+static int tcp_sock_read_line(string &line) {
+ static string buf;
+
+ while(true) {
+ string::size_type pos = buf.find("\r\n");
+ if(pos != string::npos) {
+ line = buf.substr(0, pos);
+ buf.erase(0, pos + 2);
+ return 0;
+ }
+
+ char tmp_buf[2048];
+ int size = rtsp->tcp_sock.receive(tmp_buf, sizeof(tmp_buf), 1);
+ if(size <= 0) {
+ return 1;
+ }
+
+ buf.append(&tmp_buf[0], &tmp_buf[size]);
+ }
+}
+
+static string compose_url(const url& u)
+{
+ stringstream res;
+ res << u.protocol << "://" << u.host;
+ if (u.port > 0)
+ res << ":" << u.port;
+ res << "/" << u.path;
+
+ return res.str();
+}
+
+static void parse_session(char *request_line, char *session, unsigned max, int *timeout) {
+ char *state;
+ char *tok;
+
+ tok = strtok_r(request_line, ";", &state);
+ if (tok == NULL)
+ return;
+ strncpy(session, tok, min(strlen(tok), (size_t)(max - 1)));
+
+ while ((tok = strtok_r(NULL, ";", &state)) != NULL) {
+ if (strncmp(tok, "timeout=", 8) == 0) {
+ *timeout = atoi(tok + 8);
+ if (*timeout > 5)
+ *timeout -= KEEPALIVE_MARGIN;
+ else if (*timeout > 0)
+ *timeout = 1;
+ }
+ }
+}
+
+static int parse_port(char *str, uint16_t *port)
+{
+ int p = atoi(str);
+ if (p < 0 || p > UINT16_MAX)
+ return -1;
+
+ *port = p;
+
+ return 0;
+}
+
+static int parse_transport(char *request_line) {
+ char *state;
+ char *tok;
+ int err;
+
+ tok = strtok_r(request_line, ";", &state);
+ if (tok == NULL || strncmp(tok, "RTP/AVP", 7) != 0)
+ return -1;
+
+ tok = strtok_r(NULL, ";", &state);
+ if (tok == NULL || strncmp(tok, "multicast", 9) != 0)
+ return 0;
+
+ while ((tok = strtok_r(NULL, ";", &state)) != NULL) {
+ if (strncmp(tok, "destination=", 12) == 0) {
+ strncpy(rtsp->udp_address, tok + 12, min(strlen(tok + 12), (size_t)(UDP_ADDRESS_LEN - 1)));
+ } else if (strncmp(tok, "port=", 5) == 0) {
+ char port[6];
+ char *end;
+
+ memset(port, 0x00, 6);
+ strncpy(port, tok + 5, min(strlen(tok + 5), (size_t)5));
+ if ((end = strstr(port, "-")) != NULL)
+ *end = '\0';
+ err = parse_port(port, &rtsp->udp_port);
+ if (err)
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+#define skip_whitespace(x) while(*x == ' ') x++
+static enum rtsp_result rtsp_handle() {
+ uint8_t buffer[512];
+ int rtsp_result = 0;
+ bool have_header = false;
+ size_t content_length = 0;
+ size_t read = 0;
+ char *in, *val;
+ string in_str;
+
+ /* Parse header */
+ while (!have_header) {
+ if (tcp_sock_read_line(in_str) < 0)
+ break;
+ in = const_cast(in_str.c_str());
+
+ if (strncmp(in, "RTSP/1.0 ", 9) == 0) {
+ rtsp_result = atoi(in + 9);
+ } else if (strncmp(in, "Content-Base:", 13) == 0) {
+ free(rtsp->content_base);
+
+ val = in + 13;
+ skip_whitespace(val);
+
+ rtsp->content_base = strdup(val);
+ } else if (strncmp(in, "Content-Length:", 15) == 0) {
+ val = in + 16;
+ skip_whitespace(val);
+
+ content_length = atoi(val);
+ } else if (strncmp("Session:", in, 8) == 0) {
+ val = in + 8;
+ skip_whitespace(val);
+
+ parse_session(val, rtsp->session_id, 64, &rtsp->keepalive_interval);
+ } else if (strncmp("Transport:", in, 10) == 0) {
+ val = in + 10;
+ skip_whitespace(val);
+
+ if (parse_transport(val) != 0) {
+ rtsp_result = -1;
+ break;
+ }
+ } else if (strncmp("com.ses.streamID:", in, 17) == 0) {
+ val = in + 17;
+ skip_whitespace(val);
+
+ rtsp->stream_id = atoi(val);
+ } else if (in[0] == '\0') {
+ have_header = true;
+ }
+ }
+
+ /* Discard further content */
+ while (content_length > 0 &&
+ (read = rtsp->tcp_sock.receive((char*)buffer, sizeof(buffer), min(sizeof(buffer), content_length))))
+ content_length -= read;
+
+ return (enum rtsp_result)rtsp_result;
+}
+
+bool rtsp_open(const string& url_str)
+{
+ string setup_url_str;
+ const char *psz_setup_url;
+ stringstream setup_ss;
+ stringstream play_ss;
+ url setup_url;
+
+ rtsp_close();
+ rtsp = new rtsp_client();
+ if (rtsp == NULL)
+ return false;
+
+ kodi->Log(LOG_DEBUG, "try to open '%s'", url_str.c_str());
+
+ url dst = parse_url(url_str);
+ kodi->Log(LOG_DEBUG, "connect to host '%s'", dst.host.c_str());
+
+ if(!rtsp->tcp_sock.connect(dst.host, dst.port)) {
+ kodi->Log(LOG_ERROR, "Failed to connect to RTSP server %s:%d", dst.host.c_str(), dst.port);
+ goto error;
+ }
+
+ // TODO: tcp keep alive?
+
+ if (asprintf(&rtsp->content_base, "rtsp://%s:%d/", dst.host.c_str(),
+ dst.port) < 0) {
+ rtsp->content_base = NULL;
+ goto error;
+ }
+
+ rtsp->last_seq_nr = 0;
+ rtsp->keepalive_interval = (KEEPALIVE_INTERVAL - KEEPALIVE_MARGIN);
+
+ setup_url = dst;
+
+ // reverse the satip protocol trick, as SAT>IP believes to be RTSP
+ if (!strncasecmp(setup_url.protocol.c_str(), "satip", 5)) {
+ setup_url.protocol = "rtsp";
+ }
+
+ setup_url_str = compose_url(setup_url);
+ psz_setup_url = setup_url_str.c_str();
+
+ // TODO: Find available port
+ rtsp->udp_sock = Socket(af_inet, pf_inet, sock_dgram, udp);
+ rtsp->udp_port = 6785;
+ if(!rtsp->udp_sock.bind(rtsp->udp_port)) {
+ goto error;
+ }
+
+ setup_ss << "SETUP " << setup_url_str<< " RTSP/1.0\r\n";
+ setup_ss << "CSeq: " << rtsp->cseq++ << "\r\n";
+ setup_ss << "Transport: RTP/AVP;unicast;client_port=" << rtsp->udp_port << "-" << (rtsp->udp_port + 1) << "\r\n\r\n";
+ rtsp->tcp_sock.send(setup_ss.str());
+
+ if (rtsp_handle() != RTSP_RESULT_OK) {
+ kodi->Log(LOG_ERROR, "Failed to setup RTSP session");
+ goto error;
+ }
+
+ if (asprintf(&rtsp->control, "%sstream=%d", rtsp->content_base, rtsp->stream_id) < 0) {
+ rtsp->control = NULL;
+ goto error;
+ }
+
+ play_ss << "PLAY " << rtsp->control << " RTSP/1.0\r\n";
+ play_ss << "CSeq: " << rtsp->cseq++ << "\r\n";
+ play_ss << "Session: " << rtsp->session_id << "\r\n\r\n";
+ rtsp->tcp_sock.send(play_ss.str());
+
+ if (rtsp_handle() != RTSP_RESULT_OK) {
+ kodi->Log(LOG_ERROR, "Failed to play RTSP session");
+ goto error;
+ }
+
+ return true;
+
+error:
+ rtsp_close();
+ return false;
+}
+
+int rtsp_read(void *buf, unsigned buf_size) {
+ sockaddr addr;
+ socklen_t addr_len = sizeof(addr);
+ int ret = rtsp->udp_sock.recvfrom((char *)buf, buf_size, (sockaddr *)&addr, &addr_len);
+
+ // TODO: check ip
+
+ return ret;
+}
+
+static void rtsp_teardown() {
+ if(!rtsp->tcp_sock.is_valid()) {
+ return;
+ }
+
+ if (rtsp->session_id[0] > 0) {
+ char *msg;
+ int len;
+ stringstream ss;
+
+ rtsp->udp_sock.close();
+
+ ss << "TEARDOWN " << rtsp->control << " RTSP/1.0\r\n";
+ ss << "CSeq: " << rtsp->cseq++ << "\r\n";
+ ss << "Session: " << rtsp->session_id << "\r\n\r\n";
+ rtsp->tcp_sock.send(ss.str());
+
+ if (rtsp_handle() != RTSP_RESULT_OK) {
+ kodi->Log(LOG_ERROR, "Failed to teardown RTSP session");
+ return;
+ }
+ }
+}
+
+void rtsp_close()
+{
+ if(rtsp) {
+ rtsp_teardown();
+ rtsp->tcp_sock.close();
+ rtsp->udp_sock.close();
+ delete rtsp;
+ rtsp = NULL;
+ }
+}
diff --git a/src/rtsp_client.hpp b/src/rtsp_client.hpp
new file mode 100644
index 0000000..1186922
--- /dev/null
+++ b/src/rtsp_client.hpp
@@ -0,0 +1,10 @@
+#ifndef _RTSP_CLIENT_HPP_
+#define _RTSP_CLIENT_HPP_
+
+#include
+
+bool rtsp_open(const std::string& url_str);
+void rtsp_close();
+int rtsp_read(void *buf, unsigned buf_size);
+
+#endif