Skip to content

Instantly share code, notes, and snippets.

@tomlau10
Last active March 1, 2024 03:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tomlau10/446881c3cfca2639ccda688d07df1cf9 to your computer and use it in GitHub Desktop.
Save tomlau10/446881c3cfca2639ccda688d07df1cf9 to your computer and use it in GitHub Desktop.
Fix stream video incorrect thumbnail offset
// ==UserScript==
// @name stream video thumbnail offset fix
// @namespace https://gist.github.com/tomlau10
// @version 0.4.3
// @license MIT
// @description Fix stream video incorrect thumbnail offset
// @author Tom Lau
// @include https://dood.*/*
// @match https://filemoon.sx/*
// @match https://streamwish.to/*
// @updateURL https://gist.github.com/tomlau10/446881c3cfca2639ccda688d07df1cf9/raw/stream_video_thumbnail_fix.user.js
// @downloadURL https://gist.github.com/tomlau10/446881c3cfca2639ccda688d07df1cf9/raw/stream_video_thumbnail_fix.user.js
// ==/UserScript==
(function() {
'use strict';
// prevent console.clear and restore console.log
Object.assign(unsafeWindow.console, window.console);
unsafeWindow.console.clear = () => {};
const parseTimeText = (text) => text.split(':').reduce(
(s, v, i, a) => s + parseInt(v) * Math.pow(60, a.length-1 - i),
0
);
const controllers = [
{ // doodstream
checkPlatform: () => {
const link = document.querySelector('.vjs-brand-container-link');
return link && link.href.indexOf('doodstream') >= 0;
},
getTimeThumb: () => document.querySelector('.vjs-thumbnail'),
getTimeText: () => document.querySelector('.vjs-thumbnail-text'),
getTotalSecs: () => {
const progressBar = document.querySelector('.vjs-progress-holder');
if (!progressBar || !progressBar.ariaValueText) {
return;
}
return parseTimeText(progressBar.ariaValueText.split(' of ')[1]);
},
getThumbImage: (timeThumb) => timeThumb,
setBlank: (timeThumb) => {
const clip = `rect(0px, 0px, 0px, 0px)`;
if (timeThumb.style.clip === clip) {
return;
}
timeThumb.style.clip = clip;
},
setPos: (timeThumb, left, top) => {
if (timeThumb.style.left === left && timeThumb.style.top === top) {
return;
}
timeThumb.style.left = left;
timeThumb.style.top = top;
timeThumb.style.clip = '';
},
},
{ // filemoon
checkPlatform: () => {
const link = document.querySelector('link[rel=apple-touch-icon]');
return link && link.href.indexOf('filemoon') >= 0;
},
getTimeThumb: () => document.querySelector('.jw-time-thumb'),
getTimeText: () => document.querySelector('.jw-time-tip > .jw-time-time'),
getTotalSecs: () => {
const timeSlider = document.querySelector('.jw-slider-time');
return parseFloat(timeSlider && timeSlider.ariaValueMax);
},
getThumbImage: (timeThumb) => {
if (!timeThumb.style.backgroundImage) {
return;
}
if (!timeThumb.backgroundImage) {
timeThumb.backgroundImage = new Image();
timeThumb.backgroundImage.src = timeThumb.style.backgroundImage.slice(4, -1).replace(/"/g, '');
}
return timeThumb.backgroundImage;
},
setBlank: (timeThumb) => {
if (!timeThumb.style.backgroundImage) {
return;
}
timeThumb.style.backgroundImage = '';
},
setPos: (timeThumb, left, top) => {
const backgroundPosition = `${left} ${top}`;
if (timeThumb.style.backgroundPosition === backgroundPosition) {
return;
}
timeThumb.style.backgroundPosition = backgroundPosition;
},
},
{ // streamwish
checkPlatform: () => {
const meta = document.querySelector('meta[name=keywords]');
return meta && meta.content.toLowerCase().indexOf('streamwish') >= 0;
},
getTimeThumb: () => document.querySelector('.jw-time-thumb'),
getTimeText: () => document.querySelector('.jw-time-tip > .jw-time-time'),
getTotalSecs: () => {
const durationText = document.querySelector(".jw-text-duration");
return durationText && parseTimeText(durationText.innerHTML);
},
getThumbImage: (timeThumb) => {
if (!timeThumb.style.backgroundImage) {
return;
}
if (!timeThumb.backgroundImage) {
timeThumb.backgroundImage = new Image();
timeThumb.backgroundImage.src = timeThumb.style.backgroundImage.slice(4, -1).replace(/"/g, '');
}
return timeThumb.backgroundImage;
},
setBlank: (timeThumb) => {
if (!timeThumb.style.backgroundImage) {
return;
}
timeThumb.style.backgroundImage = '';
},
setPos: (timeThumb, left, top) => {
const backgroundPosition = `${left} ${top}`;
if (timeThumb.style.backgroundPosition === backgroundPosition) {
return;
}
timeThumb.style.backgroundPosition = backgroundPosition;
},
},
];
const interval = setInterval(() => {
// check platform
const controller = controllers.find(c => c.checkPlatform());
if (!controller) {
return;
}
// get time thumb and time text html node
const timeThumb = controller.getTimeThumb();
const timeText = controller.getTimeText();
if (!timeThumb || !timeText) {
return;
}
// get video legnth
const totalSecs = controller.getTotalSecs();
if (!totalSecs) {
return;
}
// get background size
const thumbImage = controller.getThumbImage(timeThumb);
if (!thumbImage || !thumbImage.height) {
return;
}
const singleWidth = 200, singleHeight = 112;
const tnRows = thumbImage.height / singleHeight;
const tnCols = thumbImage.width / singleWidth;
const tnTotal = tnRows * tnCols;
// observe time thumb and time text changes
const observer = new MutationObserver((mutations, owner) => {
// parse time text
const secs = parseTimeText(timeText.textContent);
// calculate index
const progress = secs / totalSecs;
const index = Math.min(Math.floor(progress * (tnTotal + 1) + 0.05) - 1, tnTotal - 1);
if (index < 0) {
// show as blank if before 1st thumbnail
controller.setBlank(timeThumb);
return;
}
// update background position
const width = Math.floor(index % tnCols) * -singleWidth;
const height = Math.floor(index / tnRows) * -singleHeight;
const left = `${width}px`;
const top = `${height}px`;
controller.setPos(timeThumb, left, top);
});
observer.observe(timeThumb, { attributes: true });
observer.observe(timeText, { childList: true });
clearInterval(interval);
}, 1000);
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment