mirror of
				https://github.com/DigitalDevices/pvr.octonet.git
				synced 2025-03-01 10:53:09 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			459 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			459 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
|  *  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 "OctonetData.h"
 | |
| 
 | |
| #include "rtsp_client.hpp"
 | |
| 
 | |
| #include <json/json.h>
 | |
| #include <kodi/Filesystem.h>
 | |
| #include <kodi/General.h>
 | |
| #include <sstream>
 | |
| #include <string>
 | |
| 
 | |
| #if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__)
 | |
| #define timegm _mkgmtime
 | |
| #endif
 | |
| 
 | |
| OctonetData::OctonetData(const std::string& octonetAddress,
 | |
|                          KODI_HANDLE instance,
 | |
|                          const std::string& kodiVersion)
 | |
|   : kodi::addon::CInstancePVRClient(instance, kodiVersion)
 | |
| {
 | |
|   m_serverAddress = octonetAddress;
 | |
|   m_channels.clear();
 | |
|   m_groups.clear();
 | |
|   m_lastEpgLoad = 0;
 | |
| 
 | |
|   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)
 | |
| {
 | |
|   /*
 | |
|   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;
 | |
| }
 | |
| 
 | |
| int64_t OctonetData::ParseID(std::string id)
 | |
| {
 | |
|   std::hash<std::string> hash_fn;
 | |
|   int64_t nativeId = hash_fn(id);
 | |
| 
 | |
|   return nativeId;
 | |
| }
 | |
| 
 | |
| bool OctonetData::LoadChannelList()
 | |
| {
 | |
|   std::string jsonContent;
 | |
|   kodi::vfs::CFile f;
 | |
|   if (!f.OpenFile("http://" + m_serverAddress + "/channellist.lua?select=json", 0))
 | |
|     return false;
 | |
| 
 | |
|   char buf[1024];
 | |
|   while (int read = f.Read(buf, 1024))
 | |
|     jsonContent.append(buf, read);
 | |
| 
 | |
|   f.Close();
 | |
| 
 | |
|   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;
 | |
| }
 | |
| 
 | |
| OctonetChannel* OctonetData::FindChannel(int64_t nativeId)
 | |
| {
 | |
|   for (auto& channel : m_channels)
 | |
|   {
 | |
|     if (channel.nativeId == nativeId)
 | |
|       return &channel;
 | |
|   }
 | |
| 
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| 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);
 | |
|     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);
 | |
| }
 | |
| 
 | |
| bool OctonetData::LoadEPG(void)
 | |
| {
 | |
|   /* Reload at most every 30 seconds */
 | |
|   if (m_lastEpgLoad + 30 > time(nullptr))
 | |
|     return false;
 | |
| 
 | |
|   std::string jsonContent;
 | |
|   kodi::vfs::CFile f;
 | |
|   if (!f.OpenFile("http://" + m_serverAddress + "/epg.lua?;#|encoding=gzip", 0))
 | |
|     return false;
 | |
| 
 | |
|   char buf[1024];
 | |
|   while (int read = f.Read(buf, 1024))
 | |
|     jsonContent.append(buf, read);
 | |
| 
 | |
|   f.Close();
 | |
| 
 | |
|   Json::Value root;
 | |
|   Json::Reader reader;
 | |
| 
 | |
|   if (!reader.parse(jsonContent, root, false))
 | |
|     return false;
 | |
| 
 | |
|   const Json::Value eventList = root["EventList"];
 | |
|   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.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 = std::stoi(epgId);
 | |
| 
 | |
|     if (channel == nullptr || channel->nativeId != entry.channelId)
 | |
|       channel = FindChannel(entry.channelId);
 | |
| 
 | |
|     if (channel == nullptr)
 | |
|     {
 | |
|       kodi::Log(ADDON_LOG_ERROR, "EPG for unknown channel.");
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     channel->epg.push_back(entry);
 | |
|   }
 | |
| 
 | |
|   m_lastEpgLoad = time(nullptr);
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void OctonetData::Process()
 | |
| {
 | |
|   return;
 | |
| }
 | |
| 
 | |
| PVR_ERROR OctonetData::GetChannelsAmount(int& amount)
 | |
| {
 | |
|   amount = m_channels.size();
 | |
|   return PVR_ERROR_NO_ERROR;
 | |
| }
 | |
| 
 | |
| PVR_ERROR OctonetData::GetChannels(bool radio, kodi::addon::PVRChannelsResultSet& results)
 | |
| {
 | |
|   for (unsigned int i = 0; i < m_channels.size(); i++)
 | |
|   {
 | |
|     OctonetChannel& channel = m_channels.at(i);
 | |
|     if (channel.radio == radio)
 | |
|     {
 | |
|       kodi::addon::PVRChannel chan;
 | |
| 
 | |
|       chan.SetUniqueId(channel.id);
 | |
|       chan.SetIsRadio(channel.radio);
 | |
|       chan.SetChannelNumber(i);
 | |
|       chan.SetChannelName(channel.name);
 | |
|       chan.SetMimeType("video/x-mpegts");
 | |
|       chan.SetIsHidden(false);
 | |
| 
 | |
|       results.Add(chan);
 | |
|     }
 | |
|   }
 | |
|   return PVR_ERROR_NO_ERROR;
 | |
| }
 | |
| 
 | |
| PVR_ERROR OctonetData::GetEPGForChannel(int channelUid,
 | |
|                                         time_t start,
 | |
|                                         time_t end,
 | |
|                                         kodi::addon::PVREPGTagsResultSet& results)
 | |
| {
 | |
|   for (unsigned int i = 0; i < m_channels.size(); i++)
 | |
|   {
 | |
|     OctonetChannel& chan = m_channels.at(i);
 | |
|     if (channelUid != chan.id)
 | |
|       continue;
 | |
| 
 | |
|     if (chan.epg.empty())
 | |
|     {
 | |
|       LoadEPG();
 | |
|     }
 | |
| 
 | |
|     // FIXME: Check if reload is needed!?
 | |
| 
 | |
|     time_t last_end = 0;
 | |
|     for (const auto& epg : chan.epg)
 | |
|     {
 | |
|       if (epg.end > last_end)
 | |
|         last_end = epg.end;
 | |
| 
 | |
|       if (epg.end < start || epg.start > end)
 | |
|       {
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       kodi::addon::PVREPGTag entry;
 | |
| 
 | |
|       entry.SetUniqueChannelId(chan.id);
 | |
|       entry.SetUniqueBroadcastId(epg.id);
 | |
|       entry.SetTitle(epg.title);
 | |
|       entry.SetPlotOutline(epg.subtitle);
 | |
|       entry.SetStartTime(epg.start);
 | |
|       entry.SetEndTime(epg.end);
 | |
| 
 | |
|       results.Add(entry);
 | |
|     }
 | |
| 
 | |
|     if (last_end < end)
 | |
|       LoadEPG();
 | |
| 
 | |
|     for (const auto& epg : chan.epg)
 | |
|     {
 | |
|       if (epg.end < start || epg.start > end)
 | |
|       {
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       kodi::addon::PVREPGTag entry;
 | |
| 
 | |
|       entry.SetUniqueChannelId(chan.id);
 | |
|       entry.SetUniqueBroadcastId(epg.id);
 | |
|       entry.SetTitle(epg.title);
 | |
|       entry.SetPlotOutline(epg.subtitle);
 | |
|       entry.SetStartTime(epg.start);
 | |
|       entry.SetEndTime(epg.end);
 | |
| 
 | |
|       results.Add(entry);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return PVR_ERROR_NO_ERROR;
 | |
| }
 | |
| 
 | |
| const std::string& OctonetData::GetUrl(int id) const
 | |
| {
 | |
|   for (const auto& channel : m_channels)
 | |
|   {
 | |
|     if (channel.id == id)
 | |
|     {
 | |
|       return channel.url;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return m_channels[0].url;
 | |
| }
 | |
| 
 | |
| const std::string& OctonetData::GetName(int id) const
 | |
| {
 | |
|   for (const auto& channel : m_channels)
 | |
|   {
 | |
|     if (channel.id == id)
 | |
|     {
 | |
|       return channel.name;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   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::GetChannelGroupMembers(const kodi::addon::PVRChannelGroup& group,
 | |
|                                               kodi::addon::PVRChannelGroupMembersResultSet& results)
 | |
| {
 | |
|   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 = m_channels.at(g->members[i]);
 | |
|     kodi::addon::PVRChannelGroupMember m;
 | |
| 
 | |
|     m.SetGroupName(group.GetGroupName());
 | |
|     m.SetChannelUniqueId(channel.id);
 | |
|     m.SetChannelNumber(channel.id);
 | |
| 
 | |
|     results.Add(m);
 | |
|   }
 | |
| 
 | |
|   return PVR_ERROR_NO_ERROR;
 | |
| }
 | |
| 
 | |
| OctonetGroup* OctonetData::FindGroup(const std::string& name)
 | |
| {
 | |
|   for (auto& group : m_groups)
 | |
|   {
 | |
|     if (group.name == name)
 | |
|       return &group;
 | |
|   }
 | |
| 
 | |
|   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();
 | |
| }
 |