From 5622e10c72811fac3762793fbcdb9b83f2d4a947 Mon Sep 17 00:00:00 2001 From: phunkyfish Date: Sun, 25 Feb 2024 13:53:19 +0000 Subject: [PATCH] Change add-on to use inputstream.ffmpegdirect which also enables timeshifting --- CMakeLists.txt | 8 +- pvr.octonet/addon.xml.in | 4 +- .../resource.language.en_gb/strings.po | 38 + pvr.octonet/resources/settings.xml | 25 +- src/OctonetData.cpp | 40 +- src/OctonetData.h | 7 +- src/Socket.cpp | 728 ------------------ src/Socket.h | 284 ------- src/addon.cpp | 3 +- src/rtsp_client.cpp | 548 ------------- src/rtsp_client.hpp | 16 - 11 files changed, 92 insertions(+), 1609 deletions(-) delete mode 100644 src/Socket.cpp delete mode 100644 src/Socket.h delete mode 100644 src/rtsp_client.cpp delete mode 100644 src/rtsp_client.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index c3cdf78..abaaa84 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,14 +12,10 @@ include_directories(${KODI_INCLUDE_DIR}/.. # Hack way with "/..", need bigger Ko set(DEPLIBS ${JSONCPP_LIBRARIES}) set(OCTONET_SOURCES src/addon.cpp - src/OctonetData.cpp - src/Socket.cpp - src/rtsp_client.cpp) + src/OctonetData.cpp) set(OCTONET_HEADERS src/addon.h - src/OctonetData.h - src/Socket.h - src/rtsp_client.hpp) + src/OctonetData.h) addon_version(pvr.octonet OCTONET) add_definitions(-DOCTONET_VERSION=${OCTONET_VERSION}) diff --git a/pvr.octonet/addon.xml.in b/pvr.octonet/addon.xml.in index a4940ad..6c67e3f 100644 --- a/pvr.octonet/addon.xml.in +++ b/pvr.octonet/addon.xml.in @@ -4,7 +4,9 @@ version="20.3.0" name="Digital Devices Octopus NET Client" provider-name="digitaldevices"> - @ADDON_DEPENDS@ + @ADDON_DEPENDS@ + + diff --git a/pvr.octonet/resources/language/resource.language.en_gb/strings.po b/pvr.octonet/resources/language/resource.language.en_gb/strings.po index fbe2159..d23d5e8 100644 --- a/pvr.octonet/resources/language/resource.language.en_gb/strings.po +++ b/pvr.octonet/resources/language/resource.language.en_gb/strings.po @@ -27,3 +27,41 @@ msgstr "" msgctxt "#30001" msgid "Could not load chanellist" msgstr "" + +msgctxt "#30002" +msgid "Timeshift" +msgstr "" + +msgctxt "#30003" +msgid "Enable timeshift" +msgstr "" + +msgctxt "#30004" +msgid "- Modify inputstream.ffmpegdirect settings..." +msgstr "" + +msgctxt "#30005" +msgid "Inputstream settings" +msgstr "" + +#empty strings from id 30006 to 30599 + +msgctxt "#30600" +msgid "The IP/address of the octonet server. Note that in case of an address, the protocol (http://) has to be omitted." +msgstr "" + +msgctxt "#30601" +msgid "General settings required by the add-on." +msgstr "" + +msgctxt "#30602" +msgid "Timeshift settings for pausing/rewinding and fast-forwarding live streams." +msgstr "" + +msgctxt "#30603" +msgid "Enable the timeshift feature." +msgstr "" + +msgctxt "#30604" +msgid "Open settings dialog for inputstream.ffmpegdirect for modification of timeshift and other settings." +msgstr "" diff --git a/pvr.octonet/resources/settings.xml b/pvr.octonet/resources/settings.xml index 1c59a19..2c14977 100644 --- a/pvr.octonet/resources/settings.xml +++ b/pvr.octonet/resources/settings.xml @@ -1,10 +1,10 @@
- + - + 0 @@ -14,5 +14,26 @@ + + + + 0 + false + + + + + + 0 + Addon.OpenSettings(inputstream.ffmpegdirect) + + true + + + true + + + +
diff --git a/src/OctonetData.cpp b/src/OctonetData.cpp index 404bfb3..364eb0d 100644 --- a/src/OctonetData.cpp +++ b/src/OctonetData.cpp @@ -10,8 +10,6 @@ #include "OctonetData.h" -#include "rtsp_client.hpp" - #include #include #include @@ -23,10 +21,12 @@ #endif OctonetData::OctonetData(const std::string& octonetAddress, + bool enableTimeshift, const kodi::addon::IInstanceInfo& instance) : kodi::addon::CInstancePVRClient(instance) { m_serverAddress = octonetAddress; + m_enableTimeshift = enableTimeshift; m_channels.clear(); m_groups.clear(); m_lastEpgLoad = 0; @@ -273,6 +273,25 @@ PVR_ERROR OctonetData::GetChannels(bool radio, kodi::addon::PVRChannelsResultSet return PVR_ERROR_NO_ERROR; } +PVR_ERROR OctonetData::GetChannelStreamProperties(const kodi::addon::PVRChannel& channelinfo, std::vector& properties) +{ + properties.emplace_back(PVR_STREAM_PROPERTY_INPUTSTREAM, "inputstream.ffmpegdirect"); + properties.emplace_back("inputstream.ffmpegdirect.is_realtime_stream", "true"); + properties.emplace_back("inputstream.ffmpegdirect.open_mode", "ffmpeg"); + if (m_enableTimeshift) + { + // This property is required to support timeshifting for Radio channels + properties.emplace_back("inputstream-player", "videodefaultplayer"); + properties.emplace_back("inputstream.ffmpegdirect.stream_mode", "timeshift"); + } + properties.emplace_back(PVR_STREAM_PROPERTY_MIMETYPE, "video/x-mpegts"); + properties.emplace_back(PVR_STREAM_PROPERTY_STREAMURL, GetUrl(channelinfo.GetUniqueId())); + + kodi::Log(ADDON_LOG_INFO, "Playing channel - name: %s, url: %s, and using inputstream.ffmpegdirect", GetName(channelinfo.GetUniqueId()).c_str(), GetUrl(channelinfo.GetUniqueId()).c_str()); + + return PVR_ERROR_NO_ERROR; +} + PVR_ERROR OctonetData::GetEPGForChannel(int channelUid, time_t start, time_t end, @@ -426,20 +445,3 @@ OctonetGroup* OctonetData::FindGroup(const std::string& name) return nullptr; } -/* PVR stream handling */ -/* entirely unused, as we use standard RTSP+TS mux, which can be handlded by - * Kodi core */ -bool OctonetData::OpenLiveStream(const kodi::addon::PVRChannel& channelinfo) -{ - return rtsp_open(GetName(channelinfo.GetUniqueId()), GetUrl(channelinfo.GetUniqueId())); -} - -int OctonetData::ReadLiveStream(unsigned char* pBuffer, unsigned int iBufferSize) -{ - return rtsp_read(pBuffer, iBufferSize); -} - -void OctonetData::CloseLiveStream() -{ - rtsp_close(); -} diff --git a/src/OctonetData.h b/src/OctonetData.h index 61b988c..338a0d2 100644 --- a/src/OctonetData.h +++ b/src/OctonetData.h @@ -47,6 +47,7 @@ class ATTR_DLL_LOCAL OctonetData : public kodi::addon::CInstancePVRClient { public: OctonetData(const std::string& octonetAddress, + bool enableTimeshift, const kodi::addon::IInstanceInfo& instance); ~OctonetData() override; @@ -66,16 +67,13 @@ public: PVR_ERROR GetChannelGroups(bool radio, kodi::addon::PVRChannelGroupsResultSet& results) override; PVR_ERROR GetChannelGroupMembers(const kodi::addon::PVRChannelGroup& group, kodi::addon::PVRChannelGroupMembersResultSet& results) override; + PVR_ERROR GetChannelStreamProperties(const kodi::addon::PVRChannel& channel, std::vector& properties) override; PVR_ERROR GetEPGForChannel(int channelUid, time_t start, time_t end, kodi::addon::PVREPGTagsResultSet& results) override; - bool OpenLiveStream(const kodi::addon::PVRChannel& channelinfo) override; - int ReadLiveStream(unsigned char* buffer, unsigned int size) override; - void CloseLiveStream() override; - protected: const std::string& GetUrl(int id) const; const std::string& GetName(int id) const; @@ -89,6 +87,7 @@ protected: private: std::string m_serverAddress; + bool m_enableTimeshift = false; std::vector m_channels; std::vector m_groups; diff --git a/src/Socket.cpp b/src/Socket.cpp deleted file mode 100644 index 9edfb9d..0000000 --- a/src/Socket.cpp +++ /dev/null @@ -1,728 +0,0 @@ -/* - * Copyright (C) 2005-2021 Team Kodi (https://kodi.tv) - * - * SPDX-License-Identifier: GPL-2.0-or-later - * See LICENSE.md for more information. - */ - -#include "Socket.h" - -#include -#include -#include - -using namespace std; - -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) -{ - m_sd = INVALID_SOCKET; - m_family = family; - m_domain = domain; - m_type = type; - m_protocol = protocol; - m_port = 0; - memset(&m_sockaddr, 0, sizeof(m_sockaddr)); -} - - -Socket::Socket() -{ - // Default constructor, default settings - m_sd = INVALID_SOCKET; - m_family = af_inet; - m_domain = pf_inet; - m_type = sock_stream; - m_protocol = tcp; - m_port = 0; - memset(&m_sockaddr, 0, sizeof(m_sockaddr)); -} - - -Socket::~Socket() -{ - close(); - osCleanup(); -} - -bool Socket::setHostname(const std::string& host) -{ - m_hostname = host; - return true; -} - -bool Socket::close() -{ - if (is_valid()) - { - if (m_sd != SOCKET_ERROR) - closesocket(m_sd); - m_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(); - } - - m_sd = socket(m_family, m_type, m_protocol); - m_port = port; - m_sockaddr.sin_family = (sa_family_t)m_family; - m_sockaddr.sin_addr.s_addr = INADDR_ANY; //listen to all - m_sockaddr.sin_port = htons(m_port); - - int bind_return = ::bind(m_sd, (sockaddr*)(&m_sockaddr), sizeof(m_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(m_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(m_sockaddr); - new_socket.m_sd = - ::accept(m_sd, const_cast((const sockaddr*)&m_sockaddr), &addr_length); - -#ifdef TARGET_WINDOWS - if (new_socket.m_sd == INVALID_SOCKET) -#else - if (new_socket.m_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(m_sd, &set_w); - FD_SET(m_sd, &set_e); - - result = select(FD_SETSIZE, &set_w, nullptr, &set_e, &tv); - - if (result < 0) - { - kodi::Log(ADDON_LOG_ERROR, "Socket::send - select failed"); - close(); - return 0; - } - if (FD_ISSET(m_sd, &set_w)) - { - kodi::Log(ADDON_LOG_ERROR, "Socket::send - failed to send data"); - close(); - return 0; - } - - int status = ::send(m_sd, data, len, 0); - - if (status == -1) - { - errormessage(getLastError(), "Socket::send"); - kodi::Log(ADDON_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(m_sd, data, size, 0, (const struct sockaddr*)&m_sockaddr, sizeof(m_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 = nullptr; - 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(m_sd, &set_r); - FD_SET(m_sd, &set_e); - int result = select(FD_SETSIZE, &set_r, nullptr, &set_e, &timeout); - - if (result < 0) - { - kodi::Log(ADDON_LOG_DEBUG, "%s: select failed", __func__); - errormessage(getLastError(), __func__); - close(); - return false; - } - - if (result == 0) - { - if (retries != 0) - { - kodi::Log(ADDON_LOG_DEBUG, "%s: timeout waiting for response, retrying... (%i)", __func__, - retries); - retries--; - continue; - } - else - { - kodi::Log(ADDON_LOG_DEBUG, "%s: timeout waiting for response. Aborting after 10 retries.", - __func__); - return false; - } - } - - result = recv(m_sd, buffer, sizeof(buffer) - 1, 0); - if (result < 0) - { - kodi::Log(ADDON_LOG_DEBUG, "%s: recv failed", __func__); - errormessage(getLastError(), __func__); - 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(m_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(m_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(ADDON_LOG_ERROR, "Socket::setHostname(%s) failed.\n", host.c_str()); - return false; - } - m_port = port; - - char strPort[15]; - snprintf(strPort, 15, "%hu", port); - - struct addrinfo hints; - struct addrinfo* result = nullptr; - struct addrinfo* address = nullptr; - memset(&hints, 0, sizeof(hints)); - hints.ai_family = m_family; - hints.ai_socktype = m_type; - hints.ai_protocol = m_protocol; - - int retval = getaddrinfo(host.c_str(), strPort, &hints, &result); - if (retval != 0) - { - errormessage(getLastError(), "Socket::connect"); - return false; - } - - for (address = result; address != nullptr; address = address->ai_next) - { - // Create the socket - m_sd = socket(address->ai_family, address->ai_socktype, address->ai_protocol); - - if (m_sd == INVALID_SOCKET) - { - errormessage(getLastError(), "Socket::create"); - continue; - } - - int status = ::connect(m_sd, address->ai_addr, address->ai_addrlen); - if (status == SOCKET_ERROR) - { - close(); - continue; - } - - // We have a conection - break; - } - - freeaddrinfo(result); - - if (address == nullptr) - { - kodi::Log(ADDON_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(m_hostname, m_port); -} - -bool Socket::is_valid() const -{ - return (m_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(m_sd, FIONBIO, &iMode) == -1) - { - kodi::Log(ADDON_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 = nullptr; - - 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(ADDON_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), &m_wsaData) != 0) - { - return false; - } - - WORD wVersionRequested = MAKEWORD(2, 2); - - // check version - if (m_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(m_sd, F_GETFL); - - if (opts < 0) - { - return false; - } - - if (b) - opts = (opts | O_NONBLOCK); - else - opts = (opts & ~O_NONBLOCK); - - if (fcntl(m_sd, F_SETFL, opts) == -1) - { - kodi::Log(ADDON_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 = nullptr; - - 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(ADDON_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 deleted file mode 100644 index ef5d4ff..0000000 --- a/src/Socket.h +++ /dev/null @@ -1,284 +0,0 @@ -/* - * Copyright (C) 2005-2021 Team Kodi (https://kodi.tv) - * - * SPDX-License-Identifier: GPL-2.0-or-later - * See LICENSE.md for more information. - */ - -#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 inet_pton */ -#include -#include -#include /* for gethostbyname */ -#include /* for htons */ -#include /* for socket,connect */ -#include /* for socket,connect */ -#include /* for Unix socket */ -#include /* for read, write, close */ - -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 -#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) { m_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) { m_domain = domain; }; - - /*! - * Socket setType - * \param type Can be sock_stream or sock_dgram. Default: sock_stream. - */ - void setType(const enum SocketType type) { m_type = type; }; - - /*! - * Socket setProtocol - * \param protocol Can be tcp or udp. Default: tcp. - */ - void setProtocol(const enum SocketProtocol protocol) { m_protocol = protocol; }; - - /*! - * Socket setPort - * \param port port number for socket communication - */ - void setPort(const unsigned short port) { m_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 = nullptr, - socklen_t* fromlen = nullptr) const; - - bool set_non_blocking(const bool); - - bool ReadLine(std::string& line); - - bool is_valid() const; - -private: - SOCKET m_sd; ///< Socket Descriptor - SOCKADDR_IN m_sockaddr; ///< Socket Address - //struct addrinfo* m_addrinfo; ///< Socket address info - std::string m_hostname; ///< Hostname - unsigned short m_port; ///< Port number - - enum SocketFamily m_family; ///< Socket Address Family - enum SocketProtocol m_protocol; ///< Socket Protocol - enum SocketType m_type; ///< Socket Type - enum SocketDomain m_domain; ///< Socket domain - -#ifdef TARGET_WINDOWS - WSADATA m_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 = nullptr) const; - int getLastError(void) const; - bool osInit(); - void osCleanup(); -}; - -} //namespace OCTO diff --git a/src/addon.cpp b/src/addon.cpp index 3a22537..7208030 100644 --- a/src/addon.cpp +++ b/src/addon.cpp @@ -29,8 +29,9 @@ ADDON_STATUS COctonetAddon::CreateInstance(const kodi::addon::IInstanceInfo& ins /* IP or hostname of the octonet to be connected to */ std::string octonetAddress = kodi::addon::GetSettingString("octonetAddress"); + bool enableTimeshift = kodi::addon::GetSettingBoolean("timeshiftEnabled"); - OctonetData* usedInstance = new OctonetData(octonetAddress, instance); + OctonetData* usedInstance = new OctonetData(octonetAddress, enableTimeshift, instance); hdl = usedInstance; m_usedInstances.emplace(instance.GetID(), usedInstance); diff --git a/src/rtsp_client.cpp b/src/rtsp_client.cpp deleted file mode 100644 index 9f93427..0000000 --- a/src/rtsp_client.cpp +++ /dev/null @@ -1,548 +0,0 @@ -/* - * Copyright (C) 2005-2021 Team Kodi (https://kodi.tv) - * - * SPDX-License-Identifier: GPL-2.0-or-later - * See LICENSE.md for more information. - */ - -#include "rtsp_client.hpp" - -#include "Socket.h" - -#include -#include -#include -#include -#include - -#if defined(_WIN32) || defined(_WIN64) -#define strtok_r strtok_s -#define strncasecmp _strnicmp - -int vasprintf(char** sptr, char* fmt, va_list argv) -{ - int wanted = vsnprintf(*sptr = nullptr, 0, fmt, argv); - if ((wanted < 0) || ((*sptr = (char*)malloc(1 + wanted)) == nullptr)) - return -1; - return vsprintf(*sptr, fmt, argv); -} - -int asprintf(char** sptr, char* fmt, ...) -{ - int retval; - va_list argv; - va_start(argv, fmt); - retval = vasprintf(sptr, fmt, argv); - va_end(argv); - return retval; -} -#endif - -#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 -#define RTCP_BUFFER_SIZE 1024 - -using namespace std; -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; - Socket rtcp_sock; - - enum rtsp_state state; - int cseq; - - size_t fifo_size; - uint16_t last_seq_nr; - - string name; - int level; - int quality; -}; - -struct url -{ - string protocol; - string host; - int port; - string path; -}; - -struct rtcp_app -{ - uint8_t subtype; - uint8_t pt; - uint16_t len; - uint32_t ssrc; - char name[4]; - uint16_t identifier; - uint16_t string_len; -}; - -static rtsp_client* rtsp = nullptr; - -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; -} - -void split_string(const string& s, char delim, vector& elems) -{ - stringstream ss; - ss.str(s); - - string item; - while (getline(ss, item, delim)) - { - elems.push_back(item); - } -} - -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 == nullptr) - return; - strncpy(session, tok, min(strlen(tok), (size_t)(max - 1))); - - while ((tok = strtok_r(nullptr, ";", &state)) != nullptr) - { - 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 == nullptr || strncmp(tok, "RTP/AVP", 7) != 0) - return -1; - - tok = strtok_r(nullptr, ";", &state); - if (tok == nullptr || strncmp(tok, "multicast", 9) != 0) - return 0; - - while ((tok = strtok_r(nullptr, ";", &state)) != nullptr) - { - 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, "-")) != nullptr) - *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& name, 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 == nullptr) - return false; - - rtsp->name = name; - rtsp->level = 0; - rtsp->quality = 0; - - kodi::Log(ADDON_LOG_DEBUG, "try to open '%s'", url_str.c_str()); - - url dst = parse_url(url_str); - kodi::Log(ADDON_LOG_DEBUG, "connect to host '%s'", dst.host.c_str()); - - if (!rtsp->tcp_sock.connect(dst.host, dst.port)) - { - kodi::Log(ADDON_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 = nullptr; - 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(ADDON_LOG_ERROR, "Failed to setup RTSP session"); - goto error; - } - - if (asprintf(&rtsp->control, "%sstream=%d", rtsp->content_base, rtsp->stream_id) < 0) - { - rtsp->control = nullptr; - 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(ADDON_LOG_ERROR, "Failed to play RTSP session"); - goto error; - } - - rtsp->rtcp_sock = Socket(af_inet, pf_inet, sock_dgram, udp); - if (!rtsp->rtcp_sock.bind(rtsp->udp_port + 1)) - { - goto error; - } - if (!rtsp->rtcp_sock.set_non_blocking(true)) - { - goto error; - } - - return true; - -error: - rtsp_close(); - return false; -} - -static void parse_rtcp(const char* buf, int size) -{ - int offset = 0; - while (size > 4) - { - const rtcp_app* app = reinterpret_cast(buf + offset); - uint16_t len = 4 * (ntohs(app->len) + 1); - - if ((app->pt != 204) || (memcmp(app->name, "SES1", 4) != 0)) - { - size -= len; - offset += len; - continue; - } - - uint16_t string_len = ntohs(app->string_len); - string app_data(&buf[offset + sizeof(rtcp_app)], string_len); - - vector elems; - split_string(app_data, ';', elems); - if (elems.size() != 4) - { - return; - } - - vector tuner; - split_string(elems[2], ',', tuner); - if (tuner.size() < 4) - { - return; - } - - rtsp->level = atoi(tuner[1].c_str()); - rtsp->quality = atoi(tuner[3].c_str()); - - return; - } -} - -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); - - char rtcp_buf[RTCP_BUFFER_SIZE]; - int rtcp_len = rtsp->rtcp_sock.recvfrom(rtcp_buf, RTCP_BUFFER_SIZE, (sockaddr*)&addr, &addr_len); - parse_rtcp(rtcp_buf, rtcp_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(ADDON_LOG_ERROR, "Failed to teardown RTSP session"); - return; - } - } -} - -void rtsp_close() -{ - if (rtsp) - { - rtsp_teardown(); - rtsp->tcp_sock.close(); - rtsp->udp_sock.close(); - rtsp->rtcp_sock.close(); - delete rtsp; - rtsp = nullptr; - } -} - -void rtsp_fill_signal_status(kodi::addon::PVRSignalStatus& signal_status) -{ - if (rtsp) - { - signal_status.SetAdapterName(rtsp->name); - signal_status.SetSNR(0x1111 * rtsp->quality); - signal_status.SetSignal(0x101 * rtsp->level); - } -} diff --git a/src/rtsp_client.hpp b/src/rtsp_client.hpp deleted file mode 100644 index df94c7a..0000000 --- a/src/rtsp_client.hpp +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright (C) 2005-2021 Team Kodi (https://kodi.tv) - * - * SPDX-License-Identifier: GPL-2.0-or-later - * See LICENSE.md for more information. - */ - -#pragma once - -#include -#include - -bool rtsp_open(const std::string& name, const std::string& url_str); -void rtsp_close(); -int rtsp_read(void* buf, unsigned buf_size); -void rtsp_fill_signal_status(kodi::addon::PVRSignalStatus& signal_status);