Skip to content
15 changes: 12 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,17 @@ All notable changes to this project will be documented in this file. Dates are d

Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).

#### [0.2.0](https://github.com/rdkcentral/middleware-player-interface/compare/0.1.4...0.2.0)

- RDKEMW-19686 [Develop]Bringing RKDEMW-19683 , RDKEMW-19684, RDKEMW-19685 to middleware-player-interface [`#170`](https://github.com/rdkcentral/middleware-player-interface/pull/170)
- Revert "VPAAMP-457 middleware changes" [`da9157e`](https://github.com/rdkcentral/middleware-player-interface/commit/da9157e565ce0ff78a87c893a4246f08476b61ed)
- Merge tag '0.1.4' into develop [`7c07f2a`](https://github.com/rdkcentral/middleware-player-interface/commit/7c07f2a7f9e30f5699c86baa3d8f2709612f4dbc)
- dev_sprint_pli sync [`5537418`](https://github.com/rdkcentral/middleware-player-interface/commit/5537418a13f5ff3fecad2d89d79a9fdea76878d9)

#### [0.1.4](https://github.com/rdkcentral/middleware-player-interface/compare/0.1.3...0.1.4)

> 11 June 2026

- RDKEMW-19919 : No WebVTT subtitles shown on some channels when PTS restamping enabled [`#174`](https://github.com/rdkcentral/middleware-player-interface/pull/174)
- Revert "Revert "RDKEMW-18010 Middleware-player-interface feature development and inalign with dev_sprint"" [`#175`](https://github.com/rdkcentral/middleware-player-interface/pull/175)
- Revert "RDKEMW-18010 Middleware-player-interface feature development and inalign with dev_sprint" [`#172`](https://github.com/rdkcentral/middleware-player-interface/pull/172)
Expand All @@ -24,9 +33,9 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
- Develop [`#98`](https://github.com/rdkcentral/middleware-player-interface/pull/98)
- RDKEMW-14946 : Fix coverity workflow scan in middleware-player-interface repo [`#91`](https://github.com/rdkcentral/middleware-player-interface/pull/91)
- main sync [`#86`](https://github.com/rdkcentral/middleware-player-interface/pull/86)
- Delete sync directory [`caff23b`](https://github.com/rdkcentral/middleware-player-interface/commit/caff23b7f84d96c5314d24e033030c0e4f81aa8d)
- Update GstHandlerControlTests.cpp [`2aa5167`](https://github.com/rdkcentral/middleware-player-interface/commit/2aa51679a712158a23e65e5d58d1cd5cd4fc1411)
- Update GstHandlerControlTests.cpp [`33a8050`](https://github.com/rdkcentral/middleware-player-interface/commit/33a80506fdbe144b75a084244fa2f7927bef8cc4)
- changelog update [`0e0120d`](https://github.com/rdkcentral/middleware-player-interface/commit/0e0120d2a7c68ec0b031ad34dc2536779ce67b83)
- Integerate LLM teamgiven test cases into dev_pli branch [`8695df1`](https://github.com/rdkcentral/middleware-player-interface/commit/8695df1648e8e7fb5d2f9be24be0fac5f77df6d8)
- init commit [`41b08c8`](https://github.com/rdkcentral/middleware-player-interface/commit/41b08c887a9feda03057a129ade8fa334568eb12)

#### [0.1.3](https://github.com/rdkcentral/middleware-player-interface/compare/0.1.3-r2...0.1.3)

Expand Down
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;
}
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;
}
else
{
MW_LOG_ERR("SetPlayBackRate: Pause(false) failed — cannot resume");
ret = false;
}
}
Comment on lines +4696 to +4713

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 +4837 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
Loading
Loading