Repository: shaka-project/shaka-player (JavaScript)

Issue:
Playback is broken after seeking to video end
**Have you read the [FAQ](https://bit.ly/ShakaFAQ) and checked for duplicate open issues?**
Yes. The issue is similar to #1967 and #1014.


**What version of Shaka Player are you using?**
4.1.2 & 3.1.0

**Can you reproduce the issue with our latest release version?**
Yes


**Can you reproduce the issue with the latest code from `main`?**
Yes


**Are you using the demo app or your own custom app?**
Custom app

**If custom app, can you reproduce the issue using our demo app?**
Yes


**What browser and OS are you using?**
Chrome 102.0.5005.63 and Firefox 100.0.2. Windows 10


**For embedded devices (smart TVs, etc.), what model and firmware version are you using?**


**What are the manifest and license server URIs?**
Manifest: https://storage.googleapis.com/shaka-demo-assets/angel-one/dash.mpd
I have been able to reproduce the issue with all manifests tested.


**What configuration are you using?  What is the output of `player.getConfiguration()`?**
Default config, but I am able to also reproduce it programmatically if `streaming.durationBackoff` is set to `0`.


**What did you do?**
Seek to the duration of the video. Note: You need to drag the slider past the end.
Seek to somewhere else.
Start playback.


**What did you expect to happen?**
Playback is working.

**What actually happened?**
Playback is not working. In Chrome when clicking play the play head will jump to the end. In Firefox you cannot seek. The play head jumps to the start.
Possible to seek beyond live edge. Not corrected
<!-- NOTE: If you ignore this template, we will send it again and ask you to fill it out anyway. -->

**Have you read the [FAQ](https://bit.ly/ShakaFAQ) and checked for duplicate open issues?**
Yes

**What version of Shaka Player are you using?**
4.3.5

**Can you reproduce the issue with our latest release version?**
Yes

**Can you reproduce the issue with the latest code from `main`?**
Yes

**Are you using the demo app or your own custom app?**
Custom app, but also reproduced in demo

**If custom app, can you reproduce the issue using our demo app?**
Yes

**What browser and OS are you using?**
Various, but reproduced on Windows Chrome with the demo app

**For embedded devices (smart TVs, etc.), what model and firmware version are you using?**
N/A

**What are the manifest and license server URIs?**
Reproduced with "Live Sim (2s segments)"


**What configuration are you using?  What is the output of `player.getConfiguration()`?**
Reproduced with default config

**What did you do?**
Seek beyond the seekable range, wait half a second, then do it again
```
const vidEl = document.getElementById("video");
vidEl.currentTime = vidEl.currentTime + 3600; 
setTimeout(()=>{vidEl.currentTime = vidEl.currentTime + 3600;}, 500);
```

**What did you expect to happen?**
The video to play from the live edge

**What actually happened?**
The player got stuck waiting.
I also tried seeking a smaller amount into the future (1 minute), and it recovered and started playing after a minute

The problem seems to be in `MediaSourcePlayhead.onSeeking_()` https://github.com/shaka-project/shaka-player/blob/a8e3c9ab310b14ca71610c568cffe108b30d38bf/lib/media/playhead.js#L409

The first seek out of range is corrected, but because the next happens within a second, the correction is simply discarded.



Discussion: Confirmed.  Thank you for the report!
This is reproducible with both our UI and the native browser controls, so it's not a Shaka UI bug.

It does not appear to happen when you seek via the video element API.  I tried both video.currentTime = 60 or video.currentTime = 90 on a 60-second clip.
I still have not determined if this is a browser bug or a Shaka bug.  I will need more time to dig into this.

Since we don't have a fix, this issue will not be fixed by this week's releases.
This doesn't happen on plain HTML5 video, and it doesn't happen with single mp4 videos in Shaka Player.  So the key must be something to do with how we talk to MediaSource.
~Commenting out the inner-most arrow function in `MediaSourceEngine.endOfStream()` makes the problem go away.~
Scratch that, that was wrong.  Something hadn't reloaded properly in my browser.
Increasing the settling time in the UI (`ui/seek_bar.js`, inside `onChange()`) seems to avoid the problem, but it can still be triggered in the native browser seek UI.

I can also reliably trigger the issue in Chrome with code like this, which seeks X number of times, 125 ms apart:

```js
rapidSeek = (seeks) => {
  video.currentTime += 10;
  if (seeks >= 1) {
    console.log(`${seeks} left`);
    setTimeout(() => rapidSeek(seeks - 1), 125);
  } else {
    console.log('done');
  }
};

// seek 5 times past the end:
rapidSeek(5 + (video.duration - video.currentTime) / 10);
```

This only seems to occur for MSE playbacks, but not for plain MP4s.

I can't reproduce the same behavior on either dash.js nor hls.js, so it seems much more likely to be a Shaka bug than a browser bug.
It seems to be important to trigger this state that the seeking happen rapidly enough that the buffer is completely cleared by the time the "ended" state is reached.

This seems to have a consequence at the HTML video element level that we may not be able to do anything about: `video.ended` remains `true` even after seeking backward.  If the buffer isn't empty, seeking backward resets `video.ended` to `false`.  When `video.ended` remains `true`, the position you seek to is overridden by Chrome when you hit `play()`, and playback begins at 0 instead of your seek position.

Part of the problem seems to be that StreamingEngine's state is broken by this, and this part we can fix.  Normally, seeking kick-starts StreamingEngine through the forceClearBuffer_ method.  But if there is nothing buffered, this doesn't happen, and therefore StreamingEngine remains idle after seeking backward.
The issue is still reproduced in the main branch and in 4.6.2
The issue is still reproduced in the main branch and in 4.7.11

@Wayne-Morgan are you interested on send a PR for it? Thanks!
Encountered the same issue. 
I am interested on send a PR.
Please do!  I'll assign the issue to you.
@CeilingFANC Are you still interested in submitting the PR? Thanks!
Yes. Just updated the test. How can I reopen the PR..

What PR?
@CeilingFANC I reopened your PR. Thanks!



Fix (patch):
diff --git a/lib/media/playhead.js b/lib/media/playhead.js
index ea60821602..03277b84ca 100644
--- a/lib/media/playhead.js
+++ b/lib/media/playhead.js
@@ -18,6 +18,7 @@ goog.require('shaka.media.VideoWrapper');
 goog.require('shaka.util.EventManager');
 goog.require('shaka.util.IReleasable');
 goog.require('shaka.util.MediaReadyState');
+goog.require('shaka.util.Platform');
 goog.require('shaka.util.Timer');
 goog.requireType('shaka.media.PresentationTimeline');
 
@@ -420,12 +421,20 @@ shaka.media.MediaSourcePlayhead = class {
 
     const gapLimit = shaka.media.GapJumpingController.BROWSER_GAP_TOLERANCE;
     if (Math.abs(targetTime - currentTime) > gapLimit) {
-      // You can only seek like this every so often. This is to prevent an
-      // infinite loop on systems where changing currentTime takes a significant
-      // amount of time (e.g. Chromecast).
-      const time = Date.now() / 1000;
-      if (!this.lastCorrectiveSeek_ || this.lastCorrectiveSeek_ < time - 1) {
-        this.lastCorrectiveSeek_ = time;
+      let canCorrectiveSeek = false;
+      if (shaka.util.Platform.isSeekingSlow()) {
+        // You can only seek like this every so often. This is to prevent an
+        // infinite loop on systems where changing currentTime takes a
+        // significant amount of time (e.g. Chromecast).
+        const time = Date.now() / 1000;
+        if (!this.lastCorrectiveSeek_ || this.lastCorrectiveSeek_ < time - 1) {
+          this.lastCorrectiveSeek_ = time;
+          canCorrectiveSeek = true;
+        }
+      } else {
+        canCorrectiveSeek = true;
+      }
+      if (canCorrectiveSeek) {
         this.videoWrapper_.setTime(targetTime);
         return;
       }
diff --git a/lib/util/platform.js b/lib/util/platform.js
index c713dca56c..138d1d5d30 100644
--- a/lib/util/platform.js
+++ b/lib/util/platform.js
@@ -645,6 +645,25 @@ shaka.util.Platform = class {
     return true;
   }
 
+  /**
+   * On some platforms, such as v1 Chromecasts, the act of seeking can take a
+   * significant amount of time.
+   *
+   * @return {boolean}
+   */
+  static isSeekingSlow() {
+    const Platform = shaka.util.Platform;
+    if (Platform.isChromecast()) {
+      if (Platform.isAndroidCastDevice()) {
+        // Android-based Chromecasts are new enough to not be a problem.
+        return false;
+      } else {
+        return true;
+      }
+    }
+    return false;
+  }
+
   /**
    * Returns true if MediaKeys is polyfilled
    *