94 lines
2.9 KiB
TypeScript
94 lines
2.9 KiB
TypeScript
import { Post } from "@/types/types";
|
|
import { buildStorageUrl } from "@/components/main/BaseUrl";
|
|
import { resolvePostMediaType } from "@/lib/reelsMediaType";
|
|
|
|
const MAX_WARM_VIDEOS = 3;
|
|
|
|
export function isConstrainedNetwork(): boolean {
|
|
if (typeof navigator === "undefined") return false;
|
|
const connection = (navigator as Navigator & {
|
|
connection?: { saveData?: boolean; effectiveType?: string };
|
|
}).connection;
|
|
if (!connection) return false;
|
|
return connection.saveData === true || ["slow-2g", "2g", "3g"].includes(connection.effectiveType ?? "");
|
|
}
|
|
const warmVideos = new Map<string, HTMLVideoElement>();
|
|
const prefetchedLinks = new Set<string>();
|
|
|
|
export function getPostVideoSrc(post: Pick<Post, "files" | "type">): string | undefined {
|
|
const videoFile =
|
|
post.files?.find((file) => file.type === "video") ??
|
|
(resolvePostMediaType(post) === "video" ? post.files?.[0] : undefined);
|
|
|
|
if (!videoFile?.path) return undefined;
|
|
return buildStorageUrl(videoFile.path);
|
|
}
|
|
|
|
function evictOldestWarmVideo() {
|
|
const firstKey = warmVideos.keys().next().value as string | undefined;
|
|
if (!firstKey) return;
|
|
warmVideos.get(firstKey)?.remove();
|
|
warmVideos.delete(firstKey);
|
|
}
|
|
|
|
function warmVideoElement(url: string) {
|
|
if (typeof window === "undefined" || !url || warmVideos.has(url) || isConstrainedNetwork()) return;
|
|
|
|
const video = document.createElement("video");
|
|
video.preload = "auto";
|
|
video.muted = true;
|
|
video.playsInline = true;
|
|
video.setAttribute("playsinline", "");
|
|
video.setAttribute("webkit-playsinline", "");
|
|
video.src = url;
|
|
video.style.cssText =
|
|
"position:fixed;width:0;height:0;opacity:0;pointer-events:none;z-index:-1";
|
|
|
|
document.body.appendChild(video);
|
|
video.load();
|
|
|
|
if (warmVideos.size >= MAX_WARM_VIDEOS) evictOldestWarmVideo();
|
|
warmVideos.set(url, video);
|
|
}
|
|
|
|
function linkPrefetchVideo(url: string) {
|
|
if (typeof window === "undefined" || !url || prefetchedLinks.has(url)) return;
|
|
prefetchedLinks.add(url);
|
|
|
|
const link = document.createElement("link");
|
|
link.rel = "prefetch";
|
|
link.as = "video";
|
|
link.href = url;
|
|
document.head.appendChild(link);
|
|
}
|
|
|
|
export type VideoPrefetchPriority = "high" | "low";
|
|
|
|
/** پیشبارگذاری ویدئو در HTTP cache + buffer مخفی */
|
|
export function prefetchVideoUrl(
|
|
url: string | undefined,
|
|
priority: VideoPrefetchPriority = "high"
|
|
) {
|
|
if (!url) return;
|
|
linkPrefetchVideo(url);
|
|
if (priority === "high") warmVideoElement(url);
|
|
}
|
|
|
|
export function prefetchPostVideo(
|
|
post: Pick<Post, "files" | "type" | "_id"> | null | undefined,
|
|
priority: VideoPrefetchPriority = "high"
|
|
) {
|
|
prefetchVideoUrl(post ? getPostVideoSrc(post) : undefined, priority);
|
|
}
|
|
|
|
export function prefetchPostVideos(
|
|
posts: Array<Pick<Post, "files" | "type" | "_id">>,
|
|
priority: VideoPrefetchPriority = "low"
|
|
) {
|
|
posts.forEach((post) => prefetchPostVideo(post, priority));
|
|
}
|
|
|
|
export function isVideoWarming(url: string | undefined): boolean {
|
|
return !!url && warmVideos.has(url);
|
|
}
|