mirror of
https://github.com/DigitalDevices/pvr.octonet.git
synced 2023-10-10 13:36:57 +02:00
Merge pull request #43 from AlwinEsch/interface-change
[Matrix] change to new C++ PVR interface way
This commit is contained in:
commit
81520e3104
88
.clang-format
Normal file
88
.clang-format
Normal file
@ -0,0 +1,88 @@
|
||||
---
|
||||
# BasedOnStyle: LLVM
|
||||
AccessModifierOffset: -2
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignConsecutiveAssignments: false
|
||||
AlignConsecutiveDeclarations: false
|
||||
AlignEscapedNewlines: DontAlign
|
||||
AlignOperands: true
|
||||
AlignTrailingComments: false
|
||||
AllowAllParametersOfDeclarationOnNextLine: true
|
||||
AllowShortBlocksOnASingleLine: false
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: InlineOnly
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: false
|
||||
AlwaysBreakTemplateDeclarations: true
|
||||
BinPackArguments: true
|
||||
BinPackParameters: false
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeBraces: Allman
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializersBeforeComma: false
|
||||
BreakConstructorInitializers: BeforeColon
|
||||
BreakAfterJavaFieldAnnotations: false
|
||||
BreakStringLiterals: true
|
||||
ColumnLimit: 100
|
||||
CommentPragmas: '^ IWYU pragma:'
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||
ConstructorInitializerIndentWidth: 2
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: true
|
||||
DerivePointerAlignment: false
|
||||
DisableFormat: false
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ]
|
||||
IncludeBlocks: Regroup
|
||||
IncludeCategories:
|
||||
- Regex: '^<[a-z0-9_]+>$'
|
||||
Priority: 3
|
||||
- Regex: '^<(assert|complex|ctype|errno|fenv|float|inttypes|iso646|limits|locale|math|setjmp|signal|stdalign|stdarg|stdatomic|stdbool|stddef|stdint|stdio|stdlib|stdnoreturn|string|tgmath|threads|time|uchar|wchar|wctype)\.h>$'
|
||||
Priority: 3
|
||||
- Regex: '^<'
|
||||
Priority: 3
|
||||
- Regex: '^["<](kodi|p8-platform)\/.*\.h[">]$'
|
||||
Priority: 2
|
||||
- Regex: '.*'
|
||||
Priority: 1
|
||||
IncludeIsMainRegex: '$'
|
||||
IndentCaseLabels: true
|
||||
IndentWidth: 2
|
||||
IndentWrappedFunctionNames: false
|
||||
JavaScriptQuotes: Leave
|
||||
JavaScriptWrapImports: true
|
||||
KeepEmptyLinesAtTheStartOfBlocks: true
|
||||
MacroBlockBegin: ''
|
||||
MacroBlockEnd: ''
|
||||
MaxEmptyLinesToKeep: 2
|
||||
NamespaceIndentation: None
|
||||
ObjCBlockIndentWidth: 2
|
||||
ObjCSpaceAfterProperty: false
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
PenaltyBreakBeforeFirstCallParameter: 19
|
||||
PenaltyBreakComment: 300
|
||||
PenaltyBreakFirstLessLess: 120
|
||||
PenaltyBreakString: 1000
|
||||
PenaltyExcessCharacter: 1000000
|
||||
PenaltyReturnTypeOnItsOwnLine: 60000
|
||||
PointerAlignment: Left
|
||||
ReflowComments: false
|
||||
SortIncludes: true
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterTemplateKeyword: false
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 1
|
||||
SpacesInAngles: false
|
||||
SpacesInContainerLiterals: true
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
Standard: Cpp11
|
||||
TabWidth: 8
|
||||
UseTab: Never
|
||||
...
|
@ -4,28 +4,25 @@ project(pvr.octonet)
|
||||
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR})
|
||||
|
||||
find_package(Kodi REQUIRED)
|
||||
find_package(p8-platform REQUIRED)
|
||||
find_package(JsonCpp REQUIRED)
|
||||
|
||||
include_directories(
|
||||
${p8-platform_INCLUDE_DIRS}
|
||||
${KODI_INCLUDE_DIR}/.. # Hack way with "/..", need bigger Kodi cmake rework to match right include ways
|
||||
include_directories(${KODI_INCLUDE_DIR}/.. # Hack way with "/..", need bigger Kodi cmake rework to match right include ways
|
||||
${JSONCPP_INCLUDE_DIRS})
|
||||
|
||||
set(DEPLIBS
|
||||
${p8-platform_LIBRARIES}
|
||||
${JSONCPP_LIBRARIES})
|
||||
set(DEPLIBS ${JSONCPP_LIBRARIES})
|
||||
|
||||
set(OCTONET_SOURCES
|
||||
set(OCTONET_SOURCES src/addon.cpp
|
||||
src/OctonetData.cpp
|
||||
src/client.cpp
|
||||
src/Socket.cpp
|
||||
src/rtsp_client.cpp)
|
||||
|
||||
set(OCTONET_HEADERS
|
||||
src/client.h
|
||||
set(OCTONET_HEADERS src/addon.h
|
||||
src/OctonetData.h
|
||||
src/Socket.h)
|
||||
src/Socket.h
|
||||
src/rtsp_client.hpp)
|
||||
|
||||
addon_version(pvr.octonet OCTONET)
|
||||
add_definitions(-DOCTONET_VERSION=${OCTONET_VERSION})
|
||||
|
||||
build_addon(pvr.octonet OCTONET DEPLIBS)
|
||||
|
||||
|
@ -1 +0,0 @@
|
||||
p8-platform https://github.com/xbmc/platform.git cee64e9dc0b69e8d286dc170a78effaabfa09c44
|
@ -1 +0,0 @@
|
||||
p8-platform https://github.com/afedchin/platform.git win10
|
@ -1,12 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<addon
|
||||
id="pvr.octonet"
|
||||
version="2.0.0"
|
||||
version="3.0.0"
|
||||
name="Digital Devices Octopus NET Client"
|
||||
provider-name="digitaldevices">
|
||||
<requires>@ADDON_DEPENDS@</requires>
|
||||
<extension
|
||||
point="xbmc.pvrclient"
|
||||
point="kodi.pvrclient"
|
||||
library_@PLATFORM@="@LIBRARY_FILENAME@"/>
|
||||
<extension point="xbmc.addon.metadata">
|
||||
<summary lang="de_DE">Kodi PVR Addon für Digital Devices Octopus NET Streams</summary>
|
||||
|
@ -1,5 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
||||
<settings>
|
||||
<settings version="1">
|
||||
<section id="pvr.octonet">
|
||||
<category id="main" label="128" help="-1">
|
||||
<group id="1" label="-1">
|
||||
<!-- Octonet Server Address -->
|
||||
<setting id="octonetAddress" type="text" label="30000" default="" />
|
||||
<setting id="octonetAddress" type="string" label="30000" help="-1">
|
||||
<level>0</level>
|
||||
<default></default>
|
||||
<constraints>
|
||||
<allowempty>true</allowempty>
|
||||
</constraints>
|
||||
<control type="edit" format="string" />
|
||||
</setting>
|
||||
</group>
|
||||
</category>
|
||||
</section>
|
||||
</settings>
|
||||
|
@ -8,37 +8,105 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include "OctonetData.h"
|
||||
|
||||
#include "rtsp_client.hpp"
|
||||
|
||||
#include <json/json.h>
|
||||
|
||||
#include "OctonetData.h"
|
||||
#include <kodi/Filesystem.h>
|
||||
#include <kodi/General.h>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#ifdef __WINDOWS__
|
||||
#define timegm _mkgmtime
|
||||
#endif
|
||||
|
||||
using namespace ADDON;
|
||||
|
||||
OctonetData::OctonetData()
|
||||
OctonetData::OctonetData(const std::string& octonetAddress,
|
||||
KODI_HANDLE instance,
|
||||
const std::string& kodiVersion)
|
||||
: kodi::addon::CInstancePVRClient(instance, kodiVersion)
|
||||
{
|
||||
serverAddress = octonetAddress;
|
||||
channels.clear();
|
||||
groups.clear();
|
||||
lastEpgLoad = 0;
|
||||
m_serverAddress = octonetAddress;
|
||||
m_channels.clear();
|
||||
m_groups.clear();
|
||||
m_lastEpgLoad = 0;
|
||||
|
||||
if (!loadChannelList())
|
||||
libKodi->QueueNotification(QUEUE_ERROR, libKodi->GetLocalizedString(30001), channels.size());
|
||||
if (!LoadChannelList())
|
||||
kodi::QueueFormattedNotification(QUEUE_ERROR, kodi::GetLocalizedString(30001).c_str(),
|
||||
m_channels.size());
|
||||
|
||||
/*
|
||||
// Currently unused, as thread was already present before with
|
||||
// p8platform, by remove of them was it added as C++11 thread way.
|
||||
kodi::Log(ADDON_LOG_INFO, "%s Starting separate client update thread...", __func__);
|
||||
m_running = true;
|
||||
m_thread = std::thread([&] { Process(); });
|
||||
*/
|
||||
}
|
||||
|
||||
OctonetData::~OctonetData(void)
|
||||
{
|
||||
channels.clear();
|
||||
groups.clear();
|
||||
/*
|
||||
m_running = false;
|
||||
if (m_thread.joinable())
|
||||
m_thread.join();
|
||||
*/
|
||||
}
|
||||
|
||||
int64_t OctonetData::parseID(std::string id)
|
||||
PVR_ERROR OctonetData::GetCapabilities(kodi::addon::PVRCapabilities& capabilities)
|
||||
{
|
||||
capabilities.SetSupportsTV(true);
|
||||
capabilities.SetSupportsRadio(true);
|
||||
capabilities.SetSupportsChannelGroups(true);
|
||||
capabilities.SetSupportsEPG(true);
|
||||
capabilities.SetSupportsRecordings(false);
|
||||
capabilities.SetSupportsRecordingsRename(false);
|
||||
capabilities.SetSupportsRecordingsLifetimeChange(false);
|
||||
capabilities.SetSupportsDescrambleInfo(false);
|
||||
|
||||
return PVR_ERROR_NO_ERROR;
|
||||
}
|
||||
|
||||
PVR_ERROR OctonetData::GetBackendName(std::string& name)
|
||||
{
|
||||
name = "Digital Devices Octopus NET Client";
|
||||
return PVR_ERROR_NO_ERROR;
|
||||
}
|
||||
|
||||
PVR_ERROR OctonetData::GetBackendVersion(std::string& version)
|
||||
{
|
||||
version = STR(OCTONET_VERSION);
|
||||
return PVR_ERROR_NO_ERROR;
|
||||
}
|
||||
|
||||
PVR_ERROR OctonetData::GetConnectionString(std::string& connection)
|
||||
{
|
||||
connection = "connected"; // FIXME: translate?
|
||||
return PVR_ERROR_NO_ERROR;
|
||||
}
|
||||
|
||||
PVR_ERROR OctonetData::GetBackendHostname(std::string& hostname)
|
||||
{
|
||||
hostname = m_serverAddress;
|
||||
return PVR_ERROR_NO_ERROR;
|
||||
}
|
||||
|
||||
PVR_ERROR OctonetData::OnSystemSleep()
|
||||
{
|
||||
kodi::Log(ADDON_LOG_INFO, "Received event: %s", __func__);
|
||||
// FIXME: Disconnect?
|
||||
return PVR_ERROR_NO_ERROR;
|
||||
}
|
||||
|
||||
PVR_ERROR OctonetData::OnSystemWake()
|
||||
{
|
||||
kodi::Log(ADDON_LOG_INFO, "Received event: %s", __func__);
|
||||
// FIXME:Reconnect?
|
||||
return PVR_ERROR_NO_ERROR;
|
||||
}
|
||||
|
||||
int64_t OctonetData::ParseID(std::string id)
|
||||
{
|
||||
std::hash<std::string> hash_fn;
|
||||
int64_t nativeId = hash_fn(id);
|
||||
@ -46,18 +114,18 @@ int64_t OctonetData::parseID(std::string id)
|
||||
return nativeId;
|
||||
}
|
||||
|
||||
bool OctonetData::loadChannelList()
|
||||
bool OctonetData::LoadChannelList()
|
||||
{
|
||||
std::string jsonContent;
|
||||
void *f = libKodi->OpenFile(("http://" + serverAddress + "/channellist.lua?select=json").c_str(), 0);
|
||||
if (!f)
|
||||
kodi::vfs::CFile f;
|
||||
if (!f.OpenFile("http://" + m_serverAddress + "/channellist.lua?select=json", 0))
|
||||
return false;
|
||||
|
||||
char buf[1024];
|
||||
while (int read = libKodi->ReadFile(f, buf, 1024))
|
||||
while (int read = f.Read(buf, 1024))
|
||||
jsonContent.append(buf, read);
|
||||
|
||||
libKodi->CloseFile(f);
|
||||
f.Close();
|
||||
|
||||
Json::Value root;
|
||||
Json::Reader reader;
|
||||
@ -66,58 +134,61 @@ bool OctonetData::loadChannelList()
|
||||
return false;
|
||||
|
||||
const Json::Value groupList = root["GroupList"];
|
||||
for (unsigned int i = 0; i < groupList.size(); i++) {
|
||||
for (unsigned int i = 0; i < groupList.size(); i++)
|
||||
{
|
||||
const Json::Value channelList = groupList[i]["ChannelList"];
|
||||
OctonetGroup group;
|
||||
|
||||
group.name = groupList[i]["Title"].asString();
|
||||
group.radio = group.name.compare(0, 5, "Radio") ? false : true;
|
||||
|
||||
for (unsigned int j = 0; j < channelList.size(); j++) {
|
||||
for (unsigned int j = 0; j < channelList.size(); j++)
|
||||
{
|
||||
const Json::Value channel = channelList[j];
|
||||
OctonetChannel chan;
|
||||
|
||||
chan.name = channel["Title"].asString();
|
||||
chan.url = "rtsp://" + serverAddress + "/" + channel["Request"].asString();
|
||||
chan.url = "rtsp://" + m_serverAddress + "/" + channel["Request"].asString();
|
||||
chan.radio = group.radio;
|
||||
chan.nativeId = parseID(channel["ID"].asString());
|
||||
chan.nativeId = ParseID(channel["ID"].asString());
|
||||
|
||||
chan.id = 1000 + channels.size();
|
||||
group.members.push_back(channels.size());
|
||||
channels.push_back(chan);
|
||||
chan.id = 1000 + m_channels.size();
|
||||
group.members.push_back(m_channels.size());
|
||||
m_channels.push_back(chan);
|
||||
}
|
||||
groups.push_back(group);
|
||||
m_groups.push_back(group);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
OctonetChannel* OctonetData::findChannel(int64_t nativeId)
|
||||
OctonetChannel* OctonetData::FindChannel(int64_t nativeId)
|
||||
{
|
||||
std::vector<OctonetChannel>::iterator it;
|
||||
for (it = channels.begin(); it < channels.end(); ++it) {
|
||||
if (it->nativeId == nativeId)
|
||||
return &*it;
|
||||
for (auto& channel : m_channels)
|
||||
{
|
||||
if (channel.nativeId == nativeId)
|
||||
return &channel;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
time_t OctonetData::parseDateTime(std::string date)
|
||||
time_t OctonetData::ParseDateTime(std::string date)
|
||||
{
|
||||
struct tm timeinfo;
|
||||
|
||||
memset(&timeinfo, 0, sizeof(timeinfo));
|
||||
|
||||
if (date.length() > 8) {
|
||||
sscanf(date.c_str(), "%04d-%02d-%02dT%02d:%02d:%02dZ",
|
||||
&timeinfo.tm_year, &timeinfo.tm_mon, &timeinfo.tm_mday,
|
||||
&timeinfo.tm_hour, &timeinfo.tm_min, &timeinfo.tm_sec);
|
||||
if (date.length() > 8)
|
||||
{
|
||||
sscanf(date.c_str(), "%04d-%02d-%02dT%02d:%02d:%02dZ", &timeinfo.tm_year, &timeinfo.tm_mon,
|
||||
&timeinfo.tm_mday, &timeinfo.tm_hour, &timeinfo.tm_min, &timeinfo.tm_sec);
|
||||
timeinfo.tm_mon -= 1;
|
||||
timeinfo.tm_year -= 1900;
|
||||
} else {
|
||||
sscanf(date.c_str(), "%02d:%02d:%02d",
|
||||
&timeinfo.tm_hour, &timeinfo.tm_min, &timeinfo.tm_sec);
|
||||
}
|
||||
else
|
||||
{
|
||||
sscanf(date.c_str(), "%02d:%02d:%02d", &timeinfo.tm_hour, &timeinfo.tm_min, &timeinfo.tm_sec);
|
||||
timeinfo.tm_year = 70; // unix timestamps start 1970
|
||||
timeinfo.tm_mday = 1;
|
||||
}
|
||||
@ -127,22 +198,22 @@ time_t OctonetData::parseDateTime(std::string date)
|
||||
return timegm(&timeinfo);
|
||||
}
|
||||
|
||||
bool OctonetData::loadEPG(void)
|
||||
bool OctonetData::LoadEPG(void)
|
||||
{
|
||||
/* Reload at most every 30 seconds */
|
||||
if (lastEpgLoad + 30 > time(NULL))
|
||||
if (m_lastEpgLoad + 30 > time(nullptr))
|
||||
return false;
|
||||
|
||||
std::string jsonContent;
|
||||
void *f = libKodi->OpenFile(("http://" + serverAddress + "/epg.lua?;#|encoding=gzip").c_str(), 0);
|
||||
if (!f)
|
||||
kodi::vfs::CFile f;
|
||||
if (!f.OpenFile("http://" + m_serverAddress + "/epg.lua?;#|encoding=gzip", 0))
|
||||
return false;
|
||||
|
||||
char buf[1024];
|
||||
while (int read = libKodi->ReadFile(f, buf, 1024))
|
||||
while (int read = f.Read(buf, 1024))
|
||||
jsonContent.append(buf, read);
|
||||
|
||||
libKodi->CloseFile(f);
|
||||
f.Close();
|
||||
|
||||
Json::Value root;
|
||||
Json::Reader reader;
|
||||
@ -151,213 +222,237 @@ bool OctonetData::loadEPG(void)
|
||||
return false;
|
||||
|
||||
const Json::Value eventList = root["EventList"];
|
||||
OctonetChannel *channel = NULL;
|
||||
for (unsigned int i = 0; i < eventList.size(); i++) {
|
||||
OctonetChannel* channel = nullptr;
|
||||
for (unsigned int i = 0; i < eventList.size(); i++)
|
||||
{
|
||||
const Json::Value event = eventList[i];
|
||||
OctonetEpgEntry entry;
|
||||
|
||||
entry.start = parseDateTime(event["Time"].asString());
|
||||
entry.end = entry.start + parseDateTime(event["Duration"].asString());
|
||||
entry.start = ParseDateTime(event["Time"].asString());
|
||||
entry.end = entry.start + ParseDateTime(event["Duration"].asString());
|
||||
entry.title = event["Name"].asString();
|
||||
entry.subtitle = event["Text"].asString();
|
||||
std::string channelId = event["ID"].asString();
|
||||
std::string epgId = channelId.substr(channelId.rfind(":") + 1);
|
||||
channelId = channelId.substr(0, channelId.rfind(":"));
|
||||
|
||||
entry.channelId = parseID(channelId);
|
||||
entry.id = atoi(epgId.c_str());
|
||||
entry.channelId = ParseID(channelId);
|
||||
entry.id = std::stoi(epgId);
|
||||
|
||||
if (channel == NULL || channel->nativeId != entry.channelId)
|
||||
channel = findChannel(entry.channelId);
|
||||
if (channel == nullptr || channel->nativeId != entry.channelId)
|
||||
channel = FindChannel(entry.channelId);
|
||||
|
||||
if (channel == NULL) {
|
||||
libKodi->Log(LOG_ERROR, "EPG for unknown channel.");
|
||||
if (channel == nullptr)
|
||||
{
|
||||
kodi::Log(ADDON_LOG_ERROR, "EPG for unknown channel.");
|
||||
continue;
|
||||
}
|
||||
|
||||
channel->epg.push_back(entry);
|
||||
}
|
||||
|
||||
lastEpgLoad = time(NULL);
|
||||
m_lastEpgLoad = time(nullptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
void *OctonetData::Process(void)
|
||||
void OctonetData::Process()
|
||||
{
|
||||
return NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
int OctonetData::getChannelCount(void)
|
||||
PVR_ERROR OctonetData::GetChannelsAmount(int& amount)
|
||||
{
|
||||
return channels.size();
|
||||
amount = m_channels.size();
|
||||
return PVR_ERROR_NO_ERROR;
|
||||
}
|
||||
|
||||
PVR_ERROR OctonetData::getChannels(ADDON_HANDLE handle, bool bRadio)
|
||||
PVR_ERROR OctonetData::GetChannels(bool radio, kodi::addon::PVRChannelsResultSet& results)
|
||||
{
|
||||
for (unsigned int i = 0; i < channels.size(); i++)
|
||||
for (unsigned int i = 0; i < m_channels.size(); i++)
|
||||
{
|
||||
OctonetChannel &channel = channels.at(i);
|
||||
if (channel.radio == bRadio)
|
||||
OctonetChannel& channel = m_channels.at(i);
|
||||
if (channel.radio == radio)
|
||||
{
|
||||
PVR_CHANNEL chan;
|
||||
memset(&chan, 0, sizeof(PVR_CHANNEL));
|
||||
kodi::addon::PVRChannel chan;
|
||||
|
||||
chan.iUniqueId = channel.id;
|
||||
chan.bIsRadio = channel.radio;
|
||||
chan.iChannelNumber = i;
|
||||
strncpy(chan.strChannelName, channel.name.c_str(), strlen(channel.name.c_str()));
|
||||
strcpy(chan.strInputFormat, "video/x-mpegts");
|
||||
chan.bIsHidden = false;
|
||||
chan.SetUniqueId(channel.id);
|
||||
chan.SetIsRadio(channel.radio);
|
||||
chan.SetChannelNumber(i);
|
||||
chan.SetChannelName(channel.name);
|
||||
chan.SetMimeType("video/x-mpegts");
|
||||
chan.SetIsHidden(false);
|
||||
|
||||
pvr->TransferChannelEntry(handle, &chan);
|
||||
results.Add(chan);
|
||||
}
|
||||
}
|
||||
return PVR_ERROR_NO_ERROR;
|
||||
}
|
||||
|
||||
PVR_ERROR OctonetData::getEPG(ADDON_HANDLE handle, int iChannelUid, time_t start, time_t end)
|
||||
PVR_ERROR OctonetData::GetEPGForChannel(int channelUid,
|
||||
time_t start,
|
||||
time_t end,
|
||||
kodi::addon::PVREPGTagsResultSet& results)
|
||||
{
|
||||
for (unsigned int i = 0; i < channels.size(); i++)
|
||||
for (unsigned int i = 0; i < m_channels.size(); i++)
|
||||
{
|
||||
OctonetChannel &chan = channels.at(i);
|
||||
if (iChannelUid != chan.id)
|
||||
OctonetChannel& chan = m_channels.at(i);
|
||||
if (channelUid != chan.id)
|
||||
continue;
|
||||
|
||||
if(chan.epg.empty()) {
|
||||
loadEPG();
|
||||
if (chan.epg.empty())
|
||||
{
|
||||
LoadEPG();
|
||||
}
|
||||
|
||||
// FIXME: Check if reload is needed!?
|
||||
|
||||
std::vector<OctonetEpgEntry>::iterator it;
|
||||
time_t last_end = 0;
|
||||
for (it = chan.epg.begin(); it != chan.epg.end(); ++it) {
|
||||
if (it->end > last_end)
|
||||
last_end = it->end;
|
||||
for (const auto& epg : chan.epg)
|
||||
{
|
||||
if (epg.end > last_end)
|
||||
last_end = epg.end;
|
||||
|
||||
if (it->end < start || it->start > end) {
|
||||
if (epg.end < start || epg.start > end)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
EPG_TAG entry;
|
||||
memset(&entry, 0, sizeof(EPG_TAG));
|
||||
entry.iSeriesNumber = EPG_TAG_INVALID_SERIES_EPISODE;
|
||||
entry.iEpisodeNumber = EPG_TAG_INVALID_SERIES_EPISODE;
|
||||
entry.iEpisodePartNumber = EPG_TAG_INVALID_SERIES_EPISODE;
|
||||
kodi::addon::PVREPGTag entry;
|
||||
|
||||
entry.iUniqueChannelId = chan.id;
|
||||
entry.iUniqueBroadcastId = it->id;
|
||||
entry.strTitle = it->title.c_str();
|
||||
entry.strPlotOutline = it->subtitle.c_str();
|
||||
entry.startTime = it->start;
|
||||
entry.endTime = it->end;
|
||||
entry.SetUniqueChannelId(chan.id);
|
||||
entry.SetUniqueBroadcastId(epg.id);
|
||||
entry.SetTitle(epg.title);
|
||||
entry.SetPlotOutline(epg.subtitle);
|
||||
entry.SetStartTime(epg.start);
|
||||
entry.SetEndTime(epg.end);
|
||||
|
||||
pvr->TransferEpgEntry(handle, &entry);
|
||||
results.Add(entry);
|
||||
}
|
||||
|
||||
if (last_end < end)
|
||||
loadEPG();
|
||||
LoadEPG();
|
||||
|
||||
for (it = chan.epg.begin(); it != chan.epg.end(); ++it) {
|
||||
if (it->end < start || it->start > end) {
|
||||
for (const auto& epg : chan.epg)
|
||||
{
|
||||
if (epg.end < start || epg.start > end)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
EPG_TAG entry;
|
||||
memset(&entry, 0, sizeof(EPG_TAG));
|
||||
entry.iSeriesNumber = EPG_TAG_INVALID_SERIES_EPISODE;
|
||||
entry.iEpisodeNumber = EPG_TAG_INVALID_SERIES_EPISODE;
|
||||
entry.iEpisodePartNumber = EPG_TAG_INVALID_SERIES_EPISODE;
|
||||
kodi::addon::PVREPGTag entry;
|
||||
|
||||
entry.iUniqueChannelId = chan.id;
|
||||
entry.iUniqueBroadcastId = it->id;
|
||||
entry.strTitle = it->title.c_str();
|
||||
entry.strPlotOutline = it->subtitle.c_str();
|
||||
entry.startTime = it->start;
|
||||
entry.endTime = it->end;
|
||||
entry.SetUniqueChannelId(chan.id);
|
||||
entry.SetUniqueBroadcastId(epg.id);
|
||||
entry.SetTitle(epg.title);
|
||||
entry.SetPlotOutline(epg.subtitle);
|
||||
entry.SetStartTime(epg.start);
|
||||
entry.SetEndTime(epg.end);
|
||||
|
||||
pvr->TransferEpgEntry(handle, &entry);
|
||||
results.Add(entry);
|
||||
}
|
||||
}
|
||||
|
||||
return PVR_ERROR_NO_ERROR;
|
||||
}
|
||||
|
||||
const std::string& OctonetData::getUrl(int id) const {
|
||||
for(std::vector<OctonetChannel>::const_iterator iter = channels.begin(); iter != channels.end(); ++iter) {
|
||||
if(iter->id == id) {
|
||||
return iter->url;
|
||||
}
|
||||
}
|
||||
|
||||
return channels[0].url;
|
||||
}
|
||||
|
||||
const std::string& OctonetData::getName(int id) const {
|
||||
for(std::vector<OctonetChannel>::const_iterator iter = channels.begin(); iter != channels.end(); ++iter) {
|
||||
if(iter->id == id) {
|
||||
return iter->name;
|
||||
}
|
||||
}
|
||||
|
||||
return channels[0].name;
|
||||
}
|
||||
|
||||
int OctonetData::getGroupCount(void)
|
||||
const std::string& OctonetData::GetUrl(int id) const
|
||||
{
|
||||
return groups.size();
|
||||
for (const auto& channel : m_channels)
|
||||
{
|
||||
if (channel.id == id)
|
||||
{
|
||||
return channel.url;
|
||||
}
|
||||
}
|
||||
|
||||
PVR_ERROR OctonetData::getGroups(ADDON_HANDLE handle, bool bRadio)
|
||||
{
|
||||
for (unsigned int i = 0; i < groups.size(); i++)
|
||||
{
|
||||
OctonetGroup &group = groups.at(i);
|
||||
if (group.radio == bRadio)
|
||||
{
|
||||
PVR_CHANNEL_GROUP g;
|
||||
memset(&g, 0, sizeof(PVR_CHANNEL_GROUP));
|
||||
return m_channels[0].url;
|
||||
}
|
||||
|
||||
g.iPosition = 0;
|
||||
g.bIsRadio = group.radio;
|
||||
strncpy(g.strGroupName, group.name.c_str(), strlen(group.name.c_str()));
|
||||
const std::string& OctonetData::GetName(int id) const
|
||||
{
|
||||
for (const auto& channel : m_channels)
|
||||
{
|
||||
if (channel.id == id)
|
||||
{
|
||||
return channel.name;
|
||||
}
|
||||
}
|
||||
|
||||
pvr->TransferChannelGroup(handle, &g);
|
||||
return m_channels[0].name;
|
||||
}
|
||||
|
||||
PVR_ERROR OctonetData::GetChannelGroupsAmount(int& amount)
|
||||
{
|
||||
amount = m_groups.size();
|
||||
return PVR_ERROR_NO_ERROR;
|
||||
}
|
||||
|
||||
PVR_ERROR OctonetData::GetChannelGroups(bool radio, kodi::addon::PVRChannelGroupsResultSet& results)
|
||||
{
|
||||
for (const auto& group : m_groups)
|
||||
{
|
||||
if (group.radio == radio)
|
||||
{
|
||||
kodi::addon::PVRChannelGroup g;
|
||||
|
||||
g.SetPosition(0);
|
||||
g.SetIsRadio(group.radio);
|
||||
g.SetGroupName(group.name);
|
||||
|
||||
results.Add(g);
|
||||
}
|
||||
}
|
||||
|
||||
return PVR_ERROR_NO_ERROR;
|
||||
}
|
||||
|
||||
PVR_ERROR OctonetData::getGroupMembers(ADDON_HANDLE handle, const PVR_CHANNEL_GROUP &group)
|
||||
PVR_ERROR OctonetData::GetChannelGroupMembers(const kodi::addon::PVRChannelGroup& group,
|
||||
kodi::addon::PVRChannelGroupMembersResultSet& results)
|
||||
{
|
||||
OctonetGroup *g = findGroup(group.strGroupName);
|
||||
if (g == NULL)
|
||||
const OctonetGroup* g = FindGroup(group.GetGroupName());
|
||||
if (g == nullptr)
|
||||
return PVR_ERROR_UNKNOWN;
|
||||
|
||||
for (unsigned int i = 0; i < g->members.size(); i++)
|
||||
{
|
||||
OctonetChannel &channel = channels.at(g->members[i]);
|
||||
PVR_CHANNEL_GROUP_MEMBER m;
|
||||
memset(&m, 0, sizeof(PVR_CHANNEL_GROUP_MEMBER));
|
||||
OctonetChannel& channel = m_channels.at(g->members[i]);
|
||||
kodi::addon::PVRChannelGroupMember m;
|
||||
|
||||
strncpy(m.strGroupName, group.strGroupName, strlen(group.strGroupName));
|
||||
m.iChannelUniqueId = channel.id;
|
||||
m.iChannelNumber = channel.id;
|
||||
m.SetGroupName(group.GetGroupName());
|
||||
m.SetChannelUniqueId(channel.id);
|
||||
m.SetChannelNumber(channel.id);
|
||||
|
||||
pvr->TransferChannelGroupMember(handle, &m);
|
||||
results.Add(m);
|
||||
}
|
||||
|
||||
return PVR_ERROR_NO_ERROR;
|
||||
}
|
||||
|
||||
OctonetGroup* OctonetData::findGroup(const std::string &name)
|
||||
OctonetGroup* OctonetData::FindGroup(const std::string& name)
|
||||
{
|
||||
for (unsigned int i = 0; i < groups.size(); i++)
|
||||
for (auto& group : m_groups)
|
||||
{
|
||||
if (groups.at(i).name == name)
|
||||
return &groups.at(i);
|
||||
if (group.name == name)
|
||||
return &group;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
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();
|
||||
}
|
||||
|
@ -10,11 +10,11 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <kodi/addon-instance/PVR.h>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "p8-platform/threads/threads.h"
|
||||
#include "client.h"
|
||||
|
||||
struct OctonetEpgEntry
|
||||
{
|
||||
int64_t channelId;
|
||||
@ -43,38 +43,60 @@ struct OctonetGroup
|
||||
std::vector<int> members;
|
||||
};
|
||||
|
||||
class OctonetData : public P8PLATFORM::CThread
|
||||
class ATTRIBUTE_HIDDEN OctonetData : public kodi::addon::CInstancePVRClient
|
||||
{
|
||||
public:
|
||||
OctonetData(void);
|
||||
virtual ~OctonetData(void);
|
||||
OctonetData(const std::string& octonetAddress,
|
||||
KODI_HANDLE instance,
|
||||
const std::string& kodiVersion);
|
||||
~OctonetData() override;
|
||||
|
||||
virtual int getChannelCount(void);
|
||||
virtual PVR_ERROR getChannels(ADDON_HANDLE handle, bool bRadio);
|
||||
PVR_ERROR GetCapabilities(kodi::addon::PVRCapabilities& capabilities) override;
|
||||
PVR_ERROR GetBackendName(std::string& name) override;
|
||||
PVR_ERROR GetBackendVersion(std::string& version) override;
|
||||
PVR_ERROR GetConnectionString(std::string& connection) override;
|
||||
PVR_ERROR GetBackendHostname(std::string& hostname) override;
|
||||
|
||||
virtual int getGroupCount(void);
|
||||
virtual PVR_ERROR getGroups(ADDON_HANDLE handle, bool bRadio);
|
||||
virtual PVR_ERROR getGroupMembers(ADDON_HANDLE handle, const PVR_CHANNEL_GROUP &group);
|
||||
PVR_ERROR OnSystemSleep() override;
|
||||
PVR_ERROR OnSystemWake() override;
|
||||
|
||||
virtual PVR_ERROR getEPG(ADDON_HANDLE handle, int iChannelUid, time_t start, time_t end);
|
||||
const std::string& getUrl(int id) const;
|
||||
const std::string& getName(int id) const;
|
||||
PVR_ERROR GetChannelsAmount(int& amount) override;
|
||||
PVR_ERROR GetChannels(bool radio, kodi::addon::PVRChannelsResultSet& results) override;
|
||||
|
||||
PVR_ERROR GetChannelGroupsAmount(int& amount) override;
|
||||
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 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:
|
||||
virtual bool loadChannelList(void);
|
||||
virtual bool loadEPG(void);
|
||||
virtual OctonetGroup* findGroup(const std::string &name);
|
||||
void Process();
|
||||
|
||||
virtual void *Process(void);
|
||||
const std::string& GetUrl(int id) const;
|
||||
const std::string& GetName(int id) const;
|
||||
|
||||
OctonetChannel* findChannel(int64_t nativeId);
|
||||
time_t parseDateTime(std::string date);
|
||||
int64_t parseID(std::string id);
|
||||
bool LoadChannelList(void);
|
||||
bool LoadEPG(void);
|
||||
OctonetGroup* FindGroup(const std::string& name);
|
||||
OctonetChannel* FindChannel(int64_t nativeId);
|
||||
time_t ParseDateTime(std::string date);
|
||||
int64_t ParseID(std::string id);
|
||||
|
||||
private:
|
||||
std::string serverAddress;
|
||||
std::vector<OctonetChannel> channels;
|
||||
std::vector<OctonetGroup> groups;
|
||||
std::string m_serverAddress;
|
||||
std::vector<OctonetChannel> m_channels;
|
||||
std::vector<OctonetGroup> m_groups;
|
||||
|
||||
time_t lastEpgLoad;
|
||||
time_t m_lastEpgLoad;
|
||||
|
||||
std::atomic<bool> m_running = {false};
|
||||
std::thread m_thread;
|
||||
};
|
||||
|
199
src/Socket.cpp
199
src/Socket.cpp
@ -6,15 +6,13 @@
|
||||
* See LICENSE.md for more information.
|
||||
*/
|
||||
|
||||
#include "kodi/libXBMC_addon.h"
|
||||
#include <string>
|
||||
#include "p8-platform/os.h"
|
||||
#include "client.h"
|
||||
#include "Socket.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <kodi/General.h>
|
||||
#include <string>
|
||||
|
||||
using namespace std;
|
||||
using namespace ADDON;
|
||||
|
||||
namespace OCTO
|
||||
{
|
||||
@ -22,28 +20,31 @@ 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)
|
||||
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 ) );
|
||||
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
|
||||
_sd = INVALID_SOCKET;
|
||||
_family = af_inet;
|
||||
_domain = pf_inet;
|
||||
_type = sock_stream;
|
||||
_protocol = tcp;
|
||||
_port = 0;
|
||||
memset (&_sockaddr, 0, sizeof( _sockaddr ) );
|
||||
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));
|
||||
}
|
||||
|
||||
|
||||
@ -55,7 +56,7 @@ Socket::~Socket()
|
||||
|
||||
bool Socket::setHostname(const std::string& host)
|
||||
{
|
||||
_hostname = host;
|
||||
m_hostname = host;
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -63,9 +64,9 @@ bool Socket::close()
|
||||
{
|
||||
if (is_valid())
|
||||
{
|
||||
if (_sd != SOCKET_ERROR)
|
||||
closesocket(_sd);
|
||||
_sd = INVALID_SOCKET;
|
||||
if (m_sd != SOCKET_ERROR)
|
||||
closesocket(m_sd);
|
||||
m_sd = INVALID_SOCKET;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@ -92,13 +93,13 @@ bool Socket::bind ( const unsigned short port )
|
||||
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 );
|
||||
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(_sd, (sockaddr*)(&_sockaddr), sizeof(_sockaddr));
|
||||
int bind_return = ::bind(m_sd, (sockaddr*)(&m_sockaddr), sizeof(m_sockaddr));
|
||||
|
||||
if (bind_return == -1)
|
||||
{
|
||||
@ -118,7 +119,7 @@ bool Socket::listen() const
|
||||
return false;
|
||||
}
|
||||
|
||||
int listen_return = ::listen (_sd, SOMAXCONN);
|
||||
int listen_return = ::listen(m_sd, SOMAXCONN);
|
||||
//This is defined as 5 in winsock.h, and 0x7FFFFFFF in winsock2.h.
|
||||
//linux 128//MAXCONNECTIONS =1
|
||||
|
||||
@ -139,13 +140,14 @@ bool Socket::accept ( Socket& new_socket ) const
|
||||
return false;
|
||||
}
|
||||
|
||||
socklen_t addr_length = sizeof( _sockaddr );
|
||||
new_socket._sd = ::accept(_sd, const_cast<sockaddr*>( (const sockaddr*) &_sockaddr), &addr_length );
|
||||
socklen_t addr_length = sizeof(m_sockaddr);
|
||||
new_socket.m_sd =
|
||||
::accept(m_sd, const_cast<sockaddr*>((const sockaddr*)&m_sockaddr), &addr_length);
|
||||
|
||||
#ifdef TARGET_WINDOWS
|
||||
if (new_socket._sd == INVALID_SOCKET)
|
||||
if (new_socket.m_sd == INVALID_SOCKET)
|
||||
#else
|
||||
if (new_socket._sd <= 0)
|
||||
if (new_socket.m_sd <= 0)
|
||||
#endif
|
||||
{
|
||||
errormessage(getLastError(), "Socket::accept");
|
||||
@ -179,30 +181,30 @@ int Socket::send ( const char* data, const unsigned int len )
|
||||
|
||||
FD_ZERO(&set_w);
|
||||
FD_ZERO(&set_e);
|
||||
FD_SET(_sd, &set_w);
|
||||
FD_SET(_sd, &set_e);
|
||||
FD_SET(m_sd, &set_w);
|
||||
FD_SET(m_sd, &set_e);
|
||||
|
||||
result = select(FD_SETSIZE, &set_w, NULL, &set_e, &tv);
|
||||
result = select(FD_SETSIZE, &set_w, nullptr, &set_e, &tv);
|
||||
|
||||
if (result < 0)
|
||||
{
|
||||
libKodi->Log(LOG_ERROR, "Socket::send - select failed");
|
||||
kodi::Log(ADDON_LOG_ERROR, "Socket::send - select failed");
|
||||
close();
|
||||
return 0;
|
||||
}
|
||||
if (FD_ISSET(_sd, &set_w))
|
||||
if (FD_ISSET(m_sd, &set_w))
|
||||
{
|
||||
libKodi->Log(LOG_ERROR, "Socket::send - failed to send data");
|
||||
kodi::Log(ADDON_LOG_ERROR, "Socket::send - failed to send data");
|
||||
close();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int status = ::send(_sd, data, len, 0 );
|
||||
int status = ::send(m_sd, data, len, 0);
|
||||
|
||||
if (status == -1)
|
||||
{
|
||||
errormessage(getLastError(), "Socket::send");
|
||||
libKodi->Log(LOG_ERROR, "Socket::send - failed to send data");
|
||||
kodi::Log(ADDON_LOG_ERROR, "Socket::send - failed to send data");
|
||||
close();
|
||||
return 0;
|
||||
}
|
||||
@ -217,7 +219,7 @@ int Socket::sendto ( const char* data, unsigned int size, bool sendcompletebuffe
|
||||
|
||||
do
|
||||
{
|
||||
i = ::sendto(_sd, data, size, 0, (const struct sockaddr*) &_sockaddr, sizeof( _sockaddr ) );
|
||||
i = ::sendto(m_sd, data, size, 0, (const struct sockaddr*)&m_sockaddr, sizeof(m_sockaddr));
|
||||
|
||||
if (i <= 0)
|
||||
{
|
||||
@ -234,7 +236,7 @@ int Socket::sendto ( const char* data, unsigned int size, bool sendcompletebuffe
|
||||
|
||||
int Socket::receive(std::string& data, unsigned int minpacketsize) const
|
||||
{
|
||||
char * buf = NULL;
|
||||
char* buf = nullptr;
|
||||
int status = 0;
|
||||
|
||||
if (!is_valid())
|
||||
@ -280,14 +282,14 @@ bool Socket::ReadLine (string& line)
|
||||
// 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);
|
||||
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)
|
||||
{
|
||||
libKodi->Log(LOG_DEBUG, "%s: select failed", __FUNCTION__);
|
||||
errormessage(getLastError(), __FUNCTION__);
|
||||
kodi::Log(ADDON_LOG_DEBUG, "%s: select failed", __func__);
|
||||
errormessage(getLastError(), __func__);
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
@ -296,20 +298,24 @@ bool Socket::ReadLine (string& line)
|
||||
{
|
||||
if (retries != 0)
|
||||
{
|
||||
libKodi->Log(LOG_DEBUG, "%s: timeout waiting for response, retrying... (%i)", __FUNCTION__, retries);
|
||||
kodi::Log(ADDON_LOG_DEBUG, "%s: timeout waiting for response, retrying... (%i)", __func__,
|
||||
retries);
|
||||
retries--;
|
||||
continue;
|
||||
} else {
|
||||
libKodi->Log(LOG_DEBUG, "%s: timeout waiting for response. Aborting after 10 retries.", __FUNCTION__);
|
||||
}
|
||||
else
|
||||
{
|
||||
kodi::Log(ADDON_LOG_DEBUG, "%s: timeout waiting for response. Aborting after 10 retries.",
|
||||
__func__);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
result = recv(_sd, buffer, sizeof(buffer) - 1, 0);
|
||||
result = recv(m_sd, buffer, sizeof(buffer) - 1, 0);
|
||||
if (result < 0)
|
||||
{
|
||||
libKodi->Log(LOG_DEBUG, "%s: recv failed", __FUNCTION__);
|
||||
errormessage(getLastError(), __FUNCTION__);
|
||||
kodi::Log(ADDON_LOG_DEBUG, "%s: recv failed", __func__);
|
||||
errormessage(getLastError(), __func__);
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
@ -339,7 +345,9 @@ int Socket::receive ( std::string& data) const
|
||||
return status;
|
||||
}
|
||||
|
||||
int Socket::receive ( char* data, const unsigned int buffersize, const unsigned int minpacketsize ) const
|
||||
int Socket::receive(char* data,
|
||||
const unsigned int buffersize,
|
||||
const unsigned int minpacketsize) const
|
||||
{
|
||||
unsigned int receivedsize = 0;
|
||||
|
||||
@ -350,7 +358,7 @@ int Socket::receive ( char* data, const unsigned int buffersize, const unsigned
|
||||
|
||||
while ((receivedsize <= minpacketsize) && (receivedsize < buffersize))
|
||||
{
|
||||
int status = ::recv(_sd, data+receivedsize, (buffersize - receivedsize), 0 );
|
||||
int status = ::recv(m_sd, data + receivedsize, (buffersize - receivedsize), 0);
|
||||
|
||||
if (status == SOCKET_ERROR)
|
||||
{
|
||||
@ -365,9 +373,12 @@ int Socket::receive ( char* data, const unsigned int buffersize, const unsigned
|
||||
}
|
||||
|
||||
|
||||
int Socket::recvfrom ( char* data, const int buffersize, struct sockaddr* from, socklen_t* fromlen) const
|
||||
int Socket::recvfrom(char* data,
|
||||
const int buffersize,
|
||||
struct sockaddr* from,
|
||||
socklen_t* fromlen) const
|
||||
{
|
||||
int status = ::recvfrom(_sd, data, buffersize, 0, from, fromlen);
|
||||
int status = ::recvfrom(m_sd, data, buffersize, 0, from, fromlen);
|
||||
|
||||
return status;
|
||||
}
|
||||
@ -379,21 +390,21 @@ bool Socket::connect ( const std::string& host, const unsigned short port )
|
||||
|
||||
if (!setHostname(host))
|
||||
{
|
||||
libKodi->Log(LOG_ERROR, "Socket::setHostname(%s) failed.\n", host.c_str());
|
||||
kodi::Log(ADDON_LOG_ERROR, "Socket::setHostname(%s) failed.\n", host.c_str());
|
||||
return false;
|
||||
}
|
||||
_port = port;
|
||||
m_port = port;
|
||||
|
||||
char strPort[15];
|
||||
snprintf(strPort, 15, "%hu", port);
|
||||
|
||||
struct addrinfo hints;
|
||||
struct addrinfo* result = NULL;
|
||||
struct addrinfo *address = NULL;
|
||||
struct addrinfo* result = nullptr;
|
||||
struct addrinfo* address = nullptr;
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = _family;
|
||||
hints.ai_socktype = _type;
|
||||
hints.ai_protocol = _protocol;
|
||||
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)
|
||||
@ -402,18 +413,18 @@ bool Socket::connect ( const std::string& host, const unsigned short port )
|
||||
return false;
|
||||
}
|
||||
|
||||
for (address = result; address != NULL; address = address->ai_next)
|
||||
for (address = result; address != nullptr; address = address->ai_next)
|
||||
{
|
||||
// Create the socket
|
||||
_sd = socket(address->ai_family, address->ai_socktype, address->ai_protocol);
|
||||
m_sd = socket(address->ai_family, address->ai_socktype, address->ai_protocol);
|
||||
|
||||
if (_sd == INVALID_SOCKET)
|
||||
if (m_sd == INVALID_SOCKET)
|
||||
{
|
||||
errormessage(getLastError(), "Socket::create");
|
||||
continue;
|
||||
}
|
||||
|
||||
int status = ::connect(_sd, address->ai_addr, address->ai_addrlen);
|
||||
int status = ::connect(m_sd, address->ai_addr, address->ai_addrlen);
|
||||
if (status == SOCKET_ERROR)
|
||||
{
|
||||
close();
|
||||
@ -426,9 +437,9 @@ bool Socket::connect ( const std::string& host, const unsigned short port )
|
||||
|
||||
freeaddrinfo(result);
|
||||
|
||||
if (address == NULL)
|
||||
if (address == nullptr)
|
||||
{
|
||||
libKodi->Log(LOG_ERROR, "Socket::connect %s:%u\n", host.c_str(), port);
|
||||
kodi::Log(ADDON_LOG_ERROR, "Socket::connect %s:%u\n", host.c_str(), port);
|
||||
errormessage(getLastError(), "Socket::connect");
|
||||
close();
|
||||
return false;
|
||||
@ -444,12 +455,12 @@ bool Socket::reconnect()
|
||||
return true;
|
||||
}
|
||||
|
||||
return connect(_hostname, _port);
|
||||
return connect(m_hostname, m_port);
|
||||
}
|
||||
|
||||
bool Socket::is_valid() const
|
||||
{
|
||||
return (_sd != INVALID_SOCKET);
|
||||
return (m_sd != INVALID_SOCKET);
|
||||
}
|
||||
|
||||
#if defined(TARGET_WINDOWS)
|
||||
@ -462,9 +473,10 @@ bool Socket::set_non_blocking ( const bool b )
|
||||
else
|
||||
iMode = 0; // disable non_blocking
|
||||
|
||||
if (ioctlsocket(_sd, FIONBIO, &iMode) == -1)
|
||||
if (ioctlsocket(m_sd, FIONBIO, &iMode) == -1)
|
||||
{
|
||||
libKodi->Log(LOG_ERROR, "Socket::set_non_blocking - Can't set socket condition to: %i", iMode);
|
||||
kodi::Log(ADDON_LOG_ERROR, "Socket::set_non_blocking - Can't set socket condition to: %i",
|
||||
iMode);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -473,7 +485,7 @@ bool Socket::set_non_blocking ( const bool b )
|
||||
|
||||
void Socket::errormessage(int errnum, const char* functionname) const
|
||||
{
|
||||
const char* errmsg = NULL;
|
||||
const char* errmsg = nullptr;
|
||||
|
||||
switch (errnum)
|
||||
{
|
||||
@ -555,7 +567,7 @@ void Socket::errormessage( int errnum, const char* functionname) const
|
||||
default:
|
||||
errmsg = "WSA Error";
|
||||
}
|
||||
libKodi->Log(LOG_ERROR, "%s: (Winsock error=%i) %s\n", functionname, errnum, errmsg);
|
||||
kodi::Log(ADDON_LOG_ERROR, "%s: (Winsock error=%i) %s\n", functionname, errnum, errmsg);
|
||||
}
|
||||
|
||||
int Socket::getLastError() const
|
||||
@ -569,7 +581,7 @@ bool Socket::osInit()
|
||||
{
|
||||
win_usage_count++;
|
||||
// initialize winsock:
|
||||
if (WSAStartup(MAKEWORD(2,2),&_wsaData) != 0)
|
||||
if (WSAStartup(MAKEWORD(2, 2), &m_wsaData) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@ -577,7 +589,7 @@ bool Socket::osInit()
|
||||
WORD wVersionRequested = MAKEWORD(2, 2);
|
||||
|
||||
// check version
|
||||
if (_wsaData.wVersion != wVersionRequested)
|
||||
if (m_wsaData.wVersion != wVersionRequested)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@ -599,7 +611,7 @@ bool Socket::set_non_blocking ( const bool b )
|
||||
{
|
||||
int opts;
|
||||
|
||||
opts = fcntl(_sd, F_GETFL);
|
||||
opts = fcntl(m_sd, F_GETFL);
|
||||
|
||||
if (opts < 0)
|
||||
{
|
||||
@ -611,9 +623,9 @@ bool Socket::set_non_blocking ( const bool b )
|
||||
else
|
||||
opts = (opts & ~O_NONBLOCK);
|
||||
|
||||
if(fcntl (_sd , F_SETFL, opts) == -1)
|
||||
if (fcntl(m_sd, F_SETFL, opts) == -1)
|
||||
{
|
||||
libKodi->Log(LOG_ERROR, "Socket::set_non_blocking - Can't set socket flags to: %i", opts);
|
||||
kodi::Log(ADDON_LOG_ERROR, "Socket::set_non_blocking - Can't set socket flags to: %i", opts);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@ -621,7 +633,7 @@ bool Socket::set_non_blocking ( const bool b )
|
||||
|
||||
void Socket::errormessage(int errnum, const char* functionname) const
|
||||
{
|
||||
const char* errmsg = NULL;
|
||||
const char* errmsg = nullptr;
|
||||
|
||||
switch (errnum)
|
||||
{
|
||||
@ -650,7 +662,8 @@ void Socket::errormessage( int errnum, const char* functionname) const
|
||||
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";
|
||||
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";
|
||||
@ -662,7 +675,8 @@ void Socket::errormessage( int errnum, const char* functionname) const
|
||||
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";
|
||||
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";
|
||||
@ -674,13 +688,16 @@ void Socket::errormessage( int errnum, const char* functionname) const
|
||||
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";
|
||||
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)";
|
||||
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";
|
||||
errmsg = "ENOTCONN: The socket is associated with a connection-oriented protocol and has not "
|
||||
"been connected";
|
||||
break;
|
||||
//case E:
|
||||
// errmsg = "";
|
||||
@ -689,7 +706,7 @@ void Socket::errormessage( int errnum, const char* functionname) const
|
||||
break;
|
||||
}
|
||||
|
||||
libKodi->Log(LOG_ERROR, "%s: (errno=%i) %s\n", functionname, errnum, errmsg);
|
||||
kodi::Log(ADDON_LOG_ERROR, "%s: (errno=%i) %s\n", functionname, errnum, errmsg);
|
||||
}
|
||||
|
||||
int Socket::getLastError() const
|
||||
|
78
src/Socket.h
78
src/Socket.h
@ -12,8 +12,8 @@
|
||||
#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 <winsock2.h>
|
||||
#include <WS2tcpip.h>
|
||||
#include <winsock2.h>
|
||||
#pragma warning(default : 4005)
|
||||
#include <windows.h>
|
||||
|
||||
@ -37,15 +37,15 @@
|
||||
#ifdef SOCKADDR_IN
|
||||
#undef SOCKADDR_IN
|
||||
#endif
|
||||
#include <sys/types.h> /* for socket,connect */
|
||||
#include <sys/socket.h> /* for socket,connect */
|
||||
#include <sys/un.h> /* for Unix socket */
|
||||
#include <arpa/inet.h> /* for inet_pton */
|
||||
#include <netdb.h> /* for gethostbyname */
|
||||
#include <netinet/in.h> /* for htons */
|
||||
#include <unistd.h> /* for read, write, close */
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <netdb.h> /* for gethostbyname */
|
||||
#include <netinet/in.h> /* for htons */
|
||||
#include <sys/socket.h> /* for socket,connect */
|
||||
#include <sys/types.h> /* for socket,connect */
|
||||
#include <sys/un.h> /* for Unix socket */
|
||||
#include <unistd.h> /* for read, write, close */
|
||||
|
||||
typedef int SOCKET;
|
||||
typedef sockaddr SOCKADDR;
|
||||
@ -60,7 +60,7 @@
|
||||
#error Platform specific socket support is not yet available on this platform!
|
||||
#endif
|
||||
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace OCTO
|
||||
@ -100,7 +100,6 @@ enum SocketProtocol
|
||||
class Socket
|
||||
{
|
||||
public:
|
||||
|
||||
/*!
|
||||
* An unconnected socket may be created directly on the local
|
||||
* machine. The socket type (SOCK_STREAM, SOCK_DGRAM) and
|
||||
@ -113,7 +112,10 @@ class Socket
|
||||
* \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(const enum SocketFamily family,
|
||||
const enum SocketDomain domain,
|
||||
const enum SocketType type,
|
||||
const enum SocketProtocol protocol = tcp);
|
||||
Socket(void);
|
||||
virtual ~Socket();
|
||||
|
||||
@ -123,46 +125,31 @@ class Socket
|
||||
* Socket setFamily
|
||||
* \param family Can be af_inet or af_inet6. Default: af_inet
|
||||
*/
|
||||
void setFamily(const enum SocketFamily family)
|
||||
{
|
||||
_family = family;
|
||||
};
|
||||
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)
|
||||
{
|
||||
_domain = domain;
|
||||
};
|
||||
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)
|
||||
{
|
||||
_type = type;
|
||||
};
|
||||
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)
|
||||
{
|
||||
_protocol = protocol;
|
||||
};
|
||||
void setProtocol(const enum SocketProtocol protocol) { m_protocol = protocol; };
|
||||
|
||||
/*!
|
||||
* Socket setPort
|
||||
* \param port port number for socket communication
|
||||
*/
|
||||
void setPort (const unsigned short port)
|
||||
{
|
||||
_sockaddr.sin_port = htons ( port );
|
||||
};
|
||||
void setPort(const unsigned short port) { m_sockaddr.sin_port = htons(port); };
|
||||
|
||||
bool setHostname(const std::string& host);
|
||||
|
||||
@ -260,7 +247,10 @@ class Socket
|
||||
* \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;
|
||||
int recvfrom(char* data,
|
||||
const int buffersize,
|
||||
struct sockaddr* from = nullptr,
|
||||
socklen_t* fromlen = nullptr) const;
|
||||
|
||||
bool set_non_blocking(const bool);
|
||||
|
||||
@ -269,24 +259,24 @@ class Socket
|
||||
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
|
||||
|
||||
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
|
||||
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 _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
|
||||
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 = NULL) const;
|
||||
void errormessage(int errornum, const char* functionname = nullptr) const;
|
||||
int getLastError(void) const;
|
||||
bool osInit();
|
||||
void osCleanup();
|
||||
|
62
src/addon.cpp
Normal file
62
src/addon.cpp
Normal file
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Julian Scheel <julian@jusst.de>
|
||||
* Copyright (C) 2015 jusst technologies GmbH
|
||||
* Copyright (C) 2015 Digital Devices GmbH
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
* See LICENSE.md for more information.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "addon.h"
|
||||
|
||||
#include "OctonetData.h"
|
||||
|
||||
ADDON_STATUS COctonetAddon::SetSetting(const std::string& settingName,
|
||||
const kodi::CSettingValue& settingValue)
|
||||
{
|
||||
/* For simplicity do a full addon restart whenever settings are
|
||||
* changed */
|
||||
return ADDON_STATUS_NEED_RESTART;
|
||||
}
|
||||
|
||||
ADDON_STATUS COctonetAddon::CreateInstance(int instanceType,
|
||||
const std::string& instanceID,
|
||||
KODI_HANDLE instance,
|
||||
const std::string& version,
|
||||
KODI_HANDLE& addonInstance)
|
||||
{
|
||||
if (instanceType == ADDON_INSTANCE_PVR)
|
||||
{
|
||||
kodi::Log(ADDON_LOG_DEBUG, "%s: Creating octonet pvr instance", __func__);
|
||||
|
||||
/* IP or hostname of the octonet to be connected to */
|
||||
std::string octonetAddress = kodi::GetSettingString("octonetAddress");
|
||||
|
||||
OctonetData* usedInstance = new OctonetData(octonetAddress, instance, version);
|
||||
addonInstance = usedInstance;
|
||||
|
||||
m_usedInstances.emplace(instanceID, usedInstance);
|
||||
return ADDON_STATUS_OK;
|
||||
}
|
||||
|
||||
return ADDON_STATUS_UNKNOWN;
|
||||
}
|
||||
|
||||
void COctonetAddon::DestroyInstance(int instanceType,
|
||||
const std::string& instanceID,
|
||||
KODI_HANDLE addonInstance)
|
||||
{
|
||||
if (instanceType == ADDON_INSTANCE_PVR)
|
||||
{
|
||||
kodi::Log(ADDON_LOG_DEBUG, "%s: Destoying octonet pvr instance", __func__);
|
||||
|
||||
const auto& it = m_usedInstances.find(instanceID);
|
||||
if (it != m_usedInstances.end())
|
||||
{
|
||||
m_usedInstances.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ADDONCREATOR(COctonetAddon)
|
36
src/addon.h
Normal file
36
src/addon.h
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Julian Scheel <julian@jusst.de>
|
||||
* Copyright (C) 2015 jusst technologies GmbH
|
||||
* Copyright (C) 2015 Digital Devices GmbH
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
* See LICENSE.md for more information.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <kodi/AddonBase.h>
|
||||
#include <unordered_map>
|
||||
|
||||
class OctonetData;
|
||||
|
||||
class ATTRIBUTE_HIDDEN COctonetAddon : public kodi::addon::CAddonBase
|
||||
{
|
||||
public:
|
||||
COctonetAddon() = default;
|
||||
|
||||
ADDON_STATUS SetSetting(const std::string& settingName,
|
||||
const kodi::CSettingValue& settingValue) override;
|
||||
ADDON_STATUS CreateInstance(int instanceType,
|
||||
const std::string& instanceID,
|
||||
KODI_HANDLE instance,
|
||||
const std::string& version,
|
||||
KODI_HANDLE& addonInstance) override;
|
||||
void DestroyInstance(int instanceType,
|
||||
const std::string& instanceID,
|
||||
KODI_HANDLE addonInstance) override;
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string, OctonetData*> m_usedInstances;
|
||||
};
|
274
src/client.cpp
274
src/client.cpp
@ -1,274 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Julian Scheel <julian@jusst.de>
|
||||
* Copyright (C) 2015 jusst technologies GmbH
|
||||
* Copyright (C) 2015 Digital Devices GmbH
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
* See LICENSE.md for more information.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "client.h"
|
||||
#include <kodi/xbmc_pvr_dll.h>
|
||||
#include <kodi/libXBMC_addon.h>
|
||||
#include <p8-platform/util/util.h>
|
||||
|
||||
#include "OctonetData.h"
|
||||
#include "rtsp_client.hpp"
|
||||
|
||||
using namespace ADDON;
|
||||
|
||||
/* setting variables with defaults */
|
||||
std::string octonetAddress = "";
|
||||
|
||||
/* internal state variables */
|
||||
ADDON_STATUS addonStatus = ADDON_STATUS_UNKNOWN;
|
||||
CHelper_libXBMC_addon *libKodi = NULL;
|
||||
CHelper_libXBMC_pvr *pvr = NULL;
|
||||
|
||||
OctonetData *data = NULL;
|
||||
|
||||
/* KODI Core Addon functions
|
||||
* see xbmc_addon_dll.h */
|
||||
|
||||
extern "C" {
|
||||
|
||||
void ADDON_ReadSettings(void)
|
||||
{
|
||||
char buffer[2048];
|
||||
if (libKodi->GetSetting("octonetAddress", &buffer))
|
||||
octonetAddress = buffer;
|
||||
}
|
||||
|
||||
ADDON_STATUS ADDON_Create(void* callbacks, const char* globalApiVersion, void* props)
|
||||
{
|
||||
if (callbacks == NULL || props == NULL)
|
||||
return ADDON_STATUS_UNKNOWN;
|
||||
|
||||
AddonProperties_PVR *pvrprops = (AddonProperties_PVR*)props;
|
||||
libKodi = new CHelper_libXBMC_addon;
|
||||
if (!libKodi->RegisterMe(callbacks)) {
|
||||
libKodi->Log(LOG_ERROR, "%s: Failed to register octonet addon", __func__);
|
||||
SAFE_DELETE(libKodi);
|
||||
return ADDON_STATUS_PERMANENT_FAILURE;
|
||||
}
|
||||
|
||||
pvr = new CHelper_libXBMC_pvr;
|
||||
if (!pvr->RegisterMe(callbacks)) {
|
||||
libKodi->Log(LOG_ERROR, "%s: Failed to register octonet pvr addon", __func__);
|
||||
SAFE_DELETE(pvr);
|
||||
SAFE_DELETE(libKodi);
|
||||
return ADDON_STATUS_PERMANENT_FAILURE;
|
||||
}
|
||||
|
||||
libKodi->Log(LOG_DEBUG, "%s: Creating octonet pvr addon", __func__);
|
||||
ADDON_ReadSettings();
|
||||
|
||||
data = new OctonetData;
|
||||
|
||||
addonStatus = ADDON_STATUS_OK;
|
||||
return addonStatus;
|
||||
}
|
||||
|
||||
void ADDON_Destroy()
|
||||
{
|
||||
delete pvr;
|
||||
delete libKodi;
|
||||
addonStatus = ADDON_STATUS_UNKNOWN;
|
||||
}
|
||||
|
||||
ADDON_STATUS ADDON_GetStatus()
|
||||
{
|
||||
return addonStatus;
|
||||
}
|
||||
|
||||
ADDON_STATUS ADDON_SetSetting(const char *settingName, const void *settingValue)
|
||||
{
|
||||
/* For simplicity do a full addon restart whenever settings are
|
||||
* changed */
|
||||
return ADDON_STATUS_NEED_RESTART;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* KODI PVR Addon functions
|
||||
* see xbmc_pvr_dll.h */
|
||||
extern "C"
|
||||
{
|
||||
|
||||
PVR_ERROR GetCapabilities(PVR_ADDON_CAPABILITIES *pCapabilities)
|
||||
{
|
||||
pCapabilities->bSupportsTV = true;
|
||||
pCapabilities->bSupportsRadio = true;
|
||||
pCapabilities->bSupportsChannelGroups = true;
|
||||
pCapabilities->bSupportsEPG = true;
|
||||
pCapabilities->bSupportsRecordings = false;
|
||||
pCapabilities->bSupportsRecordingsRename = false;
|
||||
pCapabilities->bSupportsRecordingsLifetimeChange = false;
|
||||
pCapabilities->bSupportsDescrambleInfo = false;
|
||||
|
||||
return PVR_ERROR_NO_ERROR;
|
||||
}
|
||||
|
||||
const char* GetBackendName(void)
|
||||
{
|
||||
return "Digital Devices Octopus NET Client";
|
||||
}
|
||||
|
||||
const char* GetBackendVersion(void)
|
||||
{
|
||||
return STR(OCTONET_VERSION);
|
||||
}
|
||||
|
||||
const char* GetConnectionString(void)
|
||||
{
|
||||
return "connected"; // FIXME: translate?
|
||||
}
|
||||
|
||||
PVR_ERROR GetDriveSpace(long long* iTotal, long long* iUsed) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
||||
PVR_ERROR CallMenuHook(const PVR_MENUHOOK& menuhook, const PVR_MENUHOOK_DATA &item) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
||||
|
||||
void OnSystemSleep() {
|
||||
libKodi->Log(LOG_INFO, "Received event: %s", __FUNCTION__);
|
||||
// FIXME: Disconnect?
|
||||
}
|
||||
|
||||
void OnSystemWake() {
|
||||
libKodi->Log(LOG_INFO, "Received event: %s", __FUNCTION__);
|
||||
// FIXME:Reconnect?
|
||||
}
|
||||
|
||||
void OnPowerSavingActivated() {}
|
||||
void OnPowerSavingDeactivated() {}
|
||||
|
||||
/* EPG */
|
||||
PVR_ERROR GetEPGForChannel(ADDON_HANDLE handle, int iChannelUid, time_t iStart, time_t iEnd)
|
||||
{
|
||||
return data->getEPG(handle, iChannelUid, iStart, iEnd);
|
||||
}
|
||||
|
||||
PVR_ERROR IsEPGTagRecordable(const EPG_TAG*, bool*) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
||||
PVR_ERROR IsEPGTagPlayable(const EPG_TAG*, bool*) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
||||
|
||||
/* Channel groups */
|
||||
int GetChannelGroupsAmount(void)
|
||||
{
|
||||
return data->getGroupCount();
|
||||
}
|
||||
|
||||
PVR_ERROR GetChannelGroups(ADDON_HANDLE handle, bool bRadio)
|
||||
{
|
||||
return data->getGroups(handle, bRadio);
|
||||
}
|
||||
|
||||
PVR_ERROR GetChannelGroupMembers(ADDON_HANDLE handle, const PVR_CHANNEL_GROUP& group)
|
||||
{
|
||||
return data->getGroupMembers(handle, group);
|
||||
}
|
||||
|
||||
/* Channels */
|
||||
PVR_ERROR OpenDialogChannelScan(void) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
||||
|
||||
int GetChannelsAmount(void)
|
||||
{
|
||||
return data->getChannelCount();
|
||||
}
|
||||
|
||||
PVR_ERROR GetChannels(ADDON_HANDLE handle, bool bRadio)
|
||||
{
|
||||
return data->getChannels(handle, bRadio);
|
||||
}
|
||||
|
||||
PVR_ERROR DeleteChannel(const PVR_CHANNEL& channel) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
||||
PVR_ERROR RenameChannel(const PVR_CHANNEL& channel) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
||||
PVR_ERROR OpenDialogChannelSettings(const PVR_CHANNEL& channel) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
||||
PVR_ERROR OpenDialogChannelAdd(const PVR_CHANNEL& channel) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
||||
|
||||
/* Recordings */
|
||||
int GetRecordingsAmount(bool deleted) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
||||
PVR_ERROR GetRecordings(ADDON_HANDLE handle, bool deleted) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
||||
PVR_ERROR DeleteRecording(const PVR_RECORDING& recording) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
||||
PVR_ERROR UndeleteRecording(const PVR_RECORDING& recording) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
||||
PVR_ERROR DeleteAllRecordingsFromTrash() { return PVR_ERROR_NOT_IMPLEMENTED; }
|
||||
PVR_ERROR RenameRecording(const PVR_RECORDING& recording) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
||||
PVR_ERROR SetRecordingLifetime(const PVR_RECORDING*) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
||||
PVR_ERROR SetRecordingPlayCount(const PVR_RECORDING& recording, int count) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
||||
PVR_ERROR SetRecordingLastPlayedPosition(const PVR_RECORDING& recording, int lastplayedposition) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
||||
int GetRecordingLastPlayedPosition(const PVR_RECORDING& recording) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
||||
PVR_ERROR GetRecordingEdl(const PVR_RECORDING&, PVR_EDL_ENTRY edl[], int *size) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
||||
PVR_ERROR GetRecordingSize(const PVR_RECORDING* recording, int64_t* sizeInBytes) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
||||
PVR_ERROR GetTimerTypes(PVR_TIMER_TYPE types[], int *size) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
||||
int GetTimersAmount(void) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
||||
PVR_ERROR GetTimers(ADDON_HANDLE handle) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
||||
PVR_ERROR AddTimer(const PVR_TIMER& timer) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
||||
PVR_ERROR DeleteTimer(const PVR_TIMER& timer, bool bForceDelete) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
||||
PVR_ERROR UpdateTimer(const PVR_TIMER& timer) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
||||
|
||||
/* PVR stream properties handling */
|
||||
PVR_ERROR GetStreamReadChunkSize(int* chunksize) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
||||
PVR_ERROR GetChannelStreamProperties(const PVR_CHANNEL*, PVR_NAMED_VALUE*, unsigned int*) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
||||
PVR_ERROR GetRecordingStreamProperties(const PVR_RECORDING*, PVR_NAMED_VALUE*, unsigned int*) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
||||
PVR_ERROR GetEPGTagStreamProperties(const EPG_TAG*, PVR_NAMED_VALUE*, unsigned int*) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
||||
PVR_ERROR GetEPGTagEdl(const EPG_TAG* epgTag, PVR_EDL_ENTRY edl[], int *size) { 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 rtsp_open(data->getName(channel.iUniqueId), 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 LengthLiveStream(void) { return -1; }
|
||||
bool IsRealTimeStream(void) { return true; }
|
||||
|
||||
PVR_ERROR GetSignalStatus(int channelUid, PVR_SIGNAL_STATUS* signalStatus) {
|
||||
memset(signalStatus, 0, sizeof(PVR_SIGNAL_STATUS));
|
||||
rtsp_fill_signal_status(signalStatus);
|
||||
return PVR_ERROR_NO_ERROR;
|
||||
}
|
||||
|
||||
PVR_ERROR GetStreamTimes(PVR_STREAM_TIMES *times) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
||||
PVR_ERROR GetStreamProperties(PVR_STREAM_PROPERTIES* pProperties) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
||||
PVR_ERROR GetDescrambleInfo(int, PVR_DESCRAMBLE_INFO*) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
||||
|
||||
/* Recording stream handling */
|
||||
bool OpenRecordedStream(const PVR_RECORDING& recording) { return false; }
|
||||
void CloseRecordedStream(void) {}
|
||||
int ReadRecordedStream(unsigned char* pBuffer, unsigned int iBufferSize) { return -1; }
|
||||
long long SeekRecordedStream(long long iPosition, int iWhence) { return -1; }
|
||||
long long LengthRecordedStream(void) { return -1; }
|
||||
|
||||
/* PVR demuxer */
|
||||
/* entirey unused, as we use TS */
|
||||
void DemuxReset(void) {}
|
||||
void DemuxAbort(void) {}
|
||||
void DemuxFlush(void) {}
|
||||
DemuxPacket* DemuxRead(void) { return NULL; }
|
||||
void FillBuffer(bool mode) {}
|
||||
|
||||
/* Various helper functions */
|
||||
bool CanPauseStream() { return false; }
|
||||
bool CanSeekStream() { return false; }
|
||||
|
||||
/* Callbacks */
|
||||
void PauseStream(bool bPaused) {}
|
||||
bool SeekTime(double time, bool backwards, double *startpts) { return false; }
|
||||
void SetSpeed(int speed) {}
|
||||
PVR_ERROR SetEPGTimeFrame(int) { return PVR_ERROR_NOT_IMPLEMENTED; }
|
||||
|
||||
const char* GetBackendHostname()
|
||||
{
|
||||
return octonetAddress.c_str();
|
||||
}
|
||||
|
||||
}
|
24
src/client.h
24
src/client.h
@ -1,24 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Julian Scheel <julian@jusst.de>
|
||||
* Copyright (C) 2015 jusst technologies GmbH
|
||||
* Copyright (C) 2015 Digital Devices GmbH
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
* See LICENSE.md for more information.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "kodi/libXBMC_addon.h"
|
||||
#include "kodi/libXBMC_pvr.h"
|
||||
|
||||
#ifndef __func__
|
||||
#define __func__ __FUNCTION__
|
||||
#endif
|
||||
|
||||
extern ADDON::CHelper_libXBMC_addon *libKodi;
|
||||
extern CHelper_libXBMC_pvr *pvr;
|
||||
|
||||
/* IP or hostname of the octonet to be connected to */
|
||||
extern std::string octonetAddress;
|
@ -1,26 +1,35 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2020 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 <algorithm>
|
||||
#include <cctype>
|
||||
#include <iterator>
|
||||
#include "Socket.h"
|
||||
#include "client.h"
|
||||
#include <p8-platform/util/util.h>
|
||||
#include <kodi/libXBMC_addon.h>
|
||||
#include <cstring>
|
||||
#include <iterator>
|
||||
#include <sstream>
|
||||
|
||||
#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 = NULL, 0, fmt, argv);
|
||||
if((wanted < 0) || ((*sptr = (char *)malloc(1 + wanted)) == NULL))
|
||||
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 asprintf(char** sptr, char* fmt, ...)
|
||||
{
|
||||
int retval;
|
||||
va_list argv;
|
||||
va_start(argv, fmt);
|
||||
@ -40,10 +49,10 @@ int asprintf(char **sptr, char *fmt, ...) {
|
||||
#define RTCP_BUFFER_SIZE 1024
|
||||
|
||||
using namespace std;
|
||||
using namespace ADDON;
|
||||
using namespace OCTO;
|
||||
|
||||
enum rtsp_state {
|
||||
enum rtsp_state
|
||||
{
|
||||
RTSP_IDLE,
|
||||
RTSP_DESCRIBE,
|
||||
RTSP_SETUP,
|
||||
@ -51,11 +60,13 @@ enum rtsp_state {
|
||||
RTSP_RUNNING
|
||||
};
|
||||
|
||||
enum rtsp_result {
|
||||
enum rtsp_result
|
||||
{
|
||||
RTSP_RESULT_OK = 200,
|
||||
};
|
||||
|
||||
struct rtsp_client {
|
||||
struct rtsp_client
|
||||
{
|
||||
char* content_base;
|
||||
char* control;
|
||||
char session_id[64];
|
||||
@ -80,14 +91,16 @@ struct rtsp_client {
|
||||
int quality;
|
||||
};
|
||||
|
||||
struct url {
|
||||
struct url
|
||||
{
|
||||
string protocol;
|
||||
string host;
|
||||
int port;
|
||||
string path;
|
||||
};
|
||||
|
||||
struct rtcp_app {
|
||||
struct rtcp_app
|
||||
{
|
||||
uint8_t subtype;
|
||||
uint8_t pt;
|
||||
uint16_t len;
|
||||
@ -97,9 +110,10 @@ struct rtcp_app {
|
||||
uint16_t string_len;
|
||||
};
|
||||
|
||||
static rtsp_client *rtsp = NULL;
|
||||
static rtsp_client* rtsp = nullptr;
|
||||
|
||||
static url parse_url(const std::string& str) {
|
||||
static url parse_url(const std::string& str)
|
||||
{
|
||||
static const string prot_end = "://";
|
||||
static const string host_end = "/";
|
||||
url result;
|
||||
@ -125,22 +139,27 @@ static url parse_url(const std::string& str) {
|
||||
return result;
|
||||
}
|
||||
|
||||
void split_string(const string& s, char delim, vector<string>& elems) {
|
||||
void split_string(const string& s, char delim, vector<string>& elems)
|
||||
{
|
||||
stringstream ss;
|
||||
ss.str(s);
|
||||
|
||||
string item;
|
||||
while(getline(ss, item, delim)) {
|
||||
while (getline(ss, item, delim))
|
||||
{
|
||||
elems.push_back(item);
|
||||
}
|
||||
}
|
||||
|
||||
static int tcp_sock_read_line(string &line) {
|
||||
static int tcp_sock_read_line(string& line)
|
||||
{
|
||||
static string buf;
|
||||
|
||||
while(true) {
|
||||
while (true)
|
||||
{
|
||||
string::size_type pos = buf.find("\r\n");
|
||||
if(pos != string::npos) {
|
||||
if (pos != string::npos)
|
||||
{
|
||||
line = buf.substr(0, pos);
|
||||
buf.erase(0, pos + 2);
|
||||
return 0;
|
||||
@ -148,7 +167,8 @@ static int tcp_sock_read_line(string &line) {
|
||||
|
||||
char tmp_buf[2048];
|
||||
int size = rtsp->tcp_sock.receive(tmp_buf, sizeof(tmp_buf), 1);
|
||||
if(size <= 0) {
|
||||
if (size <= 0)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -167,17 +187,20 @@ static string compose_url(const url& u)
|
||||
return res.str();
|
||||
}
|
||||
|
||||
static void parse_session(char *request_line, char *session, unsigned max, int *timeout) {
|
||||
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)
|
||||
if (tok == nullptr)
|
||||
return;
|
||||
strncpy(session, tok, min(strlen(tok), (size_t)(max - 1)));
|
||||
|
||||
while ((tok = strtok_r(NULL, ";", &state)) != NULL) {
|
||||
if (strncmp(tok, "timeout=", 8) == 0) {
|
||||
while ((tok = strtok_r(nullptr, ";", &state)) != nullptr)
|
||||
{
|
||||
if (strncmp(tok, "timeout=", 8) == 0)
|
||||
{
|
||||
*timeout = atoi(tok + 8);
|
||||
if (*timeout > 5)
|
||||
*timeout -= KEEPALIVE_MARGIN;
|
||||
@ -198,29 +221,34 @@ static int parse_port(char *str, uint16_t *port)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_transport(char *request_line) {
|
||||
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)
|
||||
if (tok == nullptr || strncmp(tok, "RTP/AVP", 7) != 0)
|
||||
return -1;
|
||||
|
||||
tok = strtok_r(NULL, ";", &state);
|
||||
if (tok == NULL || strncmp(tok, "multicast", 9) != 0)
|
||||
tok = strtok_r(nullptr, ";", &state);
|
||||
if (tok == nullptr || strncmp(tok, "multicast", 9) != 0)
|
||||
return 0;
|
||||
|
||||
while ((tok = strtok_r(NULL, ";", &state)) != NULL) {
|
||||
if (strncmp(tok, "destination=", 12) == 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) {
|
||||
}
|
||||
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)
|
||||
if ((end = strstr(port, "-")) != nullptr)
|
||||
*end = '\0';
|
||||
err = parse_port(port, &rtsp->udp_port);
|
||||
if (err)
|
||||
@ -231,8 +259,11 @@ static int parse_transport(char *request_line) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define skip_whitespace(x) while(*x == ' ') x++
|
||||
static enum rtsp_result rtsp_handle() {
|
||||
#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;
|
||||
@ -242,51 +273,66 @@ static enum rtsp_result rtsp_handle() {
|
||||
string in_str;
|
||||
|
||||
/* Parse header */
|
||||
while (!have_header) {
|
||||
while (!have_header)
|
||||
{
|
||||
if (tcp_sock_read_line(in_str) < 0)
|
||||
break;
|
||||
in = const_cast<char*>(in_str.c_str());
|
||||
|
||||
if (strncmp(in, "RTSP/1.0 ", 9) == 0) {
|
||||
if (strncmp(in, "RTSP/1.0 ", 9) == 0)
|
||||
{
|
||||
rtsp_result = atoi(in + 9);
|
||||
} else if (strncmp(in, "Content-Base:", 13) == 0) {
|
||||
}
|
||||
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) {
|
||||
}
|
||||
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) {
|
||||
}
|
||||
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) {
|
||||
}
|
||||
else if (strncmp("Transport:", in, 10) == 0)
|
||||
{
|
||||
val = in + 10;
|
||||
skip_whitespace(val);
|
||||
|
||||
if (parse_transport(val) != 0) {
|
||||
if (parse_transport(val) != 0)
|
||||
{
|
||||
rtsp_result = -1;
|
||||
break;
|
||||
}
|
||||
} else if (strncmp("com.ses.streamID:", in, 17) == 0) {
|
||||
}
|
||||
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') {
|
||||
}
|
||||
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))))
|
||||
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;
|
||||
@ -302,28 +348,30 @@ bool rtsp_open(const string& name, const string& url_str)
|
||||
|
||||
rtsp_close();
|
||||
rtsp = new rtsp_client();
|
||||
if (rtsp == NULL)
|
||||
if (rtsp == nullptr)
|
||||
return false;
|
||||
|
||||
rtsp->name = name;
|
||||
rtsp->level = 0;
|
||||
rtsp->quality = 0;
|
||||
|
||||
libKodi->Log(LOG_DEBUG, "try to open '%s'", url_str.c_str());
|
||||
kodi::Log(ADDON_LOG_DEBUG, "try to open '%s'", url_str.c_str());
|
||||
|
||||
url dst = parse_url(url_str);
|
||||
libKodi->Log(LOG_DEBUG, "connect to host '%s'", dst.host.c_str());
|
||||
kodi::Log(ADDON_LOG_DEBUG, "connect to host '%s'", dst.host.c_str());
|
||||
|
||||
if(!rtsp->tcp_sock.connect(dst.host, dst.port)) {
|
||||
libKodi->Log(LOG_ERROR, "Failed to connect to RTSP server %s:%d", dst.host.c_str(), dst.port);
|
||||
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 = NULL;
|
||||
if (asprintf(&rtsp->content_base, "rtsp://%s:%d/", dst.host.c_str(), dst.port) < 0)
|
||||
{
|
||||
rtsp->content_base = nullptr;
|
||||
goto error;
|
||||
}
|
||||
|
||||
@ -333,7 +381,8 @@ bool rtsp_open(const string& name, const string& url_str)
|
||||
setup_url = dst;
|
||||
|
||||
// reverse the satip protocol trick, as SAT>IP believes to be RTSP
|
||||
if (!strncasecmp(setup_url.protocol.c_str(), "satip", 5)) {
|
||||
if (!strncasecmp(setup_url.protocol.c_str(), "satip", 5))
|
||||
{
|
||||
setup_url.protocol = "rtsp";
|
||||
}
|
||||
|
||||
@ -343,22 +392,26 @@ bool rtsp_open(const string& name, const string& url_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)) {
|
||||
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";
|
||||
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) {
|
||||
libKodi->Log(LOG_ERROR, "Failed to setup RTSP session");
|
||||
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 = NULL;
|
||||
if (asprintf(&rtsp->control, "%sstream=%d", rtsp->content_base, rtsp->stream_id) < 0)
|
||||
{
|
||||
rtsp->control = nullptr;
|
||||
goto error;
|
||||
}
|
||||
|
||||
@ -367,16 +420,19 @@ bool rtsp_open(const string& name, const string& url_str)
|
||||
play_ss << "Session: " << rtsp->session_id << "\r\n\r\n";
|
||||
rtsp->tcp_sock.send(play_ss.str());
|
||||
|
||||
if (rtsp_handle() != RTSP_RESULT_OK) {
|
||||
libKodi->Log(LOG_ERROR, "Failed to play RTSP session");
|
||||
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)) {
|
||||
if (!rtsp->rtcp_sock.bind(rtsp->udp_port + 1))
|
||||
{
|
||||
goto error;
|
||||
}
|
||||
if(!rtsp->rtcp_sock.set_non_blocking(true)) {
|
||||
if (!rtsp->rtcp_sock.set_non_blocking(true))
|
||||
{
|
||||
goto error;
|
||||
}
|
||||
|
||||
@ -387,13 +443,16 @@ error:
|
||||
return false;
|
||||
}
|
||||
|
||||
static void parse_rtcp(const char *buf, int size) {
|
||||
static void parse_rtcp(const char* buf, int size)
|
||||
{
|
||||
int offset = 0;
|
||||
while(size > 4) {
|
||||
while (size > 4)
|
||||
{
|
||||
const rtcp_app* app = reinterpret_cast<const rtcp_app*>(buf + offset);
|
||||
uint16_t len = 4 * (ntohs(app->len) + 1);
|
||||
|
||||
if((app->pt != 204) || (memcmp(app->name, "SES1", 4) != 0)) {
|
||||
if ((app->pt != 204) || (memcmp(app->name, "SES1", 4) != 0))
|
||||
{
|
||||
size -= len;
|
||||
offset += len;
|
||||
continue;
|
||||
@ -404,13 +463,15 @@ static void parse_rtcp(const char *buf, int size) {
|
||||
|
||||
vector<string> elems;
|
||||
split_string(app_data, ';', elems);
|
||||
if(elems.size() != 4) {
|
||||
if (elems.size() != 4)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
vector<string> tuner;
|
||||
split_string(elems[2], ',', tuner);
|
||||
if(tuner.size() < 4) {
|
||||
if (tuner.size() < 4)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@ -421,7 +482,8 @@ static void parse_rtcp(const char *buf, int size) {
|
||||
}
|
||||
}
|
||||
|
||||
int rtsp_read(void *buf, unsigned buf_size) {
|
||||
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);
|
||||
@ -435,12 +497,15 @@ int rtsp_read(void *buf, unsigned buf_size) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void rtsp_teardown() {
|
||||
if(!rtsp->tcp_sock.is_valid()) {
|
||||
static void rtsp_teardown()
|
||||
{
|
||||
if (!rtsp->tcp_sock.is_valid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (rtsp->session_id[0] > 0) {
|
||||
if (rtsp->session_id[0] > 0)
|
||||
{
|
||||
char* msg;
|
||||
int len;
|
||||
stringstream ss;
|
||||
@ -452,8 +517,9 @@ static void rtsp_teardown() {
|
||||
ss << "Session: " << rtsp->session_id << "\r\n\r\n";
|
||||
rtsp->tcp_sock.send(ss.str());
|
||||
|
||||
if (rtsp_handle() != RTSP_RESULT_OK) {
|
||||
libKodi->Log(LOG_ERROR, "Failed to teardown RTSP session");
|
||||
if (rtsp_handle() != RTSP_RESULT_OK)
|
||||
{
|
||||
kodi::Log(ADDON_LOG_ERROR, "Failed to teardown RTSP session");
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -461,20 +527,23 @@ static void rtsp_teardown() {
|
||||
|
||||
void rtsp_close()
|
||||
{
|
||||
if(rtsp) {
|
||||
if (rtsp)
|
||||
{
|
||||
rtsp_teardown();
|
||||
rtsp->tcp_sock.close();
|
||||
rtsp->udp_sock.close();
|
||||
rtsp->rtcp_sock.close();
|
||||
delete rtsp;
|
||||
rtsp = NULL;
|
||||
rtsp = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void rtsp_fill_signal_status(PVR_SIGNAL_STATUS* signal_status) {
|
||||
if(rtsp) {
|
||||
strncpy(signal_status->strServiceName, rtsp->name.c_str(), PVR_ADDON_NAME_STRING_LENGTH - 1);
|
||||
signal_status->iSNR = 0x1111 * rtsp->quality;
|
||||
signal_status->iSignal = 0x101 * rtsp->level;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,17 @@
|
||||
#ifndef _RTSP_CLIENT_HPP_
|
||||
#define _RTSP_CLIENT_HPP_
|
||||
/*
|
||||
* Copyright (C) 2005-2020 Team Kodi
|
||||
* https://kodi.tv
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
* See LICENSE.md for more information.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <kodi/addon-instance/pvr/Channels.h>
|
||||
#include <string>
|
||||
#include <kodi/xbmc_pvr_types.h>
|
||||
|
||||
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(PVR_SIGNAL_STATUS* signal_status);
|
||||
|
||||
#endif
|
||||
void rtsp_fill_signal_status(kodi::addon::PVRSignalStatus& signal_status);
|
||||
|
Loading…
x
Reference in New Issue
Block a user