some more features and fixes (title and show info)
This commit is contained in:
@@ -52,7 +52,8 @@ App.videos = App.videos || {};
|
||||
};
|
||||
|
||||
const updateTitleActive = function(card) {
|
||||
if (!card || !card.classList.contains('has-marquee')) {
|
||||
const titleWrap = card && card.querySelector('.video-title');
|
||||
if (!titleWrap || !titleWrap.classList.contains('has-marquee')) {
|
||||
if (card) card.classList.remove('is-title-active');
|
||||
return;
|
||||
}
|
||||
@@ -68,25 +69,13 @@ App.videos = App.videos || {};
|
||||
const titleWrap = card.querySelector('.video-title');
|
||||
const titleText = card.querySelector('.video-title-text');
|
||||
if (!titleWrap || !titleText) return;
|
||||
const overflow = titleText.scrollWidth - titleWrap.clientWidth;
|
||||
if (overflow > 4) {
|
||||
card.classList.add('has-marquee');
|
||||
const distance = overflow + 12;
|
||||
titleText.style.setProperty('--marquee-distance', `${distance}px`);
|
||||
// Drive the duration off the distance so every title scrolls at the
|
||||
// same gentle speed (px/sec) instead of a fixed duration that made
|
||||
// longer titles whip past. A floor keeps short titles from snapping.
|
||||
const MARQUEE_SPEED = 28; // px per second
|
||||
const MARQUEE_MIN_DURATION = 6; // seconds
|
||||
const duration = Math.max(MARQUEE_MIN_DURATION, distance / MARQUEE_SPEED);
|
||||
titleText.style.setProperty('--marquee-duration', `${duration.toFixed(2)}s`);
|
||||
if (App.marquee.measure(titleWrap, titleText)) {
|
||||
// Only marquee cards need the scroll-position observer that picks the
|
||||
// centered card to animate; observing every card made scrolling a
|
||||
// large grid needlessly expensive.
|
||||
if (titleObserver) titleObserver.observe(card);
|
||||
} else {
|
||||
card.classList.remove('has-marquee', 'is-title-active');
|
||||
titleText.style.removeProperty('--marquee-distance');
|
||||
card.classList.remove('is-title-active');
|
||||
if (titleObserver) {
|
||||
titleObserver.unobserve(card);
|
||||
titleVisibility.delete(card);
|
||||
@@ -548,7 +537,7 @@ App.videos = App.videos || {};
|
||||
if (showInfoBtn) {
|
||||
showInfoBtn.onclick = (event) => {
|
||||
event.stopPropagation();
|
||||
App.ui.showInfo(v);
|
||||
App.ui.openInfo(v);
|
||||
App.videos.closeAllMenus();
|
||||
};
|
||||
}
|
||||
@@ -1530,6 +1519,46 @@ App.videos = App.videos || {};
|
||||
});
|
||||
};
|
||||
|
||||
// Everything the extractor knows about a video, for the Show info panel:
|
||||
// description, dates, counts, categories, thumbnails, every field of every
|
||||
// format. Deliberately not `ensureFormats`' payload and deliberately not
|
||||
// attached to `video.meta` -- meta is fetched for every card that scrolls
|
||||
// past and is kept small on purpose, and it names things playback's way
|
||||
// (`isLive`) rather than the extractor's (`is_live`). Cached per video for
|
||||
// the session, like meta, so reopening the panel is free.
|
||||
const fullInfoCache = new Map();
|
||||
|
||||
App.videos.fetchFullInfo = function(video) {
|
||||
if (!video || typeof video !== 'object' || !video.url) return Promise.resolve(null);
|
||||
// Same reasoning as ensureFormats: a URL that already names a media file
|
||||
// has nothing to extract, and re-fetching a signed one can burn a
|
||||
// single-use link that playback still needs.
|
||||
if (isDirectMediaUrl(video.url)) return Promise.resolve(null);
|
||||
|
||||
const cacheKey = video.id || video.url;
|
||||
let promise = fullInfoCache.get(cacheKey);
|
||||
if (!promise) {
|
||||
promise = (async () => {
|
||||
try {
|
||||
const response = await fetch('/api/resolve', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ url: video.url, full: '1' })
|
||||
});
|
||||
if (!response.ok) return null;
|
||||
const data = await response.json();
|
||||
if (!data || typeof data !== 'object' || !Object.keys(data).length) return null;
|
||||
return data;
|
||||
} catch (err) {
|
||||
// Best-effort: the panel still shows what the client holds.
|
||||
return null;
|
||||
}
|
||||
})();
|
||||
fullInfoCache.set(cacheKey, promise);
|
||||
}
|
||||
return promise;
|
||||
};
|
||||
|
||||
const probeObserver = new IntersectionObserver((entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (!entry.isIntersecting) return;
|
||||
|
||||
Reference in New Issue
Block a user