5

When using DOMParser, <video> tag is not working in Safari, its preview is rendered but without controls and without the ability to play:

const parser = new DOMParser()
const parsed = parser.parseFromString(
  document.querySelector('video').outerHTML, 
  'text/html'
)
document.body.appendChild(parsed.querySelector('body').childNodes[0])
<video
  width="250"
  controls
  playsinline
  src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4#t=0.001"
></video>

The code above works as expected in Chrome:

enter image description here

But Safari renders this (both macOS and iOS):

enter image description here

There are no controls and it's impossible to play the second video which was produced by DOMParser.

Is that some kind of security feature or a bug? Is there any workaround for this behavior?

Limon Monte
  • 52,539
  • 45
  • 182
  • 213
  • I have had problems with video tag in safari browser before. Simply adding `playsinline` attribute to video tag solved my problem. Removing it was breaking the videos. – Erenn Sep 28 '22 at 14:03
  • Unfortunately, `playsinline` doesn't have any effect, I've added it to the code snipped in the question to verify that – Limon Monte Sep 28 '22 at 14:05
  • Note: This question is _extremely_ similar to [this other one](https://stackoverflow.com/q/58240755/11107541) _**except**_ that the other one doesn't ask for a workaround- only to know what's wrong with `DOMParser` in Safari. – starball Oct 01 '22 at 07:15

2 Answers2

4

It's a bug. Try:

const video = document.querySelector('video')
const videoClone = video.cloneNode(true)
videoClone.src = video.src
videoClone.load()
document.body.appendChild(videoClone)

Another option is to use a <div> tag instead of <video> tag:

const parser = new DOMParser()
const parsed = parser.parseFromString(
  document.querySelector('video').outerHTML.replace('<video', '<div').replace('</video>', '</div>'),
  'text/html'
)
document.body.appendChild(parsed.querySelector('body').childNodes[0])
<div
  width="250"
  controls
  playsinline
  src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4#t=0.001"
></div> 
Tibic4
  • 3,709
  • 1
  • 13
3

This is a Bug

Here's a link to the webkit bug ticket.

  • comment #6 (2021-03-04 22:50:29 PST):

    [...] media element not initialize its UA shadow root when it's created inside a document without a browsing context. The fix is to initialize the media element controls once it's inserted into a document with a browsing context.

  • comment #12 (2022-08-02 11:16:50 PDT): Pull request: https://github.com/WebKit/WebKit/pull/2955

  • comment #13 (2022-08-08 11:47:08 PDT): Committed 253225@main (f331cc98c1de): https://commits.webkit.org/253225@main Reviewed commits have been landed. Closing PR #2955 and removing active labels.

Here's a Workaround

// keep doing what you were doing:
const parser = new DOMParser()
const parsed = parser.parseFromString(
  document.querySelector('video').outerHTML, 
  'text/html'
);
const child = document.body.appendChild(parsed.querySelector('body').childNodes[0]);

// but then force the media elements to reload afterward:
[child].concat(Array.from(child.querySelectorAll("video, audio")))
  .forEach(e => e.outerHTML = e.outerHTML);

There are other workarounds like the cloneNode one that @Tibic4 suggested, but I think this one is nice in that you can still keep using DOMParser for most of the work.

Once the webkit fix gets deployed you won't need the workaround for any of the Safari versions where the fix gets applied. Hopefully it gets reasonably backported.

starball
  • 20,030
  • 7
  • 43
  • 238