Repository navigation
Description
Problem
I need to create an HTML element, just how the video_player does it, and let JW Player control it.
The JavaScript syntax JW Player expects is jwplayer("<DIV ID>"), where DIV ID is the id of a <div> element. Example can be found here.
I want to integrate the HTML div element into Flutter to achieve the same effect that video_player does.
HTML equivalent
This is everything required to make the whole player work:
Click to expand
// In head:
// <script src="https://cdn.jwplayer.com/libraries/cdD21GnT.js"></script>
// In body:
// <div id="jw-player-example"></div>
// In JS:
jwplayer("jw-player-example").setup({file: "https://content.jwplatform.com/manifests/VKDKBLZm.m3u8"})I only want to do this and have the div in an HTMLElementView, but Flutter seems to be doing something that makes this not work.
It does not work in Flutter
The concept seems straight forward, however, there is a big problem: JW Player does not seem t obe happy with how Flutter inserts DOM elements, i.e. Flutter inserts them into a shadow DOM.
The jwplayer function actually does not only accept strings - it also accepts DOM elements directly. However, I have no idea how to pass the element I create to that function.
Below I will showcase a few approaches that did not work:
Click to expand
const viewType = 'jw-player-view';
final div = DivElement()..id = viewType;
// ignore: UNDEFINED_PREFIXED_NAME
ui.platformViewRegistry.registerViewFactory(
viewType,
(int viewId) => div,
);
// jwplayer("jw-player-view")
(js.context['jwplayer'] as js.JsFunction).apply([viewType]);
// Does not work because the element is created in the shadow DOM.
// jwplayer(div)
(js.context['jwplayer'] as js.JsFunction).apply([div]);
// Seems to kind of work to some extent (no exception), but the player does not appear
// to really do anything to the actual DOM element because it probably is only an object
// that does not actually point to the dom. jwplayer().getContainer() will reveal a "jw-flag-play-rejected".
// jwplayer(document.getElementsByTagName('flt-platform-view')[0].shadowRoot.querySelector('#jw-player-view'))
// This seems the most promising, but I can only get it to work from the JS console and it
// has the problem of not displaying anything in any way again.
// I assume that the CSS that should apply does not reach the element because it is in the shadow DOM.
document.getElementsByTagName('flt-platform-view')[0].append(_div);
// Does not seem to add the element to the DOM and more importantly I will not be able to use it
// in a HTMLElementView.Putting the script into the shadow DOM
I saw that @hterkelsen proposed the following in a similar seeming issue:
If you want to bundle JS with your platform view, you could register a
BodyElementwith aScriptElement. Will this give you the functionality you need?
Click to see the problem with that
I had two problems with that: first of all, I did not get it to work with the platformViewRegistry:
final body = BodyElement()..id = 'BODYELEMENTID';
final script = ScriptElement()
// Inserts cloud hosted JW Player into the shadow DOM.
..src = 'https://cdn.jwplayer.com/libraries/ID.js';
body.insertAdjacentElement('afterBegin', script);
// ignore: UNDEFINED_PREFIXED_NAME
ui.platformViewRegistry.registerViewFactory(
'shadow-body',
(int viewId) => body,
);This does not do anything - so I do not really have an idea how to use it. It seems that I first need to return it
to an HTMLElementView, but that does also not really seem to help.
More importantly however, even when I inserted the element from the JS console, it as though the
jwplayer object was still part of the main document and still not able to access the div element.
Questions
Is there any way I can make JW Player accept the HTML elements created in Flutter?
- Is there a way to achieve this currently?
- Is there maybe even a way to not use a shadow DOM if we want?
Activity
[-][web] Cannot expose Registered HTML element to JW Player[/-][+][web] Cannot expose Registered HTML element to JW Player (shadow DOM)[/+]ditman commentedon Sep 3, 2020
jwplayer().getContainer() will reveal a "jw-flag-play-rejected".That looks like something that's set when the player tries to auto-play but it fails (I'm not 100% certain). I think your second option is working, but there's some issues with the video that you're playing.
Have you to "mute" the video before autoplaying, and/or try with a video with no sound track. (There's browser limitations around autoplaying video with sound.)
creativecreatorormaybenot commentedon Sep 3, 2020
@ditman Thanks for taking a look!
Actually, nothing is displayed with my second option. I executed it on tap on purpose to prevent automute (and used the console to execute it, which will always allow playing sound).
Workaround
I managed to make it work using the following workaround: create a second HTML document (next to
index.html) that contains only thejwplayerscript in its<head>. I was able to place this document in anIFrameElementand get the video to play this way: both unmuted autoplay works (because I load it on tap) and I am able to see the video.I can access
jwplayerusingcurrentWindowon the iframe.I will still have to see if I am able to control it from Flutter as well as I can from the console, but I am positive.
The reason I think this workaround works is because
jwplayercan insert CSS etc. in a way that will affect thedivin the iframe (that is in the shadow DOM).ditman commentedon Sep 3, 2020
So odd, we used a similar technique for google maps, but I guess google maps does embed more stuff so it can work within the shadow DOM.
yjbanov commentedon Sep 3, 2020
Deduplicating with #50452
creativecreatorormaybenot commentedon Sep 3, 2020
@yjbanov Not sure if it is the same - I think this issue is probably more related to a feature request of being able to add elements to the actual DOM.
creativecreatorormaybenot commentedon Sep 3, 2020
@ditman JW Player creates all of the CSS in the main DOM, so I bet that this is the problem.
According to an answer to an Angular question, they also access
document.body, which probably breaks when thedivis in the shadow DOM, but I am not exactly sure about that.[-][web] Cannot expose Registered HTML element to JW Player (shadow DOM)[/-][+][web] Cannot register HTML Element in the Regular DOM[/+]ditman commentedon Sep 3, 2020
I think the solution to this, and others, would be to opt-out of the shadowDOM wrapper for the HTMLElementViews, if we got rid of that, many more scripts would be happier (but who knows what weird interactions they can have with the default Flutter CSS/JS) I'll take a look in the other Issue.
creativecreatorormaybenot commentedon Mar 30, 2021
Note that this is already solved in CanvasKit 🎉
Specifically, CanvasKit does not use shadow DOM, which means that this is not an issue in CanvasKit 🚀
This should also be solved in HTML by slots for the HTML renderer, see #50452 (comment).
For the JW Player specific example, it is quite simple. You need to create two
DivElements. Two because one will be tinkered with by JW Player and the other one will be mutated by the Flutter platform view integration. So you basically build another container for the JW Player div:You can then use the
'viewType'for yourHtmlElementViewand the'divId'for your JW Player setup.Note that this currently also suffers from #75195 / #79065 in CanvasKit.
[-][web] Cannot register HTML Element in the Regular DOM[/-][+][web] Cannot register HTML Element in the Regular DOM (JW Player)[/+]github-actions commentedon Aug 3, 2021
This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of
flutter doctor -vand a minimal reproduction of the issue.