← Back to list

Translating MDN Web APIs docs to Flutter:Video and Audio API

HTML Video element for Flutter Web

Andi · 2026-03-31 12:56 · 0 claps · 4.5 min read
#html-element #flutter-web #flutter #mdn-web-docs #webapi-for-flutter
Open on Medium ↗
Wiki topics: 🌐 · Web Development 📱 · Mobile Development 🎵 · Music & Audio

Translating MDN Web APIs docs to Flutter:Video and Audio API

The next topic MDN covers is the Video and audio APIs.

You’ll ask, “These can be done by Flutter itself.” I’ll respond, “It is entirely possible to have an audio/video format supported by HTML that cannot be processed by Flutter natively.” You, “You can’t even see the HTML elements, what’s the point?”

That’s why this article will be covered in these parts:

  1. Making the flutter app disappear to make the video visible
  2. Knowledge of the <video> HTML element
  3. Implementing the HTML element for our Flutter app.

As for <audio> , we’ve already covered it in the first article.

Making the flutter app disappear…

This is also an example of DOM Manipulation but, this certainly requires digging a bit deeper.

If you’ve been watching the HTML code generated for our flutter app (Right click > Inspect > Elements tab), you can see the <body> has an element called the <flutter-view> . This is the HtmlElement responsible for displaying the Flutter app and we are going to mess with this.

Unfortunately, <flutter-view> is not an HTML tag recognised outside of Flutter. So, we have t dig through the children/child nodes found inside the <body> . The following code helps:

    // get <flutter-view>
    final count = document.body!.childNodes.length;
    int flutterViewIndex = 0;
    for (int i = 0; i < count; i++) {
      final item = document.body!.childNodes.item(i);
      flutterViewIndex = item.toString() == "flutter-view" ? i : 0;
    }
    flutterElement =
        document.body!.childNodes.item(flutterViewIndex) as HTMLElement;

Why do we need flutterViewIndex ?

We don’t have a direct access to the element so we have to go through each childNode individually. With a very simple HTML you may be able to find it at the first or the last place but, if you Hot Reload your app, you’ll find the flutterViewIndex has now increased by one.

And your <body> may not be as simple. You, probably, need to add a logo to HTML to make sure there’s a visible element to mark your branding before the Flutter app loads up.

Now to make it disappear…

We have to update the style property of this element and set the opacity to 0. Let me add an animation to do this:

// initialise
    _opacityAnimationController =
        AnimationController(vsync: vsync)
          ..duration = Duration(seconds: 1)
          ..addListener(() {
            final newOpacity = _opacityAnimationController.value;
            flutterElement.style.opacity = newOpacity.toString();
            isFlutterViewVisible = newOpacity == 1;
          });

// trigger code
      isFlutterViewVisible
          ? _opacityAnimationController.reverse(from: 1.0)
          : null;

Let’s set the HTML background colour to black…

// makes the animation visible
    document.body?.style.backgroundColor = "black";

Here’s the result:

Looks familiar, doesn’t it?

Looks familiar, doesn’t it?

The HTML <video> element

I’ll summarise what the MDN documentation says. You can study the <video> element here.

It has an src attribute where we can provide the address of the video we are trying to play. However, the more compatibility rich approach would avoid adding an src and focus on building the <source> child nodes.

Here’s an example in HTML:

<video controls>
  <source src="rabbit320.mp4" type="video/mp4" />
  <source src="rabbit320.webm" type="video/webm" />
  <p>
    Your browser doesn't support HTML video. Here is a
    <a href="rabbit320.mp4">link to the video</a> instead.
  </p>
</video>
  1. Multiple <source> elements will make the browser go through the different sources provided and play the first one that the browser/host has the codec to support. Read about it here.
  2. The <p> is a paragraph, this will be visible if the browser doesn’t support any of the provided video sources.
  3. controls attribute, if mentioned, ensures the browser display it’s own video controls. Otherwise, it doesn’t.

Now, read more to learn more. Here’s the web plugin documentation on the HTMLVideoElement.

Implementation

Let’s add the video element:

  void _addVideoElement() {
    final windowSize = MediaQuery.of(_navigator.context).size;
    final src = "assets/video/example_video.mp4";
    _videoElement =
        HTMLVideoElement()
          ..controls = false
          ..muted = false
          ..src = src
          ..height = windowSize.height.toInt()
          ..width = windowSize.width.toInt()
          ..appendChild(Text("Browser not supported"));

    document.body?.prepend(_videoElement);
  }

Here, we use src because the Dart documentation on [srcObject](https://pub.dev/documentation/web/latest/web/HTMLMediaElement/srcObject.html) says:

Note: As of March 2020, only Safari has full support for srcObject, i.e. using MediaSource, MediaStream, Blob, and File objects as values. Other browsers support MediaStream objects; until they catch up, consider falling back to creating a URL with URL.createObjectURL_static and assigning it to HTMLMediaElement.src (see below for an example). In addition, as of version 108 Chromium supports attaching a dedicated worker MediaSource object by assigning that object's MediaSourceHandle instance (transferred from the worker) to srcObject.

And frankly, I couldn’t figure out how to add multiple sources anyway.

We use this static sizing because otherwise the video plays in it’s original resolution and that may be much bigger than the browser window.

We prepend our video element here because calling append adds the child to the bottom of the <body> , which makes the element render on top of the <flutter-view> .

Let me add a few event listeners and move the code around a bit:


  void _addVideoElement() {
    final windowSize = MediaQuery.of(_navigator.context).size;
    final src = "assets/video/example_video.mp4";
    _videoElement =
        HTMLVideoElement()
          ..controls = false
          ..muted = false
          ..src = src
          ..height = windowSize.height.toInt()
          ..width = windowSize.width.toInt()
          ..appendChild(Text("Browser not supported"));

    _textElement =
        HTMLHeadingElement.h4()
          ..style.color = "yellow"
          ..style.visibility = "true"
          ..style.fontSize = "20dp";
  }

  void _addEventListener() {
    _opacityAnimationController.addListener(() {
      final newOpacity = _opacityAnimationController.value;
      flutterElement.style.opacity = newOpacity.toString();
      isFlutterViewVisible = newOpacity == 1;
      if (newOpacity == 0 && _videoElement.checkVisibility()) {
        _videoElement.play();
      } else if (newOpacity == 1) {
        _videoElement.pause();
      }
    });

    _videoElement
      ..onTimeUpdate.listen((Event timeEvent) {
        final currentTimestampInMilliseconds = timeEvent.timeStamp.toInt();
        final currentTimestampInSeconds =
            currentTimestampInMilliseconds ~/ 1000;
        _textElement.textContent = currentTimestampInSeconds.toString();
      })
      ..onEnded.listen((Event _) {
        if (!isFlutterViewVisible) {
          _opacityAnimationController.forward(from: 0);
          _textElement.remove();
          _videoElement.remove();
        }
      });
  }

  void onButtonClick() {
    isFlutterViewVisible
        ? _opacityAnimationController.reverse(from: 1.0)
        : null;
    document.body
      ?..prepend(_videoElement)
      ..prepend(_textElement);
  }

And here’s the result:

Thanks for reading! Next up- Canvas and Storage APIs

Update 1: I was going to cover the Canvas API but it seems the documentation in the web package is not clear enough while also missing some crucial methods. The 2D graphics wouldn’t find themselves much use with Flutter anyway. However, since Flutter struggles with 3D graphics, using WebGL with HTML deserves it’s own separate article. I will cover that only if it isn’t too heavy on the Javascript.


메타데이터
post_id
752aeec577fc
slug
translating-mdn-web-apis-docs-to-flutter-video-and-audio-api-752aeec577fc
url
https://medium.com/@uncoded-decimal/translating-mdn-web-apis-docs-to-flutter-video-and-audio-api-752aeec577fc
canonical_url
https://medium.com/@uncoded-decimal/translating-mdn-web-apis-docs-to-flutter-video-and-audio-api-752aeec577fc
author_url
https://medium.com/@uncoded-decimal
status
ok
fetched_at
2026-07-16 06:15:53