Add a multi-server video player directly into IMDB movie/series pages.
// ==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;
}
})();