diff --git a/pvr.hts/addon.xml.in b/pvr.hts/addon.xml.in index e2b27340..fc54ab35 100644 --- a/pvr.hts/addon.xml.in +++ b/pvr.hts/addon.xml.in @@ -8,6 +8,7 @@ + RegisterMe(hdl) || + if (!XBMC->RegisterMe(hdl) || !GUI->RegisterMe(hdl) || !CODEC->RegisterMe(hdl) || !PVR->RegisterMe(hdl)) { SAFE_DELETE(PVR); SAFE_DELETE(CODEC); SAFE_DELETE(XBMC); + SAFE_DELETE(GUI); return ADDON_STATUS_PERMANENT_FAILURE; } @@ -107,6 +109,7 @@ ADDON_STATUS ADDON_Create(void* hdl, void* _unused(props)) SAFE_DELETE(PVR); SAFE_DELETE(CODEC); SAFE_DELETE(XBMC); + SAFE_DELETE(GUI); return ADDON_STATUS_LOST_CONNECTION; } @@ -133,6 +136,7 @@ void ADDON_Destroy() SAFE_DELETE(PVR); SAFE_DELETE(CODEC); SAFE_DELETE(XBMC); + SAFE_DELETE(GUI); SAFE_DELETE(menuHook); m_CurStatus = ADDON_STATUS_UNKNOWN; } diff --git a/src/client.h b/src/client.h index 3a5f950e..8196a07f 100644 --- a/src/client.h +++ b/src/client.h @@ -25,10 +25,12 @@ #include "kodi/libXBMC_addon.h" #include "kodi/libXBMC_pvr.h" #include "kodi/libXBMC_codec.h" +#include "kodi/libKODI_guilib.h" extern ADDON::CHelper_libXBMC_addon* XBMC; extern CHelper_libXBMC_pvr* PVR; extern CHelper_libXBMC_codec* CODEC; +extern CHelper_libKODI_guilib* GUI; /* timer type ids */ #define TIMER_ONCE_MANUAL (PVR_TIMER_TYPE_NONE + 1) diff --git a/src/tvheadend/Subscription.cpp b/src/tvheadend/Subscription.cpp index 9c94ad66..a0f63aa0 100644 --- a/src/tvheadend/Subscription.cpp +++ b/src/tvheadend/Subscription.cpp @@ -227,6 +227,10 @@ void Subscription::ParseSubscriptionStatus ( htsmsg_t *m ) return; } + /* Switched from preTuning or postTuning to an active subscription, initiate a virtual start */ + if (GetState() == SUBSCRIPTION_PREPOSTTUNING) + SetState(SUBSCRIPTION_STARTING); + const char *status = htsmsg_get_str(m, "status"); if (status != NULL) @@ -247,7 +251,22 @@ void Subscription::ParseSubscriptionStatus ( htsmsg_t *m ) else if (!strcmp("userLimit", error)) SetState(SUBSCRIPTION_USERLIMIT); else if (!strcmp("noFreeAdapter", error)) - SetState(SUBSCRIPTION_NOFREEADAPTER); + { + /* No free adapter, AKA subscription conflict */ + if (GetState() == SUBSCRIPTION_RUNNING || + GetState() == SUBSCRIPTION_STARTING) + { + /* Show conflict dialog only if it was running before or if it's a new subscription */ + /* NOTE: the dialog will set the state to 'SUBSCRIPTION_NOFREEADAPTER' so we don't have to do this here */ + std::thread(&Subscription::ShowConflictDialog, this).detach(); + } + else + { + /* Do not overwrite 'SUBSCRIPTION_NOFREEADAPTER_SOLVE_FAILED' */ + if (GetState() != SUBSCRIPTION_NOFREEADAPTER_SOLVE_FAILED) + SetState(SUBSCRIPTION_NOFREEADAPTER); + } + } else if (!strcmp("tuningFailed", error)) SetState(SUBSCRIPTION_TUNINGFAILED); else if (!strcmp("userAccess", error)) @@ -292,6 +311,106 @@ void Subscription::ShowStateNotification(void) XBMC->QueueNotification(ADDON::QUEUE_WARNING, XBMC->GetLocalizedString(30455)); else if (GetState() == SUBSCRIPTION_UNKNOWN) XBMC->QueueNotification(ADDON::QUEUE_WARNING, XBMC->GetLocalizedString(30456)); + else if (GetState() == SUBSCRIPTION_NOFREEADAPTER_SOLVE_FAILED) + XBMC->QueueNotification(ADDON::QUEUE_WARNING, XBMC->GetLocalizedString(30457)); +} + +void Subscription::ShowConflictDialog(void) +{ + tvhdebug("demux conflict dialog: open, state: %i, subscription id: %i", GetState(), GetId()); + + /* Save the initial subscription id */ + uint32_t initialId = GetId(); + + /* Save the initial subscription state */ + eSubsriptionState initialState = GetState(); + + /* As we saved the initial state, we can change this now */ + SetState(SUBSCRIPTION_NOFREEADAPTER); + + /* + * Conflict case 1: (initialState == SUBSCRIPTION_RUNNING) + * Subscription was running before, but the adapter got stolen by an other subscription + * Ask user if he wants to continue watching by hijacking an adapter back (increase weight) + * + * Conflict case 2: (initialState == SUBSCRIPTION_STARTING) + * No free adapter found to start this channel from the beginning on + * Ask user if he wants to hijack an adapter (increase weight) after 'DIALOG_NOSTART_DELAY' seconds of idling + * This time of idling is needed to prevent the dialog from popping up when zapping + */ + + if (initialState == SUBSCRIPTION_STARTING) + { + for (int i = 0; i < DIALOG_NOSTART_DELAY; i++) + { + sleep(1); + + /* Break if the conflict isn't valid anymore */ + if (!IsConflictStillValid(initialId)) + return; + } + } + + bool bDialogForceStart = GUI->Dialog_YesNo_ShowAndGetInput( + XBMC->GetLocalizedString(30500), XBMC->GetLocalizedString(initialState == SUBSCRIPTION_STARTING ? 30450 : 30501), + XBMC->GetLocalizedString(30502), XBMC->GetLocalizedString(30503), + XBMC->GetLocalizedString(30504), XBMC->GetLocalizedString(30505)); + + if (bDialogForceStart) + { + while (GetWeight() <= SUBSCRIPTION_WEIGHT_MAX) + { + /* Break if the conflict isn't valid anymore */ + if (!IsConflictStillValid(initialId)) + return; + + /* Set first to minimum and then increase in steps */ + if (GetWeight() < SUBSCRIPTION_WEIGHT_MIN) + SendWeight(SUBSCRIPTION_WEIGHT_MIN); + else + SendWeight(GetWeight() + SUBSCRIPTION_WEIGHT_STEPSIZE); + + sleep(1); + } + + /* Still not running, we failed to solve this conflict */ + if (GetState() != SUBSCRIPTION_RUNNING) + SetState(SUBSCRIPTION_NOFREEADAPTER_SOLVE_FAILED); + } +} + +bool Subscription::IsConflictStillValid(uint32_t id) +{ + /* Check if the current id is different -> channel was switched meanwhile*/ + if (GetId() != id) + { + tvhinfo("demux conflict dialog: abort, channel was switched"); + return false; + } + + /* Check if the conflict was solved meanwhile */ + if (GetState() == SUBSCRIPTION_RUNNING) + { + tvhinfo("demux conflict dialog: abort, conflict was solved!"); + return false; + } + + /* Check if the state was changed meanwhile */ + if (GetState() != SUBSCRIPTION_NOFREEADAPTER) + { + tvhinfo("demux conflict dialog: abort, subscription state was changed"); + return false; + } + + /* Check if the subscription changed to preTuning or postTuning meanwhile */ + if (GetWeight() == static_cast(SUBSCRIPTION_WEIGHT_PRETUNING) || + GetWeight() == static_cast(SUBSCRIPTION_WEIGHT_POSTTUNING)) + { + tvhinfo("demux conflict dialog: abort, subscription was changed to pre or posttuning"); + return false; + } + + return true; } uint32_t Subscription::GetNextId() diff --git a/src/tvheadend/Subscription.h b/src/tvheadend/Subscription.h index cea83aa4..d2365a4e 100644 --- a/src/tvheadend/Subscription.h +++ b/src/tvheadend/Subscription.h @@ -22,6 +22,7 @@ */ #include "platform/threads/mutex.h" +#include extern "C" { @@ -43,12 +44,17 @@ namespace tvheadend SUBSCRIPTION_WEIGHT_SERVERCONF = 0, }; + /* Used when gradually increasing the subscription weight to solve a conflict */ + static const int SUBSCRIPTION_WEIGHT_MIN = 50; + static const int SUBSCRIPTION_WEIGHT_MAX = 600; + static const int SUBSCRIPTION_WEIGHT_STEPSIZE = 50; + enum eSubsriptionState { SUBSCRIPTION_STOPPED = 0, /* subscription is stopped or not started yet */ SUBSCRIPTION_STARTING = 1, /* subscription is starting */ SUBSCRIPTION_RUNNING = 2, /* subscription is running normal */ - SUBSCRIPTION_NOFREEADAPTER = 3, /* subscription has no free adapter to use */ + SUBSCRIPTION_NOFREEADAPTER = 3, /* subscription has no free adapter to use, AKA subscription conflict */ SUBSCRIPTION_SCRAMBLED = 4, /* subscription is not running because the channel is scrambled */ SUBSCRIPTION_NOSIGNAL = 5, /* subscription is not running because of a weak/no input signal */ SUBSCRIPTION_TUNINGFAILED = 6, /* subscription could not be started because of a tuning error */ @@ -56,9 +62,11 @@ namespace tvheadend SUBSCRIPTION_NOACCESS = 8, /* we have no rights to watch this channel */ SUBSCRIPTION_UNKNOWN = 9, /* subscription state is unknown, also used for pretuning and posttuning subscriptions */ SUBSCRIPTION_PREPOSTTUNING = 10, /* used for pre and posttuning subscriptions (we do not care what the actual state is) */ + SUBSCRIPTION_NOFREEADAPTER_SOLVE_FAILED = 11, /* our subscription was in conflict (=SUBSCRIPTION_NOFREEADAPTER) and we tried to so solve it with increasing the weight, but we failed */ }; - static const int PACKET_QUEUE_DEPTH = 2000000; + static const int PACKET_QUEUE_DEPTH = 2000000; + static const int DIALOG_NOSTART_DELAY = 5; class Subscription { @@ -124,6 +132,19 @@ namespace tvheadend */ void ShowStateNotification(); + /** + * Show a dialog to the user on a subscription conflict + * Ask him to increase the subscription weight as this might solve the conflict + */ + void ShowConflictDialog(); + + /** + * Check if the subscription is in conflict and that the conflict is still valid + * @param id the subscription id of the subscription to check + * @return true if the conflict is still valid, false otherwise + */ + bool IsConflictStillValid(uint32_t id); + /** * Get the next unique subscription Id */