Improve video embed overlay and proxy behavior

This commit is contained in:
Zed 2026-08-12 01:36:40 +07:00
commit 5fa4eefb9d
5 changed files with 82 additions and 32 deletions

View file

@ -1,25 +1,41 @@
(function() { (function () {
var embedElement = document.querySelector('.embed-wrapper, .tweet-embed, .embed-video'); var embedElement = document.querySelector(
".embed-wrapper, .tweet-embed, .embed-video",
);
if (!embedElement) return; if (!embedElement) return;
// Video play state for overlay (hidden while playing, visible on hover/pause)
var video = embedElement.querySelector("video");
if (video) {
video.onplay = function () {
embedElement.classList.add("video-playing");
};
video.onpause = video.onended = function () {
embedElement.classList.remove("video-playing");
};
}
var lastHeight = 0; var lastHeight = 0;
function sendHeight() { function sendHeight() {
var currentHeight = embedElement.offsetHeight; var currentHeight = embedElement.offsetHeight;
if (currentHeight !== lastHeight && currentHeight > 0) { if (currentHeight !== lastHeight && currentHeight > 0) {
lastHeight = currentHeight; lastHeight = currentHeight;
window.parent.postMessage(['resizeIframe', { h: currentHeight, url: location.href }], '*'); window.parent.postMessage(
["resizeIframe", { h: currentHeight, url: location.href }],
"*",
);
} }
} }
// Respond to height requests from parent via MessageChannel // Respond to height requests from parent via MessageChannel
window.addEventListener('message', function(event) { window.addEventListener("message", function (event) {
if (event.source === window.parent && event.ports && event.ports[0]) { if (event.source === window.parent && event.ports && event.ports[0]) {
event.ports[0].postMessage(embedElement.offsetHeight); event.ports[0].postMessage(embedElement.offsetHeight);
} }
}); });
window.addEventListener('load', sendHeight); window.addEventListener("load", sendHeight);
new ResizeObserver(sendHeight).observe(embedElement); new ResizeObserver(sendHeight).observe(embedElement);
// Expose for embedTweet.js // Expose for embedTweet.js

View file

@ -8,10 +8,7 @@
if (window.__nitterWidgets) return; if (window.__nitterWidgets) return;
window.__nitterWidgets = true; window.__nitterWidgets = true;
var scripts = document.querySelectorAll('script[src*="widgets.js"]'); var NITTER = new URL(document.currentScript.src).origin;
var NITTER = scripts.length
? new URL(scripts[scripts.length - 1].src).origin
: location.origin;
var TWEET_RE = /(?:twitter\.com|x\.com)\/([^\/]+)\/status\/(\d+)/i; var TWEET_RE = /(?:twitter\.com|x\.com)\/([^\/]+)\/status\/(\d+)/i;
var SELECTOR = "blockquote.twitter-tweet, blockquote.twitter-video"; var SELECTOR = "blockquote.twitter-tweet, blockquote.twitter-video";
@ -21,11 +18,15 @@
var isReady = false; var isReady = false;
function safeCall(fn, arg) { function safeCall(fn, arg) {
try { fn(arg); } catch (e) {} try {
fn(arg);
} catch (e) { }
} }
function fireEvent(name, data) { function fireEvent(name, data) {
(eventCallbacks[name] || []).forEach(function (cb) { safeCall(cb, data); }); (eventCallbacks[name] || []).forEach(function (cb) {
safeCall(cb, data);
});
} }
function parseTweetUrl(url) { function parseTweetUrl(url) {
@ -44,8 +45,12 @@
var path = tweet.user ? "/" + tweet.user : "/i"; var path = tweet.user ? "/" + tweet.user : "/i";
url = NITTER + path + "/status/" + tweet.id + "/embed"; url = NITTER + path + "/status/" + tweet.id + "/embed";
if (opts.theme) { if (opts.theme) {
var theme = opts.theme === "dark" ? "nitter" : var theme =
opts.theme === "light" ? "twitter" : opts.theme; opts.theme === "dark"
? "nitter"
: opts.theme === "light"
? "twitter"
: opts.theme;
url += "?theme=" + encodeURIComponent(theme); url += "?theme=" + encodeURIComponent(theme);
} }
} }
@ -60,11 +65,18 @@
if (opts.videoOnly) iframe.setAttribute("allowfullscreen", "true"); if (opts.videoOnly) iframe.setAttribute("allowfullscreen", "true");
var width = opts.width || 550; var width = opts.width || 550;
var margin = opts.align === "center" ? "10px auto" : var margin =
opts.align === "right" ? "10px 0 10px auto" : "10px 0"; opts.align === "center"
? "10px auto"
: opts.align === "right"
? "10px 0 10px auto"
: "10px 0";
iframe.style.cssText = iframe.style.cssText =
"width:100%;max-width:" + width + "px;height:250px;" + "width:100%;max-width:" +
"border:none;display:block;margin:" + margin; width +
"px;height:300px;" +
"border:none;display:block;margin:" +
margin;
iframe.addEventListener("load", function () { iframe.addEventListener("load", function () {
fireEvent("rendered", { target: iframe }); fireEvent("rendered", { target: iframe });
@ -89,7 +101,7 @@
width: d.mediaMaxWidth || d.width, width: d.mediaMaxWidth || d.width,
align: d.align, align: d.align,
theme: d.theme, theme: d.theme,
videoOnly: d.mediaMaxWidth !== undefined videoOnly: d.mediaMaxWidth !== undefined,
}); });
bq.style.display = "none"; bq.style.display = "none";
@ -98,7 +110,9 @@
} }
function processEmbeds(root) { function processEmbeds(root) {
var bqs = (root || document).querySelectorAll(SELECTOR + ":not([data-nitter-processed])"); var bqs = (root || document).querySelectorAll(
SELECTOR + ":not([data-nitter-processed])",
);
for (var i = 0; i < bqs.length; i++) processBlockquote(bqs[i]); for (var i = 0; i < bqs.length; i++) processBlockquote(bqs[i]);
} }
@ -147,7 +161,7 @@
createTweet: embedTweet, createTweet: embedTweet,
createTweetEmbed: embedTweet, createTweetEmbed: embedTweet,
createVideo: embedTweet, createVideo: embedTweet,
loaded: true loaded: true,
}, },
events: { events: {
bind: function (name, cb) { bind: function (name, cb) {
@ -158,33 +172,44 @@
unbind: function (name, cb) { unbind: function (name, cb) {
if (!eventCallbacks[name]) return; if (!eventCallbacks[name]) return;
eventCallbacks[name] = cb eventCallbacks[name] = cb
? eventCallbacks[name].filter(function (f) { return f !== cb; }) ? eventCallbacks[name].filter(function (f) {
return f !== cb;
})
: []; : [];
} },
}, },
ready: function (cb) { ready: function (cb) {
if (typeof cb !== "function") return; if (typeof cb !== "function") return;
if (isReady) cb(window.twttr); if (isReady) cb(window.twttr);
else readyCallbacks.push(cb); else readyCallbacks.push(cb);
}, },
_e: [] _e: [],
}; };
// Process callbacks queued before load (twttr._e pattern) // Process callbacks queued before load (twttr._e pattern)
if (prevTwttr && prevTwttr._e) { if (prevTwttr && prevTwttr._e) {
prevTwttr._e.forEach(function (cb) { safeCall(cb); }); prevTwttr._e.forEach(function (cb) {
safeCall(cb);
});
} }
// Remove any Twitter scripts that snuck through // Remove any Twitter scripts that snuck through
document.querySelectorAll('script[src*="platform.twitter.com"], script[src*="platform.x.com"]') document
.forEach(function (s) { s.remove(); }); .querySelectorAll(
'script[src*="platform.twitter.com"], script[src*="platform.x.com"]',
)
.forEach(function (s) {
s.remove();
});
function init() { function init() {
window.addEventListener("message", handleResize); window.addEventListener("message", handleResize);
processEmbeds(); processEmbeds();
observeDOM(); observeDOM();
isReady = true; isReady = true;
readyCallbacks.forEach(function (cb) { safeCall(cb, window.twttr); }); readyCallbacks.forEach(function (cb) {
safeCall(cb, window.twttr);
});
readyCallbacks = []; readyCallbacks = [];
} }

View file

@ -88,6 +88,7 @@ html:has(body > .embed-video) {
// Video-only embed // Video-only embed
.embed-video { .embed-video {
position: relative; position: relative;
min-height: 300px;
background-color: black; background-color: black;
border: 1px solid var(--border_grey); border: 1px solid var(--border_grey);
@ -106,7 +107,7 @@ html:has(body > .embed-video) {
width: 100%; width: 100%;
} }
.gallery-video > .attachment { .gallery-video>.attachment {
max-height: 560px; max-height: 560px;
width: 100%; width: 100%;
} }
@ -135,7 +136,9 @@ html:has(body > .embed-video) {
text-decoration: none; text-decoration: none;
border-radius: 9999px; border-radius: 9999px;
border: 1px solid transparent; border: 1px solid transparent;
transition: background 0.15s, opacity 0.15s; transition:
background 0.15s,
opacity 0.15s;
z-index: 10; z-index: 10;
&:hover { &:hover {
@ -143,8 +146,14 @@ html:has(body > .embed-video) {
} }
} }
&:has(video:playing) .video-overlay-link { // Hide button while playing, show on hover or when paused
&.video-playing .video-overlay-link {
opacity: 0; opacity: 0;
pointer-events: none; pointer-events: none;
} }
&.video-playing:hover .video-overlay-link {
opacity: 1;
pointer-events: auto;
}
} }

View file

@ -14,7 +14,7 @@ proc renderVideoEmbed*(tweet: Tweet; cfg: Config; req: Request): string =
video = tweet.getVideos()[0] video = tweet.getVideos()[0]
thumb = video.thumb thumb = video.thumb
vidUrl = getVideoEmbed(cfg, tweet.id) vidUrl = getVideoEmbed(cfg, tweet.id)
prefs = Prefs(hlsPlayback: true, mp4Playback: true) prefs = Prefs(hlsPlayback: true, mp4Playback: true, proxyVideos: true)
tweetUrl = getLink(tweet) tweetUrl = getLink(tweet)
let node = buildHtml(html(lang="en")): let node = buildHtml(html(lang="en")):

View file

@ -50,7 +50,7 @@ proc renderHead*(prefs: Prefs; cfg: Config; req: Request; titleText=""; desc="";
let opensearchUrl = getUrlPrefix(cfg) & "/opensearch" let opensearchUrl = getUrlPrefix(cfg) & "/opensearch"
buildHtml(head): buildHtml(head):
link(rel="stylesheet", type="text/css", href="/css/style.css?v=101") link(rel="stylesheet", type="text/css", href="/css/style.css?v=106")
link(rel="stylesheet", type="text/css", href="/css/fontello.css?v=7") link(rel="stylesheet", type="text/css", href="/css/fontello.css?v=7")
if theme.len > 0: if theme.len > 0: