pvr.octonet/src/OctonetData.cpp

459 lines
11 KiB
C++
Raw Normal View History

/*
2020-03-25 05:24:55 +01:00
* Copyright (C) 2015 Julian Scheel <julian@jusst.de>
* Copyright (C) 2015 jusst technologies GmbH
* Copyright (C) 2015 Digital Devices GmbH
*
2020-03-25 05:24:55 +01:00
* SPDX-License-Identifier: GPL-2.0-or-later
* See LICENSE.md for more information.
*
*/
2020-06-07 16:56:16 +02:00
#include "OctonetData.h"
2020-06-07 19:06:40 +02:00
#include "rtsp_client.hpp"
#include <json/json.h>
2020-06-07 19:06:40 +02:00
#include <kodi/Filesystem.h>
#include <kodi/General.h>
2020-06-07 16:56:16 +02:00
#include <sstream>
#include <string>
#ifdef __WINDOWS__
#define timegm _mkgmtime
#endif
2020-06-07 19:06:40 +02:00
OctonetData::OctonetData(const std::string& octonetAddress,
KODI_HANDLE instance,
const std::string& kodiVersion)
: kodi::addon::CInstancePVRClient(instance, kodiVersion)
{
2020-06-07 16:56:16 +02:00
m_serverAddress = octonetAddress;
m_channels.clear();
m_groups.clear();
m_lastEpgLoad = 0;
2020-06-07 16:56:16 +02:00
if (!LoadChannelList())
2020-06-07 19:06:40 +02:00
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)
{
2020-06-07 19:06:40 +02:00
/*
m_running = false;
if (m_thread.joinable())
m_thread.join();
*/
}
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;
}
2020-06-07 16:56:16 +02:00
int64_t OctonetData::ParseID(std::string id)
{
2020-06-07 16:56:16 +02:00
std::hash<std::string> hash_fn;
int64_t nativeId = hash_fn(id);
2020-06-07 16:56:16 +02:00
return nativeId;
}
2020-06-07 16:56:16 +02:00
bool OctonetData::LoadChannelList()
{
2020-06-07 16:56:16 +02:00
std::string jsonContent;
2020-06-07 19:06:40 +02:00
kodi::vfs::CFile f;
if (!f.OpenFile("http://" + m_serverAddress + "/channellist.lua?select=json", 0))
2020-06-07 16:56:16 +02:00
return false;
char buf[1024];
2020-06-07 19:06:40 +02:00
while (int read = f.Read(buf, 1024))
2020-06-07 16:56:16 +02:00
jsonContent.append(buf, read);
2020-06-07 19:06:40 +02:00
f.Close();
2020-06-07 16:56:16 +02:00
Json::Value root;
Json::Reader reader;
if (!reader.parse(jsonContent, root, false))
return false;
const Json::Value groupList = root["GroupList"];
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++)
{
const Json::Value channel = channelList[j];
OctonetChannel chan;
chan.name = channel["Title"].asString();
chan.url = "rtsp://" + m_serverAddress + "/" + channel["Request"].asString();
chan.radio = group.radio;
chan.nativeId = ParseID(channel["ID"].asString());
chan.id = 1000 + m_channels.size();
group.members.push_back(m_channels.size());
m_channels.push_back(chan);
}
m_groups.push_back(group);
}
return true;
}
2020-06-07 16:56:16 +02:00
OctonetChannel* OctonetData::FindChannel(int64_t nativeId)
{
2020-06-07 19:06:40 +02:00
for (auto& channel : m_channels)
2020-06-07 16:56:16 +02:00
{
2020-06-07 19:06:40 +02:00
if (channel.nativeId == nativeId)
return &channel;
2020-06-07 16:56:16 +02:00
}
2020-06-07 19:06:40 +02:00
return nullptr;
}
2020-06-07 16:56:16 +02:00
time_t OctonetData::ParseDateTime(std::string date)
{
2020-06-07 16:56:16 +02:00
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);
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);
timeinfo.tm_year = 70; // unix timestamps start 1970
timeinfo.tm_mday = 1;
}
timeinfo.tm_isdst = -1;
return timegm(&timeinfo);
}
2020-06-07 16:56:16 +02:00
bool OctonetData::LoadEPG(void)
{
2020-06-07 16:56:16 +02:00
/* Reload at most every 30 seconds */
2020-06-07 19:06:40 +02:00
if (m_lastEpgLoad + 30 > time(nullptr))
2020-06-07 16:56:16 +02:00
return false;
std::string jsonContent;
2020-06-07 19:06:40 +02:00
kodi::vfs::CFile f;
if (!f.OpenFile("http://" + m_serverAddress + "/epg.lua?;#|encoding=gzip", 0))
2020-06-07 16:56:16 +02:00
return false;
char buf[1024];
2020-06-07 19:06:40 +02:00
while (int read = f.Read(buf, 1024))
2020-06-07 16:56:16 +02:00
jsonContent.append(buf, read);
2020-06-07 19:06:40 +02:00
f.Close();
2020-06-07 16:56:16 +02:00
Json::Value root;
Json::Reader reader;
if (!reader.parse(jsonContent, root, false))
return false;
const Json::Value eventList = root["EventList"];
2020-06-07 19:06:40 +02:00
OctonetChannel* channel = nullptr;
2020-06-07 16:56:16 +02:00
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.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);
2020-06-07 19:06:40 +02:00
entry.id = std::stoi(epgId);
2020-06-07 16:56:16 +02:00
2020-06-07 19:06:40 +02:00
if (channel == nullptr || channel->nativeId != entry.channelId)
2020-06-07 16:56:16 +02:00
channel = FindChannel(entry.channelId);
2020-06-07 19:06:40 +02:00
if (channel == nullptr)
2020-06-07 16:56:16 +02:00
{
2020-06-07 19:06:40 +02:00
kodi::Log(ADDON_LOG_ERROR, "EPG for unknown channel.");
2020-06-07 16:56:16 +02:00
continue;
}
channel->epg.push_back(entry);
}
2020-06-07 19:06:40 +02:00
m_lastEpgLoad = time(nullptr);
2020-06-07 16:56:16 +02:00
return true;
}
2020-06-07 19:06:40 +02:00
void OctonetData::Process()
{
2020-06-07 19:06:40 +02:00
return;
}
2020-06-07 19:06:40 +02:00
PVR_ERROR OctonetData::GetChannelsAmount(int& amount)
{
2020-06-07 19:06:40 +02:00
amount = m_channels.size();
return PVR_ERROR_NO_ERROR;
}
2020-06-07 19:06:40 +02:00
PVR_ERROR OctonetData::GetChannels(bool radio, kodi::addon::PVRChannelsResultSet& results)
{
2020-06-07 16:56:16 +02:00
for (unsigned int i = 0; i < m_channels.size(); i++)
{
OctonetChannel& channel = m_channels.at(i);
2020-06-07 19:06:40 +02:00
if (channel.radio == radio)
2020-06-07 16:56:16 +02:00
{
2020-06-07 19:06:40 +02:00
kodi::addon::PVRChannel chan;
2020-06-07 16:56:16 +02:00
2020-06-07 19:06:40 +02:00
chan.SetUniqueId(channel.id);
chan.SetIsRadio(channel.radio);
chan.SetChannelNumber(i);
chan.SetChannelName(channel.name);
chan.SetMimeType("video/x-mpegts");
chan.SetIsHidden(false);
2020-06-07 16:56:16 +02:00
2020-06-07 19:06:40 +02:00
results.Add(chan);
2020-06-07 16:56:16 +02:00
}
}
return PVR_ERROR_NO_ERROR;
}
2020-06-07 19:06:40 +02:00
PVR_ERROR OctonetData::GetEPGForChannel(int channelUid,
time_t start,
time_t end,
kodi::addon::PVREPGTagsResultSet& results)
{
2020-06-07 16:56:16 +02:00
for (unsigned int i = 0; i < m_channels.size(); i++)
{
OctonetChannel& chan = m_channels.at(i);
2020-06-07 19:06:40 +02:00
if (channelUid != chan.id)
2020-06-07 16:56:16 +02:00
continue;
if (chan.epg.empty())
{
LoadEPG();
}
// FIXME: Check if reload is needed!?
time_t last_end = 0;
2020-06-07 19:06:40 +02:00
for (const auto& epg : chan.epg)
2020-06-07 16:56:16 +02:00
{
2020-06-07 19:06:40 +02:00
if (epg.end > last_end)
last_end = epg.end;
2020-06-07 16:56:16 +02:00
2020-06-07 19:06:40 +02:00
if (epg.end < start || epg.start > end)
2020-06-07 16:56:16 +02:00
{
continue;
}
2020-06-07 19:06:40 +02:00
kodi::addon::PVREPGTag entry;
2020-06-07 16:56:16 +02:00
2020-06-07 19:06:40 +02:00
entry.SetUniqueChannelId(chan.id);
entry.SetUniqueBroadcastId(epg.id);
entry.SetTitle(epg.title);
entry.SetPlotOutline(epg.subtitle);
entry.SetStartTime(epg.start);
entry.SetEndTime(epg.end);
2020-06-07 16:56:16 +02:00
2020-06-07 19:06:40 +02:00
results.Add(entry);
2020-06-07 16:56:16 +02:00
}
if (last_end < end)
LoadEPG();
2020-06-07 19:06:40 +02:00
for (const auto& epg : chan.epg)
2020-06-07 16:56:16 +02:00
{
2020-06-07 19:06:40 +02:00
if (epg.end < start || epg.start > end)
2020-06-07 16:56:16 +02:00
{
continue;
}
2020-06-07 19:06:40 +02:00
kodi::addon::PVREPGTag entry;
2020-06-07 16:56:16 +02:00
2020-06-07 19:06:40 +02:00
entry.SetUniqueChannelId(chan.id);
entry.SetUniqueBroadcastId(epg.id);
entry.SetTitle(epg.title);
entry.SetPlotOutline(epg.subtitle);
entry.SetStartTime(epg.start);
entry.SetEndTime(epg.end);
2020-06-07 16:56:16 +02:00
2020-06-07 19:06:40 +02:00
results.Add(entry);
2020-06-07 16:56:16 +02:00
}
}
return PVR_ERROR_NO_ERROR;
}
2020-06-07 16:56:16 +02:00
const std::string& OctonetData::GetUrl(int id) const
{
2020-06-07 19:06:40 +02:00
for (const auto& channel : m_channels)
2020-06-07 16:56:16 +02:00
{
2020-06-07 19:06:40 +02:00
if (channel.id == id)
2020-06-07 16:56:16 +02:00
{
2020-06-07 19:06:40 +02:00
return channel.url;
2020-06-07 16:56:16 +02:00
}
}
return m_channels[0].url;
}
2020-06-07 16:56:16 +02:00
const std::string& OctonetData::GetName(int id) const
{
2020-06-07 19:06:40 +02:00
for (const auto& channel : m_channels)
2020-06-07 16:56:16 +02:00
{
2020-06-07 19:06:40 +02:00
if (channel.id == id)
2020-06-07 16:56:16 +02:00
{
2020-06-07 19:06:40 +02:00
return channel.name;
2020-06-07 16:56:16 +02:00
}
}
return m_channels[0].name;
}
2020-06-07 19:06:40 +02:00
PVR_ERROR OctonetData::GetChannelGroupsAmount(int& amount)
{
2020-06-07 19:06:40 +02:00
amount = m_groups.size();
return PVR_ERROR_NO_ERROR;
}
2020-06-07 19:06:40 +02:00
PVR_ERROR OctonetData::GetChannelGroups(bool radio, kodi::addon::PVRChannelGroupsResultSet& results)
{
2020-06-07 19:06:40 +02:00
for (const auto& group : m_groups)
2020-06-07 16:56:16 +02:00
{
2020-06-07 19:06:40 +02:00
if (group.radio == radio)
2020-06-07 16:56:16 +02:00
{
2020-06-07 19:06:40 +02:00
kodi::addon::PVRChannelGroup g;
2020-06-07 16:56:16 +02:00
2020-06-07 19:06:40 +02:00
g.SetPosition(0);
g.SetIsRadio(group.radio);
g.SetGroupName(group.name);
2020-06-07 16:56:16 +02:00
2020-06-07 19:06:40 +02:00
results.Add(g);
2020-06-07 16:56:16 +02:00
}
}
return PVR_ERROR_NO_ERROR;
}
2020-06-07 19:06:40 +02:00
PVR_ERROR OctonetData::GetChannelGroupMembers(const kodi::addon::PVRChannelGroup& group,
kodi::addon::PVRChannelGroupMembersResultSet& results)
{
2020-06-07 19:06:40 +02:00
const OctonetGroup* g = FindGroup(group.GetGroupName());
if (g == nullptr)
2020-06-07 16:56:16 +02:00
return PVR_ERROR_UNKNOWN;
2020-06-07 16:56:16 +02:00
for (unsigned int i = 0; i < g->members.size(); i++)
{
OctonetChannel& channel = m_channels.at(g->members[i]);
2020-06-07 19:06:40 +02:00
kodi::addon::PVRChannelGroupMember m;
2020-06-07 19:06:40 +02:00
m.SetGroupName(group.GetGroupName());
m.SetChannelUniqueId(channel.id);
m.SetChannelNumber(channel.id);
2020-06-07 19:06:40 +02:00
results.Add(m);
2020-06-07 16:56:16 +02:00
}
2020-06-07 16:56:16 +02:00
return PVR_ERROR_NO_ERROR;
}
2020-06-07 16:56:16 +02:00
OctonetGroup* OctonetData::FindGroup(const std::string& name)
{
2020-06-07 19:06:40 +02:00
for (auto& group : m_groups)
2020-06-07 16:56:16 +02:00
{
2020-06-07 19:06:40 +02:00
if (group.name == name)
return &group;
2020-06-07 16:56:16 +02:00
}
2020-06-07 19:06:40 +02:00
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();
}