Troubleshooting the compatibility issue of HTML5 video tag on iOS Safari browser (iPhone)

Scenario

I encountered an issue while trying to play a video in the Edge browser. The project framework consists of:
.NET 6 MVC and VUE for the client side
.NET 6 WebApi for the server side

The Vue component on the client will send a request with a range header (1MB) to retrieve fragmented MP4, and utilize the Media Source Extensions (MSE) to append arrayBuffer to the blobUrl that points to video.src.

For example:

var mediaSource = new MediaSource;
mediaSource.addEventListener('sourceopen', sourceOpen);
video.src = URL.createObjectURL(mediaSource);

This setup works flawlessly in Windows' EDGE browser, BUT it does not function properly on an iPhone (tested using iPhone SE's Edge browser). In this case, the video tag displays a blank page only.
Image from iPhone SE (EDGE Version 100.0.1185.50): https://i.sstatic.net/E3Sdr.jpg

However, it works perfectly on Windows' EDGE Version 100.0.1185.50.
Image from Windows 10: https://i.sstatic.net/D3SkZ.png

Solution Attempts

I have attempted adding the playsinline property to the video tag and tried other solutions mentioned in HTML5 Video tag not working in Safari, iPhone, and iPad, but unfortunately, none of them resolved the issue.

Relevant Code

The method within the Vue component is as follows:

    /*
         * check if the videoId matches the course or not.
         * If not, fetch a new video stream and create a blob URL pointing to this.videoUrl
         */
        async displayVideo() {
          if (this.videoId != this.course) {
            //common.loader.show("#255AC4", 1.5, "Loading...");
            this.videoId = this.course;
    
            let video = document.querySelector("video");
            let assetURL = FrontEndUrl + `/Stream/${this.videoId}`;
            let mimeCodec = 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"';
            let sourceBuffer;
            let chunkSize;
            let contentRange;
            let loop;
            let index;
    
            const token = common.cookieManager.getCookieValue(`signin`);
    
            if ('MediaSource' in window && MediaSource.isTypeSupported(mimeCodec)) {
              let mediaSource = new MediaSource;
              mediaSource.addEventListener('sourceopen', sourceOpen);
              video.src = URL.createObjectURL(mediaSource);
            } else {
              console.error('Unsupported MIME type or codec: ', mimeCodec);
            }
    
            function sourceOpen() {
              // chunkSize set to 1MB
              chunkSize = 1024 * 1024 * 1;
    
              // index set to 1 because the fetchNextSegment starts from the second fragment
              index = 1;
    
              // Retrieve mediaSource and add updateend event to sourceBuffer
              let mediaSource = this;
              sourceBuffer = mediaSource.addSourceBuffer(mimeCodec);
              sourceBuffer.addEventListener("updateend", fetchNextSegment);
    
              fetchArrayBuffer(assetURL, chunkSize * 0, appendBuffer);
    
              // 📌 Here we encounter a DOM interact error
              //video.play();
            }
    
            function fetchNextSegment() {
              if (index > loop) {
                sourceBuffer.removeEventListener("updateend", fetchNextSegment);
                return;
              }
    
              fetchArrayBuffer(assetURL, chunkSize * index, appendBuffer);
    
              index++;
            }
    
            function fetchArrayBuffer(url, range, appendBuffer) {
              fetch(url, {
                headers: {
                  "Range": `bytes=${range}-`,
                  "Authorization": `Bearer ${token}`,
                }
              })
                .then(response => {
                  if (!loop) {
                    contentRange = response.headers.get("x-content-range-total");
                    loop = Math.floor(contentRange / chunkSize);
                  }
    
                  return response.arrayBuffer();
                })
                .then(data => { appendBuffer(data) });
            }
    
            function appendBuffer(videoChunk) {
              if (videoChunk) {
                sourceBuffer.appendBuffer(videoChunk);
              }
            }
    
            // 📌 This was the old way, which did not attach the JWT token
            // this.videoUrl = await this.fetchVideoStream(this.videoId); //FrontEndUrl + `/Stream/${this.videoId}`;
    
            //common.loader.close();
          }
        },

And My Vue's template

    <p-card>
              <template #header>
              </template>
              <template #title>
                  <h2>Course Name: {{courseName}}</h2>
              </template>
              <template #subtitle>
              </template>
              <template #content>
              <video id="video" style="width: 100%; height: auto; overflow: hidden;" :key="videoUrl" v-on:loadeddata="setCurrentTime" v-on:playing="playing" v-on:pause="pause" controls autoplay playsinline>
              </video>
              </template>
              <template #footer>
              All Rights Reserved. Unauthorized reproduction prohibited.
              </template>
              </p-card>

In addition to checking logs on my machine, everything appeared normal. Any insights on why this isn't working on iOS's EDGE would be greatly appreciated. Thank you!

Answer â„–1

Summary

Upon reviewing at Can I use MSE, it was discovered that the iOS of iPhone does not support MSE (Media Source Extension). Hence, the MSE functionality was ineffective.

An image from Can I use MSE
https://i.sstatic.net/68SgE.png

Solution

The iOS of iPhone supports HLS originally (apple dev docs), hence you need to convert the MP4 to HLS format(using bento4 HLS)
📌 MP4 format must be in fMP4 format

Post conversion, the output directory for the HLS format will resemble the following
https://i.sstatic.net/KPmDx.png

The file extension should be .m3u8, with the master.m3u8 serving as a guide detailing all video information.

Next, set the video tag's src attribute to point towards the URL of the HLS resource(master.m3u8).
similar to the provided code snippet

<video src="https://XXX/master.m3u8">
</video>

You can also utilize the videoJS library, with the srcObject being of type

"application/x-mpegURL"

var player = videojs("videoId");
if (this.iOS()) {
        // âš  Verification may fail, reason unknown at the moment, backend verification appears normal
        //const token = common.cookieManager.getCookieValue(`signin`);
        url = FrontEndUrl + `/Media/Video/${this.videoId}/master.m3u8`;
        srcObject = { src: url, type: "application/x-mpegURL" }
      }

      try {
        player.src(srcObject);
        player.play();
      } catch (e) {
        alert(e.message);
        console.log(e);
      }

a simple demonstration of HLS on github page
However, this demo employs hls.js instead of video.js

<iframe width="auto" src="https://s780609.github.io/hlsJsDemo/">
</iframe>

Reference of bento4

Example command for converting to HLS format
📌 -f: force replacement of old files
-o: designated output directory

mp4hls -f -o [output directory] [source mp4 file]

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

I'm attempting to integrate the map function into my React Redux application, but after implementing useDispatch, I'm encountering an error message in the console

I am currently troubleshooting an app I'm working on, but I keep encountering errors in the console. Included below is a picture of the error as well as the code snippet triggering the issue. Can anyone provide insight into what might be causing this ...

Solving repeated function firing with ng-checked in an AngularJS ng-repeat loop

In the HTML code below, there is an ng-repeat that creates checkboxes: <span ng-repeat="name in $ctrl.widgetSelectorNames" class="widget-selectors"> <label class="checkbox" for="{{name}}"> <input type="checkbox" value="{ ...

Trouble in sending email with attachment using Microsoft Graph

I have been working on an application that has the functionality to send emails from a User, following the guidelines provided in this article. Despite everything else functioning correctly, I'm facing an issue when trying to include attachments. The ...

Displaying [object Object] in Angular Material datatable

I am currently working on implementing a datatable component using Express and Firebase DB. Below is the service request data: getText() { return this.http.get<nomchamp[]>(this.url) .map(res => { console.log(res); return res }); ...

Collect data entered into the input box and store them in an array for the purpose of

I need assistance with a code that involves input boxes for users to enter numerical values, which are then stored in an array. My goal is to add these values together and display the sum using an alert when a button is clicked. However, I am struggling to ...

Having trouble generating an image with JavaScript

I am currently working on incorporating an image onto a webpage using JavaScript. Surprisingly, even the alert('This function works!') is not displaying anything! What could be causing this issue? Please assist! <!DOCTYPE html> <html> ...

Ways to verify that a javascript function generates an object and executes a method within that object

Currently, I am in the process of updating server code written in nodejs and incorporating unit tests into the mix. However, I have encountered a challenge that I need assistance with: classX.prototype.methodX = function () { // Create new session ...

Generating data within an express route (with the use of socket.io-client)

Recently, I made the decision to refactor my code in order to separate the front and back end, which were previously combined into one service. However, I am now facing an issue where I need the web server to emit data to the data server after validation. ...

Is it necessary for me to address the audit problems with the most recent version of nuxt.js?

While using nuxt for my app, I encountered some audit issues when running "yarn audit." These issues are related to dependencies of nuxt. Do I need to address these audit issues? If so, what is the process to fix them? I am currently on nuxt version 2.15 ...

Modifying the information depending on the option chosen from the dropdown menu using angularJS

I have a dropdown menu where I can choose between two options, and when selected, the corresponding data is displayed. However, I would like to display the data that is inside a div tag instead. Check out this Fiddle Demo HTML: <div ng-controller="Ct ...

Modifying the Path for Vite Manifest File

I'm encountering an issue where the Vite manifest is not being found at public_html/public/build/manifest.json on my website. I would like it to be available in the public_html/build/ directory instead. How can I modify my configuration to achieve thi ...

What exactly does Vue :state mean?

While exploring , I encountered a Vue attribute called :state. I couldn't find any information about it in the documentation and now I am confused. Where does it come from and what is its purpose? It seems like I need to use this to validate an input ...

What is the best way to ensure the constant rotation speed of this simple cube demo?

Currently delving into the world of Three.js. I'm curious about how to make the cube in this demo rotate at a consistent speed rather than depending on mouse interactions. Any tips on achieving this? ...

Get back a variety of substitutions

I have a variety of different text strings that I need to swap out on the client side. For example, let's say I need to replace "Red Apple" with "Orange Orange" and "Sad Cat" with "Happy Dog". I've been working on enhancing this particular ques ...

Next.js encountered an API resolution issue while uploading a file, resulting in no response being

Even though my code is functioning properly, a warning appears in the console: The API call was resolved without sending any response for /api/image-upload This particular endpoint is responsible for uploading an image to Digital Ocean's object sto ...

Implementing a PHP back button to navigate through previous pages

Currently, I am in the process of coding a project. In this project, there are a total of 10 pages, each equipped with back and forward buttons. However, when I attempt to navigate using either javascript:history.go(-1) or $url = htmlspecialchars($_SERVER ...

What steps should I take to solve this wheel-related issue in my Vue 3 application?

I have been developing a Single Page Application (SPA) using Vue 3, TypeScript, and The Movie Database (TMDB). Currently, I am tackling the implementation of a search feature and I've encountered a bug. The "search-box" component is located in src&b ...

What is the best way to conditionally wrap a useState variable in an if statement without losing its value when accessing it outside the if block in reactjs?

I am facing a coding challenge with my cards state variable in React using the useState hook. I tried adding my array data to it but ended up with an empty array. Placing the state inside an if statement resulted in undefined variables. I attempted various ...

What is the command to invoke an asynchronous method from the node console?

I am working on a Bot class with an async printInfo method: class TradeBot { async printInfo() { //..... } } When I start 'node', and create an object from the console to call the method: >const createBot = require('./BotFactory&ap ...

Is it possible to selectively implement a transition in VueJS based on certain conditions?

My goal is to design an alert box component with a customizable reveal transition, which can be optional. Here's a simplified version of what I have in mind: <template> <transition v-if="withTransition"> <b-alert v-bind="th ...