IMDB VidSrc Player

Add a multi-server video player directly into IMDB movie/series pages.

Du musst eine Erweiterung wie Tampermonkey, Greasemonkey oder Violentmonkey installieren, um dieses Skript zu installieren.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

Sie müssten eine Skript Manager Erweiterung installieren damit sie dieses Skript installieren können

(Ich habe schon ein Skript Manager, Lass mich es installieren!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         IMDB VidSrc Player
// @version      1.3.2
// @description  Add a multi-server video player directly into IMDB movie/series pages.
// @author       https://github.com/atefr
// @license      MIT
// @match        https://www.imdb.com/title/*
// @match        https://m.imdb.com/title/*
// @icon         https://m.media-amazon.com/images/G/01/imdb/images-ANDW73HA/favicon_desktop_32x32._CB1582158068_.png
// @connect      imdb.com
// @connect      m.imdb.com
// @connect      vidsrc-embed.ru
// @connect      vidsrc-embed.su
// @connect      111movies.com
// @connect      2embed.cc
// @connect      www.2embed.cc
// @connect      vidfast.pro
// @connect      vidsrc.mov
// @connect      autoembed.co
// @connect      multiembed.mov
// @connect      hnembed.cc
// @connect      vaplayer.ru
// @connect      hndrama.cc
// @namespace https://greatest.deepsurf.us/users/1397577
// ==/UserScript==

(function () {
  "use strict";

  const logError = (error) => console.error(`Error: ${error.message || error}`);
  let cachedNextData = null;
  const PLAYER_CONTAINER_ID = "gm-vidsrc-player";
  const SERVER_STORAGE_KEY = "imdb-vidsrc-server";
  let lastPath = "";

  const SERVERS = [
    {
      id: "vidsrc-embed-ru",
      name: "VidSrc",
      buildUrl: (context) => buildVidsrcEmbedUrl("https://vidsrc-embed.ru", context),
    },
    {
      id: "vidsrc-embed-su",
      name: "VidSrc (su)",
      buildUrl: (context) => buildVidsrcEmbedUrl("https://vidsrc-embed.su", context),
    },
    {
      id: "111movies",
      name: "111Movies",
      buildUrl: (context) => build111MoviesUrl(context),
    },
    {
      id: "2embed",
      name: "2Embed",
      buildUrl: (context) => build2EmbedUrl(context),
    },
    {
      id: "vidfast",
      name: "VidFast",
      buildUrl: (context) => buildVidfastUrl(context),
    },
    {
      id: "vidsrc-mov",
      name: "VidSrc (mov)",
      buildUrl: (context) => buildVidsrcMovUrl(context),
    },
    {
      id: "autoembed",
      name: "AutoEmbed",
      buildUrl: (context) => buildAutoEmbedUrl(context),
    },
    {
      id: "multiembed",
      name: "MultiEmbed",
      buildUrl: (context) => buildMultiEmbedUrl(context),
    },
    {
      id: "hnembed",
      name: "HnEmbed",
      buildUrl: (context) => buildHnembedUrl(context),
    },
    {
      id: "vaplayer",
      name: "VaPlayer",
      buildUrl: (context) => buildVaplayerUrl(context),
    },
    {
      id: "hndrama",
      name: "HnDrama",
      buildUrl: (context) => buildHndramaUrl(context),
    },
  ];

  init();

  function init() {
    lastPath = `${window.location.pathname}${window.location.search}`;
    scheduleInsert();

    const rawPushState = history.pushState;
    history.pushState = function (...args) {
      const result = rawPushState.apply(this, args);
      handleLocationChange();
      return result;
    };

    const rawReplaceState = history.replaceState;
    history.replaceState = function (...args) {
      const result = rawReplaceState.apply(this, args);
      handleLocationChange();
      return result;
    };

    window.addEventListener("popstate", handleLocationChange, true);

    const observer = new MutationObserver(() => {
      if (isTitlePage() && !document.getElementById(PLAYER_CONTAINER_ID)) {
        scheduleInsert();
      }
    });

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

  function isTitlePage() {
    return /^\/title\/tt\d+/.test(window.location.pathname);
  }

  function scheduleInsert() {
    window.requestAnimationFrame(() => {
      insertPlayer();
    });
  }

  function handleLocationChange() {
    const currentPath = `${window.location.pathname}${window.location.search}`;
    if (currentPath === lastPath) {
      return;
    }
    lastPath = currentPath;
    cachedNextData = null;
    scheduleInsert();
  }

  function extractSeasonEpisode() {
    const seasonEpisodeDiv = document.querySelector(
      '[data-testid="hero-subnav-bar-season-episode-numbers-section"]',
    );
    if (seasonEpisodeDiv) {
      const seasonEpisodeText = seasonEpisodeDiv.textContent.trim();
      const match = seasonEpisodeText.match(/S(\d+)\s*.\s*E(\d+)/);
      if (match) {
        return {
          season: match[1],
          episode: match[2],
        };
      }
    }

    const nextData = getNextData();
    const episodeNumber = nextData?.props?.pageProps?.aboveTheFoldData?.series?.episodeNumber;
    if (episodeNumber?.seasonNumber && episodeNumber?.episodeNumber) {
      return {
        season: String(episodeNumber.seasonNumber),
        episode: String(episodeNumber.episodeNumber),
      };
    }
    return null;
  }

  function extractSeriesId() {
    const seriesLink = document.querySelector('[data-testid="hero-title-block__series-link"]');
    if (seriesLink) {
      const href = seriesLink.getAttribute("href");
      const match = href.match(/\/title\/(tt\d+)\//);
      if (match) {
        return match[1];
      }
    }

    const nextData = getNextData();
    const seriesId = nextData?.props?.pageProps?.aboveTheFoldData?.series?.series?.id;
    return seriesId || null;
  }

  function insertPlayer() {
    if (!isTitlePage()) {
      return;
    }

    const imdbId = window.location.pathname.split("/")[2];
    if (!imdbId || !/^tt\d+$/.test(imdbId)) {
      return;
    }

    const existing = document.getElementById(PLAYER_CONTAINER_ID);
    if (existing) {
      existing.remove();
    }

    const context = buildContentContext(imdbId);
    const container = createPlayerContainer(context);

    const targetLocation = document.querySelector("main");
    if (targetLocation) {
      targetLocation.before(container);
    } else {
      logError("Target location for player insertion not found on the page");
      document.body.prepend(container);
    }
  }

  function getContentType() {
    const ogType = document.querySelector('meta[property="og:type"]')?.getAttribute("content");
    if (ogType === "video.episode") {
      return "episode";
    }
    if (ogType === "video.tv_show") {
      return "series";
    }

    const nextData = getNextData();
    const titleType = nextData?.props?.pageProps?.aboveTheFoldData?.titleType;
    if (titleType?.id === "tvEpisode" || titleType?.isEpisode) {
      return "episode";
    }
    if (titleType?.id === "tvSeries" || titleType?.isSeries) {
      return "series";
    }

    const title = document.title || "";
    if (title.includes("TV Episode")) {
      return "episode";
    }
    if (title.includes("TV Series")) {
      return "series";
    }
    return "movie";
  }

  function buildContentContext(imdbId) {
    const contentType = getContentType();
    const seasonEpisode = extractSeasonEpisode();
    return {
      imdbId,
      contentType,
      season: seasonEpisode ? seasonEpisode.season : null,
      episode: seasonEpisode ? seasonEpisode.episode : null,
      seriesId: extractSeriesId() || imdbId,
    };
  }

  function buildVidsrcEmbedUrl(baseUrl, context) {
    if (context.contentType === "movie") {
      return `${baseUrl}/embed/${context.imdbId}/`;
    }
    if (context.contentType === "series") {
      return `${baseUrl}/embed/tv?imdb=${context.seriesId}`;
    }
    if (context.season && context.episode) {
      return `${baseUrl}/embed/${context.seriesId}/${context.season}-${context.episode}/`;
    }
    return null;
  }

  function build111MoviesUrl(context) {
    if (context.contentType === "movie") {
      return `https://111movies.com/movie/${context.imdbId}`;
    }
    const episode = getPreferredEpisode(context);
    if (episode) {
      return `https://111movies.com/tv/${context.seriesId}/${episode.season}/${episode.episode}`;
    }
    return null;
  }

  function build2EmbedUrl(context) {
    if (context.contentType === "movie") {
      return `https://www.2embed.cc/embed/${context.imdbId}`;
    }
    if (context.contentType === "series") {
      return `https://www.2embed.cc/embedtvfull/${context.seriesId}`;
    }
    if (context.season && context.episode) {
      return `https://www.2embed.cc/embedtv/${context.seriesId}?s=${context.season}&e=${context.episode}`;
    }
    return null;
  }

  function buildVidfastUrl(context) {
    if (context.contentType === "movie") {
      return `https://vidfast.pro/movie/${context.imdbId}`;
    }
    const episode = getPreferredEpisode(context);
    if (episode) {
      return `https://vidfast.pro/tv/${context.seriesId}/${episode.season}/${episode.episode}`;
    }
    return null;
  }

  function buildVidsrcMovUrl(context) {
    if (context.contentType === "movie") {
      return `https://vidsrc.mov/embed/movie/${context.imdbId}`;
    }
    const episode = getPreferredEpisode(context);
    if (episode) {
      return `https://vidsrc.mov/embed/tv/${context.seriesId}/${episode.season}/${episode.episode}`;
    }
    return null;
  }

  function buildAutoEmbedUrl(context) {
    if (context.contentType === "movie") {
      return `https://autoembed.co/movie/imdb/${context.imdbId}`;
    }
    const episode = getPreferredEpisode(context);
    if (episode) {
      return `https://autoembed.co/tv/imdb/${context.seriesId}-${episode.season}-${episode.episode}`;
    }
    return null;
  }

  function buildMultiEmbedUrl(context) {
    const baseId = context.contentType === "movie" ? context.imdbId : context.seriesId;
    const params = new URLSearchParams({
      video_id: baseId,
      tmdb: "0",
    });

    if (context.contentType !== "movie") {
      const episode = getPreferredEpisode(context);
      if (!episode) {
        return null;
      }
      params.set("s", episode.season);
      params.set("e", episode.episode);
    }

    return `https://multiembed.mov/?${params.toString()}`;
  }

  function buildHnembedUrl(context) {
    if (context.contentType === "movie") {
      return `https://hnembed.cc/embed/movie/${context.imdbId}`;
    }
    const episode = getPreferredEpisode(context);
    if (episode) {
      return `https://hnembed.cc/embed/tv/${context.seriesId}/${episode.season}/${episode.episode}`;
    }
    return null;
  }

  function buildVaplayerUrl(context) {
    if (context.contentType === "movie") {
      return `https://vaplayer.ru/embed/movie/${context.imdbId}`;
    }
    const episode = getPreferredEpisode(context);
    if (episode) {
      return `https://vaplayer.ru/embed/tv/${context.seriesId}/${episode.season}/${episode.episode}`;
    }
    return null;
  }

  function buildHndramaUrl(context) {
    if (context.contentType === "movie") {
      return `https://hndrama.cc/embed/movie/${context.imdbId}`;
    }
    const episode = getPreferredEpisode(context);
    if (episode) {
      return `https://hndrama.cc/embed/tv/${context.seriesId}/${episode.season}/${episode.episode}`;
    }
    return null;
  }

  function getSavedServerId(availableServers) {
    const savedId = localStorage.getItem(SERVER_STORAGE_KEY);
    return availableServers.some((entry) => entry.server.id === savedId)
      ? savedId
      : availableServers[0].server.id;
  }

  function getNextData() {
    if (cachedNextData !== null) {
      return cachedNextData;
    }
    const script = document.getElementById("__NEXT_DATA__");
    if (!script || !script.textContent) {
      cachedNextData = null;
      return cachedNextData;
    }
    try {
      cachedNextData = JSON.parse(script.textContent);
    } catch (error) {
      logError(error);
      cachedNextData = null;
    }
    return cachedNextData;
  }

  function getPreferredEpisode(context) {
    if (context.season && context.episode) {
      return {
        season: context.season,
        episode: context.episode,
      };
    }
    if (context.contentType !== "series") {
      return null;
    }
    return {
      season: "1",
      episode: "1",
    };
  }

  function injectStyles() {
    if (document.getElementById("gm-vidsrc-style")) {
      return;
    }
    const style = document.createElement("style");
    style.id = "gm-vidsrc-style";
    style.textContent = `
            #gm-vidsrc-player {
                width: 100%;
                padding: 12px 16px 16px;
                background: #101010;
                border: 1px solid #1f1f1f;
                border-radius: 4px;
                box-shadow: 0 8px 16px rgba(0, 0, 0, 0.35);
                color: #f5f5f5;
                box-sizing: border-box;
            }

            #gm-vidsrc-header {
                display: flex;
                align-items: center;
                justify-content: space-between;
                gap: 12px;
                margin-bottom: 12px;
                flex-wrap: wrap;
            }

            #gm-vidsrc-title {
                font-size: 12px;
                letter-spacing: 0.18em;
                text-transform: uppercase;
                color: #f5c518;
            }

            #gm-vidsrc-controls {
                display: flex;
                align-items: center;
                gap: 8px;
            }

            #gm-vidsrc-controls label {
                font-size: 10px;
                text-transform: uppercase;
                letter-spacing: 0.12em;
                color: #b3b3b3;
            }

            #gm-vidsrc-controls select {
                background: #1a1a1a;
                color: #f5f5f5;
                border: 1px solid #2a2a2a;
                border-radius: 6px;
                padding: 6px 12px;
                font-size: 13px;
            }

            #gm-vidsrc-controls select:focus {
                outline: 2px solid #f5c518;
                outline-offset: 2px;
            }

            #gm-vidsrc-iframe {
                width: 100%;
                height: 65vh;
                min-height: 360px;
                border: 0;
                border-radius: 4px;
                background: #000;
            }

            #gm-vidsrc-message {
                padding: 18px;
                border-radius: 6px;
                background: #171717;
                border: 1px dashed #2a2a2a;
                font-size: 13px;
                color: #d0d0d0;
                display: none;
            }

            @media (max-width: 768px) {
                #gm-vidsrc-player {
                    margin: 0 0 20px;
                    padding: 12px 12px 14px;
                    border-radius: 0;
                }

                #gm-vidsrc-iframe {
                    height: 50vh;
                    min-height: 280px;
                }
            }
        `;
    document.head.appendChild(style);
  }

  function createPlayerContainer(context) {
    injectStyles();

    const container = document.createElement("section");
    container.id = PLAYER_CONTAINER_ID;

    const header = document.createElement("div");
    header.id = "gm-vidsrc-header";

    const title = document.createElement("div");
    title.id = "gm-vidsrc-title";
    title.textContent = "Stream";

    const controls = document.createElement("div");
    controls.id = "gm-vidsrc-controls";

    const label = document.createElement("label");
    label.setAttribute("for", "gm-vidsrc-server");
    label.textContent = "Server";

    const select = document.createElement("select");
    select.id = "gm-vidsrc-server";

    controls.appendChild(label);
    controls.appendChild(select);
    header.appendChild(title);
    header.appendChild(controls);
    container.appendChild(header);

    const availableServers = SERVERS.map((server) => ({
      server,
      url: server.buildUrl(context),
    })).filter((entry) => entry.url);

    const message = document.createElement("div");
    message.id = "gm-vidsrc-message";
    container.appendChild(message);

    if (!availableServers.length) {
      message.textContent =
        context.contentType === "episode"
          ? "Season or episode info is missing on this page."
          : "No available servers support this title.";
      message.style.display = "block";
      return container;
    }

    const savedServerId = getSavedServerId(availableServers);
    availableServers.forEach((entry) => {
      const option = document.createElement("option");
      option.value = entry.server.id;
      option.textContent = entry.server.name;
      if (entry.server.id === savedServerId) {
        option.selected = true;
      }
      select.appendChild(option);
    });
    select.disabled = availableServers.length === 1;

    const iframe = createIframe();
    const initialEntry =
      availableServers.find((entry) => entry.server.id === savedServerId) ||
      availableServers[0];
    iframe.src = initialEntry.url;
    container.appendChild(iframe);

    select.addEventListener("change", () => {
      const entry = availableServers.find((item) => item.server.id === select.value);
      if (!entry) {
        return;
      }
      localStorage.setItem(SERVER_STORAGE_KEY, entry.server.id);
      iframe.src = entry.url;
    });

    return container;
  }

  function createIframe() {
    const iframe = document.createElement("iframe");
    iframe.id = "gm-vidsrc-iframe";
    iframe.allowFullscreen = true;
    iframe.loading = "lazy";
    iframe.setAttribute("webkitallowfullscreen", "true");
    iframe.setAttribute("mozallowfullscreen", "true");
    return iframe;
  }
})();