Repository navigation
Expand file tree
/
Copy pathTeaserMedia.astro
More file actions
101 lines (101 loc) · 4.7 KB
/
Copy pathTeaserMedia.astro
File metadata and controls
101 lines (101 loc) · 4.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
---
import ProjectImage from './ProjectImage.astro';
import type { SiteImage } from '../data/media';
import type { Teaser } from '../data/teasers';
interface Props { teaser?: Teaser; fallback: SiteImage; compact?: boolean }
const { teaser, fallback, compact = false } = Astro.props;
const poster = teaser?.poster ?? fallback;
---
<div class="teaser-media">
{teaser?.figures ? (
<div class="teaser-gallery">
{teaser.figures.map((figure) => (
<figure class="teaser-result">
<figcaption>{figure.label}</figcaption>
<ProjectImage image={figure.image} />
</figure>
))}
</div>
) : (
<div class="teaser-viewport">
<div data-teaser-poster><ProjectImage image={poster} /></div>
{teaser?.video && <video class="teaser-video" data-teaser-video data-src={teaser.video} poster={poster.src.src} width={poster.src.width} height={poster.src.height} aria-label={teaser.caption} controls muted loop playsinline preload="none" hidden />}
</div>
)}
{teaser && !teaser.figures && !compact && <p class="teaser-caption">{teaser.caption}</p>}
{teaser?.video && <p class="teaser-error" data-teaser-error role="status" hidden>The animated preview could not play. The static preview is shown; use the source link or watch the presentation.</p>}
</div>
<script>
const dialog = document.querySelector<HTMLDialogElement>('#video-dialog');
const items = [...document.querySelectorAll<HTMLVideoElement>('[data-teaser-video]')].map((video) => {
const container = video.closest<HTMLElement>('.teaser-media');
const poster = container?.querySelector<HTMLElement>('[data-teaser-poster]');
const error = container?.querySelector<HTMLElement>('[data-teaser-error]');
const src = video.dataset.src;
if (!container || !poster || !error || !src) throw new Error('Teaser is missing required media markup.');
return { video, container, poster, error, src, visible: false, failed: false, userPaused: false, autoplayBlocked: false, automaticPauses: 0 };
});
type Item = typeof items[number];
const canPlay = (item: Item) => item.visible && !document.hidden && !dialog?.open && item.container.getClientRects().length > 0;
const pauseAutomatically = (item: Item) => {
if (!item.video.paused) {
item.automaticPauses++;
item.video.pause();
}
};
const fail = (item: Item, error: unknown) => {
item.failed = true;
pauseAutomatically(item);
item.video.hidden = true;
item.poster.hidden = false;
item.error.textContent = 'The animated preview could not play. The static preview is shown; use the source link or watch the presentation.';
item.error.hidden = false;
console.error('Research teaser playback failed:', item.src, error);
};
const update = (item: Item) => {
if (!canPlay(item)) { pauseAutomatically(item); return; }
if (item.failed || item.userPaused || item.autoplayBlocked) return;
if (!item.video.getAttribute('src')) item.video.src = item.src;
item.video.muted = true;
item.video.hidden = false;
item.poster.hidden = true;
void item.video.play().catch((error: unknown) => {
// Pausing or changing panels can cancel an in-flight play request.
if (error instanceof DOMException && error.name === 'AbortError') return;
if (error instanceof DOMException && error.name === 'NotAllowedError') {
item.autoplayBlocked = true;
item.error.textContent = 'Your browser blocked autoplay. Use the video controls to play this preview.';
item.error.hidden = false;
console.warn('Research teaser autoplay was blocked:', item.src);
return;
}
fail(item, error);
});
};
const updateAll = () => items.forEach(update);
if (items.length) {
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
const item = items.find((candidate) => candidate.container === entry.target);
if (item) { item.visible = entry.isIntersecting; update(item); }
});
}, { threshold: 0.15 });
items.forEach((item) => {
observer.observe(item.container);
item.video.addEventListener('error', () => fail(item, item.video.error));
item.video.addEventListener('pause', () => {
if (item.automaticPauses) item.automaticPauses--;
else item.userPaused = true;
});
item.video.addEventListener('play', () => {
item.userPaused = false;
item.autoplayBlocked = false;
if (!item.failed) item.error.hidden = true;
if (!canPlay(item)) pauseAutomatically(item);
});
});
document.addEventListener('visibilitychange', updateAll);
if (dialog) new MutationObserver(updateAll).observe(dialog, { attributes: true, attributeFilter: ['open'] });
updateAll();
}
</script>