Skip to content
Open
12 changes: 12 additions & 0 deletions StreamSink.h
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,18 @@ class StreamSink
*/
virtual void Flush(double position = 0, int rate = AAMP_NORMAL_PLAY_RATE, bool shouldTearDown = true, bool keepPausedSeek = false){}

/**
* @brief RDKEMW-21923: query the underlying pipeline GstState.
* @param[out] currentState current pipeline state
* @param[out] pendingState pending pipeline state
* @return GstStateChangeReturn from gst_element_get_state (default: SUCCESS/no-op)
*/
virtual GstStateChangeReturn GetPipelineState(GstState *currentState, GstState *pendingState) {
// Default: assume pipeline is healthy (PLAYING). Concrete overrides query real state.
if (currentState) *currentState = GST_STATE_PLAYING;
if (pendingState) *pendingState = GST_STATE_VOID_PENDING;
return GST_STATE_CHANGE_SUCCESS;
}
/**
* @brief Flush the audio playbin
* @param[in] position - playback position
Expand Down
12 changes: 12 additions & 0 deletions aampgstplayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1483,3 +1483,15 @@ bool AAMPGstPlayer::SendSample(AampMediaType mediaType, AampMediaSample&& sample
std::move(sample.mDrmMetadata)
});
}

/**
* @brief Get the current and pending state of the GStreamer pipeline
*
* @param[out] currentState - Pointer to store the current state of the pipeline
* @param[out] pendingState - Pointer to store the pending state of the pipeline
* @return GstStateChangeReturn indicating the result of the state query
*/
GstStateChangeReturn AAMPGstPlayer::GetPipelineState(GstState *currentState, GstState *pendingState)
{
return playerInstance->GetPipelineState(currentState, pendingState);
}
8 changes: 8 additions & 0 deletions aampgstplayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,14 @@ class AAMPGstPlayer : public StreamSink
* @param[in] codecInfo - Codec information
*/
void SetStreamCaps(AampMediaType type, MediaCodecInfo&& codecInfo) override;

/**
* @brief Get the stream capabilities for a given media type
*
* @param[in] type - Media type
* @return const reference to MediaCodecInfo for the specified media type
*/
GstStateChangeReturn GetPipelineState(GstState *currentState, GstState *pendingState) override;

private:
std::mutex mBufferingLock;
Expand Down
216 changes: 213 additions & 3 deletions main_aamp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -762,6 +762,87 @@ void PlayerInstanceAAMP::SetRateInternal(float rate,int overshootcorrection)
bool isPipelinePaused = aamp->mSinkPaused.load();
if ((!isPipelinePaused && rate == aamp->rate && !aamp->GetPauseOnFirstVideoFrameDisp()) || (rate == 0 && isPipelinePaused))
{
// RDKEMW-21923: During rapid scrubbing (e.g. 1x->2x->1x->PAUSE within ~1s),
// TuneHelper may still be rebuilding the pipeline (NULL/READY state).
// If we skip here, downstream Pause()/Resume() will race with the rebuild
// and cause a PAUSED->PAUSED wedge. Detect and bail out early.
if (!isPipelinePaused && rate == aamp->rate && rate == AAMP_NORMAL_PLAY_RATE)
{
StreamSink *sink = AampStreamSinkManager::GetInstance().GetStreamSink(aamp);
if (sink)
{
GstState curState = GST_STATE_VOID_PENDING;
GstState pendState = GST_STATE_VOID_PENDING;
sink->GetPipelineState(&curState, &pendState);
if (curState == GST_STATE_NULL || curState == GST_STATE_VOID_PENDING || curState == GST_STATE_READY)
{
AAMPLOG_WARN("SetRateInternal: rate=%.1f but pipeline in state %d "
"(rebuild in progress) — skipping, TuneHelper will complete", rate, curState);
return;
}
}
}

if (!isPipelinePaused && rate == aamp->rate && rate == AAMP_NORMAL_PLAY_RATE)
{
StreamSink *sink = AampStreamSinkManager::GetInstance().GetStreamSink(aamp);
if (sink)
{
GstState curState;
GstState pendState;
sink->GetPipelineState(&curState, &pendState);

if (curState == GST_STATE_PLAYING)
{
// Pipeline is genuinely PLAYING — safe to skip
AAMPLOG_WARN("SetRateInternal: pipeline confirmed PLAYING (state=%d), safe to skip", curState);
}
else if (curState == GST_STATE_NULL || curState == GST_STATE_VOID_PENDING)
{
// Pipeline destroyed — teardown/rebuild in progress by another TuneHelper
// Do NOT re-seek; let the active TuneHelper complete naturally
AAMPLOG_WARN("SetRateInternal: pipeline is NULL/VOID (state=%d), "
"teardown in progress — skipping recovery", curState);
return;
}
else if (curState == GST_STATE_PAUSED)
{
// Pipeline stuck in PAUSED despite mSinkPaused=false (state drift)
if (!sink->Pause(false, false))
{
AAMPLOG_WARN("SetRateInternal: pipeline stuck in PAUSED, "
"resume failed; recovering via re-seek");
aamp->SetState(eSTATE_SEEKING);
aamp->seek_pos_seconds = aamp->GetPositionSeconds();
aamp->rate = AAMP_NORMAL_PLAY_RATE;
aamp->mSinkPaused = false;
{
std::lock_guard<std::recursive_mutex> lock(aamp->GetStreamLock());
aamp->TuneHelper(eTUNETYPE_SEEK, false);
}
aamp->NotifySpeedChanged(aamp->rate, false);
aamp->ResumeDownloads();
return;
}
else
{
// Un-pause succeeded — pipeline was just lagging
AAMPLOG_WARN("SetRateInternal: pipeline was PAUSED (drift), un-pause succeeded");
aamp->NotifyFirstBufferProcessed(sink->GetVideoRectangle());
return;
}
}
else if (curState == GST_STATE_READY)
{
// Pipeline in READY — being rebuilt by TuneHelper
AAMPLOG_WARN("SetRateInternal: pipeline in READY (state=%d), "
"rebuild in progress — skipping recovery", curState);
return;
}
// else: unknown state — fall through to original early-return
}
}

AAMPLOG_WARN("Already running at playback rate(%f) mSinkPaused(%d), hence skipping set rate for (%f)", aamp->rate, isPipelinePaused, rate);
return;
}
Expand Down Expand Up @@ -888,6 +969,44 @@ void PlayerInstanceAAMP::SetRateInternal(float rate,int overshootcorrection)
if (aamp->mSinkPaused.load() && rate != 0)
{
AAMPLOG_INFO("Resuming Playback at Position '%lld'.", aamp->GetPositionMilliseconds());

// RDKEMW-21923: If pipeline is NULL/READY (mid-rebuild from a prior
// TuneHelper), do NOT attempt resume — it will fail and wedge.
// Instead, clear mSinkPaused and let TuneHelper's rebuild naturally
// transition to PLAYING.
StreamSink *sinkGuard = AampStreamSinkManager::GetInstance().GetStreamSink(aamp);
if (sinkGuard)
{
GstState guardCur = GST_STATE_VOID_PENDING;
GstState guardPend = GST_STATE_VOID_PENDING;
sinkGuard->GetPipelineState(&guardCur, &guardPend);

if (guardCur == GST_STATE_NULL || guardCur == GST_STATE_VOID_PENDING || guardCur == GST_STATE_READY)
{
AAMPLOG_WARN("SetRateInternal: resume requested but pipeline in state %d "
"(rebuild in progress) — deferring, TuneHelper will resume", guardCur);
aamp->mSinkPaused = false;
aamp->ResumeDownloads();
return;
}
}

if (aamp->IsPipelineWedged())
{
AAMPLOG_WARN("SetRateInternal: pipeline wedged, recovering via re-seek instead of resume");
aamp->SetState(eSTATE_SEEKING);
aamp->seek_pos_seconds = aamp->GetPositionSeconds();
aamp->rate = AAMP_NORMAL_PLAY_RATE;
aamp->mSinkPaused = false;
{
std::lock_guard<std::recursive_mutex> lock(aamp->GetStreamLock());
aamp->TuneHelper(eTUNETYPE_SEEK, false);
}
aamp->NotifySpeedChanged(aamp->rate, false);
aamp->ResumeDownloads();
return;
}

// Resuming payback from pause
// If have local TSB, but playing from Live then seek into the TSB
// Otherwise unpause the pipeline
Expand Down Expand Up @@ -923,15 +1042,39 @@ void PlayerInstanceAAMP::SetRateInternal(float rate,int overshootcorrection)
// rapid trickplay->seek->resume race - seen on both VOD and FOG-TSB linear).
// Recover by re-seeking to the current position: the same mechanism the
// local-TSB resume path uses. Works uniformly for VOD, FOG-TSB & local TSB.
AAMPLOG_WARN("SetRateInternal: pipeline resume failed (GstState timeout); recovering via re-seek");
long long nowMs = NOW_STEADY_TS_MS;
if ((nowMs - aamp->mLastRecoveryTimeMs) > PrivateInstanceAAMP::RECOVERY_COOLDOWN_MS)
{
aamp->mRecoveryAttemptCount = 0; // healthy gap since last recovery -> reset
}
aamp->mLastRecoveryTimeMs = nowMs;

aamp->SetState(eSTATE_SEEKING);
aamp->seek_pos_seconds = aamp->GetPositionSeconds();
aamp->rate = AAMP_NORMAL_PLAY_RATE;
aamp->mSinkPaused = false;

if (aamp->mRecoveryAttemptCount >= PrivateInstanceAAMP::MAX_RECOVERY_ATTEMPTS)
{
// Repeated resume-timeouts: stop re-seeking, force a full retune once.
AAMPLOG_ERR("SetRateInternal: resume recovery exceeded %d attempts; escalating to full retune",
PrivateInstanceAAMP::MAX_RECOVERY_ATTEMPTS);
aamp->mRecoveryAttemptCount = 0;
{
std::lock_guard<std::recursive_mutex> lock(aamp->GetStreamLock());
aamp->TuneHelper(eTUNETYPE_RETUNE, false);
}
}
else
{
aamp->mRecoveryAttemptCount++;
AAMPLOG_WARN("SetRateInternal: pipeline resume failed (GstState timeout); recovering via re-seek (attempt %d/%d) ",aamp->mRecoveryAttemptCount, PrivateInstanceAAMP::MAX_RECOVERY_ATTEMPTS);

{
std::lock_guard<std::recursive_mutex> lock(aamp->GetStreamLock());
aamp->TuneHelper(eTUNETYPE_SEEK, false);
}
}

// Skip common notification (like local-TSB path): state -> PLAYING
// via NotifyFirstBufferProcessed once fragments arrive.
Expand All @@ -940,8 +1083,36 @@ void PlayerInstanceAAMP::SetRateInternal(float rate,int overshootcorrection)
}
else
{
// required since buffers are already cached in paused state
aamp->NotifyFirstBufferProcessed(sink ? sink->GetVideoRectangle() : std::string());
if (!retValue && sink)
{
// RDKEMW-21923: Resume failed — check if pipeline is now wedged.
GstState cur = GST_STATE_VOID_PENDING;
GstState pend = GST_STATE_VOID_PENDING;
GstStateChangeReturn stRet = sink->GetPipelineState(&cur, &pend);
if (stRet == GST_STATE_CHANGE_FAILURE ||
(stRet == GST_STATE_CHANGE_ASYNC && cur == pend))
{
AAMPLOG_WARN("SetRateInternal: post-resume wedge detected "
"(state=%d pending=%d ret=%d), recovering via re-seek",
cur, pend, stRet);
aamp->SetState(eSTATE_SEEKING);
aamp->seek_pos_seconds = aamp->GetPositionSeconds();
aamp->rate = AAMP_NORMAL_PLAY_RATE;
aamp->mSinkPaused = false;
{
std::lock_guard<std::recursive_mutex> lock(aamp->GetStreamLock());
aamp->TuneHelper(eTUNETYPE_SEEK, false);
}
aamp->NotifySpeedChanged(aamp->rate, false);
aamp->ResumeDownloads();
return;
}
}
else
{
// required since buffers are already cached in paused state
aamp->NotifyFirstBufferProcessed(sink ? sink->GetVideoRectangle() : std::string());
}
}
}
}
Expand All @@ -953,6 +1124,25 @@ void PlayerInstanceAAMP::SetRateInternal(float rate,int overshootcorrection)
{
if (!aamp->mSinkPaused.load())
{

// RDKEMW-21923: Guard against pausing a pipeline that is mid-rebuild.
// Calling sink->Pause() on a NULL/READY pipeline triggers a PAUSED->PAUSED
// wedge once the rebuild completes. Defer: mark paused and return.
StreamSink *sinkCheck = AampStreamSinkManager::GetInstance().GetStreamSink(aamp);
if (sinkCheck)
{
GstState cur = GST_STATE_VOID_PENDING;
GstState pend = GST_STATE_VOID_PENDING;
sinkCheck->GetPipelineState(&cur, &pend);
if (cur == GST_STATE_NULL || cur == GST_STATE_VOID_PENDING || cur == GST_STATE_READY)
{
AAMPLOG_WARN("SetRateInternal: pause requested but pipeline in state %d "
"(rebuild in progress) — deferring pause", cur);
aamp->mSinkPaused = true;
return;
}
}

aamp->mpStreamAbstractionAAMP->NotifyPlaybackPaused(true);
if (!aamp->IsLocalAAMPTsb())
{
Expand All @@ -966,12 +1156,32 @@ void PlayerInstanceAAMP::SetRateInternal(float rate,int overshootcorrection)
}
}

if (aamp->IsPipelineWedged())
{
AAMPLOG_WARN("SetRateInternal: pipeline wedged at pause-on-first-frame, skipping pause");
// Don't attempt pause, just notify completion
return;
}

StreamSink *sink = AampStreamSinkManager::GetInstance().GetStreamSink(aamp);
if (sink)
{
retValue = sink->Pause(true, false);
}
aamp->mSinkPaused = true;

if (!retValue)
{
AAMPLOG_WARN("SetRateInternal: pause failed on first frame; recovering via re-seek");
aamp->SetState(eSTATE_SEEKING);
aamp->seek_pos_seconds = aamp->GetPositionSeconds();
aamp->mSinkPaused = true;
{
std::lock_guard<std::recursive_mutex> lock(aamp->GetStreamLock());
aamp->TuneHelper(eTUNETYPE_SEEK, false);
}
return;
}

if(aamp->GetLLDashServiceData()->lowLatencyMode)
{
Expand Down
18 changes: 18 additions & 0 deletions middleware/InterfacePlayerRDK.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5543,3 +5543,21 @@ static void DecorateGstBufferWithDrmMetadata(GstBuffer *buffer, const MediaDrmMe
gst_buffer_add_protection_meta(buffer, metadata);
}
}
/**
* @brief Get the current state of the GStreamer pipeline.
* @param[out] currentState Pointer to store the current state of the pipeline.
* @param[out] pendingState Pointer to store the pending state of the pipeline.
* @return GstStateChangeReturn indicating the result of the state query.
*/
GstStateChangeReturn InterfacePlayerRDK::GetPipelineState(GstState *currentState, GstState *pendingState)
{
if (interfacePlayerPriv->gstPrivateContext->pipeline)
{
return gst_element_get_state(interfacePlayerPriv->gstPrivateContext->pipeline,
currentState, pendingState, 100 * GST_MSECOND);
}
// No pipeline
if (currentState) *currentState = GST_STATE_NULL;
if (pendingState) *pendingState = GST_STATE_VOID_PENDING;
return GST_STATE_CHANGE_SUCCESS;
}
7 changes: 7 additions & 0 deletions middleware/InterfacePlayerRDK.h
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,13 @@ class InterfacePlayerRDK
*/
void SetStreamCaps(GstMediaType type, MediaCodecInfo&& codecInfo);

/**
* @brief Gets the stream capabilities.
* @param[in] type The media type.
* @return The codec information for the specified media type.
*/
GstStateChangeReturn GetPipelineState(GstState *currentState, GstState *pendingState);

private:
InterfacePlayerPriv *interfacePlayerPriv;
};
Expand Down
Loading
Loading