Skip to content
Merged
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 InterfacePlayerPriv.h
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ 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
194 changes: 182 additions & 12 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),
lastKnownPTS(0), ptsUpdatedTimeMS(0), ptsCheckForEosOnUnderflowIdleTaskId(GST_TASK_ID_INVALID),
seekPausedState(false),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 @@ -470,6 +470,15 @@ void InterfacePlayerRDK::ConfigurePipeline(int format, int audioFormat, int subF
gboolean videoOnly = (audioFormat == GST_FORMAT_INVALID);
MW_LOG_INFO("Setting single-path-stream to %d", videoOnly);
g_object_set(vidsink, "single-path-stream", videoOnly, NULL);
// RDKEMW-18286: Reinforce show-video-window before pipeline state change
if (interfacePlayerPriv->gstPrivateContext->videoMuted)
{
MW_LOG_MIL("InterfacePlayerRDK - ConfigurePipeline: reinforcing "
"show-video-window=FALSE on %s (videoMuted=%d)",
GST_ELEMENT_NAME(vidsink),
interfacePlayerPriv->gstPrivateContext->videoMuted);
g_object_set(vidsink, "show-video-window", FALSE, NULL);
}
}
else
{
Expand Down Expand Up @@ -512,6 +521,12 @@ 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;
}
Comment on lines +524 to +529
Comment on lines +524 to +529
if (SetStateWithWarnings(interfacePlayerPriv->gstPrivateContext->pipeline, GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE)
{
MW_LOG_ERR("InterfacePlayerRDK_Configure GST_STATE_PAUSED failed");
Expand All @@ -522,12 +537,31 @@ void InterfacePlayerRDK::ConfigurePipeline(int format, int audioFormat, int subF
else
{
MW_LOG_INFO("Setting state to GST_STATE_PLAYING");
if (SetStateWithWarnings(interfacePlayerPriv->gstPrivateContext->pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
/* 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)
{
MW_LOG_ERR("InterfacePlayerRDK: GST_STATE_PLAYING failed");
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;
}
interfacePlayerPriv->gstPrivateContext->pendingPlayState = false;
interfacePlayerPriv->gstPrivateContext->paused = false;
}
interfacePlayerPriv->gstPrivateContext->eosSignalled = false;
interfacePlayerPriv->gstPrivateContext->numberOfVideoBuffersSent = 0;
Expand Down Expand Up @@ -1597,14 +1631,32 @@ bool InterfacePlayerRDK::Flush(double position, int rate, bool shouldTearDown, b
interfacePlayerPriv->gstPrivateContext->ptsCheckForEosOnUnderflowIdleTaskId = PLAYER_TASK_ID_INVALID;

}

/* 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");
}
// If the pipeline is not setup, we will cache the value for later
SetSeekPosition(position);

Expand Down Expand Up @@ -1680,8 +1732,21 @@ bool InterfacePlayerRDK::Flush(double position, int rate, bool shouldTearDown, b
{
if ((interfacePlayerPriv->socInterface->IsSimulatorSink() || interfacePlayerPriv->gstPrivateContext->usingRialtoSink) && rate != GST_NORMAL_PLAY_RATE)
{
MW_LOG_INFO("Resetting seek position to zero");
position = 0;
const bool isTrickplay = (rate != GST_NORMAL_PLAY_RATE);
const bool isLiveMedia = (static_cast<GstMediaFormat>(m_gstConfigParam->media) == eGST_MEDIAFORMAT_OTA);

if (isTrickplay)
{
if (isLiveMedia)
{
MW_LOG_WARN("Live trickplay active - preserving flush seek position %f", position);
}
else
{
MW_LOG_INFO("Resetting seek position to zero");
position = 0;
}
}
}
}
if (!gst_element_seek(interfacePlayerPriv->gstPrivateContext->pipeline, playRate, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, GST_SEEK_TYPE_SET,
Expand Down Expand Up @@ -2343,6 +2408,21 @@ int InterfacePlayerRDK::SetupStream(int streamId, void *playerInstance, std::st
g_object_set(vidsink, "has-drm", FALSE, NULL);
}
interfacePlayerPriv->gstPrivateContext->video_sink = vidsink;

// RDKEMW-18286: Set show-video-window=FALSE at sink creation time.
// This is the EARLIEST possible point. The Rialto delegate will queue
// this (m_videoMuteQueued=true) and apply it server-side when the
// source attaches — which happens BEFORE the pipeline goes to PLAYING
// and BEFORE any frame can be decoded.
if (interfacePlayerPriv->gstPrivateContext->videoMuted)
{
MW_LOG_MIL("InterfacePlayerRDK - %s: setting show-video-window=FALSE "
"at creation time (videoMuted=%d)",
GST_ELEMENT_NAME(vidsink),
interfacePlayerPriv->gstPrivateContext->videoMuted);
g_object_set(vidsink, "show-video-window", FALSE, NULL);
}

}
else
{
Expand Down Expand Up @@ -3385,6 +3465,12 @@ 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 @@ -3404,7 +3490,30 @@ bool InterfacePlayerRDK::Pause(bool pause , bool forceStopGstreamerPreBuffering)
/* wait a bit longer for the state change to conclude */
if (nextState != validateStateWithMsTimeout(this,nextState, 100))
{
MW_LOG_ERR("InterfacePlayerRDK_Pause - validateStateWithMsTimeout - FAILED GstState %d", nextState);
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;
}
}
}
else if (GST_STATE_CHANGE_SUCCESS != rc)
Expand Down Expand Up @@ -4500,7 +4609,17 @@ 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);
SetStateWithWarnings(privatePlayer->gstPrivateContext->pipeline, GST_STATE_PLAYING);
/* 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;
}
}
break;

Expand Down Expand Up @@ -4566,8 +4685,34 @@ 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);
return ret;
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;
}
Comment on lines +4702 to +4707
else
{
MW_LOG_ERR("SetPlayBackRate: Pause(false) failed — cannot resume");
ret = false;
}
}

return ret;
}

/**
Expand Down Expand Up @@ -4686,6 +4831,20 @@ 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;
}
Comment on lines +4834 to +4846

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 @@ -5097,6 +5256,16 @@ 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 Expand Up @@ -5266,6 +5435,7 @@ void InterfacePlayerRDK::InitializePlayerGstreamerPlugins()
if (!gst_init_check(nullptr, nullptr, nullptr)) {
MW_LOG_ERR("gst_init_check() failed");
}
SocUtils::Init();

#define PLUGINS_TO_LOWER_RANK_MAX 2
static const char *plugins_to_lower_rank[PLUGINS_TO_LOWER_RANK_MAX] = {
Expand Down
25 changes: 17 additions & 8 deletions SocUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,16 @@

namespace SocUtils
{
static std::shared_ptr<SocInterface> socInterface = SocInterface::CreateSocInterface();
static std::shared_ptr<SocInterface> GetSocInterface()
{
static std::shared_ptr<SocInterface> socInterface = SocInterface::CreateSocInterface();
return socInterface;
}

void Init()
{
(void)GetSocInterface();
}
/**
* @brief Checks if AppSrc should be used for progressive playback.
*
Expand All @@ -38,7 +47,7 @@ namespace SocUtils
*/
bool UseAppSrcForProgressivePlayback( void )
{
return socInterface->UseAppSrc();
return GetSocInterface()->UseAppSrc();
}

/**
Expand All @@ -51,7 +60,7 @@ namespace SocUtils
*/
bool UseWesterosSink( void )
{
return socInterface->UseWesterosSink();
return GetSocInterface()->UseWesterosSink();
}

/**
Expand All @@ -63,7 +72,7 @@ namespace SocUtils
*/
bool IsAudioFragmentSyncSupported( void )
{
return socInterface->IsAudioFragmentSyncSupported();
return GetSocInterface()->IsAudioFragmentSyncSupported();
}

/**
Expand All @@ -76,7 +85,7 @@ namespace SocUtils
*/
bool EnableLiveLatencyCorrection( void )
{
return socInterface->EnableLiveLatencyCorrection();
return GetSocInterface()->EnableLiveLatencyCorrection();
}

/**
Expand All @@ -89,7 +98,7 @@ namespace SocUtils
*/
int RequiredQueuedFrames( void )
{
return socInterface->RequiredQueuedFrames();
return GetSocInterface()->RequiredQueuedFrames();
}

/**
Expand All @@ -102,7 +111,7 @@ namespace SocUtils
*/
bool EnablePTSRestamp(void)
{
return socInterface->EnablePTSRestamp();
return GetSocInterface()->EnablePTSRestamp();
}
/**
* @brief Resets segment event flags during trickplay transitions.
Expand All @@ -111,7 +120,7 @@ namespace SocUtils
*/
bool ResetNewSegmentEvent()
{
return socInterface->ResetNewSegmentEvent();
return GetSocInterface()->ResetNewSegmentEvent();
}
/**
* @brief Check if GST Subtec is enabled
Expand Down
Loading
Loading