Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement subscription conflict handling #79

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pvr.hts/addon.xml.in
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<c-pluff version="0.1"/>
<import addon="xbmc.pvr" version="4.1.0"/>
<import addon="xbmc.codec" version="1.0.1"/>
<import addon="kodi.guilib" version="5.8.0"/>
</requires>
<extension
point="xbmc.pvrclient"
Expand Down
40 changes: 40 additions & 0 deletions pvr.hts/resources/language/resource.language.en_gb/strings.po
Original file line number Diff line number Diff line change
Expand Up @@ -241,3 +241,43 @@ msgstr ""
msgctxt "#30502"
msgid "Streaming profile %s is not available"
msgstr ""

#empty strings from id 30503 to 30549

#. Streaming conflict management

msgctxt "#30550"
msgid "TV conflict"
msgstr ""

msgctxt "#30551"
msgid "All adapters are in use."
msgstr ""

msgctxt "#30552"
msgid "To keep watching TV, you can interrupt the lowest priority service."
msgstr ""

msgctxt "#30553"
msgid "To watch TV, you can interrupt the lowest priority service."
msgstr ""

msgctxt "#30554"
msgid "This can be an active recording or an other TV client."
msgstr ""

msgctxt "#30555"
msgid "Do nothing"
msgstr ""

msgctxt "#30556"
msgid "Interrupt service"
msgstr ""

msgctxt "#30557"
msgid "Interrupting service... %i%%"
msgstr ""

msgctxt "#30558"
msgid "Streaming conflict management"
msgstr ""
1 change: 1 addition & 0 deletions pvr.hts/resources/settings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

<setting label="30500" type="lsep"/>
<setting id="streaming_profile" type="text" label="30501" default="" />
<setting id="streaming_conflict" type="bool" label="30558" default="false"/>

<setting label="30400" type="lsep"/>
<setting id="pretuner_enabled" type="bool" label="30403" default="false" />
Expand Down
18 changes: 11 additions & 7 deletions src/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@

#include "client.h"
#include "kodi/xbmc_pvr_dll.h"
#include "kodi/libKODI_guilib.h"
#include "platform/util/util.h"
#include "Tvheadend.h"
#include "tvheadend/Settings.h"
Expand All @@ -45,11 +44,12 @@ bool m_bAlertHtspVersionMismatch = true;
* Globals
*/
CMutex g_mutex;
CHelper_libXBMC_addon *XBMC = NULL;
CHelper_libXBMC_pvr *PVR = NULL;
CHelper_libXBMC_codec *CODEC = NULL;
PVR_MENUHOOK *menuHook = NULL;
CTvheadend *tvh = NULL;
CHelper_libXBMC_addon *XBMC = NULL;
CHelper_libXBMC_pvr *PVR = NULL;
CHelper_libXBMC_codec *CODEC = NULL;
CHelper_libKODI_guilib *GUI = NULL;
PVR_MENUHOOK *menuHook = NULL;
CTvheadend *tvh = NULL;

/* **************************************************************************
* ADDON setup
Expand All @@ -71,13 +71,15 @@ ADDON_STATUS ADDON_Create(void* hdl, void* _unused(props))
XBMC = new CHelper_libXBMC_addon;
CODEC = new CHelper_libXBMC_codec;
PVR = new CHelper_libXBMC_pvr;
GUI = new CHelper_libKODI_guilib;

if (!XBMC->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;
}

Expand Down Expand Up @@ -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;
}
Expand All @@ -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;
}
Expand Down
2 changes: 2 additions & 0 deletions src/client.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 6 additions & 0 deletions src/tvheadend/Settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const int Settings::DEFAULT_PRETUNER_CLOSEDELAY = 10; // secs
const int Settings::DEFAULT_AUTOREC_MAXDIFF = 15; // mins. Maximum difference between real and approximate start time for auto recordings
const int Settings::DEFAULT_APPROX_TIME = 0; // mins
const std::string Settings::DEFAULT_STREAMING_PROFILE = "";
const bool Settings::DEFAULT_STREAMING_CONFLICT = false;

void Settings::ReadSettings()
{
Expand Down Expand Up @@ -70,6 +71,9 @@ void Settings::ReadSettings()

/* Streaming */
SetStreamingProfile(ReadStringSetting("streaming_profile", DEFAULT_STREAMING_PROFILE));

/* Subscription conflict management */
SetStreamingConflict(ReadBoolSetting("streaming_conflict", DEFAULT_STREAMING_CONFLICT));
}

ADDON_STATUS Settings::SetSetting(const std::string &key, const void *value)
Expand Down Expand Up @@ -115,6 +119,8 @@ ADDON_STATUS Settings::SetSetting(const std::string &key, const void *value)
/* Streaming */
else if (key == "streaming_profile")
return SetStringSetting(GetStreamingProfile(), value);
else if (key == "streaming_conflict")
return SetBoolSetting(GetStreamingConflict(), value);
else
{
tvherror("Settings::SetSetting - unknown setting '%s'", key.c_str());
Expand Down
4 changes: 4 additions & 0 deletions src/tvheadend/Settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ namespace tvheadend {
static const int DEFAULT_AUTOREC_MAXDIFF; // mins. Maximum difference between real and approximate start time for auto recordings
static const int DEFAULT_APPROX_TIME; // mins
static const std::string DEFAULT_STREAMING_PROFILE;
static const bool DEFAULT_STREAMING_CONFLICT;

/**
* Singleton getter for the instance
Expand Down Expand Up @@ -86,6 +87,7 @@ namespace tvheadend {
bool GetAutorecApproxTime() const { return m_bAutorecApproxTime; }
int GetAutorecMaxDiff() const { return m_iPreTunerCloseDelay; }
std::string GetStreamingProfile() const { return m_strStreamingProfile; }
bool GetStreamingConflict() const { return m_bStreamingConflict; }

private:
Settings()
Expand Down Expand Up @@ -124,6 +126,7 @@ namespace tvheadend {
void SetAutorecApproxTime(bool value) { m_bAutorecApproxTime = value; }
void SetAutorecMaxDiff(int value) { m_iAutorecMaxDiff = value; }
void SetStreamingProfile(const std::string &value) { m_strStreamingProfile = value; }
void SetStreamingConflict(bool value) { m_bStreamingConflict = value; }

/**
* Read/Set values according to definition in settings.xml
Expand Down Expand Up @@ -151,6 +154,7 @@ namespace tvheadend {
bool m_bAutorecApproxTime;
int m_iAutorecMaxDiff;
std::string m_strStreamingProfile;
bool m_bStreamingConflict;
};

}
138 changes: 134 additions & 4 deletions src/tvheadend/Subscription.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ Subscription::Subscription(CHTSPConnection &conn) :
m_weight(SUBSCRIPTION_WEIGHT_NORMAL),
m_id(0),
m_state(SUBSCRIPTION_STOPPED),
m_prevState(SUBSCRIPTION_STOPPED),
m_startTime(time(NULL)),
m_conn(conn)
{
}
Expand Down Expand Up @@ -95,9 +97,19 @@ eSubsriptionState Subscription::GetState() const
return m_state;
}

eSubsriptionState Subscription::GetPrevState() const
{
CLockObject lock(m_mutex);
return m_prevState;
}

void Subscription::SetState(eSubsriptionState state)
{
CLockObject lock(m_mutex);
if (state == m_state)
return;

m_prevState = m_state;
m_state = state;
}

Expand All @@ -113,6 +125,18 @@ void Subscription::SetProfile(const std::string &profile)
m_profile = profile;
}

time_t Subscription::GetStartTime() const
{
CLockObject lock(m_mutex);
return m_startTime;
}

void Subscription::SetStartTime(time_t time)
{
CLockObject lock(m_mutex);
m_startTime = time;
}

void Subscription::SendSubscribe(uint32_t channelId, uint32_t weight, bool restart)
{
/* We don't want to change anything when restarting a subscription */
Expand All @@ -122,6 +146,7 @@ void Subscription::SendSubscribe(uint32_t channelId, uint32_t weight, bool resta
SetWeight(weight);
SetId(GetNextId());
SetSpeed(1000); //set back to normal
SetStartTime(time(NULL)); // now
}

/* Build message */
Expand Down Expand Up @@ -150,6 +175,10 @@ void Subscription::SendSubscribe(uint32_t channelId, uint32_t weight, bool resta
htsmsg_destroy(m);

SetState(SUBSCRIPTION_STARTING);

/* As this might be a pre- posttuning subscription */
UpdateStateFromWeight();

tvhdebug("demux successfully subscribed to channel id %d, subscription id %d", GetChannelId(), GetId());
}

Expand Down Expand Up @@ -232,17 +261,31 @@ void Subscription::SendWeight(uint32_t weight)
}
if (m)
htsmsg_destroy(m);

/* As this might be a pre- posttuning subscription now */
UpdateStateFromWeight();
}

void Subscription::ParseSubscriptionStatus ( htsmsg_t *m )
void Subscription::UpdateStateFromWeight()
{
/* Not for preTuning and postTuning subscriptions */
if (GetWeight() == static_cast<uint32_t>(SUBSCRIPTION_WEIGHT_PRETUNING) ||
GetWeight() == static_cast<uint32_t>(SUBSCRIPTION_WEIGHT_POSTTUNING))
{
SetState(SUBSCRIPTION_PREPOSTTUNING);
return;
}
else if (GetState() == SUBSCRIPTION_PREPOSTTUNING)
{
/* Switched from pre- posttuning to active, initiate a virtual start */
SetState(SUBSCRIPTION_STARTING);
SetStartTime(time(NULL));
}
}

void Subscription::ParseSubscriptionStatus ( htsmsg_t *m )
{
/* Not for preTuning and postTuning subscriptions */
if (GetState() == SUBSCRIPTION_PREPOSTTUNING)
return;

const char *status = htsmsg_get_str(m, "status");

Expand All @@ -264,7 +307,13 @@ void Subscription::ParseSubscriptionStatus ( htsmsg_t *m )
else if (!strcmp("userLimit", error))
SetState(SUBSCRIPTION_USERLIMIT);
else if (!strcmp("noFreeAdapter", error))
SetState(SUBSCRIPTION_NOFREEADAPTER);
{
/* If streaming conflict management enabled */
if (Settings::GetInstance().GetStreamingConflict())
HandleConflict(); // no free adapter, AKA conflict
else
SetState(SUBSCRIPTION_NOFREEADAPTER);
}
else if (!strcmp("tuningFailed", error))
SetState(SUBSCRIPTION_TUNINGFAILED);
else if (!strcmp("userAccess", error))
Expand Down Expand Up @@ -311,6 +360,87 @@ void Subscription::ShowStateNotification(void)
XBMC->QueueNotification(ADDON::QUEUE_WARNING, XBMC->GetLocalizedString(30456));
}

void Subscription::HandleConflict(void)
{
if (GetState() != SUBSCRIPTION_NOFREEADAPTER_HANDLING)
SetState(SUBSCRIPTION_NOFREEADAPTER);

/*
* Conflict case 1: (GetPrevState() == SUBSCRIPTION_RUNNING)
* Subscription was running before, but the adapter got stolen by an other subscription
* Ask user if he wants to continue watching by interrupting an other subscription (weight based)
*
* Conflict case 2: (GetPrevState() == SUBSCRIPTION_STARTING)
* No free adapter found to start this channel from the beginning on
* Ask user if he wants to start watching by interrupting an other subscription (weight based)
* 'DIALOG_NOSTART_DELAY' is to prevent the dialog from popping up when zapping
*/

if (GetPrevState() == SUBSCRIPTION_RUNNING ||
(GetPrevState() == SUBSCRIPTION_STARTING && GetStartTime() + DIALOG_NOSTART_DELAY < time(NULL)))
{
std::thread(&Subscription::ShowConflictDialog, this).detach();
}
}

void Subscription::ShowConflictDialog(void)
{
tvhinfo("demux conflict dialog: open, state: %i, previous state: %i, weight: %i ,subscription id: %i", GetState(), GetPrevState(), GetWeight(), GetId());

if (GetWeight() >= SUBSCRIPTION_WEIGHT_MAX)
return;

/* Save the initial subscription id */
uint32_t initialId = GetId();

/* Make a copy before changing the state */
eSubsriptionState prevState = GetPrevState();

/* Mark this conflict as handling */
SetState(SUBSCRIPTION_NOFREEADAPTER_HANDLING);

/*
* Dialog Heading: TV conflict
* All adapters are in use.
* To keep watching TV, you can interrupt the lowest priority service.
* This can be an active recording or an other TV client.
*
* Interrupt service <--> Do nothing
*/

bool bDialogInterrrupt = GUI->Dialog_YesNo_ShowAndGetInput(
XBMC->GetLocalizedString(30550), XBMC->GetLocalizedString(30551),
XBMC->GetLocalizedString(prevState == SUBSCRIPTION_STARTING ? 30553 : 30552),
XBMC->GetLocalizedString(30554), XBMC->GetLocalizedString(30555), XBMC->GetLocalizedString(30556));

if (bDialogInterrrupt)
{
while (GetWeight() < SUBSCRIPTION_WEIGHT_MAX)
{
/* Channel changed or conflict solved */
if (GetId() != initialId || GetState() != SUBSCRIPTION_NOFREEADAPTER_HANDLING)
break;

/* Gradually increase weight between min and max */
if (GetWeight() < SUBSCRIPTION_WEIGHT_MIN)
SendWeight(SUBSCRIPTION_WEIGHT_MIN);
else if (GetWeight() + SUBSCRIPTION_WEIGHT_STEPSIZE > SUBSCRIPTION_WEIGHT_MAX)
SendWeight(SUBSCRIPTION_WEIGHT_MAX);
else
SendWeight(GetWeight() + SUBSCRIPTION_WEIGHT_STEPSIZE);

XBMC->QueueNotification(ADDON::QUEUE_INFO, XBMC->GetLocalizedString(30557),
(GetWeight()-SUBSCRIPTION_WEIGHT_MIN)/((SUBSCRIPTION_WEIGHT_MAX-SUBSCRIPTION_WEIGHT_MIN)/100)); // Interrupting service... %i%

sleep(1);
}
}

/* Handling done and still not running, set state back to original */
if (GetState() == SUBSCRIPTION_NOFREEADAPTER_HANDLING)
SetState(SUBSCRIPTION_NOFREEADAPTER);
}

uint32_t Subscription::GetNextId()
{
static uint32_t id = 0;
Expand Down
Loading