Changeset 150066 in webkit
- Timestamp:
- May 14, 2013, 5:49:06 AM (13 years ago)
- Location:
- trunk
- Files:
-
- 2 added
- 4 edited
-
LayoutTests/ChangeLog (modified) (1 diff)
-
LayoutTests/media/video-seek-after-end-expected.txt (added)
-
LayoutTests/media/video-seek-after-end.html (added)
-
Source/WebCore/ChangeLog (modified) (1 diff)
-
Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp (modified) (21 diffs)
-
Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.h (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
trunk/LayoutTests/ChangeLog
r150065 r150066 1 2013-04-30 Balazs Kelemen <b.kelemen@sisa.samsung.com> 2 3 [GStreamer] cannot seek after video finished 4 https://bugs.webkit.org/show_bug.cgi?id=114044 5 6 Reviewed by Philippe Normand. 7 8 * media/video-seek-after-end-expected.txt: Added. 9 * media/video-seek-after-end.html: Added. 10 1 11 2013-05-14 Zalan Bujtas <zalan@apple.com> 2 12 -
trunk/Source/WebCore/ChangeLog
r150065 r150066 1 2013-04-30 Balazs Kelemen <b.kelemen@sisa.samsung.com> 2 3 [GStreamer] cannot seek after video finished 4 https://bugs.webkit.org/show_bug.cgi?id=114044 5 6 Reviewed by Philippe Normand. 7 8 Test: media/video-seek-after-end.html 9 10 Reland without wrong assertion. If seek is called after didEnd the pipeline 11 state will not be in GST_STATE_NULL yet but it is not a problem because we handle that. 12 13 Rework the seeking logic to be able to seek after reseting the pipeline. 14 In addition to solve the actual problem this patch supposed to make seeking 15 more robust and correct. 16 The previous implementation tried to hide the complexity of asynchronous operations 17 on the pipeline. It did not handle the GST_MESSAGE_ASYNC_DONE message from the bus 18 but instead reported the seek as finished when it saw an asynchronous pending state 19 (GST_STATE_CHANGE_ASYNC) which could happen way before the seek is really done. 20 Now we pay attention to the GST_MESSAGE_ASYNC_DONE message to track the status of seeks. 21 Seeks are not the only operations executed asynchronously, changing the pipeling state is 22 similar. It means a seek can overlap with onother ongoing asynchronous operation. 23 This change address this by introducing an invariant for seeks, which is that we only request 24 a seek if there are no other ongoing asynchronous operations and the pipeline state is either 25 paused or playing (which is recommended anyway according to GStreamer's documentation). 26 This way we can be sure that the time when we get the next GST_MESSAGE_ASYNC_DONE message the 27 seek has been completed. 28 29 * platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp: 30 (WebCore::toGstClockTime): Factored time conversation into a helper. 31 (WebCore::MediaPlayerPrivateGStreamer::MediaPlayerPrivateGStreamer): 32 33 (WebCore::MediaPlayerPrivateGStreamer::playbackPosition): The position might not be available 34 if the pipeline still has a pending state. As a workaround, if we are right after a seek we can 35 use the seek time. Avoiding this situation would be possible by not allowing any asynchronous 36 operation to overlap. I believe it would add a lot more complexity so I decided to rather introduce 37 this workaround. Otherwise those overlapping operations are handled fine by GStreamer. 38 39 (WebCore::MediaPlayerPrivateGStreamer::prepareToPlay): Do not reset internal state variables. 40 This function called when there is an intent to restart playback but it does not actually restart it. 41 (WebCore::MediaPlayerPrivateGStreamer::currentTime): Just removed a staling newline. 42 (WebCore::MediaPlayerPrivateGStreamer::seek): Take a look to the pipeline state and act upon that. 43 If there is an ongoing asynchronous operation make the seek pending, otherwise do it now. 44 Now we handle overlapping seeks as well because I saw that it can happen in some tests. 45 Added an early return for live streams as it doesn't makes sense to try seeking in them. 46 47 (WebCore::MediaPlayerPrivateGStreamer::handleMessage): Handle GST_MESSAGE_ASYNC_DONE and some refactoring. 48 (WebCore::MediaPlayerPrivateGStreamer::asyncStateChangeDone): 49 (WebCore::MediaPlayerPrivateGStreamer::updateStates): Only handle seeks in the pending case, the rest is 50 now handled in asyncStateChangeDone. 51 (WebCore::MediaPlayerPrivateGStreamer::cacheDuration): Do not reset the m_mediaDurationKnown if the pipeline 52 has an asynchronous pending state because it would fail. It does actually happen when we get a duration message 53 after restarting the pipeline and it would result in restarting playback from the start. It seems to be a bug 54 in GStreamer that it sends the duration message too early. Also sanitized this function by merging redundant branches. 55 * platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.h: 56 (MediaPlayerPrivateGStreamer): 57 1 58 2013-05-14 Zalan Bujtas <zalan@apple.com> 2 59 -
trunk/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp
r150031 r150066 139 139 } 140 140 141 static GstClockTime toGstClockTime(float time) 142 { 143 // Extract the integer part of the time (seconds) and the fractional part (microseconds). Attempt to 144 // round the microseconds so no floating point precision is lost and we can perform an accurate seek. 145 float seconds; 146 float microSeconds = modf(time, &seconds) * 1000000; 147 GTimeVal timeValue; 148 timeValue.tv_sec = static_cast<glong>(seconds); 149 timeValue.tv_usec = static_cast<glong>(roundf(microSeconds / 10000) * 10000); 150 return GST_TIMEVAL_TO_TIME(timeValue); 151 } 152 141 153 void MediaPlayerPrivateGStreamer::setAudioStreamProperties(GObject* object) 142 154 { … … 200 212 , m_paused(true) 201 213 , m_seeking(false) 214 , m_seekIsPending(false) 215 , m_timeOfOverlappingSeek(-1) 202 216 , m_buffering(false) 203 217 , m_playbackRate(1) … … 334 348 if (m_mediaDuration) 335 349 return m_mediaDuration; 336 } 337 338 float ret = 0.0f; 339 340 GstQuery* query = gst_query_new_position(GST_FORMAT_TIME); 341 if (!gst_element_query(m_playBin.get(), query)) { 342 LOG_MEDIA_MESSAGE("Position query failed..."); 343 gst_query_unref(query); 344 return ret; 345 } 346 347 gint64 position; 348 gst_query_parse_position(query, 0, &position); 349 350 // Position is available only if the pipeline is not in GST_STATE_NULL or 351 // GST_STATE_READY state. 352 if (position != static_cast<gint64>(GST_CLOCK_TIME_NONE)) 353 ret = static_cast<double>(position) / GST_SECOND; 350 return 0; 351 } 352 353 // Position is only available if no async state change is going on and the state is either paused or playing. 354 gint64 position = GST_CLOCK_TIME_NONE; 355 GstQuery* query= gst_query_new_position(GST_FORMAT_TIME); 356 if (gst_element_query(m_playBin.get(), query)) 357 gst_query_parse_position(query, 0, &position); 358 359 float result = 0.0f; 360 if (static_cast<GstClockTime>(position) != GST_CLOCK_TIME_NONE) 361 result = static_cast<double>(position) / GST_SECOND; 362 else if (m_canFallBackToLastFinishedSeekPositon) 363 result = m_seekTime; 354 364 355 365 LOG_MEDIA_MESSAGE("Position %" GST_TIME_FORMAT, GST_TIME_ARGS(position)); … … 357 367 gst_query_unref(query); 358 368 359 return re t;369 return result; 360 370 } 361 371 … … 388 398 void MediaPlayerPrivateGStreamer::prepareToPlay() 389 399 { 390 m_isEndReached = false;391 m_seeking = false;392 393 400 if (m_delayingLoad) { 394 401 m_delayingLoad = false; … … 468 475 469 476 return playbackPosition(); 470 471 477 } 472 478 … … 479 485 return; 480 486 481 LOG_MEDIA_MESSAGE(" Seek attempt to %f secs", time);487 LOG_MEDIA_MESSAGE("[Seek] seek attempt to %f secs", time); 482 488 483 489 // Avoid useless seeking. … … 485 491 return; 486 492 487 // Extract the integer part of the time (seconds) and the 488 // fractional part (microseconds). Attempt to round the 489 // microseconds so no floating point precision is lost and we can 490 // perform an accurate seek. 491 float seconds; 492 float microSeconds = modf(time, &seconds) * 1000000; 493 GTimeVal timeValue; 494 timeValue.tv_sec = static_cast<glong>(seconds); 495 timeValue.tv_usec = static_cast<glong>(roundf(microSeconds / 10000) * 10000); 496 497 GstClockTime clockTime = GST_TIMEVAL_TO_TIME(timeValue); 498 LOG_MEDIA_MESSAGE("Seek: %" GST_TIME_FORMAT, GST_TIME_ARGS(clockTime)); 499 500 if (!gst_element_seek(m_playBin.get(), m_player->rate(), 501 GST_FORMAT_TIME, 502 (GstSeekFlags)(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE), 503 GST_SEEK_TYPE_SET, clockTime, 504 GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE)) 505 LOG_MEDIA_MESSAGE("Seek to %f failed", time); 506 else { 507 m_seeking = true; 508 m_seekTime = time; 509 } 493 if (isLiveStream()) 494 return; 495 496 GstClockTime clockTime = toGstClockTime(time); 497 LOG_MEDIA_MESSAGE("[Seek] seeking to %" GST_TIME_FORMAT " (%f)", GST_TIME_ARGS(clockTime), time); 498 499 if (m_seeking) { 500 if (m_seekIsPending) { 501 m_seekTime = time; 502 return; 503 } 504 m_timeOfOverlappingSeek = time; 505 } 506 507 GstState state; 508 GstStateChangeReturn getStateResult = gst_element_get_state(m_playBin.get(), &state, 0, 0); 509 if (getStateResult == GST_STATE_CHANGE_FAILURE || getStateResult == GST_STATE_CHANGE_NO_PREROLL) { 510 LOG_MEDIA_MESSAGE("[Seek] cannot seek, current state change is %s", gst_element_state_change_return_get_name(getStateResult)); 511 return; 512 } 513 if (getStateResult == GST_STATE_CHANGE_ASYNC || state < GST_STATE_PAUSED || m_isEndReached) { 514 m_seekIsPending = true; 515 if (m_isEndReached) { 516 LOG_MEDIA_MESSAGE("[Seek] reset pipeline"); 517 m_resetPipeline = true; 518 changePipelineState(GST_STATE_PAUSED); 519 } 520 } else { 521 // We can seek now. 522 if (!gst_element_seek(m_playBin.get(), m_player->rate(), GST_FORMAT_TIME, static_cast<GstSeekFlags>(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE), 523 GST_SEEK_TYPE_SET, clockTime, GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE)) { 524 LOG_MEDIA_MESSAGE("[Seek] seeking to %f failed", time); 525 return; 526 } 527 } 528 529 m_seeking = true; 530 m_seekTime = time; 531 m_isEndReached = false; 510 532 } 511 533 … … 681 703 GstState requestedState, currentState; 682 704 705 m_canFallBackToLastFinishedSeekPositon = false; 706 683 707 if (structure) { 684 708 const gchar* messageTypeName = gst_structure_get_name(structure); … … 692 716 } 693 717 694 LOG_MEDIA_MESSAGE("Message received from element %s", GST_MESSAGE_SRC_NAME(message)); 718 // We ignore state changes from internal elements. They are forwarded to playbin2 anyway. 719 bool messageSourceIsPlaybin = GST_MESSAGE_SRC(message) == reinterpret_cast<GstObject*>(m_playBin.get()); 720 721 LOG_MEDIA_MESSAGE("Message %s received from element %s", GST_MESSAGE_TYPE_NAME(message), GST_MESSAGE_SRC_NAME(message)); 695 722 switch (GST_MESSAGE_TYPE(message)) { 696 723 case GST_MESSAGE_ERROR: … … 730 757 break; 731 758 case GST_MESSAGE_EOS: 732 LOG_MEDIA_MESSAGE("End of Stream");733 759 didEnd(); 734 760 break; 735 case GST_MESSAGE_STATE_CHANGED: 736 // Ignore state changes if load is delayed (preload=none). The 737 // player state will be updated once commitLoad() is called. 738 if (m_delayingLoad) { 739 LOG_MEDIA_MESSAGE("Media load has been delayed. Ignoring state changes for now"); 761 case GST_MESSAGE_ASYNC_DONE: 762 if (!messageSourceIsPlaybin || m_delayingLoad) 740 763 break; 741 } 742 743 // Ignore state changes from internal elements. They are 744 // forwarded to playbin2 anyway. 745 if (GST_MESSAGE_SRC(message) == reinterpret_cast<GstObject*>(m_playBin.get())) { 746 updateStates(); 747 748 // Construct a filename for the graphviz dot file output. 749 GstState newState; 750 gst_message_parse_state_changed(message, ¤tState, &newState, 0); 751 752 CString dotFileName = String::format("webkit-video.%s_%s", 753 gst_element_state_get_name(currentState), 754 gst_element_state_get_name(newState)).utf8(); 755 756 GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(m_playBin.get()), GST_DEBUG_GRAPH_SHOW_ALL, dotFileName.data()); 757 } 764 asyncStateChangeDone(); 758 765 break; 766 case GST_MESSAGE_STATE_CHANGED: { 767 if (!messageSourceIsPlaybin || m_delayingLoad) 768 break; 769 updateStates(); 770 771 // Construct a filename for the graphviz dot file output. 772 GstState newState; 773 gst_message_parse_state_changed(message, ¤tState, &newState, 0); 774 CString dotFileName = String::format("webkit-video.%s_%s", gst_element_state_get_name(currentState), gst_element_state_get_name(newState)).utf8(); 775 GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(m_playBin.get()), GST_DEBUG_GRAPH_SHOW_ALL, dotFileName.data()); 776 777 break; 778 } 759 779 case GST_MESSAGE_BUFFERING: 760 780 processBufferingStats(message); … … 765 785 case GST_MESSAGE_DURATION: 766 786 #endif 767 LOG_MEDIA_MESSAGE("Duration changed");768 durationChanged();787 if (messageSourceIsPlaybin) 788 durationChanged(); 769 789 break; 770 790 case GST_MESSAGE_REQUEST_STATE: … … 1034 1054 } 1035 1055 1056 void MediaPlayerPrivateGStreamer::asyncStateChangeDone() 1057 { 1058 if (!m_playBin || m_errorOccured) 1059 return; 1060 1061 if (m_seeking) { 1062 if (m_seekIsPending) 1063 updateStates(); 1064 else { 1065 LOG_MEDIA_MESSAGE("[Seek] seeked to %f", m_seekTime); 1066 m_seeking = false; 1067 if (m_timeOfOverlappingSeek != -1) { 1068 seek(m_timeOfOverlappingSeek); 1069 m_timeOfOverlappingSeek = -1; 1070 return; 1071 } 1072 1073 // The pipeline can still have a pending state. In this case a position query will fail. 1074 // Right now we can use m_seekTime as a fallback. 1075 m_canFallBackToLastFinishedSeekPositon = true; 1076 timeChanged(); 1077 } 1078 } else 1079 updateStates(); 1080 } 1081 1036 1082 void MediaPlayerPrivateGStreamer::updateStates() 1037 1083 { … … 1047 1093 GstState pending; 1048 1094 1049 GstStateChangeReturn ret = gst_element_get_state(m_playBin.get(), 1050 &state, &pending, 250 * GST_NSECOND); 1051 1052 bool shouldUpdateAfterSeek = false; 1095 GstStateChangeReturn getStateResult = gst_element_get_state(m_playBin.get(), &state, &pending, 250 * GST_NSECOND); 1096 1053 1097 bool shouldUpdatePlaybackState = false; 1054 switch ( ret) {1098 switch (getStateResult) { 1055 1099 case GST_STATE_CHANGE_SUCCESS: 1056 LOG_MEDIA_MESSAGE("State: %s, pending: %s", 1057 gst_element_state_get_name(state), 1058 gst_element_state_get_name(pending)); 1100 LOG_MEDIA_MESSAGE("State: %s, pending: %s", gst_element_state_get_name(state), gst_element_state_get_name(pending)); 1059 1101 1060 1102 m_resetPipeline = state <= GST_STATE_READY; … … 1133 1175 } 1134 1176 1135 if (m_seeking) {1136 shouldUpdateAfterSeek = true;1137 m_seeking = false;1138 }1139 1140 1177 if (m_requestedState == GST_STATE_PAUSED && state == GST_STATE_PAUSED) { 1141 1178 shouldUpdatePlaybackState = true; … … 1145 1182 break; 1146 1183 case GST_STATE_CHANGE_ASYNC: 1147 LOG_MEDIA_MESSAGE("Async: State: %s, pending: %s", 1148 gst_element_state_get_name(state), 1149 gst_element_state_get_name(pending)); 1184 LOG_MEDIA_MESSAGE("Async: State: %s, pending: %s", gst_element_state_get_name(state), gst_element_state_get_name(pending)); 1150 1185 // Change in progress 1151 1186 … … 1165 1200 } 1166 1201 1167 if (!isLiveStream() && !m_buffering)1168 return;1169 1170 if (m_seeking) {1171 shouldUpdateAfterSeek = true;1172 m_seeking = false;1173 }1174 1202 break; 1175 1203 case GST_STATE_CHANGE_FAILURE: 1176 LOG_MEDIA_MESSAGE("Failure: State: %s, pending: %s", 1177 gst_element_state_get_name(state), 1178 gst_element_state_get_name(pending)); 1204 LOG_MEDIA_MESSAGE("Failure: State: %s, pending: %s", gst_element_state_get_name(state), gst_element_state_get_name(pending)); 1179 1205 // Change failed 1180 1206 return; 1181 1207 case GST_STATE_CHANGE_NO_PREROLL: 1182 LOG_MEDIA_MESSAGE("No preroll: State: %s, pending: %s", 1183 gst_element_state_get_name(state), 1184 gst_element_state_get_name(pending)); 1208 LOG_MEDIA_MESSAGE("No preroll: State: %s, pending: %s", gst_element_state_get_name(state), gst_element_state_get_name(pending)); 1185 1209 1186 1210 if (state == GST_STATE_READY) … … 1194 1218 m_paused = false; 1195 1219 1196 if (m_seeking) { 1197 shouldUpdateAfterSeek = true; 1198 m_seeking = false; 1199 if (!m_paused) 1200 gst_element_set_state(m_playBin.get(), GST_STATE_PLAYING); 1201 } else if (!m_paused) 1220 if (!m_paused) 1202 1221 gst_element_set_state(m_playBin.get(), GST_STATE_PLAYING); 1203 1222 … … 1205 1224 break; 1206 1225 default: 1207 LOG_MEDIA_MESSAGE("Else : %d", ret);1226 LOG_MEDIA_MESSAGE("Else : %d", getStateResult); 1208 1227 break; 1209 1228 } 1210 1229 1211 1230 m_requestedState = GST_STATE_VOID_PENDING; 1212 1213 if (seeking())1214 m_readyState = MediaPlayer::HaveNothing;1215 1216 if (shouldUpdateAfterSeek)1217 timeChanged();1218 1231 1219 1232 if (shouldUpdatePlaybackState) … … 1221 1234 1222 1235 if (m_networkState != oldNetworkState) { 1223 LOG_MEDIA_MESSAGE("Network State Changed from %u to %u", 1224 oldNetworkState, m_networkState); 1236 LOG_MEDIA_MESSAGE("Network State Changed from %u to %u", oldNetworkState, m_networkState); 1225 1237 m_player->networkStateChanged(); 1226 1238 } 1227 1239 if (m_readyState != oldReadyState) { 1228 LOG_MEDIA_MESSAGE("Ready State Changed from %u to %u", 1229 oldReadyState, m_readyState); 1240 LOG_MEDIA_MESSAGE("Ready State Changed from %u to %u", oldReadyState, m_readyState); 1230 1241 m_player->readyStateChanged(); 1242 } 1243 1244 if (m_seekIsPending && getStateResult == GST_STATE_CHANGE_SUCCESS && (state == GST_STATE_PAUSED || state == GST_STATE_PLAYING)) { 1245 LOG_MEDIA_MESSAGE("[Seek] committing pending seek to %f", m_seekTime); 1246 m_seekIsPending = false; 1247 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), 1248 GST_SEEK_TYPE_SET, toGstClockTime(m_seekTime), GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE); 1249 if (!m_seeking) 1250 LOG_MEDIA_MESSAGE("[Seek] seeking to %f failed", m_seekTime); 1231 1251 } 1232 1252 } … … 1362 1382 // And re-cache it if possible. 1363 1383 GstState state; 1364 gst_element_get_state(m_playBin.get(), &state, 0, 0);1384 GstStateChangeReturn getStateResult = gst_element_get_state(m_playBin.get(), &state, 0, 0); 1365 1385 float newDuration = duration(); 1366 1386 1367 if (state <= GST_STATE_READY) {1387 if (state > GST_STATE_READY && getStateResult == GST_STATE_CHANGE_SUCCESS) { 1368 1388 // Don't set m_mediaDurationKnown yet if the pipeline is not 1369 // paused. This allows duration() query to fail at least once1389 // stable. This allows duration() query to fail at least once 1370 1390 // before playback starts and duration becomes known. 1371 if (!std::isinf(newDuration))1372 m_mediaDuration = newDuration;1373 } else {1374 1391 m_mediaDurationKnown = !std::isinf(newDuration); 1375 if (m_mediaDurationKnown)1376 m_mediaDuration = newDuration;1377 1392 } 1378 1393 -
trunk/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.h
r150031 r150066 115 115 void cacheDuration(); 116 116 void updateStates(); 117 void asyncStateChangeDone(); 117 118 118 119 void createGSTPlayBin(); … … 141 142 bool m_paused; 142 143 bool m_seeking; 144 bool m_seekIsPending; 145 float m_timeOfOverlappingSeek; 146 bool m_canFallBackToLastFinishedSeekPositon; 143 147 bool m_buffering; 144 148 float m_playbackRate;
Note:
See TracChangeset
for help on using the changeset viewer.