Skip to content
1 change: 0 additions & 1 deletion InterfacePlayerPriv.h
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,6 @@ struct GstPlayerPriv
gboolean buffering_in_progress; /**< buffering is in progress */
guint buffering_timeout_cnt; /**< make sure buffering_timeout doesn't get stuck */
GstState buffering_target_state; /**< the target state after buffering */
bool seekPausedState; /** < true when seek with keepPaused is active — guards buffering_timeout from setting PLAYING */
gint64 lastKnownPTS; /**< To store the PTS of last displayed video */
long long ptsUpdatedTimeMS; /**< Timestamp when PTS was last updated */
guint ptsCheckForEosOnUnderflowIdleTaskId; /**< ID of task to ensure video PTS is not moving before notifying EOS on underflow. */
Expand Down
154 changes: 11 additions & 143 deletions InterfacePlayerRDK.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ firstFrameCallbackIdleTaskId(GST_TASK_ID_INVALID), firstFrameCallbackIdleTaskPen
using_westerossink(false), usingRialtoSink(false), usingClosedCaptionsControl(false), pauseOnStartPlayback(false), eosSignalled(false),
buffering_enabled(FALSE), buffering_in_progress(FALSE), buffering_timeout_cnt(0),
buffering_target_state(GST_STATE_NULL),
seekPausedState(false),lastKnownPTS(0), ptsUpdatedTimeMS(0), ptsCheckForEosOnUnderflowIdleTaskId(GST_TASK_ID_INVALID),
lastKnownPTS(0), ptsUpdatedTimeMS(0), ptsCheckForEosOnUnderflowIdleTaskId(GST_TASK_ID_INVALID),
numberOfVideoBuffersSent(0), segmentStart(0), positionQuery(NULL),
paused(false), pipelineState(GST_STATE_NULL),
firstVideoFrameDisplayedCallbackTask("FirstVideoFrameDisplayedCallback"),
Expand Down Expand Up @@ -521,12 +521,6 @@ void InterfacePlayerRDK::ConfigurePipeline(int format, int audioFormat, int subF
interfacePlayerPriv->gstPrivateContext->buffering_in_progress = true;
interfacePlayerPriv->gstPrivateContext->buffering_timeout_cnt = DEFAULT_BUFFERING_MAX_CNT;

// buffering_timeout will handle the PLAYING transition, so seekPausedState must not block it.
if (interfacePlayerPriv->gstPrivateContext->seekPausedState)
{
MW_LOG_WARN("ConfigurePipeline: clearing seekPausedState — buffering will drive PLAYING transition");
interfacePlayerPriv->gstPrivateContext->seekPausedState = false;
}
if (SetStateWithWarnings(interfacePlayerPriv->gstPrivateContext->pipeline, GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE)
{
MW_LOG_ERR("InterfacePlayerRDK_Configure GST_STATE_PAUSED failed");
Expand All @@ -537,31 +531,13 @@ void InterfacePlayerRDK::ConfigurePipeline(int format, int audioFormat, int subF
else
{
MW_LOG_INFO("Setting state to GST_STATE_PLAYING");
/* If a seek-with-keepPaused is active we must not race into PLAYING.
* Defer the PLAYING transition and leave pipeline in PAUSED until
* an explicit resume (Pause(false)) clears `seekPausedState`.
*/
if (interfacePlayerPriv->gstPrivateContext->seekPausedState)

if (SetStateWithWarnings(interfacePlayerPriv->gstPrivateContext->pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
Comment on lines 531 to +535
{
MW_LOG_WARN("seekPausedState active - deferring transition to PLAYING, marking pendingPlayState");
interfacePlayerPriv->gstPrivateContext->buffering_target_state = GST_STATE_PLAYING;
interfacePlayerPriv->gstPrivateContext->pendingPlayState = true;
/* Ensure pipeline is left/returned to PAUSED to avoid accidental play */
if (SetStateWithWarnings(interfacePlayerPriv->gstPrivateContext->pipeline, GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE)
{
MW_LOG_ERR("InterfacePlayerRDK: GST_STATE_PAUSED failed while deferring PLAYING");
}
interfacePlayerPriv->gstPrivateContext->paused = true;
}
else
{
if (SetStateWithWarnings(interfacePlayerPriv->gstPrivateContext->pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
{
MW_LOG_ERR("InterfacePlayerRDK: GST_STATE_PLAYING failed");
}
interfacePlayerPriv->gstPrivateContext->pendingPlayState = false;
interfacePlayerPriv->gstPrivateContext->paused = false;
MW_LOG_ERR("InterfacePlayerRDK: GST_STATE_PLAYING failed");
}
interfacePlayerPriv->gstPrivateContext->pendingPlayState = false;
interfacePlayerPriv->gstPrivateContext->paused = false;
}
interfacePlayerPriv->gstPrivateContext->eosSignalled = false;
interfacePlayerPriv->gstPrivateContext->numberOfVideoBuffersSent = 0;
Expand Down Expand Up @@ -1632,30 +1608,11 @@ bool InterfacePlayerRDK::Flush(double position, int rate, bool shouldTearDown, b

}

/* If pipeline is paused (seek with keepPaused), mark seekPausedState
* so that when ConfigurePipeline restarts buffering, the buffering_timeout callback
* won't race to set PLAYING before Pause(1) arrives */
if (interfacePlayerPriv->gstPrivateContext->paused)
{
interfacePlayerPriv->gstPrivateContext->seekPausedState = true;
MW_LOG_MIL("InterfacePlayerRDK: Flush with paused state — setting seekPausedState");
}
if (interfacePlayerPriv->gstPrivateContext->bufferingTimeoutTimerId)
{
MW_LOG_MIL("InterfacePlayerRDK: Remove bufferingTimeoutTimerId %d", interfacePlayerPriv->gstPrivateContext->bufferingTimeoutTimerId);
g_source_remove(interfacePlayerPriv->gstPrivateContext->bufferingTimeoutTimerId);
interfacePlayerPriv->gstPrivateContext->bufferingTimeoutTimerId = PLAYER_TASK_ID_INVALID;
// Reset buffering state to prevent stale timeout_cnt from triggering error after seek
interfacePlayerPriv->gstPrivateContext->buffering_in_progress = false;
interfacePlayerPriv->gstPrivateContext->buffering_timeout_cnt = DEFAULT_BUFFERING_MAX_CNT;


}
// If rate indicates playback (not paused seek), clear seekPausedState
if (rate > 0 && !interfacePlayerPriv->gstPrivateContext->paused)
{
interfacePlayerPriv->gstPrivateContext->seekPausedState = false;
MW_LOG_MIL("InterfacePlayerRDK: rate indicates playback, clearing seekPausedState");
}
Comment on lines 1611 to 1616
// If the pipeline is not setup, we will cache the value for later
SetSeekPosition(position);
Expand Down Expand Up @@ -3464,13 +3421,7 @@ bool InterfacePlayerRDK::Pause(bool pause , bool forceStopGstreamerPreBuffering)
{
GstState nextState = pause ? GST_STATE_PAUSED : GST_STATE_PLAYING;
interfacePlayerPriv->gstPrivateContext->buffering_target_state = nextState;

/* Clear seekPausedState when explicitly resuming playback */
if (!pause)
{
interfacePlayerPriv->gstPrivateContext->seekPausedState = false;
}


if (GST_STATE_PAUSED == nextState && forceStopGstreamerPreBuffering)
{
/* maybe in a timing case during the playback start,
Expand All @@ -3490,30 +3441,7 @@ bool InterfacePlayerRDK::Pause(bool pause , bool forceStopGstreamerPreBuffering)
/* wait a bit longer for the state change to conclude */
if (nextState != validateStateWithMsTimeout(this,nextState, 100))
{
GstState current, pending;
MW_LOG_INFO("InterfacePlayerRDK_Pause - validateStateWithMsTimeout - FAILED expected %s", gst_element_state_get_name(nextState));

/* Recovery: retry the state change once before reporting failure */
MW_LOG_INFO("InterfacePlayerRDK_Pause - retrying state change to GstState %d", nextState);

// Wait for any in-flight transition to settle
gst_element_get_state(interfacePlayerPriv->gstPrivateContext->pipeline, &current, &pending, 0);

// Single retry — no destructive NULL reset
GstStateChangeReturn rcRetry = SetStateWithWarnings(interfacePlayerPriv->gstPrivateContext->pipeline, nextState);
if (GST_STATE_CHANGE_ASYNC == rcRetry)
{
if (nextState != validateStateWithMsTimeout(this, nextState, 100))
{
MW_LOG_ERR("Retry also failed — reporting error");
retValue = false;
}
}
else if (GST_STATE_CHANGE_SUCCESS != rcRetry)
{
MW_LOG_ERR("Retry failed immediately with rc %d — reporting error", rcRetry);
retValue = false;
}
MW_LOG_ERR("InterfacePlayerRDK_Pause - validateStateWithMsTimeout - FAILED GstState %d", nextState);
}
}
else if (GST_STATE_CHANGE_SUCCESS != rc)
Expand Down Expand Up @@ -4609,17 +4537,7 @@ static gboolean bus_message(GstBus * bus, GstMessage * msg, InterfacePlayerRDK *
if(eGST_MEDIAFORMAT_DASH != static_cast<GstMediaFormat>(pInterfacePlayerRDK->m_gstConfigParam->media))
{
SetStateWithWarnings(privatePlayer->gstPrivateContext->pipeline, GST_STATE_PAUSED);
/* Avoid forcing PLAYING if a seek-with-keepPaused is active */
if (!privatePlayer->gstPrivateContext->seekPausedState)
{
SetStateWithWarnings(privatePlayer->gstPrivateContext->pipeline, GST_STATE_PLAYING);
}
else
{
MW_LOG_WARN("GST_MESSAGE_CLOCK_LOST: seekPausedState active - skipping PLAYING");
privatePlayer->gstPrivateContext->pendingPlayState = true;
privatePlayer->gstPrivateContext->buffering_target_state = GST_STATE_PLAYING;
}
SetStateWithWarnings(privatePlayer->gstPrivateContext->pipeline, GST_STATE_PLAYING);
}
break;

Expand Down Expand Up @@ -4685,34 +4603,8 @@ bool InterfacePlayerRDK::SetPlayBackRate(double rate)
sources.push_back(interfacePlayerPriv->gstPrivateContext->stream[iTrack].source);
}
}
ret = interfacePlayerPriv->socInterface->SetPlaybackRate(sources, interfacePlayerPriv->gstPrivateContext->pipeline, rate, interfacePlayerPriv->gstPrivateContext->video_dec,interfacePlayerPriv->gstPrivateContext->audio_dec);

/* If application requested resume via rate change but middleware's
* seek-paused protection left the pipeline in PAUSED, ensure we clear
* `seekPausedState` here at middleware level. This handles cases where
* higher-level callers may retry or skip setting rate — forcing an
* explicit resume in the middleware prevents the pipeline from being
* stuck in PAUSED. */
if (rate != 0.0 && interfacePlayerPriv->gstPrivateContext->seekPausedState && interfacePlayerPriv->gstPrivateContext->paused)
{
MW_LOG_WARN("InterfacePlayerRDK: SetPlayBackRate detected resume while seekPausedState active — forcing resume");
/* Pause(false) clears seekPausedState in Pause implementation. */
bool pauseResult = Pause(false, false);
if (pauseResult)
{
interfacePlayerPriv->gstPrivateContext->seekPausedState = false;
interfacePlayerPriv->gstPrivateContext->pendingPlayState = false;
/* After explicit resume we consider operation successful */
ret = true;
}
else
{
MW_LOG_ERR("SetPlayBackRate: Pause(false) failed — cannot resume");
ret = false;
}
}

return ret;
ret = interfacePlayerPriv->socInterface->SetPlaybackRate(sources, interfacePlayerPriv->gstPrivateContext->pipeline, rate, interfacePlayerPriv->gstPrivateContext->video_dec,interfacePlayerPriv->gstPrivateContext->audio_dec);
return ret;
}

/**
Expand Down Expand Up @@ -4831,20 +4723,6 @@ static gboolean buffering_timeout (gpointer data)
}
else if (frames == -1 || frames >= pInterfacePlayerRDK->m_gstConfigParam->framesToQueue || (privatePlayer->gstPrivateContext->buffering_timeout_cnt > 0 && --privatePlayer->gstPrivateContext->buffering_timeout_cnt == 0))
{
/* Do not set PLAYING if a seek-with-keepPaused is in progress.
* The buffering_timeout timer may fire after ConfigurePipeline restarts buffering
* but BEFORE the Pause(1) from keepPaused logic arrives — causing a race. */
if (privatePlayer->gstPrivateContext->seekPausedState)
{
MW_LOG_WARN("buffering_timeout: skipping PLAYING — seekPausedState active (cnt %u, frames %d)", privatePlayer->gstPrivateContext->buffering_timeout_cnt, frames);
if (privatePlayer->gstPrivateContext->buffering_timeout_cnt == 0)
{
MW_LOG_ERR("buffering_timeout: seekPausedState still active after timeout exhausted — clearing to unblock");
privatePlayer->gstPrivateContext->seekPausedState = false;
}
return privatePlayer->gstPrivateContext->buffering_in_progress;
}

uint32_t original_buffering_timeout_cnt = privatePlayer->gstPrivateContext->buffering_timeout_cnt;
MW_LOG_MIL("Set pipeline state to %s - buffering_timeout_cnt %u frames %i",
gst_element_state_get_name(privatePlayer->gstPrivateContext->buffering_target_state), original_buffering_timeout_cnt, frames);
Expand Down Expand Up @@ -5256,16 +5134,6 @@ void InterfacePlayerRDK::NotifyFragmentCachingComplete()
{
if(interfacePlayerPriv->gstPrivateContext->pendingPlayState)
{
/* If a seek-with-keepPaused is active, do not transition to PLAYING here.
* Leave pendingPlayState set so the explicit resume will perform the transition.
*/
if (interfacePlayerPriv->gstPrivateContext->seekPausedState)
{
MW_LOG_WARN("NotifyFragmentCachingComplete: seekPausedState active - deferring PLAYING");
interfacePlayerPriv->gstPrivateContext->buffering_target_state = GST_STATE_PLAYING;
return;
}

MW_LOG_MIL("InterfacePlayer: Setting pipeline to PLAYING state ");
interfacePlayerPriv->gstPrivateContext->buffering_target_state = GST_STATE_PLAYING;
if (SetStateWithWarnings(interfacePlayerPriv->gstPrivateContext->pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
Expand Down
2 changes: 1 addition & 1 deletion drm/ocdm/OcdmGstSessionAdapter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ int OCDMGSTSessionAdapter::decrypt(GstBuffer *keyIDBuffer, GstBuffer *ivBuffer,

/* Added GST_IS_CAPS check also before passing gst caps to OCDM decrypt() as gst_caps_is_empty returns false when caps object is not of
type GST_TYPE_CAPS. This will avoid crash when caps is not of type GST_TYPE_CAPS. */
if (OCDMGSTSessionDecrypt && !gst_caps_is_empty(caps) && GST_IS_CAPS(caps))
if (OCDMGSTSessionDecrypt && caps != nullptr && GST_IS_CAPS(caps) && !gst_caps_is_empty(caps))
{
GstProtectionMeta* protectionMeta = reinterpret_cast<GstProtectionMeta*>(gst_buffer_get_protection_meta(buffer));

Expand Down
27 changes: 26 additions & 1 deletion externals/rdk/IIarm/DeviceIARMInterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,13 @@ Remove the entire folder externals/rdk/IARM
#include <libIARM.h>
#include <libIBus.h>
#include <iarmUtil.h>
#include <thread>
#include "libIBusDaemon.h"
#include <hostIf_tr69ReqHandler.h>
#include "tr181api.h"
#include "_base64.h"
#ifdef USE_PREINIT_DECODING
#include "power_controller.h"
#include <thread>
#include <system_error> // for std::system_error
#include <exception> // for std::exception base class
#endif
Expand Down Expand Up @@ -244,6 +244,18 @@ static void IARM_PowerChangeHandler (const PowerController_PowerState_t currentS
{
MW_LOG_INFO("Entering IARM_PowerChangeHandler:State Changed currentState: %d, newState: %d",
currentState, newState);


std::shared_ptr<PlayerExternalsRdkInterface> pInstance =
PlayerExternalsRdkInterface::GetPlayerExternalsRdkInterfaceInstance();
if (newState != POWER_STATE_ON) {
pInstance->SetPowerEvent(true);
MW_LOG_INFO(" Power transition to non-ON state, blocking HDMI events\n");
} else {
pInstance->SetPowerEvent(false);
MW_LOG_INFO(" Power transition to ON, allowing HDMI events\n");
}
Comment on lines +249 to +257

bool isOnOrStandby = (newState == POWER_STATE_STANDBY || newState == POWER_STATE_ON);
if((currentState == POWER_STATE_STANDBY_DEEP_SLEEP && isOnOrStandby) ||
(prevState == POWER_STATE_STANDBY_DEEP_SLEEP && currentState == POWER_STATE_STANDBY_LIGHT_SLEEP && isOnOrStandby))
Expand Down Expand Up @@ -415,6 +427,12 @@ static void HDMIEventHandler(const char *owner, IARM_EventId_t eventId, void *da
{
std::shared_ptr<PlayerExternalsRdkInterface> pInstance = PlayerExternalsRdkInterface::GetPlayerExternalsRdkInterfaceInstance();


if (pInstance->GetPowerEvent())
{
MW_LOG_WARN(" Skipping HDMI event processing during power transition\n");
return;
}
Comment on lines +431 to +435
switch (eventId)
{
case IARM_BUS_DSMGR_EVENT_HDMI_HOTPLUG :
Expand All @@ -428,6 +446,10 @@ static void HDMIEventHandler(const char *owner, IARM_EventId_t eventId, void *da

pInstance->SetHDMIStatus();

// Dispatch to detached worker thread — do NOT block IARM dispatch thread
std::thread([pInstance]() {
pInstance->SetHDMIStatus();
}).detach();
Comment on lines 447 to +452
Comment on lines +449 to +452
Comment on lines 447 to +452
break;
}
case IARM_BUS_DSMGR_EVENT_HDCP_STATUS :
Expand All @@ -439,6 +461,9 @@ static void HDMIEventHandler(const char *owner, IARM_EventId_t eventId, void *da
hdcpStatus, hdcpStatusStr);

pInstance->SetHDMIStatus();
std::thread([pInstance]() {
pInstance->SetHDMIStatus();
}).detach();
Comment on lines 463 to +466
Comment on lines 463 to +466
break;
}
default:
Expand Down
6 changes: 6 additions & 0 deletions externals/rdk/PlayerExternalsRdkInterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,11 @@ void PlayerExternalsRdkInterface::SetResolution(int width, int height)
*/
void PlayerExternalsRdkInterface::SetHDMIStatus()
{
std::unique_lock<std::mutex> lock(m_hdmiStatusMutex, std::try_to_lock);
if (!lock.owns_lock()) {
MW_LOG_WARN("SetHDMIStatus: Already in progress on another thread, skipping\n");
return;
}
bool isConnected = false;
bool isHDCPCompliant = false;
bool isHDCPEnabled = true;
Expand Down Expand Up @@ -306,6 +311,7 @@ void PlayerExternalsRdkInterface::SetHDMIStatus()
}
catch (...) {
MW_LOG_WARN("DeviceSettings unknown exception caught\n");
try { device::Manager::DeInitialize(); } catch (...) {}
}

m_isHDCPEnabled = isHDCPEnabled;
Expand Down
3 changes: 2 additions & 1 deletion externals/rdk/PlayerExternalsRdkInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@

#include <memory>
#include "PlayerExternalsInterfaceBase.h"

#include <mutex>
/*
IARM Deprecation Note:
IARM is to be deprecated in favor of DeviceSettings and Firebolt Device API.
Expand All @@ -63,6 +63,7 @@ class PlayerExternalsRdkInterface : public PlayerExternalsInterfaceBase
, public device::Host::IVideoOutputPortEvents
#endif
{
std::mutex m_hdmiStatusMutex;
enum InitState{
NOT_INITIALIZED,
FIREBOLT,
Expand Down
Loading
Loading