Commit 55ee27ae authored by kbalazs@webkit.org's avatar kbalazs@webkit.org

[GStreamer] cannot seek after video finished

https://bugs.webkit.org/show_bug.cgi?id=114044

Patch by Balazs Kelemen <b.kelemen@sisa.samsung.com> on 2013-04-30
Reviewed by Philippe Normand.

Source/WebCore:

Test: media/video-seek-after-end.html

Rework the seeking logic to be able to seek after reseting the pipeline.
In addition to solve the actual problem this patch supposed to make seeking
more robust and correct.
The previous implementation tried to hide the complexity of asynchronous operations
on the pipeline. It did not handle the GST_MESSAGE_ASYNC_DONE message from the bus
but instead reported the seek as finished when it saw an asynchronous pending state
(GST_STATE_CHANGE_ASYNC) which could happen way before the seek is really done.
Now we pay attention to the GST_MESSAGE_ASYNC_DONE message to track the status of seeks.
Seeks are not the only operations executed asynchronously, changing the pipeling state is
similar. It means a seek can overlap with onother ongoing asynchronous operation.
This change address this by introducing an invariant for seeks, which is that we only request
a seek if there are no other ongoing asynchronous operations and the pipeline state is either
paused or playing (which is recommended anyway according to GStreamer's documentation).
This way we can be sure that the time when we get the next GST_MESSAGE_ASYNC_DONE message the
seek has been completed.

* platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp:
(WebCore::toGstClockTime): Factored time conversation into a helper.
(WebCore::MediaPlayerPrivateGStreamer::MediaPlayerPrivateGStreamer):

(WebCore::MediaPlayerPrivateGStreamer::playbackPosition): The position might not be available
if the pipeline still has a pending state. As a workaround, if we are right after a seek we can
use the seek time. Avoiding this situation would be possible by not allowing any asynchronous
operation to overlap. I believe it would add a lot more complexity so I decided to rather introduce
this workaround. Otherwise those overlapping operations are handled fine by GStreamer.

(WebCore::MediaPlayerPrivateGStreamer::prepareToPlay): Do not reset internal state variables.
This function called when there is an intent to restart playback but it does not actually restart it.
(WebCore::MediaPlayerPrivateGStreamer::currentTime): Just removed a staling newline.
(WebCore::MediaPlayerPrivateGStreamer::seek): Take a look to the pipeline state and act upon that.
If there is an ongoing asynchronous operation make the seek pending, otherwise do it now.
Now we handle overlapping seeks as well because I saw that it can happen in some tests.
Added an early return for live streams as it doesn't makes sense to try seeking in them.

(WebCore::MediaPlayerPrivateGStreamer::handleMessage): Handle GST_MESSAGE_ASYNC_DONE and some refactoring.
(WebCore::MediaPlayerPrivateGStreamer::asyncStateChangeDone):
(WebCore::MediaPlayerPrivateGStreamer::updateStates): Only handle seeks in the pending case, the rest is
now handled in asyncStateChangeDone.
(WebCore::MediaPlayerPrivateGStreamer::cacheDuration): Do not reset the m_mediaDurationKnown if the pipeline
has an asynchronous pending state because it would fail. It does actually happen when we get a duration message
after restarting the pipeline and it would result in restarting playback from the start. It seems to be a bug
in GStreamer that it sends the duration message too early. Also sanitized this function by merging redundant branches.
* platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.h:
(MediaPlayerPrivateGStreamer):

LayoutTests:

* media/video-seek-after-end-expected.txt: Added.
* media/video-seek-after-end.html: Added.

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@150022 268f45cc-cd09-0410-ab3c-d52691b4dbfc
parent 08fb760e
2013-04-30 Balazs Kelemen <b.kelemen@sisa.samsung.com>
[GStreamer] cannot seek after video finished
https://bugs.webkit.org/show_bug.cgi?id=114044
Reviewed by Philippe Normand.
* media/video-seek-after-end-expected.txt: Added.
* media/video-seek-after-end.html: Added.
2013-05-13 Christophe Dumez <ch.dumez@sisa.samsung.com>
Unreviewed EFL gardening.
......
Test that we can seek after reached the end of the video.
EVENT(playing)
EVENT(ended)
EVENT(seeked)
EVENT(playing)
EXPECTED (video.currentTime.toFixed(2) == '3') OK
END OF TEST
<!DOCTYPE HTML>
<html>
<head>
<script src=media-file.js></script>
<script src=video-test.js></script>
<script>
function start()
{
findMediaElement();
var countPlaying = 0;
waitForEvent('playing', function() {
if (++countPlaying > 2) {
logResult(false, 'FAIL: playing fired more then twice.');
endTest();
return;
}
var seekTarget = 3;
if (countPlaying == 2) {
testExpected('video.currentTime.toFixed(2)', seekTarget, '==');
endTest();
return;
}
waitForEventOnce('ended', function() {
video.currentTime = seekTarget;
waitForEventOnce('seeked', function() { video.play(); })
});
video.currentTime = video.duration - 0.2;
});
video.src = findMediaFile('video', 'content/test');
video.play();
setTimeout(function() {
logResult(false, 'FAIL: timeout');
endTest();
}, 1000);
}
</script>
</head>
<body onload="start()">
<p>Test that we can seek after reached the end of the video.</p>
<video controls></video>
</body>
</html>
2013-04-30 Balazs Kelemen <b.kelemen@sisa.samsung.com>
[GStreamer] cannot seek after video finished
https://bugs.webkit.org/show_bug.cgi?id=114044
Reviewed by Philippe Normand.
Test: media/video-seek-after-end.html
Rework the seeking logic to be able to seek after reseting the pipeline.
In addition to solve the actual problem this patch supposed to make seeking
more robust and correct.
The previous implementation tried to hide the complexity of asynchronous operations
on the pipeline. It did not handle the GST_MESSAGE_ASYNC_DONE message from the bus
but instead reported the seek as finished when it saw an asynchronous pending state
(GST_STATE_CHANGE_ASYNC) which could happen way before the seek is really done.
Now we pay attention to the GST_MESSAGE_ASYNC_DONE message to track the status of seeks.
Seeks are not the only operations executed asynchronously, changing the pipeling state is
similar. It means a seek can overlap with onother ongoing asynchronous operation.
This change address this by introducing an invariant for seeks, which is that we only request
a seek if there are no other ongoing asynchronous operations and the pipeline state is either
paused or playing (which is recommended anyway according to GStreamer's documentation).
This way we can be sure that the time when we get the next GST_MESSAGE_ASYNC_DONE message the
seek has been completed.
* platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp:
(WebCore::toGstClockTime): Factored time conversation into a helper.
(WebCore::MediaPlayerPrivateGStreamer::MediaPlayerPrivateGStreamer):
(WebCore::MediaPlayerPrivateGStreamer::playbackPosition): The position might not be available
if the pipeline still has a pending state. As a workaround, if we are right after a seek we can
use the seek time. Avoiding this situation would be possible by not allowing any asynchronous
operation to overlap. I believe it would add a lot more complexity so I decided to rather introduce
this workaround. Otherwise those overlapping operations are handled fine by GStreamer.
(WebCore::MediaPlayerPrivateGStreamer::prepareToPlay): Do not reset internal state variables.
This function called when there is an intent to restart playback but it does not actually restart it.
(WebCore::MediaPlayerPrivateGStreamer::currentTime): Just removed a staling newline.
(WebCore::MediaPlayerPrivateGStreamer::seek): Take a look to the pipeline state and act upon that.
If there is an ongoing asynchronous operation make the seek pending, otherwise do it now.
Now we handle overlapping seeks as well because I saw that it can happen in some tests.
Added an early return for live streams as it doesn't makes sense to try seeking in them.
(WebCore::MediaPlayerPrivateGStreamer::handleMessage): Handle GST_MESSAGE_ASYNC_DONE and some refactoring.
(WebCore::MediaPlayerPrivateGStreamer::asyncStateChangeDone):
(WebCore::MediaPlayerPrivateGStreamer::updateStates): Only handle seeks in the pending case, the rest is
now handled in asyncStateChangeDone.
(WebCore::MediaPlayerPrivateGStreamer::cacheDuration): Do not reset the m_mediaDurationKnown if the pipeline
has an asynchronous pending state because it would fail. It does actually happen when we get a duration message
after restarting the pipeline and it would result in restarting playback from the start. It seems to be a bug
in GStreamer that it sends the duration message too early. Also sanitized this function by merging redundant branches.
* platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.h:
(MediaPlayerPrivateGStreamer):
2013-05-12 Darin Adler <darin@apple.com>
[Mac] Move writeRange/PlainText/URL from ClipboardMac to PasteboardMac
......
......@@ -138,6 +138,18 @@ static void mediaPlayerPrivatePluginInstallerResultFunction(GstInstallPluginsRet
player->handlePluginInstallerResult(result);
}
static GstClockTime toGstClockTime(float time)
{
// Extract the integer part of the time (seconds) and the fractional part (microseconds). Attempt to
// round the microseconds so no floating point precision is lost and we can perform an accurate seek.
float seconds;
float microSeconds = modf(time, &seconds) * 1000000;
GTimeVal timeValue;
timeValue.tv_sec = static_cast<glong>(seconds);
timeValue.tv_usec = static_cast<glong>(roundf(microSeconds / 10000) * 10000);
return GST_TIMEVAL_TO_TIME(timeValue);
}
void MediaPlayerPrivateGStreamer::setAudioStreamProperties(GObject* object)
{
if (g_strcmp0(G_OBJECT_TYPE_NAME(object), "GstPulseSink"))
......@@ -199,6 +211,8 @@ MediaPlayerPrivateGStreamer::MediaPlayerPrivateGStreamer(MediaPlayer* player)
, m_resetPipeline(false)
, m_paused(true)
, m_seeking(false)
, m_seekIsPending(false)
, m_timeOfOverlappingSeek(-1)
, m_buffering(false)
, m_playbackRate(1)
, m_errorOccured(false)
......@@ -333,30 +347,26 @@ float MediaPlayerPrivateGStreamer::playbackPosition() const
return m_seekTime;
if (m_mediaDuration)
return m_mediaDuration;
return 0;
}
float ret = 0.0f;
GstQuery* query = gst_query_new_position(GST_FORMAT_TIME);
if (!gst_element_query(m_playBin.get(), query)) {
LOG_MEDIA_MESSAGE("Position query failed...");
gst_query_unref(query);
return ret;
}
gint64 position;
gst_query_parse_position(query, 0, &position);
// Position is only available if no async state change is going on and the state is either paused or playing.
gint64 position = GST_CLOCK_TIME_NONE;
GstQuery* query= gst_query_new_position(GST_FORMAT_TIME);
if (gst_element_query(m_playBin.get(), query))
gst_query_parse_position(query, 0, &position);
// Position is available only if the pipeline is not in GST_STATE_NULL or
// GST_STATE_READY state.
if (position != static_cast<gint64>(GST_CLOCK_TIME_NONE))
ret = static_cast<double>(position) / GST_SECOND;
float result = 0.0f;
if (static_cast<GstClockTime>(position) != GST_CLOCK_TIME_NONE)
result = static_cast<double>(position) / GST_SECOND;
else if (m_canFallBackToLastFinishedSeekPositon)
result = m_seekTime;
LOG_MEDIA_MESSAGE("Position %" GST_TIME_FORMAT, GST_TIME_ARGS(position));
gst_query_unref(query);
return ret;
return result;
}
bool MediaPlayerPrivateGStreamer::changePipelineState(GstState newState)
......@@ -387,9 +397,6 @@ bool MediaPlayerPrivateGStreamer::changePipelineState(GstState newState)
void MediaPlayerPrivateGStreamer::prepareToPlay()
{
m_isEndReached = false;
m_seeking = false;
if (m_delayingLoad) {
m_delayingLoad = false;
commitLoad();
......@@ -467,7 +474,6 @@ float MediaPlayerPrivateGStreamer::currentTime() const
return 0.0f;
return playbackPosition();
}
void MediaPlayerPrivateGStreamer::seek(float time)
......@@ -478,35 +484,52 @@ void MediaPlayerPrivateGStreamer::seek(float time)
if (m_errorOccured)
return;
LOG_MEDIA_MESSAGE("Seek attempt to %f secs", time);
LOG_MEDIA_MESSAGE("[Seek] seek attempt to %f secs", time);
// Avoid useless seeking.
if (time == currentTime())
return;
// Extract the integer part of the time (seconds) and the
// fractional part (microseconds). Attempt to round the
// microseconds so no floating point precision is lost and we can
// perform an accurate seek.
float seconds;
float microSeconds = modf(time, &seconds) * 1000000;
GTimeVal timeValue;
timeValue.tv_sec = static_cast<glong>(seconds);
timeValue.tv_usec = static_cast<glong>(roundf(microSeconds / 10000) * 10000);
if (isLiveStream())
return;
GstClockTime clockTime = toGstClockTime(time);
LOG_MEDIA_MESSAGE("[Seek] seeking to %" GST_TIME_FORMAT " (%f)", GST_TIME_ARGS(clockTime), time);
GstClockTime clockTime = GST_TIMEVAL_TO_TIME(timeValue);
LOG_MEDIA_MESSAGE("Seek: %" GST_TIME_FORMAT, GST_TIME_ARGS(clockTime));
if (!gst_element_seek(m_playBin.get(), m_player->rate(),
GST_FORMAT_TIME,
(GstSeekFlags)(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE),
GST_SEEK_TYPE_SET, clockTime,
GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE))
LOG_MEDIA_MESSAGE("Seek to %f failed", time);
else {
m_seeking = true;
m_seekTime = time;
if (m_seeking) {
if (m_seekIsPending) {
m_seekTime = time;
return;
}
m_timeOfOverlappingSeek = time;
}
GstState state;
GstStateChangeReturn getStateResult = gst_element_get_state(m_playBin.get(), &state, 0, 0);
if (getStateResult == GST_STATE_CHANGE_FAILURE || getStateResult == GST_STATE_CHANGE_NO_PREROLL) {
LOG_MEDIA_MESSAGE("[Seek] cannot seek, current state change is %s", gst_element_state_change_return_get_name(getStateResult));
return;
}
if (getStateResult == GST_STATE_CHANGE_ASYNC || state < GST_STATE_PAUSED || m_isEndReached) {
m_seekIsPending = true;
if (m_isEndReached) {
LOG_MEDIA_MESSAGE("[Seek] reset pipeline");
ASSERT(state == GST_STATE_NULL);
m_resetPipeline = true;
changePipelineState(GST_STATE_PAUSED);
}
} else {
// We can seek now.
if (!gst_element_seek(m_playBin.get(), m_player->rate(), GST_FORMAT_TIME, static_cast<GstSeekFlags>(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE),
GST_SEEK_TYPE_SET, clockTime, GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE)) {
LOG_MEDIA_MESSAGE("[Seek] seeking to %f failed", time);
return;
}
}
m_seeking = true;
m_seekTime = time;
m_isEndReached = false;
}
bool MediaPlayerPrivateGStreamer::paused() const
......@@ -680,6 +703,8 @@ gboolean MediaPlayerPrivateGStreamer::handleMessage(GstMessage* message)
const GstStructure* structure = gst_message_get_structure(message);
GstState requestedState, currentState;
m_canFallBackToLastFinishedSeekPositon = false;
if (structure) {
const gchar* messageTypeName = gst_structure_get_name(structure);
......@@ -691,7 +716,10 @@ gboolean MediaPlayerPrivateGStreamer::handleMessage(GstMessage* message)
}
}
LOG_MEDIA_MESSAGE("Message received from element %s", GST_MESSAGE_SRC_NAME(message));
// We ignore state changes from internal elements. They are forwarded to playbin2 anyway.
bool messageSourceIsPlaybin = GST_MESSAGE_SRC(message) == reinterpret_cast<GstObject*>(m_playBin.get());
LOG_MEDIA_MESSAGE("Message %s received from element %s", GST_MESSAGE_TYPE_NAME(message), GST_MESSAGE_SRC_NAME(message));
switch (GST_MESSAGE_TYPE(message)) {
case GST_MESSAGE_ERROR:
if (m_resetPipeline)
......@@ -729,33 +757,26 @@ gboolean MediaPlayerPrivateGStreamer::handleMessage(GstMessage* message)
loadingFailed(error);
break;
case GST_MESSAGE_EOS:
LOG_MEDIA_MESSAGE("End of Stream");
didEnd();
break;
case GST_MESSAGE_STATE_CHANGED:
// Ignore state changes if load is delayed (preload=none). The
// player state will be updated once commitLoad() is called.
if (m_delayingLoad) {
LOG_MEDIA_MESSAGE("Media load has been delayed. Ignoring state changes for now");
case GST_MESSAGE_ASYNC_DONE:
if (!messageSourceIsPlaybin || m_delayingLoad)
break;
}
// Ignore state changes from internal elements. They are
// forwarded to playbin2 anyway.
if (GST_MESSAGE_SRC(message) == reinterpret_cast<GstObject*>(m_playBin.get())) {
updateStates();
// Construct a filename for the graphviz dot file output.
GstState newState;
gst_message_parse_state_changed(message, &currentState, &newState, 0);
asyncStateChangeDone();
break;
case GST_MESSAGE_STATE_CHANGED: {
if (!messageSourceIsPlaybin || m_delayingLoad)
break;
updateStates();
CString dotFileName = String::format("webkit-video.%s_%s",
gst_element_state_get_name(currentState),
gst_element_state_get_name(newState)).utf8();
// Construct a filename for the graphviz dot file output.
GstState newState;
gst_message_parse_state_changed(message, &currentState, &newState, 0);
CString dotFileName = String::format("webkit-video.%s_%s", gst_element_state_get_name(currentState), gst_element_state_get_name(newState)).utf8();
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(m_playBin.get()), GST_DEBUG_GRAPH_SHOW_ALL, dotFileName.data());
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(m_playBin.get()), GST_DEBUG_GRAPH_SHOW_ALL, dotFileName.data());
}
break;
}
case GST_MESSAGE_BUFFERING:
processBufferingStats(message);
break;
......@@ -764,8 +785,8 @@ gboolean MediaPlayerPrivateGStreamer::handleMessage(GstMessage* message)
#else
case GST_MESSAGE_DURATION:
#endif
LOG_MEDIA_MESSAGE("Duration changed");
durationChanged();
if (messageSourceIsPlaybin)
durationChanged();
break;
case GST_MESSAGE_REQUEST_STATE:
gst_message_parse_request_state(message, &requestedState);
......@@ -1033,6 +1054,32 @@ void MediaPlayerPrivateGStreamer::cancelLoad()
gst_element_set_state(m_playBin.get(), GST_STATE_NULL);
}
void MediaPlayerPrivateGStreamer::asyncStateChangeDone()
{
if (!m_playBin || m_errorOccured)
return;
if (m_seeking) {
if (m_seekIsPending)
updateStates();
else {
LOG_MEDIA_MESSAGE("[Seek] seeked to %f", m_seekTime);
m_seeking = false;
if (m_timeOfOverlappingSeek != -1) {
seek(m_timeOfOverlappingSeek);
m_timeOfOverlappingSeek = -1;
return;
}
// The pipeline can still have a pending state. In this case a position query will fail.
// Right now we can use m_seekTime as a fallback.
m_canFallBackToLastFinishedSeekPositon = true;
timeChanged();
}
} else
updateStates();
}
void MediaPlayerPrivateGStreamer::updateStates()
{
if (!m_playBin)
......@@ -1046,16 +1093,12 @@ void MediaPlayerPrivateGStreamer::updateStates()
GstState state;
GstState pending;
GstStateChangeReturn ret = gst_element_get_state(m_playBin.get(),
&state, &pending, 250 * GST_NSECOND);
GstStateChangeReturn getStateResult = gst_element_get_state(m_playBin.get(), &state, &pending, 250 * GST_NSECOND);
bool shouldUpdateAfterSeek = false;
bool shouldUpdatePlaybackState = false;
switch (ret) {
switch (getStateResult) {
case GST_STATE_CHANGE_SUCCESS:
LOG_MEDIA_MESSAGE("State: %s, pending: %s",
gst_element_state_get_name(state),
gst_element_state_get_name(pending));
LOG_MEDIA_MESSAGE("State: %s, pending: %s", gst_element_state_get_name(state), gst_element_state_get_name(pending));
m_resetPipeline = state <= GST_STATE_READY;
......@@ -1132,11 +1175,6 @@ void MediaPlayerPrivateGStreamer::updateStates()
m_changingRate = false;
}
if (m_seeking) {
shouldUpdateAfterSeek = true;
m_seeking = false;
}
if (m_requestedState == GST_STATE_PAUSED && state == GST_STATE_PAUSED) {
shouldUpdatePlaybackState = true;
LOG_MEDIA_MESSAGE("Requested state change to %s was completed", gst_element_state_get_name(state));
......@@ -1144,9 +1182,7 @@ void MediaPlayerPrivateGStreamer::updateStates()
break;
case GST_STATE_CHANGE_ASYNC:
LOG_MEDIA_MESSAGE("Async: State: %s, pending: %s",
gst_element_state_get_name(state),
gst_element_state_get_name(pending));
LOG_MEDIA_MESSAGE("Async: State: %s, pending: %s", gst_element_state_get_name(state), gst_element_state_get_name(pending));
// Change in progress
// On-disk buffering was attempted but the media is live. This
......@@ -1164,24 +1200,13 @@ void MediaPlayerPrivateGStreamer::updateStates()
gst_element_set_state(m_playBin.get(), GST_STATE_PLAYING);
}
if (!isLiveStream() && !m_buffering)
return;
if (m_seeking) {
shouldUpdateAfterSeek = true;
m_seeking = false;
}
break;
case GST_STATE_CHANGE_FAILURE:
LOG_MEDIA_MESSAGE("Failure: State: %s, pending: %s",
gst_element_state_get_name(state),
gst_element_state_get_name(pending));
LOG_MEDIA_MESSAGE("Failure: State: %s, pending: %s", gst_element_state_get_name(state), gst_element_state_get_name(pending));
// Change failed
return;
case GST_STATE_CHANGE_NO_PREROLL:
LOG_MEDIA_MESSAGE("No preroll: State: %s, pending: %s",
gst_element_state_get_name(state),
gst_element_state_get_name(pending));
LOG_MEDIA_MESSAGE("No preroll: State: %s, pending: %s", gst_element_state_get_name(state), gst_element_state_get_name(pending));
if (state == GST_STATE_READY)
m_readyState = MediaPlayer::HaveNothing;
......@@ -1193,42 +1218,38 @@ void MediaPlayerPrivateGStreamer::updateStates()
} else if (state == GST_STATE_PLAYING)
m_paused = false;
if (m_seeking) {
shouldUpdateAfterSeek = true;
m_seeking = false;
if (!m_paused)
gst_element_set_state(m_playBin.get(), GST_STATE_PLAYING);
} else if (!m_paused)
if (!m_paused)
gst_element_set_state(m_playBin.get(), GST_STATE_PLAYING);
m_networkState = MediaPlayer::Loading;
break;
default:
LOG_MEDIA_MESSAGE("Else : %d", ret);
LOG_MEDIA_MESSAGE("Else : %d", getStateResult);
break;
}
m_requestedState = GST_STATE_VOID_PENDING;
if (seeking())
m_readyState = MediaPlayer::HaveNothing;
if (shouldUpdateAfterSeek)
timeChanged();
if (shouldUpdatePlaybackState)
m_player->playbackStateChanged();
if (m_networkState != oldNetworkState) {
LOG_MEDIA_MESSAGE("Network State Changed from %u to %u",
oldNetworkState, m_networkState);
LOG_MEDIA_MESSAGE("Network State Changed from %u to %u", oldNetworkState, m_networkState);
m_player->networkStateChanged();
}
if (m_readyState != oldReadyState) {
LOG_MEDIA_MESSAGE("Ready State Changed from %u to %u",
oldReadyState, m_readyState);
LOG_MEDIA_MESSAGE("Ready State Changed from %u to %u", oldReadyState, m_readyState);
m_player->readyStateChanged();
}
if (m_seekIsPending && getStateResult == GST_STATE_CHANGE_SUCCESS && (state == GST_STATE_PAUSED || state == GST_STATE_PLAYING)) {
LOG_MEDIA_MESSAGE("[Seek] committing pending seek to %f", m_seekTime);
m_seekIsPending = false;
m_seeking = gst_element_seek(m_playBin.get(), m_player->rate(), GST_FORMAT_TIME, static_cast<GstSeekFlags>(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE),
GST_SEEK_TYPE_SET, toGstClockTime(m_seekTime), GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE);
if (!m_seeking)
LOG_MEDIA_MESSAGE("[Seek] seeking to %f failed", m_seekTime);
}
}
void MediaPlayerPrivateGStreamer::mediaLocationChanged(GstMessage* message)
......@@ -1361,19 +1382,14 @@ void MediaPlayerPrivateGStreamer::cacheDuration()
// And re-cache it if possible.
GstState state;
gst_element_get_state(m_playBin.get(), &state, 0, 0);
GstStateChangeReturn getStateResult = gst_element_get_state(m_playBin.get(), &state, 0, 0);
float newDuration = duration();
if (state <= GST_STATE_READY) {
if (state > GST_STATE_READY && getStateResult == GST_STATE_CHANGE_SUCCESS) {
// Don't set m_mediaDurationKnown yet if the pipeline is not
// paused. This allows duration() query to fail at least once
// stable. This allows duration() query to fail at least once
// before playback starts and duration becomes known.
if (!std::isinf(newDuration))
m_mediaDuration = newDuration;
} else {
m_mediaDurationKnown = !std::isinf(newDuration);
if (m_mediaDurationKnown)
m_mediaDuration = newDuration;
}
if (!std::isinf(newDuration))
......
......@@ -114,6 +114,7 @@ private:
void cacheDuration();
void updateStates();
void asyncStateChangeDone();
void createGSTPlayBin();
bool changePipelineState(GstState);
......@@ -140,6 +141,9 @@ private:
bool m_resetPipeline;
bool m_paused;
bool m_seeking;
bool m_seekIsPending;
float m_timeOfOverlappingSeek;
bool m_canFallBackToLastFinishedSeekPositon;
bool m_buffering;
float m_playbackRate;
bool m_errorOccured;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment