diff --git a/pvr.mediaportal.tvserver/addon.xml.in b/pvr.mediaportal.tvserver/addon.xml.in index d6e96039..b8f87c29 100644 --- a/pvr.mediaportal.tvserver/addon.xml.in +++ b/pvr.mediaportal.tvserver/addon.xml.in @@ -1,7 +1,7 @@ diff --git a/pvr.mediaportal.tvserver/changelog.txt b/pvr.mediaportal.tvserver/changelog.txt index b0a2e9c3..cc7ee6b6 100644 --- a/pvr.mediaportal.tvserver/changelog.txt +++ b/pvr.mediaportal.tvserver/changelog.txt @@ -1,3 +1,12 @@ +v2.4.10 +- Add support for asynchronous connect. The addon will now regularly try to connect to the backend if it is not connected already. +- Added support for the recording channelType property to distinguish between radio and tv recordings (requires TVServerKodi v1.15.0.136 or above) +- Added a fallback for in-progress recording playback using the TSReader. When the filename is empty, try the RTSP url and vice versa. +- OpenRecordedStream: send additional error messages to the log in case things fail here. + +V2.4.9 +- Adapt to API change - SeekTime + v2.4.8 - Add Estuary skin support for the old series timer dialog - Fixed: various Coverity reported issues for Live555 (part 2) diff --git a/src/GUIDialogRecordSettings.cpp b/src/GUIDialogRecordSettings.cpp index e1a24a38..a32d21c6 100644 --- a/src/GUIDialogRecordSettings.cpp +++ b/src/GUIDialogRecordSettings.cpp @@ -69,11 +69,11 @@ CGUIDialogRecordSettings::CGUIDialogRecordSettings(const PVR_TIMER &timerinfo, c // needed for every dialog m_retVal = -1; // init to failed load value (due to xml file not being found) - // Default skin should actually be "skin.confluence", but the fallback mechanism will only + // Default skin should actually be "skin.estuary", but the fallback mechanism will only // find the xml file and not the used image files. This will result in a transparent window // which is basically useless. Therefore, it is better to let the dialog fail by using the // incorrect fallback skin name "Confluence" - m_window = GUI->Window_create("DialogRecordSettings.xml", "skin.confluence", false, true); + m_window = GUI->Window_create("DialogRecordSettings.xml", "skin.estuary", false, true); if (m_window) { m_window->m_cbhdl = this; diff --git a/src/Socket.cpp b/src/Socket.cpp index b0e1deb1..27107d3c 100644 --- a/src/Socket.cpp +++ b/src/Socket.cpp @@ -61,6 +61,7 @@ Socket::Socket() #ifdef TARGET_WINDOWS memset(&_wsaData, 0, sizeof(_wsaData)); #endif + osInit(); } @@ -80,8 +81,10 @@ bool Socket::close() { if (is_valid()) { - if (_sd != SOCKET_ERROR) + if ((_sd != SOCKET_ERROR) && (_sd != INVALID_SOCKET)) + { closesocket(_sd); + } _sd = INVALID_SOCKET; return true; } @@ -92,11 +95,6 @@ bool Socket::create() { close(); - if(!osInit()) - { - return false; - } - return true; } @@ -400,8 +398,9 @@ bool Socket::connect ( const std::string& host, const unsigned short port ) } _port = port; - char strPort[15]; + char strPort[16]; snprintf(strPort, 15, "%hu", port); + strPort[15] = '\0'; struct addrinfo hints; struct addrinfo* result = NULL; @@ -417,6 +416,11 @@ bool Socket::connect ( const std::string& host, const unsigned short port ) errormessage(getLastError(), "Socket::connect"); return false; } + if (result == NULL) + { + XBMC->Log(LOG_ERROR, "Socket::connect %s:%u: no address info found\n", host.c_str(), port); + return false; + } for (address = result; address != NULL; address = address->ai_next) { @@ -442,11 +446,8 @@ bool Socket::connect ( const std::string& host, const unsigned short port ) freeaddrinfo(result); - if (address == NULL) + if (_sd == INVALID_SOCKET) { - XBMC->Log(LOG_ERROR, "Socket::connect %s:%u\n", host.c_str(), port); - errormessage(getLastError(), "Socket::connect"); - close(); return false; } diff --git a/src/channels.h b/src/channels.h index 3c5bb23c..b242f828 100644 --- a/src/channels.h +++ b/src/channels.h @@ -24,6 +24,19 @@ #include "libXBMC_pvr.h" #include +namespace TvDatabase +{ + // From MediaPortal: TvDatabase.ChannelType + namespace ChannelType + { + const int Unknown = -1; //Added + const int Tv = 0; + const int Radio = 1; + const int Web = 2; + const int All = 3; + }; +} + class cChannel { private: diff --git a/src/client.cpp b/src/client.cpp index 96dc04c9..0c6477f0 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -49,7 +49,7 @@ bool g_bFastChannelSwitch = true; ///< Do bool g_bUseRTSP = false; ///< Use RTSP streaming when using the tsreader /* Client member variables */ -ADDON_STATUS m_CurStatus = ADDON_STATUS_UNKNOWN; +ADDON_STATUS m_curStatus = ADDON_STATUS_UNKNOWN; cPVRClientMediaPortal *g_client = NULL; std::string g_szUserPath = ""; std::string g_szClientPath = ""; @@ -72,7 +72,10 @@ void ADDON_ReadSettings(void); ADDON_STATUS ADDON_Create(void* hdl, void* props) { if (!hdl || !props) - return ADDON_STATUS_UNKNOWN; + { + m_curStatus = ADDON_STATUS_UNKNOWN; + return m_curStatus; + } PVR_PROPERTIES* pvrprops = (PVR_PROPERTIES*)props; @@ -80,7 +83,8 @@ ADDON_STATUS ADDON_Create(void* hdl, void* props) if (!XBMC->RegisterMe(hdl)) { SAFE_DELETE(XBMC); - return ADDON_STATUS_PERMANENT_FAILURE; + m_curStatus = ADDON_STATUS_PERMANENT_FAILURE; + return m_curStatus; } PVR = new CHelper_libXBMC_pvr; @@ -88,7 +92,8 @@ ADDON_STATUS ADDON_Create(void* hdl, void* props) { SAFE_DELETE(PVR); SAFE_DELETE(XBMC); - return ADDON_STATUS_PERMANENT_FAILURE; + m_curStatus = ADDON_STATUS_PERMANENT_FAILURE; + return m_curStatus; } GUI = new CHelper_libKODI_guilib; @@ -97,12 +102,13 @@ ADDON_STATUS ADDON_Create(void* hdl, void* props) SAFE_DELETE(GUI); SAFE_DELETE(PVR); SAFE_DELETE(XBMC); - return ADDON_STATUS_PERMANENT_FAILURE; + m_curStatus = ADDON_STATUS_PERMANENT_FAILURE; + return m_curStatus; } XBMC->Log(LOG_INFO, "Creating MediaPortal PVR-Client"); - m_CurStatus = ADDON_STATUS_UNKNOWN; + m_curStatus = ADDON_STATUS_UNKNOWN; g_szUserPath = pvrprops->strUserPath; g_szClientPath = pvrprops->strClientPath; @@ -111,16 +117,21 @@ ADDON_STATUS ADDON_Create(void* hdl, void* props) /* Create connection to MediaPortal XBMC TV client */ g_client = new cPVRClientMediaPortal(); - m_CurStatus = g_client->Connect(); - if (m_CurStatus != ADDON_STATUS_OK) + m_curStatus = g_client->TryConnect(); + if (m_curStatus == ADDON_STATUS_PERMANENT_FAILURE) { SAFE_DELETE(g_client); SAFE_DELETE(GUI); SAFE_DELETE(PVR); SAFE_DELETE(XBMC); } + else if (m_curStatus == ADDON_STATUS_LOST_CONNECTION) + { + // The addon will try to reconnect, so don't show the permanent failure. + return ADDON_STATUS_OK; + } - return m_CurStatus; + return m_curStatus; } //-- Destroy ------------------------------------------------------------------ @@ -134,7 +145,7 @@ void ADDON_Destroy() SAFE_DELETE(PVR); SAFE_DELETE(XBMC); - m_CurStatus = ADDON_STATUS_UNKNOWN; + m_curStatus = ADDON_STATUS_UNKNOWN; } //-- GetStatus ---------------------------------------------------------------- @@ -143,10 +154,10 @@ void ADDON_Destroy() ADDON_STATUS ADDON_GetStatus() { /* check whether we're still connected */ - if (m_CurStatus == ADDON_STATUS_OK && g_client && !g_client->IsUp()) - m_CurStatus = ADDON_STATUS_LOST_CONNECTION; + if (m_curStatus == ADDON_STATUS_OK && g_client && !g_client->IsUp()) + m_curStatus = ADDON_STATUS_LOST_CONNECTION; - return m_CurStatus; + return m_curStatus; } //-- HasSettings -------------------------------------------------------------- diff --git a/src/pvrclient-mediaportal.cpp b/src/pvrclient-mediaportal.cpp index f51b0b4f..d3888c00 100644 --- a/src/pvrclient-mediaportal.cpp +++ b/src/pvrclient-mediaportal.cpp @@ -49,19 +49,19 @@ int g_iTVServerXBMCBuild = 0; /* TVServerXBMC plugin supported versions */ #define TVSERVERXBMC_MIN_VERSION_STRING "1.1.7.107" #define TVSERVERXBMC_MIN_VERSION_BUILD 107 -#define TVSERVERXBMC_RECOMMENDED_VERSION_STRING "1.2.3.122 till 1.15.0.134" -#define TVSERVERXBMC_RECOMMENDED_VERSION_BUILD 134 +#define TVSERVERXBMC_RECOMMENDED_VERSION_STRING "1.2.3.122 till 1.15.0.136" +#define TVSERVERXBMC_RECOMMENDED_VERSION_BUILD 136 /************************************************************/ /** Class interface */ -cPVRClientMediaPortal::cPVRClientMediaPortal() +cPVRClientMediaPortal::cPVRClientMediaPortal() : + m_state(PVR_CONNECTION_STATE_UNKNOWN) { m_iCurrentChannel = -1; m_bCurrentChannelIsRadio = false; m_iCurrentCard = -1; m_tcpclient = new MPTV::Socket(MPTV::af_unspec, MPTV::pf_inet, MPTV::sock_stream, MPTV::tcp); - m_bConnected = false; m_bStop = true; m_bTimeShiftStarted = false; m_BackendUTCoffset = 0; @@ -72,13 +72,15 @@ cPVRClientMediaPortal::cPVRClientMediaPortal() m_signalStateCounter = 0; m_iSignal = 0; m_iSNR = 0; + + /* Generate the recording life time strings */ + Timer::lifetimeValues = new cLifeTimeValues(); } cPVRClientMediaPortal::~cPVRClientMediaPortal() { XBMC->Log(LOG_DEBUG, "->~cPVRClientMediaPortal()"); - if (m_bConnected) - Disconnect(); + Disconnect(); SAFE_DELETE(Timer::lifetimeValues); SAFE_DELETE(m_tcpclient); SAFE_DELETE(m_genretable); @@ -92,8 +94,10 @@ string cPVRClientMediaPortal::SendCommand(string command) { if ( !m_tcpclient->is_valid() ) { + SetConnectionState(PVR_CONNECTION_STATE_DISCONNECTED); + // Connection lost, try to reconnect - if ( Connect() == ADDON_STATUS_OK ) + if (TryConnect() == ADDON_STATUS_OK) { // Resend the command if (!m_tcpclient->send(command)) @@ -104,59 +108,35 @@ string cPVRClientMediaPortal::SendCommand(string command) } else { - XBMC->Log(LOG_ERROR, "SendCommand2: reconnect failed."); + XBMC->Log(LOG_ERROR, "SendCommand: reconnect failed."); return ""; } } } - string line; + string result; - if ( !m_tcpclient->ReadLine( line ) ) + if ( !m_tcpclient->ReadLine( result ) ) { XBMC->Log(LOG_ERROR, "SendCommand - Failed."); + return ""; } - return line; -} -bool cPVRClientMediaPortal::SendCommand2(string command, vector& lines) -{ - P8PLATFORM::CLockObject critsec(m_mutex); - - if ( !m_tcpclient->send(command) ) + if (result.find("[ERROR]:") != std::string::npos) { - if ( !m_tcpclient->is_valid() ) - { - XBMC->Log(LOG_ERROR, "SendCommand2: connection lost, attempt to reconnect..."); - // Connection lost, try to reconnect - if ( Connect() == ADDON_STATUS_OK ) - { - // Resend the command - if (!m_tcpclient->send(command)) - { - XBMC->Log(LOG_ERROR, "SendCommand2('%s') failed.", command.c_str()); - return false; - } - } - else - { - XBMC->Log(LOG_ERROR, "SendCommand2: reconnect failed."); - return false; - } - } + XBMC->Log(LOG_ERROR, "TVServerKodi error: %s", result.c_str()); } - string result; + return result; +} - if (!m_tcpclient->ReadLine(result)) - { - XBMC->Log(LOG_ERROR, "SendCommand2 - Failed."); - return false; - } - if (result.find("[ERROR]:") != std::string::npos) +bool cPVRClientMediaPortal::SendCommand2(string command, vector& lines) +{ + string result = SendCommand(command); + + if (result.empty()) { - XBMC->Log(LOG_ERROR, "TVServerXBMC error: %s", result.c_str()); return false; } @@ -165,23 +145,53 @@ bool cPVRClientMediaPortal::SendCommand2(string command, vector& lines) return true; } -ADDON_STATUS cPVRClientMediaPortal::Connect() +ADDON_STATUS cPVRClientMediaPortal::TryConnect() { - string result; - /* Open Connection to MediaPortal Backend TV Server via the XBMC TV Server plugin */ XBMC->Log(LOG_INFO, "Mediaportal pvr addon " PVRCLIENT_MEDIAPORTAL_VERSION_STRING " connecting to %s:%i", g_szHostname.c_str(), g_iPort); + PVR_CONNECTION_STATE result = Connect(); + + switch (result) + { + case PVR_CONNECTION_STATE_ACCESS_DENIED: + case PVR_CONNECTION_STATE_UNKNOWN: + case PVR_CONNECTION_STATE_SERVER_MISMATCH: + case PVR_CONNECTION_STATE_VERSION_MISMATCH: + return ADDON_STATUS_PERMANENT_FAILURE; + case PVR_CONNECTION_STATE_DISCONNECTED: + case PVR_CONNECTION_STATE_SERVER_UNREACHABLE: + XBMC->Log(LOG_ERROR, "Could not connect to MediaPortal TV Server backend."); + // Start background thread for connecting to the backend + if (!IsRunning()) + { + XBMC->Log(LOG_INFO, "Waiting for a connection in the background."); + CreateThread(); + } + return ADDON_STATUS_LOST_CONNECTION; + } + + return ADDON_STATUS_OK; +} + +PVR_CONNECTION_STATE cPVRClientMediaPortal::Connect() +{ + P8PLATFORM::CLockObject critsec(m_connectionMutex); + + string result; + if (!m_tcpclient->create()) { XBMC->Log(LOG_ERROR, "Could not connect create socket"); - return ADDON_STATUS_PERMANENT_FAILURE; + SetConnectionState(PVR_CONNECTION_STATE_UNKNOWN); + return PVR_CONNECTION_STATE_UNKNOWN; } + SetConnectionState(PVR_CONNECTION_STATE_CONNECTING); if (!m_tcpclient->connect(g_szHostname, (unsigned short) g_iPort)) { - XBMC->Log(LOG_ERROR, "Could not connect to MediaPortal TV Server backend"); - return ADDON_STATUS_LOST_CONNECTION; + SetConnectionState(PVR_CONNECTION_STATE_SERVER_UNREACHABLE); + return PVR_CONNECTION_STATE_SERVER_UNREACHABLE; } m_tcpclient->set_non_blocking(1); @@ -190,12 +200,16 @@ ADDON_STATUS cPVRClientMediaPortal::Connect() result = SendCommand("PVRclientXBMC:0-1\n"); if (result.length() == 0) - return ADDON_STATUS_UNKNOWN; + { + SetConnectionState(PVR_CONNECTION_STATE_UNKNOWN); + return PVR_CONNECTION_STATE_UNKNOWN; + } if(result.find("Unexpected protocol") != std::string::npos) { XBMC->Log(LOG_ERROR, "TVServer does not accept protocol: PVRclientXBMC:0-1"); - return ADDON_STATUS_UNKNOWN; + SetConnectionState(PVR_CONNECTION_STATE_SERVER_MISMATCH); + return PVR_CONNECTION_STATE_SERVER_MISMATCH; } vector fields; @@ -207,7 +221,8 @@ ADDON_STATUS cPVRClientMediaPortal::Connect() { XBMC->Log(LOG_ERROR, "Your TVServerXBMC version is too old. Please upgrade to '%s' or higher!", TVSERVERXBMC_MIN_VERSION_STRING); XBMC->QueueNotification(QUEUE_ERROR, XBMC->GetLocalizedString(30051), TVSERVERXBMC_MIN_VERSION_STRING); - return ADDON_STATUS_PERMANENT_FAILURE; + SetConnectionState(PVR_CONNECTION_STATE_VERSION_MISMATCH); + return PVR_CONNECTION_STATE_VERSION_MISMATCH; } // Ok, this TVServerXBMC version answers with a version string @@ -215,7 +230,8 @@ ADDON_STATUS cPVRClientMediaPortal::Connect() if( count < 4 ) { XBMC->Log(LOG_ERROR, "Could not parse the TVServerXBMC version string '%s'", fields[1].c_str()); - return ADDON_STATUS_UNKNOWN; + SetConnectionState(PVR_CONNECTION_STATE_VERSION_MISMATCH); + return PVR_CONNECTION_STATE_VERSION_MISMATCH; } // Check for the minimal requirement: 1.1.0.70 @@ -223,7 +239,8 @@ ADDON_STATUS cPVRClientMediaPortal::Connect() { XBMC->Log(LOG_ERROR, "Your TVServerXBMC version '%s' is too old. Please upgrade to '%s' or higher!", fields[1].c_str(), TVSERVERXBMC_MIN_VERSION_STRING); XBMC->QueueNotification(QUEUE_ERROR, XBMC->GetLocalizedString(30050), fields[1].c_str(), TVSERVERXBMC_MIN_VERSION_STRING); - return ADDON_STATUS_PERMANENT_FAILURE; + SetConnectionState(PVR_CONNECTION_STATE_VERSION_MISMATCH); + return PVR_CONNECTION_STATE_VERSION_MISMATCH; } else { @@ -241,19 +258,16 @@ ADDON_STATUS cPVRClientMediaPortal::Connect() snprintf(buffer, 512, "%s:%i", g_szHostname.c_str(), g_iPort); m_ConnectionString = buffer; - m_bConnected = true; + SetConnectionState(PVR_CONNECTION_STATE_CONNECTED); /* Load additional settings */ LoadGenreTable(); LoadCardSettings(); - /* Generate the recording life time strings */ - Timer::lifetimeValues = new cLifeTimeValues(); - /* The pvr addon cannot access XBMC's current locale settings, so just use the system default */ setlocale(LC_ALL, ""); - return ADDON_STATUS_OK; + return PVR_CONNECTION_STATE_CONNECTED; } void cPVRClientMediaPortal::Disconnect() @@ -262,6 +276,11 @@ void cPVRClientMediaPortal::Disconnect() XBMC->Log(LOG_INFO, "Disconnect"); + if (IsRunning()) + { + StopThread(1000); + } + if (m_tcpclient->is_valid() && m_bTimeShiftStarted) { result = SendCommand("IsTimeshifting:\n"); @@ -281,7 +300,7 @@ void cPVRClientMediaPortal::Disconnect() m_tcpclient->close(); - m_bConnected = false; + SetConnectionState(PVR_CONNECTION_STATE_DISCONNECTED); } /* IsUp() @@ -291,20 +310,48 @@ void cPVRClientMediaPortal::Disconnect() */ bool cPVRClientMediaPortal::IsUp() { - if(!m_tcpclient->is_valid()) + if (m_state == PVR_CONNECTION_STATE_CONNECTED) { - if( Connect() != ADDON_STATUS_OK ) - { - XBMC->Log(LOG_DEBUG, "Backend not connected!"); - return false; - } + return true; + } + else + { + return false; } - return true; } -void* cPVRClientMediaPortal::Process(void*) +void* cPVRClientMediaPortal::Process(void) { - XBMC->Log(LOG_DEBUG, "->Process() Not yet implemented"); + XBMC->Log(LOG_DEBUG, "Background thread started."); + + bool keepWaiting = true; + + while (!IsStopped() && keepWaiting) + { + PVR_CONNECTION_STATE result = Connect(); + + switch (result) + { + case PVR_CONNECTION_STATE_ACCESS_DENIED: + case PVR_CONNECTION_STATE_UNKNOWN: + case PVR_CONNECTION_STATE_SERVER_MISMATCH: + case PVR_CONNECTION_STATE_VERSION_MISMATCH: + keepWaiting = false; + case PVR_CONNECTION_STATE_CONNECTED: + keepWaiting = false; + default: + break; + } + + if (keepWaiting) + { + // Wait for 1 minute before re-trying + usleep(60000000); + } + } + + XBMC->Log(LOG_DEBUG, "Background thread finished."); + return NULL; } @@ -985,9 +1032,7 @@ PVR_ERROR cPVRClientMediaPortal::GetRecordings(ADDON_HANDLE handle) tag.iEpisodeNumber = recording.GetEpisodeNumber(); tag.iSeriesNumber = recording.GetSeriesNumber(); tag.iEpgEventId = EPG_TAG_INVALID_UID; - - /* TODO: PVR API 5.1.0: Implement this */ - tag.channelType = PVR_RECORDING_CHANNEL_TYPE_UNKNOWN; + tag.channelType = recording.GetChannelType(); strDirectory = recording.Directory(); if (strDirectory.length() > 0) @@ -1283,6 +1328,9 @@ PVR_ERROR cPVRClientMediaPortal::GetTimerTypes(PVR_TIMER_TYPE types[], int *size int& count = *size; // the amount of filled items in the types[] array count = 0; + if (Timer::lifetimeValues == NULL) + return PVR_ERROR_FAILED; + if (count > maxsize) return PVR_ERROR_NO_ERROR; @@ -1971,7 +2019,7 @@ PVR_ERROR cPVRClientMediaPortal::SignalStatus(PVR_SIGNAL_STATUS &signalStatus) // These URLs are stored in the field PVR_RECORDINGINFO_OLD.stream_url bool cPVRClientMediaPortal::OpenRecordedStream(const PVR_RECORDING &recording) { - XBMC->Log(LOG_NOTICE, "OpenRecordedStream (id=%s)", recording.strRecordingId); + XBMC->Log(LOG_NOTICE, "OpenRecordedStream (id=%s, RTSP=%d)", recording.strRecordingId, (g_bUseRTSP ? "true" : "false")); m_bTimeShiftStarted = false; @@ -1980,7 +2028,7 @@ bool cPVRClientMediaPortal::OpenRecordedStream(const PVR_RECORDING &recording) if (g_eStreamingMethod == ffmpeg) { - XBMC->Log(LOG_ERROR, "Addon is in 'ffmpeg' mode. XBMC should play the RTSP url directly. Please reset your XBMC PVR database!"); + XBMC->Log(LOG_ERROR, "Addon is in 'ffmpeg' mode. Kodi should play the RTSP url directly. Please reset your Kodi PVR database!"); return false; } @@ -1997,46 +2045,63 @@ bool cPVRClientMediaPortal::OpenRecordedStream(const PVR_RECORDING &recording) snprintf(command, 256, "GetRecordingInfo:%s|True\n", recording.strRecordingId); result = SendCommand(command); - if(result.length() > 0) + if (result.empty()) { - cRecording myrecording; - if (myrecording.ParseLine(result)) - { - XBMC->Log(LOG_NOTICE, "RECORDING: %s", result.c_str() ); + XBMC->Log(LOG_ERROR, "Backend command '%s' returned a zero-length answer.", command); + return false; + } - if (!g_bUseRTSP) - { - recfile = myrecording.FilePath(); - } - else + cRecording myrecording; + if (!myrecording.ParseLine(result)) + { + XBMC->Log(LOG_ERROR, "Parsing result from '%s' command failed. Result='%s'.", command, result.c_str()); + return false; + } + + XBMC->Log(LOG_NOTICE, "RECORDING: %s", result.c_str() ); + if (!g_bUseRTSP) + { + recfile = myrecording.FilePath(); + if (recfile.length() == 0) + { + XBMC->Log(LOG_ERROR, "Backend returned an empty recording filename for recording id %s.", recording.strRecordingId); + recfile = myrecording.Stream(); + if (recfile.length() > 0) { - recfile = myrecording.Stream(); + XBMC->Log(LOG_NOTICE, "Trying to use the recording RTSP stream URL name instead."); } } } else { - XBMC->Log(LOG_ERROR, "Backend command '%s' returned a zero-length answer", command); + recfile = myrecording.Stream(); + if (recfile.length() == 0) + { + XBMC->Log(LOG_ERROR, "Backend returned an empty RTSP stream URL for recording id %s.", recording.strRecordingId); + recfile = myrecording.FilePath(); + if (recfile.length() > 0) + { + XBMC->Log(LOG_NOTICE, "Trying to use the filename instead."); + } + } } - if (recfile.length() > 0) - { - m_tsreader = new CTsReader(); - m_tsreader->SetCardSettings(&m_cCards); - if ( m_tsreader->Open(recfile.c_str()) != S_OK ) - return false; - else - return true; - } - else + if (recfile.empty()) { - XBMC->Log(LOG_ERROR, "Recording playback not possible. Backend returned empty filename or stream URL for recording id %s", recording.strRecordingId ); + XBMC->Log(LOG_ERROR, "Recording playback not possible. Backend returned an empty filename and no RTSP stream URL for recording id %s", recording.strRecordingId); XBMC->QueueNotification(QUEUE_ERROR, XBMC->GetLocalizedString(30052)); // Tell XBMC to re-read the list with recordings to remove deleted/non-existing recordings as a result of backend auto-deletion. PVR->TriggerRecordingUpdate(); + return false; } - return false; + // We have a recording file name or RTSP url, time to open it... + m_tsreader = new CTsReader(); + m_tsreader->SetCardSettings(&m_cCards); + if ( m_tsreader->Open(recfile.c_str()) != S_OK ) + return false; + + return true; } void cPVRClientMediaPortal::CloseRecordedStream(void) @@ -2184,3 +2249,15 @@ void cPVRClientMediaPortal::LoadCardSettings() m_cCards.ParseLines(lines); } } + +void cPVRClientMediaPortal::SetConnectionState(PVR_CONNECTION_STATE newState) +{ + if (newState != m_state) + { + XBMC->Log(LOG_DEBUG, "Connection state change (%d -> %d)", m_state, newState); + m_state = newState; + + /* Notify connection state change (callback!) */ + PVR->ConnectionStateChange(GetConnectionString(), m_state, NULL); + } +} diff --git a/src/pvrclient-mediaportal.h b/src/pvrclient-mediaportal.h index 6a02feab..e7320046 100644 --- a/src/pvrclient-mediaportal.h +++ b/src/pvrclient-mediaportal.h @@ -28,6 +28,7 @@ #include "Cards.h" #include "epg.h" #include "p8-platform/threads/mutex.h" +#include "p8-platform/threads/threads.h" /* Use a forward declaration here. Including RTSPClient.h via TSReader.h at this point gives compile errors */ namespace MPTV @@ -35,18 +36,15 @@ namespace MPTV class CTsReader; } -class cPVRClientMediaPortal: public P8PLATFORM::PreventCopy +class cPVRClientMediaPortal: public P8PLATFORM::PreventCopy, public P8PLATFORM::CThread { public: /* Class interface */ cPVRClientMediaPortal(); ~cPVRClientMediaPortal(); - /* TVServerKodi Listening Thread */ - static void* Process(void*); - /* Server handling */ - ADDON_STATUS Connect(); + ADDON_STATUS TryConnect(); void Disconnect(); bool IsUp(); @@ -113,14 +111,20 @@ class cPVRClientMediaPortal: public P8PLATFORM::PreventCopy MPTV::Socket *m_tcpclient; private: + /* TVServerKodi Listening Thread */ + void* Process(void); + PVR_CONNECTION_STATE Connect(); + + bool GetChannel(unsigned int number, PVR_CHANNEL &channeldata); void LoadGenreTable(void); void LoadCardSettings(void); + void SetConnectionState(PVR_CONNECTION_STATE newState); int m_iCurrentChannel; int m_iCurrentCard; bool m_bCurrentChannelIsRadio; - bool m_bConnected; + PVR_CONNECTION_STATE m_state; bool m_bStop; bool m_bTimeShiftStarted; std::string m_ConnectionString; @@ -132,6 +136,7 @@ class cPVRClientMediaPortal: public P8PLATFORM::PreventCopy CCards m_cCards; CGenreTable* m_genretable; P8PLATFORM::CMutex m_mutex; + P8PLATFORM::CMutex m_connectionMutex; int64_t m_iLastRecordingUpdate; MPTV::CTsReader* m_tsreader; std::map m_channelNames; diff --git a/src/recordings.cpp b/src/recordings.cpp index cef61839..0b1cb498 100644 --- a/src/recordings.cpp +++ b/src/recordings.cpp @@ -30,7 +30,8 @@ using namespace std; using namespace ADDON; -cRecording::cRecording() +cRecording::cRecording() : + m_channelType(TvDatabase::ChannelType::Unknown) { m_duration = 0; m_Index = -1; @@ -85,6 +86,7 @@ bool cRecording::ParseLine(const std::string& data) //[18] isrecording (bool) //[19] timesWatched (int) //[20] stopTime (int) + //[21] channelType (int) m_Index = atoi(fields[0].c_str()); @@ -170,6 +172,14 @@ bool cRecording::ParseLine(const std::string& data) if (fields.size() >= 21) // Since TVServerXBMC 1.2.x.121 { m_lastPlayedPosition = atoi( fields[20].c_str() ); + if (fields.size() >= 22) // Since TVServerKodi 1.15.136 + { + m_channelType = atoi(fields[21].c_str()); + } + else + { + m_channelType = TvDatabase::ChannelType::Unknown; + } } } } @@ -178,6 +188,7 @@ bool cRecording::ParseLine(const std::string& data) } else { + XBMC->Log(LOG_ERROR, "Recording information has not enough fields. At least 9 fields expected, got only %d fields.", fields.size()); return false; } } @@ -330,3 +341,18 @@ int cRecording::GetEpisodeNumber(void) const return atoi(m_episodeNumber.c_str()); } + +PVR_RECORDING_CHANNEL_TYPE cRecording::GetChannelType(void) const +{ + switch (m_channelType) + { + case TvDatabase::ChannelType::Tv: + return PVR_RECORDING_CHANNEL_TYPE_TV; + break; + case TvDatabase::ChannelType::Radio: + return PVR_RECORDING_CHANNEL_TYPE_RADIO; + break; + default: + return PVR_RECORDING_CHANNEL_TYPE_UNKNOWN; + } +} diff --git a/src/recordings.h b/src/recordings.h index 8709d783..920478c1 100644 --- a/src/recordings.h +++ b/src/recordings.h @@ -23,7 +23,7 @@ #include "Cards.h" #include "GenreTable.h" #include "DateTime.h" - +#include "channels.h" #define DEFAULTFRAMESPERSECOND 25.0 #define MAXPRIORITY 99 @@ -44,9 +44,9 @@ class cRecording MPTV::CDateTime m_startTime; MPTV::CDateTime m_endTime; int m_duration; - std::string m_title; // Title of this event - std::string m_description; // Description of this event - std::string m_episodeName; // Short description of this event (typically the episode name in case of a series) + std::string m_title; ///< Title of this event + std::string m_description; ///< Description of this event + std::string m_episodeName; ///< Short description of this event (typically the episode name in case of a series) std::string m_seriesNumber; std::string m_episodeNumber; std::string m_episodePart; @@ -61,6 +61,7 @@ class cRecording CGenreTable* m_genretable; int m_timesWatched; int m_lastPlayedPosition; + int m_channelType; public: cRecording(); @@ -83,6 +84,7 @@ class cRecording int LastPlayedPosition(void) const { return m_lastPlayedPosition; } bool IsRecording(void) {return m_isRecording; } int ChannelID(void) const { return m_channelID; } + PVR_RECORDING_CHANNEL_TYPE GetChannelType(void) const; /** * \brief Filename of this recording with full path (at server side)