speed up shitpost mode upload
This commit is contained in:
@@ -1261,3 +1261,15 @@
|
|||||||
height: 200px;
|
height: 200px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Video thumbnail loading animation */
|
||||||
|
.video-thumbnail-loading {
|
||||||
|
animation: thumbPulse 1.5s ease-in-out infinite;
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes thumbPulse {
|
||||||
|
0% { opacity: 0.4; }
|
||||||
|
50% { opacity: 0.8; }
|
||||||
|
100% { opacity: 0.4; }
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,101 @@ window.escapeHtmlUpload = window.escapeHtmlUpload || ((unsafe) => {
|
|||||||
.replace(/'/g, "'");
|
.replace(/'/g, "'");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Throttled queue to capture the first frame of video files asynchronously without blocking the browser
|
||||||
|
class VideoThumbnailQueue {
|
||||||
|
constructor(concurrency = 3) {
|
||||||
|
this.concurrency = concurrency;
|
||||||
|
this.activeCount = 0;
|
||||||
|
this.queue = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
add(file, callback) {
|
||||||
|
this.queue.push({ file, callback });
|
||||||
|
this.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
next() {
|
||||||
|
if (this.activeCount >= this.concurrency || this.queue.length === 0) return;
|
||||||
|
const { file, callback } = this.queue.shift();
|
||||||
|
this.activeCount++;
|
||||||
|
this.capture(file)
|
||||||
|
.then(dataUrl => callback(dataUrl))
|
||||||
|
.catch(err => {
|
||||||
|
console.warn('[VideoThumbnailQueue] Error capturing thumbnail:', err);
|
||||||
|
callback(null);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.activeCount--;
|
||||||
|
this.next();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
capture(file) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const video = document.createElement('video');
|
||||||
|
const objectUrl = URL.createObjectURL(file);
|
||||||
|
video.src = objectUrl;
|
||||||
|
video.muted = true;
|
||||||
|
video.playsInline = true;
|
||||||
|
video.preload = 'metadata';
|
||||||
|
|
||||||
|
let cleanedUp = false;
|
||||||
|
const cleanup = () => {
|
||||||
|
if (cleanedUp) return;
|
||||||
|
cleanedUp = true;
|
||||||
|
video.src = '';
|
||||||
|
video.load();
|
||||||
|
URL.revokeObjectURL(objectUrl);
|
||||||
|
};
|
||||||
|
|
||||||
|
video.onloadeddata = () => {
|
||||||
|
video.currentTime = 0.1;
|
||||||
|
};
|
||||||
|
|
||||||
|
video.onseeked = () => {
|
||||||
|
try {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const maxDim = 320;
|
||||||
|
let width = video.videoWidth || 160;
|
||||||
|
let height = video.videoHeight || 120;
|
||||||
|
if (width > maxDim || height > maxDim) {
|
||||||
|
if (width > height) {
|
||||||
|
height = Math.round((height * maxDim) / width);
|
||||||
|
width = maxDim;
|
||||||
|
} else {
|
||||||
|
width = Math.round((width * maxDim) / height);
|
||||||
|
height = maxDim;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
canvas.width = width;
|
||||||
|
canvas.height = height;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
ctx.drawImage(video, 0, 0, width, height);
|
||||||
|
const dataUrl = canvas.toDataURL('image/jpeg', 0.8);
|
||||||
|
cleanup();
|
||||||
|
resolve(dataUrl);
|
||||||
|
} catch (e) {
|
||||||
|
cleanup();
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
video.onerror = () => {
|
||||||
|
cleanup();
|
||||||
|
reject(new Error('Video loading failed'));
|
||||||
|
};
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!cleanedUp) {
|
||||||
|
cleanup();
|
||||||
|
reject(new Error('Capture timeout'));
|
||||||
|
}
|
||||||
|
}, 8000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const videoThumbnailQueue = new VideoThumbnailQueue(3);
|
||||||
|
|
||||||
window.initUploadForm = (selector) => {
|
window.initUploadForm = (selector) => {
|
||||||
const form = (typeof selector === 'string') ? document.querySelector(selector) : selector;
|
const form = (typeof selector === 'string') ? document.querySelector(selector) : selector;
|
||||||
if (!form) return;
|
if (!form) return;
|
||||||
@@ -820,9 +915,25 @@ window.initUploadForm = (selector) => {
|
|||||||
mediaElem = document.createElement('video');
|
mediaElem = document.createElement('video');
|
||||||
mediaElem.src = URL.createObjectURL(file);
|
mediaElem.src = URL.createObjectURL(file);
|
||||||
mediaElem.muted = true;
|
mediaElem.muted = true;
|
||||||
mediaElem.autoplay = true;
|
|
||||||
mediaElem.controls = true;
|
mediaElem.controls = true;
|
||||||
mediaElem.loop = true;
|
mediaElem.loop = true;
|
||||||
|
|
||||||
|
if (isShitpost) {
|
||||||
|
mediaElem.autoplay = false;
|
||||||
|
mediaElem.preload = 'none';
|
||||||
|
mediaElem.classList.add('video-thumbnail-loading');
|
||||||
|
|
||||||
|
videoThumbnailQueue.add(file, (dataUrl) => {
|
||||||
|
if (dataUrl) {
|
||||||
|
mediaElem.poster = dataUrl;
|
||||||
|
mediaElem.classList.remove('video-thumbnail-loading');
|
||||||
|
} else {
|
||||||
|
mediaElem.classList.remove('video-thumbnail-loading');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
mediaElem.autoplay = true;
|
||||||
|
}
|
||||||
} else if (file.type.startsWith('audio/')) {
|
} else if (file.type.startsWith('audio/')) {
|
||||||
mediaElem = document.createElement('audio');
|
mediaElem = document.createElement('audio');
|
||||||
mediaElem.src = URL.createObjectURL(file);
|
mediaElem.src = URL.createObjectURL(file);
|
||||||
|
|||||||
Reference in New Issue
Block a user