Skip to content

Commit 8d2941e

Browse files
committed
fix thumbs for videos in apk
1 parent 4855db2 commit 8d2941e

3 files changed

Lines changed: 114 additions & 5 deletions

File tree

src/lib/components/MessagesView.svelte

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@
2222
import { shouldSubmitTextareaOnEnter } from '$lib/keyboard';
2323
import { extractMediaAttachments, extractQuotedNoteReferences, parseNoteText } from '$lib/nostr/media';
2424
import { appPath } from '$lib/paths';
25-
import { pauseWhenHidden } from '$lib/actions/pauseWhenHidden';
2625
import { findOrCreatePomegranateConnection } from '$lib/nostr/pomegranateAuth';
2726
import type { DirectMessage, MediaAttachment, NostrEvent, Profile } from '$lib/nostr/types';
2827
import QuotedNotePreview from './QuotedNotePreview.svelte';
28+
import VideoAttachment from './VideoAttachment.svelte';
2929
3030
const nostrChatUrl = 'https://chat.nostr.com';
3131
const nostrChatConnectionName = 'chat.nostr.com';
@@ -425,8 +425,7 @@
425425
<div class="dm-media-grid">
426426
{#each media as item}
427427
{#if item.type === 'video'}
428-
<!-- svelte-ignore a11y_media_has_caption -->
429-
<video use:pauseWhenHidden src={item.url} poster={item.poster} controls preload="metadata" playsinline title={item.alt}></video>
428+
<VideoAttachment src={item.url} poster={item.poster} title={item.alt} />
430429
{:else}
431430
<button type="button" on:click={() => (openImage = { url: item.url, alt: item.alt })} aria-label="Open image">
432431
<img src={item.url} alt={item.alt ?? ''} loading="lazy" referrerpolicy="no-referrer" />

src/lib/components/NoteCard.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import { saveThreadReturnTarget, currentThreadReturnTarget } from '$lib/stores/threadNavigation';
1717
import { saveThreadSeed } from '$lib/stores/threadSeed';
1818
import QuotedNotePreview from './QuotedNotePreview.svelte';
19+
import VideoAttachment from './VideoAttachment.svelte';
1920
2021
export let event: NostrEvent;
2122
export let profile: Profile | undefined;
@@ -590,8 +591,7 @@
590591
<div class="media-grid" class:single={mediaAttachments.length === 1} class:double={mediaAttachments.length === 2} class:triple={mediaAttachments.length === 3} class:quad={mediaAttachments.length >= 4}>
591592
{#each mediaAttachments as media}
592593
{#if media.type === 'video'}
593-
<!-- svelte-ignore a11y_media_has_caption -->
594-
<video use:pauseWhenHidden src={media.url} poster={media.poster} controls preload="metadata" playsinline title={media.alt}></video>
594+
<VideoAttachment src={media.url} poster={media.poster} title={media.alt} />
595595
{:else}
596596
<button class="media-image-button" on:click={() => (openImage = { url: media.url, alt: media.alt })} aria-label="Open image">
597597
<img src={media.url} alt={media.alt ?? ''} loading="lazy" referrerpolicy="no-referrer" />
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
<script lang="ts" context="module">
2+
const generatedPosters = new Map<string, Promise<string>>();
3+
4+
function generatedPosterFor(url: string) {
5+
if (!generatedPosters.has(url)) generatedPosters.set(url, captureVideoPoster(url));
6+
return generatedPosters.get(url) ?? Promise.resolve('');
7+
}
8+
9+
function captureVideoPoster(url: string) {
10+
return new Promise<string>((resolve) => {
11+
if (typeof document === 'undefined') {
12+
resolve('');
13+
return;
14+
}
15+
16+
const video = document.createElement('video');
17+
const timeout = setTimeout(() => finish(''), 10_000);
18+
let settled = false;
19+
20+
const finish = (poster: string) => {
21+
if (settled) return;
22+
settled = true;
23+
clearTimeout(timeout);
24+
video.removeAttribute('src');
25+
video.load();
26+
resolve(poster);
27+
};
28+
29+
const drawFrame = () => {
30+
const width = video.videoWidth;
31+
const height = video.videoHeight;
32+
if (!width || !height) {
33+
finish('');
34+
return;
35+
}
36+
37+
try {
38+
const maxWidth = 640;
39+
const scale = Math.min(1, maxWidth / width);
40+
const canvas = document.createElement('canvas');
41+
canvas.width = Math.max(1, Math.round(width * scale));
42+
canvas.height = Math.max(1, Math.round(height * scale));
43+
const context = canvas.getContext('2d');
44+
if (!context) {
45+
finish('');
46+
return;
47+
}
48+
context.drawImage(video, 0, 0, canvas.width, canvas.height);
49+
finish(canvas.toDataURL('image/jpeg', 0.76));
50+
} catch {
51+
finish('');
52+
}
53+
};
54+
55+
const seekToFrame = () => {
56+
if (settled) return;
57+
const targetTime = Number.isFinite(video.duration) && video.duration > 0.25 ? 0.12 : 0;
58+
if (targetTime <= 0) {
59+
drawFrame();
60+
return;
61+
}
62+
try {
63+
video.currentTime = targetTime;
64+
} catch {
65+
drawFrame();
66+
}
67+
};
68+
69+
video.crossOrigin = 'anonymous';
70+
video.muted = true;
71+
video.playsInline = true;
72+
video.preload = 'metadata';
73+
video.addEventListener('loadedmetadata', seekToFrame, { once: true });
74+
video.addEventListener('loadeddata', () => {
75+
if (video.currentTime === 0) drawFrame();
76+
}, { once: true });
77+
video.addEventListener('seeked', drawFrame, { once: true });
78+
video.addEventListener('error', () => finish(''), { once: true });
79+
video.src = url;
80+
video.load();
81+
});
82+
}
83+
</script>
84+
85+
<script lang="ts">
86+
import { onMount } from 'svelte';
87+
import { pauseWhenHidden } from '$lib/actions/pauseWhenHidden';
88+
89+
export let src: string;
90+
export let poster: string | undefined = undefined;
91+
export let title: string | undefined = undefined;
92+
93+
let generatedPoster = '';
94+
$: displayPoster = poster || generatedPoster || undefined;
95+
96+
onMount(() => {
97+
let cancelled = false;
98+
if (!poster) {
99+
void generatedPosterFor(src).then((nextPoster) => {
100+
if (!cancelled && nextPoster) generatedPoster = nextPoster;
101+
});
102+
}
103+
return () => {
104+
cancelled = true;
105+
};
106+
});
107+
</script>
108+
109+
<!-- svelte-ignore a11y_media_has_caption -->
110+
<video use:pauseWhenHidden {src} poster={displayPoster} controls preload="metadata" playsinline {title}></video>

0 commit comments

Comments
 (0)