base on this edge & chrome extension code , manifest.json "{
  "manifest_version": 3,
  "name": "Youtube Auto Close, Auto Pause",
  "version": "4.3",
  "description": "Stops Autoplay, YouTube Tab Closer, One-Click Stop All Videos, Plays One Video at a Time",
  "permissions": [
    "tabs",
    "activeTab",
    "scripting",
    "webNavigation" 
  ],
  "host_permissions": [
    "*://*/*"
  ],
  "content_scripts": [
    {
      "matches": ["*://*/*"],
      "js": ["content.js"],
      "run_at": "document_idle",
      "all_frames": true
    }
  ],
  "background": {
    "service_worker": "background.js"
  },
  "action": {
    "default_icon": {
      "16": "icon.png",
      "48": "icon.png",
      "128": "icon.png"
    },
    "default_title": "Youtube Auto Close, Auto Pause"
  },
  "icons": {
    "16": "icon.png",
    "48": "icon.png",
    "128": "icon.png"
  }
}" , background.js "let currentlyPlayingTabId = null;
const closeTimers = {}; // Map of tabId to close timer data
const closeDuration = 10000; // 10 seconds

// Listener for the extension icon click to pause all videos
chrome.action.onClicked.addListener((tab) => {
  chrome.tabs.query({}, (tabs) => {
    tabs.forEach((tab) => {
      chrome.tabs.sendMessage(tab.id, { action: 'pauseAllVideos' });
    });
  });
});

// Updates the red progress bar and closes the tab when complete
const updateRedBarProgress = (tabId) => {
  const timerData = closeTimers[tabId];
  if (!timerData) return;

  const { closeStartTime } = timerData;
  const elapsedTime = Date.now() - closeStartTime;
  const progress = Math.min((elapsedTime / closeDuration) * 100, 100);

  chrome.tabs.sendMessage(tabId, { action: 'updateRedBarProgress', progress: progress });

  if (progress < 100) {
    timerData.closeTimerId = setTimeout(() => updateRedBarProgress(tabId), 100); // Update every 100ms
  } else {
    chrome.tabs.remove(tabId, () => {
      if (chrome.runtime.lastError) {
        console.error(`Error closing tab: ${chrome.runtime.lastError.message}`);
      }
    });
    delete closeTimers[tabId];
  }
};

// Message handler from content scripts
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  const tabId = sender.tab.id;
  switch (request.action) {
    case 'videoStartedPlaying':
      if (currentlyPlayingTabId && currentlyPlayingTabId !== tabId) {
        chrome.tabs.sendMessage(currentlyPlayingTabId, { action: 'pauseAllVideos' });
      }
      currentlyPlayingTabId = tabId;
      break;
    case 'videoStoppedPlaying':
      if (tabId === currentlyPlayingTabId) {
        currentlyPlayingTabId = null;
      }
      break;
    case 'startCloseTimer':
      if (closeTimers[tabId]) {
        clearTimeout(closeTimers[tabId].closeTimerId);
      }
      closeTimers[tabId] = {
        closeStartTime: Date.now(),
        closeTimerId: null,
      };
      if (sender.tab.url.includes('youtube.com')) {
        updateRedBarProgress(tabId);
      }
      break;
    case 'cancelCloseTimer':
      if (closeTimers[tabId]) {
        clearTimeout(closeTimers[tabId].closeTimerId);
        delete closeTimers[tabId];
      }
      break;
  }
  sendResponse({ status: 'Message received' });
  return true;
});

// Listen for webNavigation events to inject content script into iframes
chrome.webNavigation.onCompleted.addListener(async (details) => {
  if (details.frameId !== 0) {
    // It's an iframe
    try {
      await chrome.scripting.executeScript({
        target: { tabId: details.tabId, frameIds: [details.frameId] },
        files: ['content.js'],
      });
    } catch (e) {
      console.error('Failed to inject script into iframe:', e);
    }
  }
});" , content.js "let currentlyPlaying = null;
let lastUserInteractionTime = 0;
let videoElements = new WeakSet();
let lastTitle = document.title;

/**
 * Clears the red progress bar and cancels the close timer.
 */
const clearRedBarAndTimer = () => {
  const redBar = document.getElementById('extension-red-bar');
  if (redBar) {
    redBar.remove();
  }
  if (chrome.runtime && chrome.runtime.sendMessage) {
    chrome.runtime.sendMessage({ action: 'cancelCloseTimer' });
  }
};

/**
 * Shows the red progress bar at the top of the page if on YouTube.
 */
const showRedBar = () => {
  clearRedBarAndTimer();
  if (location.hostname.includes('youtube.com')) {
    const redBar = document.createElement('div');
    redBar.id = 'extension-red-bar';
    redBar.style.cssText = `
      position: fixed;
      top: 0;
      left: 0;
      width: 0%;
      height: 5px;
      background: red;
      z-index: 9999;
      transition: width 0.1s linear;
    `;
    document.body.appendChild(redBar);

    if (chrome.runtime && chrome.runtime.sendMessage) {
      chrome.runtime.sendMessage({ action: 'startCloseTimer' });
    }
  }
};

/**
 * Updates the width of the red progress bar based on the provided progress percentage.
 * @param {number} progress - The progress percentage (0 to 100).
 */
const updateRedBarProgress = (progress) => {
  const redBar = document.getElementById('extension-red-bar');
  if (redBar) {
    redBar.style.width = `${progress}%`;
  }
};

/**
 * Pauses all video elements that are tracked by the extension.
 */
const pauseAllVideos = () => {
  for (const video of document.querySelectorAll('video')) {
    if (videoElements.has(video)) {
      video.pause();
    }
  }
  currentlyPlaying = null;
  clearRedBarAndTimer();
  if (chrome.runtime && chrome.runtime.sendMessage) {
    chrome.runtime.sendMessage({ action: 'videoStoppedPlaying' });
  }
};

/**
 * Handles the end of a video by showing the red progress bar.
 */
const handleVideoEnd = () => {
  showRedBar();
};

/**
 * Checks if a video element is currently within the viewport.
 * @param {HTMLVideoElement} video - The video element to check.
 * @returns {boolean} - True if the video is in the viewport; otherwise, false.
 */
const isVideoInViewport = (video) => {
  const rect = video.getBoundingClientRect();
  return (
    rect.bottom > 0 &&
    rect.right > 0 &&
    rect.top < (window.innerHeight || document.documentElement.clientHeight) &&
    rect.left < (window.innerWidth || document.documentElement.clientWidth)
  );
};

/**
 * Determines if a video is significant based on its size relative to the viewport.
 * @param {HTMLVideoElement} video - The video element to evaluate.
 * @returns {boolean} - True if the video is significant; otherwise, false.
 */
const isSignificantVideo = (video) => {
  if (location.pathname.startsWith('/shorts/')) {
    // Treat all videos on YouTube Shorts as significant
    return true;
  }
  const rect = video.getBoundingClientRect();
  const area = rect.width * rect.height;
  const viewportArea = window.innerWidth * window.innerHeight;
  const areaRatio = area / viewportArea;
  return areaRatio > 0.1; // Adjust threshold as needed
};

/**
 * Handles the play event of a video element.
 * @param {HTMLVideoElement} video - The video element that started playing.
 */
const handleVideoPlay = (video) => {
  clearRedBarAndTimer();
  const currentTime = Date.now();
  const timeSinceLastInteraction = currentTime - lastUserInteractionTime;

  if (timeSinceLastInteraction > 2000) {
    setTimeout(() => {
      if (!video.paused) {
        video.pause();
      }
    }, 100);
  } else {
    if (isVideoInViewport(video) && isSignificantVideo(video)) {
      pauseOtherVideos(video);
      if (chrome.runtime && chrome.runtime.sendMessage) {
        chrome.runtime.sendMessage({ action: 'videoStartedPlaying' });
      }
    }
  }
};

/**
 * Pauses all other videos except the newly started one.
 * @param {HTMLVideoElement} newVideo - The video element that should keep playing.
 */
const pauseOtherVideos = (newVideo) => {
  for (const video of document.querySelectorAll('video')) {
    if (video !== newVideo && videoElements.has(video)) {
      video.pause();
    }
  }
  currentlyPlaying = newVideo;
};

/**
 * Initializes control over a video element.
 * @param {HTMLVideoElement} video - The video element to control.
 */
const initVideoControl = (video) => {
  if (videoElements.has(video)) return;

  videoElements.add(video);
  video.pause(); // Only auto-pause videos on initial load
  video.addEventListener('ended', handleVideoEnd, { once: false });
  video.addEventListener('play', () => handleVideoPlay(video));

  const observer = new MutationObserver((mutations) => {
    mutations.forEach((mutation) => {
      if (
        mutation.type === 'childList' &&
        Array.from(mutation.removedNodes).includes(video)
      ) {
        cleanupVideoElement(video);
        observer.disconnect();
      }
    });
  });
  if (video.parentNode) {
    observer.observe(video.parentNode, { childList: true });
  }
};

/**
 * Cleans up event listeners and references for a video element that's been removed.
 * @param {HTMLVideoElement} video - The video element to clean up.
 */
const cleanupVideoElement = (video) => {
  video.removeEventListener('ended', handleVideoEnd);
  video.removeEventListener('play', () => handleVideoPlay(video));
  videoElements.delete(video);
  if (currentlyPlaying === video) {
    currentlyPlaying = null;
  }
};

/**
 * Observes the given document for added or removed video elements.
 * @param {Document} doc - The document to observe.
 */
const observeVideoElementsInDocument = (doc) => {
  const observer = new MutationObserver((mutations) => {
    mutations.forEach((mutation) => {
      mutation.addedNodes.forEach((node) => {
        if (node.nodeName === 'VIDEO') {
          initVideoControl(node);
        } else if (node.shadowRoot) {
          // Handle Shadow DOM elements
          node.shadowRoot.querySelectorAll('video').forEach(initVideoControl);
        } else if (node.querySelectorAll) {
          node.querySelectorAll('video').forEach(initVideoControl);
        }
      });
    });
  });

  observer.observe(doc, { childList: true, subtree: true });
};

/**
 * Tracks user interactions to prevent the extension from pausing user-initiated video playbacks.
 */
const initUserInteractionTracking = () => {
  ['mousedown', 'keydown', 'touchstart', 'click'].forEach((eventType) => {
    document.addEventListener(
      eventType,
      () => {
        lastUserInteractionTime = Date.now();
        clearRedBarAndTimer();
      },
      true
    );
  });
};

/**
 * Handles changes to the document's title to reset the extension's state on page navigation.
 */
const handleTitleChange = () => {
  if (document.title !== lastTitle) {
    lastTitle = document.title;
    clearRedBarAndTimer();
    pauseAllVideos();
  }
};

/**
 * Observes changes to the document's title element.
 */
const observeTitleChanges = () => {
  const titleObserver = new MutationObserver(handleTitleChange);
  const titleElement = document.querySelector('title');
  if (titleElement) {
    titleObserver.observe(titleElement, {
      subtree: true,
      characterData: true,
      childList: true,
    });
  }
};

/**
 * Initializes the extension by setting up observers and event listeners.
 */
const initExtension = () => {
  // Initialize videos in the main document
  document.querySelectorAll('video').forEach(initVideoControl);

  // Initialize videos in iframes
  Array.from(document.getElementsByTagName('iframe')).forEach((iframe) => {
    try {
      // Check for same-origin iframe
      const iframeDocument = iframe.contentDocument || iframe.contentWindow.document;
      iframeDocument.querySelectorAll('video').forEach(initVideoControl);

      // Observe mutations in the iframe
      observeVideoElementsInDocument(iframeDocument);
    } catch (e) {
      // Can't access cross-origin iframe
    }
  });

  // Observe the main document
  observeVideoElementsInDocument(document);

  initUserInteractionTracking();
  observeTitleChanges();

  if (chrome.runtime && chrome.runtime.onMessage) {
    chrome.runtime.onMessage.addListener(
      (request, sender, sendResponse) => {
        if (request.action === 'pauseAllVideos') {
          pauseAllVideos();
        } else if (request.action === 'updateRedBarProgress') {
          updateRedBarProgress(request.progress);
        }
        sendResponse({ status: 'Message received' });
        return true;
      }
    );
  }
};

/**
 * Starts the extension once the document is ready.
 */
if (document.readyState === 'loading') {
  document.addEventListener('DOMContentLoaded', initExtension);
} else {
  initExtension();
}" ,fix error Uncaught SyntaxError: Identifier 'currentlyPlaying' has already been declared



Context

https://hianime.to/home

Stack Trace

content.js:1 (anonymous function) (let currentlyPlaying = null;
) ,second error "Failed to inject script into iframe: Error: Frame with ID 699 was removed.



Context

extension://pfinkejmpoboegkgiholihianpppbeno/background.js

Stack Trace

background.js:85 (anonymous function) (      console.error('Failed to inject script into iframe:', e);
) "