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

Issue:
Continue loading live stream manifest when paused
**Have you read the [FAQ](https://bit.ly/ShakaFAQ) and checked for duplicate open issues?**
yes

**What version of Shaka Player are you using?**
3.2.1

**Can you reproduce the issue with our latest release version?**
yes

**Can you reproduce the issue with the latest code from `master`?**
Not tested. but this issue not in the commit log.

**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?**
Microsoft Edge 96.0.1054.34
Windows 11 22000.348

**What are the manifest and license server URIs?**
https://storage.googleapis.com/shaka-live-assets/player-source.m3u8

**What configuration are you using?  What is the output of `player.getConfiguration()`?**
default configuration

**What did you do?**
`load` live stream manifest, play video and pause.
or just `load`, not play video.

**What did you expect to happen?**
Stop load manifest also.

**What actually happened?**
Player continue loading when paused.
Load all resolution manifests every time.
Waiting many time, but not stopped load same.

Can stop loading or make option?


Discussion: Hi @TheModMaker, any update on this issue? 
Our use case, we sometimes want to pause playback and any processing of AV and do another operation and then resume playback, this is especially important on low end devices where memory is limited.
Specifically this gets more complicated if manifests are too big (live with long DVR and audio and video encoding doesn't have aligned segments) and refresh every X seconds causes big memory spikes.
I've started working on this, if anyone has a suggestion now is the time. The first question is, do we want it to be configurable or not? I vote no, but I would like to hear from the community.

Note: PR for this feature will be created this week.



Fix (patch):
diff --git a/demo/config.js b/demo/config.js
index 40c6eddf6b..04659fa9f7 100644
--- a/demo/config.js
+++ b/demo/config.js
@@ -200,7 +200,9 @@ shakaDemo.Config = class {
         .addBoolInput_('Disable Text', 'manifest.disableText')
         .addBoolInput_('Disable Thumbnails', 'manifest.disableThumbnails')
         .addBoolInput_('Enable segment-relative VTT Timing',
-            'manifest.segmentRelativeVttTiming');
+            'manifest.segmentRelativeVttTiming')
+        .addBoolInput_('Continue loading when paused',
+            'manifest.continueLoadingWhenPaused');
   }
 
   /** @private */
diff --git a/externs/shaka/manifest_parser.js b/externs/shaka/manifest_parser.js
index d1410f1135..144df1fa71 100644
--- a/externs/shaka/manifest_parser.js
+++ b/externs/shaka/manifest_parser.js
@@ -111,6 +111,14 @@ shaka.extern.ManifestParser = class {
    * @exportDoc
    */
   banLocation(uri) {}
+
+  /**
+   * Set media element.
+   *
+   * @param {HTMLMediaElement} mediaElement
+   * @exportDoc
+   */
+  setMediaElement(mediaElement) {}
 };
 
 
diff --git a/externs/shaka/player.js b/externs/shaka/player.js
index e62f40f0dd..9c6f03299c 100644
--- a/externs/shaka/player.js
+++ b/externs/shaka/player.js
@@ -1214,7 +1214,8 @@ shaka.extern.MssManifestConfiguration;
  *   dash: shaka.extern.DashManifestConfiguration,
  *   hls: shaka.extern.HlsManifestConfiguration,
  *   mss: shaka.extern.MssManifestConfiguration,
- *   raiseFatalErrorOnManifestUpdateRequestFailure: boolean
+ *   raiseFatalErrorOnManifestUpdateRequestFailure: boolean,
+ *   continueLoadingWhenPaused: boolean
  * }}
  *
  * @property {shaka.extern.RetryParameters} retryParameters
@@ -1257,7 +1258,10 @@ shaka.extern.MssManifestConfiguration;
  * @property {boolean} raiseFatalErrorOnManifestUpdateRequestFailure
  *   If true, manifest update request failures will cause a fatal error.
  *   Defaults to <code>false</code> if not provided.
- *
+ * @property {boolean} continueLoadingWhenPaused
+ *   If true, live manifest will be updated with the regular intervals even if
+ *   the video is paused.
+ *   Defaults to <code>true</code> if not provided.
  * @exportDoc
  */
 shaka.extern.ManifestConfiguration;
diff --git a/lib/dash/dash_parser.js b/lib/dash/dash_parser.js
index 8107561a6a..9e632b20dd 100644
--- a/lib/dash/dash_parser.js
+++ b/lib/dash/dash_parser.js
@@ -24,6 +24,7 @@ goog.require('shaka.net.NetworkingEngine');
 goog.require('shaka.text.TextEngine');
 goog.require('shaka.util.ContentSteeringManager');
 goog.require('shaka.util.Error');
+goog.require('shaka.util.EventManager');
 goog.require('shaka.util.Functional');
 goog.require('shaka.util.LanguageUtils');
 goog.require('shaka.util.ManifestParserUtils');
@@ -122,6 +123,14 @@ shaka.dash.DashParser = class {
 
     /** @private {shaka.util.Timer} */
     this.updateTimer_ = new shaka.util.Timer(() => {
+      if (this.mediaElement_ && !this.config_.continueLoadingWhenPaused) {
+        this.eventManager_.unlisten(this.mediaElement_, 'timeupdate');
+        if (this.mediaElement_.paused) {
+          this.eventManager_.listenOnce(
+              this.mediaElement_, 'timeupdate', () => this.onUpdate_());
+          return;
+        }
+      }
       this.onUpdate_();
     });
 
@@ -158,6 +167,12 @@ shaka.dash.DashParser = class {
 
     /** @private {boolean} */
     this.isLowLatency_ = false;
+
+    /** @private {shaka.util.EventManager} */
+    this.eventManager_ = new shaka.util.EventManager();
+
+    /** @private {HTMLMediaElement} */
+    this.mediaElement_ = null;
   }
 
   /**
@@ -259,6 +274,11 @@ shaka.dash.DashParser = class {
       this.contentSteeringManager_.destroy();
     }
 
+    if (this.eventManager_) {
+      this.eventManager_.release();
+      this.eventManager_ = null;
+    }
+
     return this.operationManager_.destroy();
   }
 
@@ -323,6 +343,14 @@ shaka.dash.DashParser = class {
     }
   }
 
+  /**
+   * @override
+   * @exportInterface
+   */
+  setMediaElement(mediaElement) {
+    this.mediaElement_ = mediaElement;
+  }
+
   /**
    * Makes a network request for the manifest and parses the resulting data.
    *
diff --git a/lib/hls/hls_parser.js b/lib/hls/hls_parser.js
index 39ad60291a..58c40ef539 100644
--- a/lib/hls/hls_parser.js
+++ b/lib/hls/hls_parser.js
@@ -28,6 +28,7 @@ goog.require('shaka.util.ArrayUtils');
 goog.require('shaka.util.BufferUtils');
 goog.require('shaka.util.ContentSteeringManager');
 goog.require('shaka.util.Error');
+goog.require('shaka.util.EventManager');
 goog.require('shaka.util.FakeEvent');
 goog.require('shaka.util.LanguageUtils');
 goog.require('shaka.util.ManifestParserUtils');
@@ -173,6 +174,14 @@ shaka.hls.HlsParser = class {
      * @private {shaka.util.Timer}
      */
     this.updatePlaylistTimer_ = new shaka.util.Timer(() => {
+      if (this.mediaElement_ && !this.config_.continueLoadingWhenPaused) {
+        this.eventManager_.unlisten(this.mediaElement_, 'timeupdate');
+        if (this.mediaElement_.paused) {
+          this.eventManager_.listenOnce(
+              this.mediaElement_, 'timeupdate', () => this.onUpdate_());
+          return;
+        }
+      }
       this.onUpdate_();
     });
 
@@ -251,6 +260,12 @@ shaka.hls.HlsParser = class {
 
     /** @private {Set.<string>} */
     this.dateRangeIdsEmitted_ = new Set();
+
+    /** @private {shaka.util.EventManager} */
+    this.eventManager_ = new shaka.util.EventManager();
+
+    /** @private {HTMLMediaElement} */
+    this.mediaElement_ = null;
   }
 
 
@@ -326,6 +341,11 @@ shaka.hls.HlsParser = class {
       this.contentSteeringManager_.destroy();
     }
 
+    if (this.eventManager_) {
+      this.eventManager_.release();
+      this.eventManager_ = null;
+    }
+
     return Promise.all(pending);
   }
 
@@ -567,6 +587,14 @@ shaka.hls.HlsParser = class {
     }
   }
 
+  /**
+   * @override
+   * @exportInterface
+   */
+  setMediaElement(mediaElement) {
+    this.mediaElement_ = mediaElement;
+  }
+
   /**
    * Align the streams by sequence number by dropping early segments.  Then
    * offset the streams to begin at presentation time 0.
diff --git a/lib/mss/mss_parser.js b/lib/mss/mss_parser.js
index 5478d04beb..d610262e75 100644
--- a/lib/mss/mss_parser.js
+++ b/lib/mss/mss_parser.js
@@ -233,6 +233,11 @@ shaka.mss.MssParser = class {
     // No-op
   }
 
+  /** @override */
+  setMediaElement(mediaElement) {
+    // No-op
+  }
+
   /**
    * Makes a network request for the manifest and parses the resulting data.
    *
diff --git a/lib/offline/offline_manifest_parser.js b/lib/offline/offline_manifest_parser.js
index 5ddd2e3dbb..d318e9bc86 100644
--- a/lib/offline/offline_manifest_parser.js
+++ b/lib/offline/offline_manifest_parser.js
@@ -122,6 +122,11 @@ shaka.offline.OfflineManifestParser = class {
   banLocation(uri) {
     // No-op
   }
+
+  /** @override */
+  setMediaElement(mediaElement) {
+    // No-op
+  }
 };
 
 
diff --git a/lib/player.js b/lib/player.js
index 86f1a4cc24..f8bbc4a583 100644
--- a/lib/player.js
+++ b/lib/player.js
@@ -1665,6 +1665,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
         this.manifestFilterer_ = preloadManager.getManifestFilterer();
         this.parserFactory_ = preloadManager.getParserFactory();
         this.parser_ = preloadManager.receiveParser();
+        if (this.parser_ && this.parser_.setMediaElement && this.video_) {
+          this.parser_.setMediaElement(this.video_);
+        }
         this.regionTimeline_ = preloadManager.receiveRegionTimeline();
         this.qualityObserver_ = preloadManager.getQualityObserver();
         this.manifest_ = preloadManager.getManifest();
@@ -1825,6 +1828,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
           /* disableVideo= */ false,
           /* allowMakeAbrManager= */ false);
       this.createdPreloadManagers_.push(preloadManager);
+      if (this.parser_ && this.parser_.setMediaElement) {
+        this.parser_.setMediaElement(/* mediaElement= */ null);
+      }
       preloadManager.attachManifest(
           this.manifest_, this.parser_, this.parserFactory_);
       preloadManager.attachAbrManager(
diff --git a/lib/util/player_configuration.js b/lib/util/player_configuration.js
index 564d506c4a..dc09fcac5a 100644
--- a/lib/util/player_configuration.js
+++ b/lib/util/player_configuration.js
@@ -118,6 +118,7 @@ shaka.util.PlayerConfiguration = class {
       defaultPresentationDelay: 0,
       segmentRelativeVttTiming: false,
       raiseFatalErrorOnManifestUpdateRequestFailure: false,
+      continueLoadingWhenPaused: true,
       dash: {
         clockSyncUri: '',
         ignoreDrmInfo: false,