How to track YouTube iframes inside a Shadow DOM


Introduction

Can’t attach the YouTube API to a shadow DOM easily but you can call it manually if you can select the iFrame.

Selecting the iFrame

You’ll need to find a way to consistently select the iFrame. When inside a shadow root, you need to select the parent and then access its shadowRoot attribute. Inside this you can make further query selector calls to get to the iFrame.

const iframe = document.querySelector('ns-video').shadowRoot.querySelector('iframe');
const player2 = new window.YT.Player(iframe, {
    events: {
        onReady: (event) => {
            console.warn("YouTube Player Ready:", event);
        },
        onStateChange: (event) => {
            let stateMessage;

            switch (event.data) {
                case -1:
                    stateMessage = "Unstarted (-1)";
                    break;
                case 0:
                    stateMessage = "Ended (0)";
                    break;
                case 1:
                    stateMessage = "Playing (1)";
                    break;
                case 2:
                    stateMessage = "Paused (2)";
                    break;
                case 3:
                    stateMessage = "Buffering (3)";
                    break;
                case 5:
                    stateMessage = "Video Cued (5)";
                    break;
                default:
                    stateMessage = "Unknown State";
            }
            console.warn(`YouTube Player State Changed: ${stateMessage}`);
        },
        onError: (event) => {
            console.error("YouTube Player Error:", event);
        }
    }
}
)
function observeIframeInShadow() {
    // Select the shadow root of `ns-video`
    const nsVideo = document.querySelector('ns-video')?.shadowRoot;
    if (!nsVideo) {
        console.warn("Shadow root for ns-video not found!");
        return;
    }

    const observer = new MutationObserver((mutations) => {
        mutations.forEach(mutation => {
            mutation.addedNodes.forEach(node => {
                // Check if the added node is an iframe
                if (node.tagName === 'IFRAME') {
                    console.warn("YouTube iframe added:", node);
                }
                // Check if the added node contains an iframe as a child
                else if (node.querySelector && node.querySelector('iframe')) {
                    console.warn("Node containing an iframe added:", node.querySelector('iframe'));
                }
            });
        });
    });

    // Observe for child elements being added inside the shadow DOM
    observer.observe(nsVideo, { childList: true, subtree: true });

    console.log("Now watching for iframes inside ns-video's shadow DOM...");
}

// Start observing
observeIframeInShadow();