@anthusai/papyrus 1.0.0-next.51 → 1.0.0-next.52
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -7,6 +7,8 @@ import { resolveThemedVideoSrc } from "../lib/themed-image";
|
|
|
7
7
|
import { useResolvedPapyrusTheme } from "./use-resolved-papyrus-theme";
|
|
8
8
|
import { getSiteBrand } from "../lib/site-brand";
|
|
9
9
|
import { normalizeDevPreviewDsl, type VideoScriptRef } from "../lib/video-script";
|
|
10
|
+
import { useNearViewport } from "./use-near-viewport";
|
|
11
|
+
import { planVideoLoading, videoElementKey } from "../lib/video-loading";
|
|
10
12
|
import { resolveVideoMode, resolveVideoModeFromEnv, type VideoMode } from "../lib/video-mode";
|
|
11
13
|
|
|
12
14
|
type ArticleVideoFigureProps = {
|
|
@@ -18,6 +20,7 @@ type ArticleVideoFigureProps = {
|
|
|
18
20
|
};
|
|
19
21
|
|
|
20
22
|
const PREVIEW_STORAGE_PREFIX = "papyrus-video-preview:";
|
|
23
|
+
const VIDEO_LOAD_ROOT_MARGIN = "200px 0px";
|
|
21
24
|
|
|
22
25
|
export function ArticleVideoFigure({
|
|
23
26
|
video,
|
|
@@ -34,6 +37,8 @@ export function ArticleVideoFigure({
|
|
|
34
37
|
const [previewReady, setPreviewReady] = useState(false);
|
|
35
38
|
const iframeRef = useRef<HTMLIFrameElement>(null);
|
|
36
39
|
const videoRef = useRef<HTMLVideoElement>(null);
|
|
40
|
+
const figureRef = useRef<HTMLElement>(null);
|
|
41
|
+
const isNearViewport = useNearViewport(figureRef, VIDEO_LOAD_ROOT_MARGIN);
|
|
37
42
|
const isFramed = getSiteBrand().videoPlayer === "framed";
|
|
38
43
|
|
|
39
44
|
useEffect(() => {
|
|
@@ -44,6 +49,12 @@ export function ArticleVideoFigure({
|
|
|
44
49
|
// Match SSR to the default dark MP4 in `video.src` until after hydration.
|
|
45
50
|
const resolvedTheme = hasHydrated ? theme : "dark";
|
|
46
51
|
const src = resolveThemedVideoSrc(video.src, video.themeVariants, resolvedTheme);
|
|
52
|
+
const loadingPlan = planVideoLoading({
|
|
53
|
+
hasHydrated,
|
|
54
|
+
isNearViewport,
|
|
55
|
+
hasPoster: Boolean(video.posterSrc),
|
|
56
|
+
});
|
|
57
|
+
const videoKey = videoElementKey(loadingPlan.attachSource, src);
|
|
47
58
|
const usePreview = videoMode === "preview" && Boolean(videoScript?.dsl);
|
|
48
59
|
const previewDsl = videoScript?.dsl
|
|
49
60
|
? normalizeDevPreviewDsl(videoScript.dsl, getSiteBrand().video?.sceneComponents)
|
|
@@ -191,6 +202,7 @@ export function ArticleVideoFigure({
|
|
|
191
202
|
if (isFramed) {
|
|
192
203
|
return (
|
|
193
204
|
<figure
|
|
205
|
+
ref={figureRef}
|
|
194
206
|
className={`${figureClassName} article-video--framed`}
|
|
195
207
|
data-media-type="video"
|
|
196
208
|
data-video-theme={resolvedTheme}
|
|
@@ -200,11 +212,11 @@ export function ArticleVideoFigure({
|
|
|
200
212
|
<video
|
|
201
213
|
ref={videoRef}
|
|
202
214
|
playsInline
|
|
203
|
-
preload=
|
|
215
|
+
preload={loadingPlan.preload}
|
|
204
216
|
poster={video.posterSrc}
|
|
205
217
|
aria-label={video.alt}
|
|
206
218
|
className="article-video__player"
|
|
207
|
-
key={
|
|
219
|
+
key={videoKey}
|
|
208
220
|
onPlay={() => setIsPlaying(true)}
|
|
209
221
|
onPause={() => setIsPlaying(false)}
|
|
210
222
|
onEnded={() => setIsPlaying(false)}
|
|
@@ -214,7 +226,7 @@ export function ArticleVideoFigure({
|
|
|
214
226
|
if (Number.isFinite(nextDuration) && nextDuration > 0) setDuration(nextDuration);
|
|
215
227
|
}}
|
|
216
228
|
>
|
|
217
|
-
<source src={src} type="video/mp4" />
|
|
229
|
+
{loadingPlan.attachSource ? <source src={src} type="video/mp4" /> : null}
|
|
218
230
|
</video>
|
|
219
231
|
</div>
|
|
220
232
|
{framedControls}
|
|
@@ -226,17 +238,17 @@ export function ArticleVideoFigure({
|
|
|
226
238
|
}
|
|
227
239
|
|
|
228
240
|
return (
|
|
229
|
-
<figure className={figureClassName} data-media-type="video" data-video-theme={resolvedTheme} data-video-mode={videoMode}>
|
|
241
|
+
<figure ref={figureRef} className={figureClassName} data-media-type="video" data-video-theme={resolvedTheme} data-video-mode={videoMode}>
|
|
230
242
|
<video
|
|
231
243
|
controls
|
|
232
244
|
playsInline
|
|
233
|
-
preload=
|
|
245
|
+
preload={loadingPlan.preload}
|
|
234
246
|
poster={video.posterSrc}
|
|
235
247
|
aria-label={video.alt}
|
|
236
248
|
className="article-video__player"
|
|
237
|
-
key={
|
|
249
|
+
key={videoKey}
|
|
238
250
|
>
|
|
239
|
-
<source src={src} type="video/mp4" />
|
|
251
|
+
{loadingPlan.attachSource ? <source src={src} type="video/mp4" /> : null}
|
|
240
252
|
</video>
|
|
241
253
|
{video.caption ? <figcaption>{video.caption}</figcaption> : null}
|
|
242
254
|
{video.credit ? <p className="article-video__credit">{video.credit}</p> : null}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState, type RefObject } from "react";
|
|
4
|
+
|
|
5
|
+
export function useNearViewport(targetRef: RefObject<Element | null>, rootMargin: string): boolean {
|
|
6
|
+
const [isNearViewport, setIsNearViewport] = useState(false);
|
|
7
|
+
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
if (isNearViewport) return;
|
|
10
|
+
const target = targetRef.current;
|
|
11
|
+
if (!target) return;
|
|
12
|
+
if (typeof IntersectionObserver === "undefined") {
|
|
13
|
+
setIsNearViewport(true);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const observer = new IntersectionObserver(
|
|
17
|
+
(entries) => {
|
|
18
|
+
if (entries.some((entry) => entry.isIntersecting)) {
|
|
19
|
+
setIsNearViewport(true);
|
|
20
|
+
observer.disconnect();
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
{ rootMargin },
|
|
24
|
+
);
|
|
25
|
+
observer.observe(target);
|
|
26
|
+
return () => observer.disconnect();
|
|
27
|
+
}, [isNearViewport, rootMargin, targetRef]);
|
|
28
|
+
|
|
29
|
+
return isNearViewport;
|
|
30
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type VideoPreload = "none" | "metadata";
|
|
2
|
+
|
|
3
|
+
export type VideoLoadingPlan = {
|
|
4
|
+
attachSource: boolean;
|
|
5
|
+
preload: VideoPreload;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export type VideoLoadingInput = {
|
|
9
|
+
hasHydrated: boolean;
|
|
10
|
+
isNearViewport: boolean;
|
|
11
|
+
hasPoster: boolean;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function planVideoLoading({ hasHydrated, isNearViewport, hasPoster }: VideoLoadingInput): VideoLoadingPlan {
|
|
15
|
+
const attachSource = hasHydrated && isNearViewport;
|
|
16
|
+
const preload: VideoPreload = attachSource && !hasPoster ? "metadata" : "none";
|
|
17
|
+
return { attachSource, preload };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function videoElementKey(attachSource: boolean, resolvedSource: string): string {
|
|
21
|
+
return attachSource ? resolvedSource : "unattached";
|
|
22
|
+
}
|
package/package.json
CHANGED
|
@@ -127,6 +127,7 @@
|
|
|
127
127
|
"./components/ui/tooltip": "./components/ui/tooltip.tsx",
|
|
128
128
|
"./components/use-blog-header-obstacles": "./components/use-blog-header-obstacles.ts",
|
|
129
129
|
"./components/use-brand-fonts-ready": "./components/use-brand-fonts-ready.ts",
|
|
130
|
+
"./components/use-near-viewport": "./components/use-near-viewport.ts",
|
|
130
131
|
"./components/use-resolved-papyrus-theme": "./components/use-resolved-papyrus-theme.ts",
|
|
131
132
|
"./components/use-rhythm-overlay": "./components/use-rhythm-overlay.ts",
|
|
132
133
|
"./lib/amplify-outputs-guard": "./lib/amplify-outputs-guard.ts",
|
|
@@ -224,6 +225,7 @@
|
|
|
224
225
|
"./lib/staging-preview-object": "./lib/staging-preview-object.ts",
|
|
225
226
|
"./lib/themed-image": "./lib/themed-image.ts",
|
|
226
227
|
"./lib/utils": "./lib/utils.ts",
|
|
228
|
+
"./lib/video-loading": "./lib/video-loading.ts",
|
|
227
229
|
"./lib/video-mode": "./lib/video-mode.ts",
|
|
228
230
|
"./lib/video-script": "./lib/video-script.ts",
|
|
229
231
|
"./renderers/markus/index": "./renderers/markus/index.ts",
|
|
@@ -306,7 +308,7 @@
|
|
|
306
308
|
"optional": true
|
|
307
309
|
}
|
|
308
310
|
},
|
|
309
|
-
"version": "1.0.0-next.
|
|
311
|
+
"version": "1.0.0-next.52",
|
|
310
312
|
"dependencies": {
|
|
311
313
|
"@aws-amplify/adapter-nextjs": "^1.7.3",
|
|
312
314
|
"@aws-sdk/client-cognito-identity-provider": "3.1045.0",
|
package/routes.manifest.json
CHANGED