Fix error "can't access property "contains", e.shadowRoot is null" and give fully fixed code

import "requestidlecallback-polyfill";
import { EventImpl } from "./EventImpl.js";
import debug from "./debug.ts";

const adKeywords = new Set([
  "advertise",
  "promo",
  "sponsor",
  "banner",
  "commercial",
  "preroll",
  "midroll",
  "postroll",
  "ad-container",
  "sponsored",
]);

const adKeywordsRegex = (() => {
  const pattern = Array.from(adKeywords).join("|");
  return new RegExp(pattern, "i");
})();

const filterVideoNodes = (nodes) => {
  const result = [];
  for (let i = 0; i < nodes.length; i++) {
    const node = nodes[i];
    if (node instanceof HTMLVideoElement) {
      result.push(node);
    } else if (node instanceof HTMLElement) {
      const videos = node.querySelectorAll("video");
      for (let j = 0; j < videos.length; j++) {
        result.push(videos[j]);
      }
    } else if (node.shadowRoot) {
      const shadowVideos = node.shadowRoot.querySelectorAll("video");
      for (let k = 0; k < shadowVideos.length; k++) {
        result.push(shadowVideos[k]);
      }
    }
  }
  return result;
};

const isAdVideo = (video) => {
  if (
    adKeywordsRegex.test(video.className) ||
    adKeywordsRegex.test(video.title)
  )
    return true;

  let parent = video.parentElement;
  while (parent) {
    if (
      adKeywordsRegex.test(parent.className) ||
      adKeywordsRegex.test(parent.id)
    )
      return true;
    parent = parent.parentElement;
  }

  return false;
};

const isMutedVideo = (video) => {
  return (
    video.hasAttribute("muted") &&
    !video.classList.contains("vjs-tech") &&
    !video.preload
  );
};

const isVideoReady = (video) => video.readyState >= 3;

const waitForVideoReady = (video, callback) => {
  const checkVideoState = () => {
    if (isVideoReady(video)) {
      callback(video);
    } else {
      requestAnimationFrame(checkVideoState);
    }
  };

  checkVideoState();
};

export class VideoObserver {
  constructor() {
    this.videoCache = new Set();
    this.onVideoAdded = new EventImpl();
    this.onVideoRemoved = new EventImpl();

    this.observer = new MutationObserver(this.handleMutations);
    this.patchAttachShadow();
  }

  patchAttachShadow() {
    const originalAttachShadow = Element.prototype.attachShadow;
    const self = this;
    Element.prototype.attachShadow = function (...args) {
      const shadowRoot = originalAttachShadow.apply(this, args);
      self.searchInRoot(shadowRoot);
      self.observeShadowRoot(shadowRoot);
      return shadowRoot;
    };
  }

  observeShadowRoot(shadowRoot) {
    const shadowObserver = new MutationObserver(this.handleMutations);
    shadowObserver.observe(shadowRoot, { childList: true, subtree: true });
  }

  handleMutations = (mutationsList) => {
    window.requestIdleCallback(
      () => {
        for (let i = 0; i < mutationsList.length; i++) {
          const mutation = mutationsList[i];
          if (mutation.type !== "childList") continue;

          const addedVideos = filterVideoNodes(mutation.addedNodes);
          for (let j = 0; j < addedVideos.length; j++) {
            this.checkAndHandleVideo(addedVideos[j]);
          }

          const removedVideos = filterVideoNodes(mutation.removedNodes);
          for (let k = 0; k < removedVideos.length; k++) {
            this.handleVideoRemoved(removedVideos[k]);
          }
        }
      },
      { timeout: 1000 }
    );
  };

  /**
   * Универсальная функция поиска видео внутри переданного корня (document или shadowRoot).
   * @param {Document | ShadowRoot} root
   */
  searchInRoot(root) {
    const findVideosInRoot = (currentRoot) => {
      const videos = new Set();

      const searchNode = (node) => {
        if (node instanceof HTMLVideoElement) {
          videos.add(node);
        }

        if (node instanceof Element) {
          const directVideos = node.querySelectorAll("video");
          for (let i = 0; i < directVideos.length; i++) {
            videos.add(directVideos[i]);
          }

          if (node.shadowRoot) {
            const shadowVideos = node.shadowRoot.querySelectorAll("video");
            for (let i = 0; i < shadowVideos.length; i++) {
              videos.add(shadowVideos[i]);
            }

            searchNode(node.shadowRoot);
          }

          const children = node.children;
          if (children) {
            for (let i = 0; i < children.length; i++) {
              searchNode(children[i]);
            }
          }
        }
      };

      searchNode(currentRoot);
      return Array.from(videos);
    };

    const foundVideos = findVideosInRoot(root);
    for (let i = 0; i < foundVideos.length; i++) {
      this.checkAndHandleVideo(foundVideos[i]);
    }
  }

  enable() {
    this.observer.observe(document, { childList: true, subtree: true });

    const regularVideos = document.querySelectorAll("video");
    for (let i = 0; i < regularVideos.length; i++) {
      this.checkAndHandleVideo(regularVideos[i]);
    }

    this.searchInRoot(document);

    const existingShadowHosts = this.getAllShadowHosts(document);
    for (let i = 0; i < existingShadowHosts.length; i++) {
      const host = existingShadowHosts[i];
      if (host.shadowRoot) {
        this.searchInRoot(host.shadowRoot);
        this.observeShadowRoot(host.shadowRoot);
      }
    }
  }

  getAllShadowHosts(root) {
    const hosts = new Set();
    const traverse = (node) => {
      if (node.shadowRoot) {
        hosts.add(node);
        const shadowElements = node.shadowRoot.querySelectorAll("*");
        for (let i = 0; i < shadowElements.length; i++) {
          traverse(shadowElements[i]);
        }
      }
      const children = node.children;
      if (children) {
        for (let i = 0; i < children.length; i++) {
          traverse(children[i]);
        }
      }
    };
    traverse(root);
    return hosts;
  }

  disable() {
    this.observer.disconnect();
  }

  checkAndHandleVideo = (video) => {
    if (this.videoCache.has(video)) return;
    this.videoCache.add(video);

    if (isAdVideo(video) || isMutedVideo(video)) {
      debug.log("The promotional/muted video was ignored", video);
      return;
    }
    waitForVideoReady(video, this.handleVideoAdded);
  };

  handleVideoAdded = (video) => {
    this.onVideoAdded.dispatch(video);
  };

  handleVideoRemoved = (video) => {
    if (!document.contains(video)) {
      this.videoCache.delete(video);
      this.onVideoRemoved.dispatch(video);
    }
  };
}