Update reddit-image-inline.user.js
This commit is contained in:
+110
-39
@@ -1,7 +1,7 @@
|
|||||||
// ==UserScript==
|
// ==UserScript==
|
||||||
// @name Inline Reddit images and Comment Redirect
|
// @name Inline Reddit images and Comment Redirect
|
||||||
// @namespace https://www.reddit.com
|
// @namespace https://www.reddit.com
|
||||||
// @version 2026-08-28
|
// @version 2026-08-28.1
|
||||||
// @description Embed images and videos posted as comments and redirect post links to comments
|
// @description Embed images and videos posted as comments and redirect post links to comments
|
||||||
// @author luxick
|
// @author luxick
|
||||||
// @updateURL https://git.luxick.de/luxick/scripts/js/raw/branch/master/reddit-image-inline.user.js
|
// @updateURL https://git.luxick.de/luxick/scripts/js/raw/branch/master/reddit-image-inline.user.js
|
||||||
@@ -30,20 +30,116 @@
|
|||||||
return links;
|
return links;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reddit's video comments link to an embedded player page rather than to a
|
// Comment media is linked through Reddit's viewer:
|
||||||
// playable file, e.g. ".../video/<id>/player".
|
// "https://reddit.com/media?url=<media url>"
|
||||||
function isPlayerUrl(href) {
|
function unwrapMediaUrl(href) {
|
||||||
return /\/player\/?(\?|$)/i.test(href);
|
let match = href.match(/[?&]url=([^&]+)/);
|
||||||
|
if (!match) {
|
||||||
|
return href;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return decodeURIComponent(match[1]);
|
||||||
|
} catch (e) {
|
||||||
|
return match[1];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildPlayerFrame(href) {
|
// Video comments point at an HLS playlist, which only Safari plays without
|
||||||
let frame = document.createElement('iframe');
|
// a library. The same upload is also served as standalone MP4 renditions,
|
||||||
frame.src = href;
|
// so use those instead.
|
||||||
frame.width = 300;
|
//
|
||||||
frame.height = 300;
|
// Newer uploads name them CMAF_<height>.mp4, older ones DASH_<height>.mp4,
|
||||||
frame.allowFullscreen = true;
|
// and no upload has every height. Missing names return 403, so just walk
|
||||||
frame.style = "max-width: 300px; border: none";
|
// the list until one loads. 720 first as a sane default for a 300px embed;
|
||||||
return frame;
|
// 1080 last, only worth fetching when nothing smaller exists.
|
||||||
|
const HEIGHTS = [720, 480, 360, 270, 240, 220, 1080];
|
||||||
|
const FAMILIES = ['CMAF', 'DASH'];
|
||||||
|
const AUDIO_NAMES = [
|
||||||
|
'CMAF_AUDIO_128', 'CMAF_AUDIO_64',
|
||||||
|
'DASH_AUDIO_128', 'DASH_AUDIO_64', 'DASH_audio',
|
||||||
|
];
|
||||||
|
|
||||||
|
function mediaUrls(id, names) {
|
||||||
|
return names.map(name => 'https://v.redd.it/' + id + '/' + name + '.mp4');
|
||||||
|
}
|
||||||
|
|
||||||
|
function videoNames() {
|
||||||
|
let names = [];
|
||||||
|
for (let family of FAMILIES) {
|
||||||
|
for (let height of HEIGHTS) {
|
||||||
|
names.push(family + '_' + height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
function vredditId(url) {
|
||||||
|
let match = url.match(/^https?:\/\/v\.redd\.it\/([A-Za-z0-9]+)/i);
|
||||||
|
return match ? match[1] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to the next candidate URL whenever the current one fails to load.
|
||||||
|
function chainSources(media, sources, onExhausted) {
|
||||||
|
let index = 0;
|
||||||
|
media.src = sources[0];
|
||||||
|
media.addEventListener('error', function() {
|
||||||
|
index += 1;
|
||||||
|
if (index < sources.length) {
|
||||||
|
media.src = sources[index];
|
||||||
|
media.load();
|
||||||
|
} else if (onExhausted) {
|
||||||
|
onExhausted();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// The MP4 renditions carry the video track only; the audio is a separate
|
||||||
|
// file that has to be kept in step with the video element by hand.
|
||||||
|
function buildAudio(video, id) {
|
||||||
|
let audio = document.createElement('audio');
|
||||||
|
audio.preload = 'metadata';
|
||||||
|
chainSources(audio, mediaUrls(id, AUDIO_NAMES));
|
||||||
|
|
||||||
|
video.addEventListener('play', function() {
|
||||||
|
audio.currentTime = video.currentTime;
|
||||||
|
audio.play().catch(() => {});
|
||||||
|
});
|
||||||
|
video.addEventListener('pause', () => audio.pause());
|
||||||
|
video.addEventListener('seeking', () => { audio.currentTime = video.currentTime; });
|
||||||
|
video.addEventListener('ratechange', () => { audio.playbackRate = video.playbackRate; });
|
||||||
|
video.addEventListener('volumechange', function() {
|
||||||
|
audio.volume = video.volume;
|
||||||
|
audio.muted = video.muted;
|
||||||
|
});
|
||||||
|
return audio;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildVideo(href) {
|
||||||
|
let container = document.createElement('span');
|
||||||
|
let mediaUrl = unwrapMediaUrl(href);
|
||||||
|
let id = vredditId(mediaUrl);
|
||||||
|
|
||||||
|
let video = document.createElement('video');
|
||||||
|
video.controls = true;
|
||||||
|
video.preload = 'metadata';
|
||||||
|
video.style = "max-width: 300px";
|
||||||
|
|
||||||
|
let sources = id ? mediaUrls(id, videoNames()) : [mediaUrl];
|
||||||
|
|
||||||
|
chainSources(video, sources, function() {
|
||||||
|
console.log("no playable source for " + href);
|
||||||
|
let anchor = document.createElement('a');
|
||||||
|
anchor.href = href;
|
||||||
|
anchor.target = '_blank';
|
||||||
|
anchor.textContent = '<video>';
|
||||||
|
container.replaceChildren(anchor);
|
||||||
|
});
|
||||||
|
|
||||||
|
container.appendChild(video);
|
||||||
|
if (id) {
|
||||||
|
container.appendChild(buildAudio(video, id));
|
||||||
|
}
|
||||||
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle image inlining
|
// Handle image inlining
|
||||||
@@ -69,32 +165,7 @@
|
|||||||
// Handle video inlining
|
// Handle video inlining
|
||||||
for (var videoElem of collectLinks('<video>')) {
|
for (var videoElem of collectLinks('<video>')) {
|
||||||
console.log("inlining video " + videoElem.href);
|
console.log("inlining video " + videoElem.href);
|
||||||
let href = videoElem.href;
|
videoElem.parentElement.appendChild(buildVideo(videoElem.href));
|
||||||
let embed;
|
|
||||||
|
|
||||||
if (isPlayerUrl(href)) {
|
|
||||||
embed = buildPlayerFrame(href);
|
|
||||||
} else {
|
|
||||||
let video = document.createElement('video');
|
|
||||||
video.src = href;
|
|
||||||
video.controls = true;
|
|
||||||
video.preload = 'metadata';
|
|
||||||
video.style = "max-width: 300px";
|
|
||||||
|
|
||||||
// Some links (v.redd.it DASH streams) a bare <video> cannot play;
|
|
||||||
// put the original link back so the video stays reachable.
|
|
||||||
video.addEventListener('error', function() {
|
|
||||||
let anchor = document.createElement('a');
|
|
||||||
anchor.href = href;
|
|
||||||
anchor.target = '_blank';
|
|
||||||
anchor.textContent = '<video>';
|
|
||||||
video.replaceWith(anchor);
|
|
||||||
});
|
|
||||||
|
|
||||||
embed = video;
|
|
||||||
}
|
|
||||||
|
|
||||||
videoElem.parentElement.appendChild(embed);
|
|
||||||
videoElem.remove();
|
videoElem.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user