shaka-project / shaka-player

JavaScript player library / DASH & HLS client / MSE-EME player
Apache License 2.0
7.22k stars 1.34k forks source link

Error when loading content with Blob URL in player.load() #6375

Closed azoozs closed 8 months ago

azoozs commented 8 months ago

Have you read the Tutorials? Yes

Have you read the FAQ and checked for duplicate open issues? Yes

If the question is related to FairPlay, have you read the tutorial?

N/A

What version of Shaka Player are you using? 4.7.11

What browser and OS are you using? OS: Windows 10 Version 22H2 Browser: Google Chrome | 122.0.6261.131 (Official Build) (64-bit)

Please ask your question I encountered an issue while attempting to load content using a Blob URL with Shaka Player's player.load() function. When trying to load content using a Blob URL generated from a modified MPD file, I encountered an error that resulted in the player failing to load the content properly.

Code Snippet:


<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Download and Play</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.0/FileSaver.min.js"
    integrity="sha512-csNcFYJniKjJxRWRV1R7fvnXrycHP6qDR21mgz1ZP55xY5d+aHLfo9/FcGDQLfn2IfngbAHd8LdfsagcCqgTcQ=="
    crossorigin="anonymous" referrerpolicy="no-referrer"> </script>

  <script src="https://cdnjs.cloudflare.com/ajax/libs/shaka-player/4.7.11/shaka-player.compiled.js"></script>
</head>
<body>
  <input type="text" id="urlInput" placeholder="Enter URL">
  <button id="downloadBtn">Download & Play</button>
  <video id="video" width="640" controls></video>
  <script>
    const shaka = window.shaka;
    const video = document.getElementById("video");
    const player = new shaka.Player(video);

    video.addEventListener("play", () => {
      player.configure({
        streaming: {
          rebufferingGoal: 10,
          bufferingGoal: 10
        }
      });
    });

    shaka.net.NetworkingEngine.registerScheme('blob', shaka.net.HttpFetchPlugin);

    document.getElementById("downloadBtn").addEventListener("click", function () {
      var url = document.getElementById("urlInput").value;

      // Modify the extension ID as needed
      var id = "jgefhgchodfjnjemnclijejgnlifdbbg";

      chrome.runtime.sendMessage(id, { url: url }, function (response) {
        console.log("Response from extension:", response.fileContents);
        mpd = response.fileContents.replace("input_000_dashinit.mp4", "https://github.com/azoozs/shaka-player-multi-period/raw/main/videos/input_000_dashinit.mp4 ")
                                   .replace("input_001_dashinit.mp4", "https://github.com/azoozs/shaka-player-multi-period/raw/main/videos/input_001_dashinit.mp4 ")
                                   .replace("input_002_dashinit.mp4", "https://github.com/azoozs/shaka-player-multi-period/raw/main/videos/input_002_dashinit.mp4 ");
        console.log("Modified MPD content:");
        console.log(mpd);

        var blob = new Blob([mpd], { type: "application/dash+xml" });
        var urlblob = URL.createObjectURL(blob);
        player.load(urlblob, null, 'application/dash+xml');
      });
    });
  </script>
</body>
</html>

Try entering the following URL https://raw.githubusercontent.com/azoozs/shaka-player-multi-period/main/videos/out.mpd into the input field and observe the behavior.

Error:

networking_engine.js:567 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'timeMs')
    at networking_engine.js:567:20
    at ff (abortable_operation.js:215:19)
    at abortable_operation.js:187:47
joeyparrish commented 8 months ago

I don't believe we should throw that exception no matter what. That may be a bug in NetworkingEngine, or it may be an incompatibility between HttpFetchPlugin and blob URLs, which is was not explicitly designed to handle.

However, I see a separate fundamental issue with your snippet. GitHub does not give you permission to load that mp4 content from all origins:

$ curl -sD /dev/stderr https://github.com/azoozs/shaka-player-multi-period/raw/main/videos/input_000_dashinit.mp4 > /dev/null
HTTP/2 302 
server: GitHub.com
date: Fri, 29 Mar 2024 17:54:25 GMT
content-type: text/html; charset=utf-8
vary: X-PJAX, X-PJAX-Container, Turbo-Visit, Turbo-Frame, Accept-Encoding, Accept, X-Requested-With
access-control-allow-origin: https://render.githubusercontent.com

The only origin other than github.com that is permitted to load that file is render.githubusercontent.com. Your browser will not fulfill that request from any other origin due to CORS (Cross-Origin Resource Sharing) rules. You can find more on that topic here: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS

azoozs commented 8 months ago

@joeyparrish Thank you for your prompt reply. Could you please provide a comprehensive example of how to download an .mpd file from a Blob URL?

Regarding the CORS issue, I'll explore alternative hosting options and test the solution.

joeyparrish commented 8 months ago

I was wrong about blobs. The fetch plugin is explicitly registered for blobs already:

https://github.com/shaka-project/shaka-player/blob/12bf6428e00b93cc2cf9cefcf4db39fe9c749ee3/lib/net/http_fetch_plugin.js#L331-L334

So it should be able to handle them. You shouldn't need to register anything at all, just load a blob URL with an explicit MIME type in player.load():

https://shaka-player-demo.appspot.com/docs/api/tutorial-blob-url.html

player.load(blobURL, startTime, 'application/dash+xml');

If that doesn't work, please file an issue using the bug template and provide an explicit example of a blob that triggers the exception. Thanks!

azoozs commented 8 months ago

Thank you, @joeyparrish, for your prompt and insightful assistance. For anyone facing the same problem in the future, here is the solution:

1- Make sure that you specify the absolute path to each <BaseURL>.

2- Ensure that there are no small gaps between the periods (you can use my Python code to create a .mpd file without small gaps between the periods).

3- Remove this line:

shaka.net.NetworkingEngine.registerScheme('blob', shaka.net.HttpFetchPlugin);

After these steps, player.load should work properly.

Full code (you can test by entering: https://raw.githubusercontent.com/azoozs/shaka-player-multi-period/main/example_works_fine/input_001_dash.mpd):

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Download and Play</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.0/FileSaver.min.js"
    integrity="sha512-csNcFYJniKjJxRWRV1R7fvnXrycHP6qDR21mgz1ZP55xY5d+aHLfo9/FcGDQLfn2IfngbAHd8LdfsagcCqgTcQ=="
    crossorigin="anonymous" referrerpolicy="no-referrer"> </script>

  <script src="https://cdnjs.cloudflare.com/ajax/libs/shaka-player/4.7.11/shaka-player.compiled.js"></script>
</head>
<body>
  <input type="text" id="urlInput" placeholder="Enter URL">
  <button id="downloadBtn">Download & Play</button>
  <video id="video" width="640" controls></video>
  <script>
    const shaka = window.shaka;
    const video = document.getElementById("video");
    const player = new shaka.Player(video);

    video.addEventListener("play", () => {
      player.configure({
        streaming: {
          rebufferingGoal: 10,
          bufferingGoal: 10
        }
      });
    });

    //shaka.net.NetworkingEngine.registerScheme('blob', shaka.net.HttpFetchPlugin);

    document.getElementById("downloadBtn").addEventListener("click", function () {
      var url = document.getElementById("urlInput").value;

      // Modify the extension ID as needed
      var id = "jgefhgchodfjnjemnclijejgnlifdbbg";

      chrome.runtime.sendMessage(id, { url: url }, function (response) {
        console.log("Response from extension:", response.fileContents);
        let mpd = response.fileContents;

        console.log("Original MPD: ", mpd);

        // Create Blob Object
        const blob = new Blob([mpd], { type: "application/dash+xml" });
        const urlblob = URL.createObjectURL(blob);
        player.load(urlblob, null, 'application/dash+xml');
      });
    });
  </script>
</body>
</html>

Regarding the CORS issue, I replaced github.com with raw.githubusercontent.com

$ curl -sD /dev/stderr https://raw.githubusercontent.com/azoozs/shaka-player-multi-period/main/example_works_fine/input_001_dash_track1_init.mp4 > /dev/null

HTTP/2 200 
cache-control: max-age=300
content-security-policy: default-src 'none'; style-src 'unsafe-inline'; sandbox
content-type: application/octet-stream
etag: "6b918d492ad649f2879f9497253fc3b70d3756132e12af87526e1677179c9e41"
strict-transport-security: max-age=31536000
x-content-type-options: nosniff
x-frame-options: deny
x-xss-protection: 1; mode=block
x-github-request-id: 9362:0DF0:30DF5E9:32FCD77:66074025
accept-ranges: bytes
date: Fri, 29 Mar 2024 22:26:47 GMT
via: 1.1 varnish
x-served-by: cache-mrs10526-MRS
x-cache: MISS
x-cache-hits: 0
x-timer: S1711751207.821885,VS0,VE184
vary: Authorization,Accept-Encoding,Origin
access-control-allow-origin: *
cross-origin-resource-policy: cross-origin
x-fastly-request-id: 4a57d82b22c1a49f16bcf947e4dea6f10e6171f7
expires: Fri, 29 Mar 2024 22:31:47 GMT
source-age: 0
content-length: 1295325