@geekapps/silo-elements-nextjs 0.2.61 → 0.2.63

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.
@@ -107,13 +107,6 @@ function Video({
107
107
  cancelled = true;
108
108
  };
109
109
  }, [parsed.chaptersSrc, parsed.chaptersData]);
110
- useEffect(() => {
111
- if (!parsed.thumbnailSrc) {
112
- setPoster(void 0);
113
- return;
114
- }
115
- setPoster(parsed.thumbnailSrc);
116
- }, [parsed.thumbnailSrc]);
117
110
  const initialSourceIndex = useMemo(() => {
118
111
  const index = parsed.sources.findIndex((source) => source.default);
119
112
  return index >= 0 ? index : 0;
@@ -150,9 +143,10 @@ function Video({
150
143
  const [activeCue, setActiveCue] = useState(null);
151
144
  const [storyboardSheetSize, setStoryboardSheetSize] = useState(null);
152
145
  const storyboardSheetUrlRef = useRef("");
153
- const [storyboardCues, setStoryboardCues] = useState(
154
- []
155
- );
146
+ const [storyboardCues, setStoryboardCues] = useState([]);
147
+ const storyboardCacheRef = useRef(/* @__PURE__ */ new Map());
148
+ const [activeStoryboardRes, setActiveStoryboardRes] = useState(null);
149
+ const preloadedSpritesRef = useRef(/* @__PURE__ */ new Set());
156
150
  const [ratingCounts, setRatingCounts] = useState(
157
151
  () => ({ LOVE: parsed.rating?.counts?.LOVE ?? 0, LIKE: parsed.rating?.counts?.LIKE ?? 0, DISLIKE: parsed.rating?.counts?.DISLIKE ?? 0 })
158
152
  );
@@ -195,6 +189,24 @@ function Video({
195
189
  const [isFullscreen, setIsFullscreen] = useState(false);
196
190
  const [playerHeight, setPlayerHeight] = useState(0);
197
191
  const [playerWidth, setPlayerWidth] = useState(0);
192
+ useEffect(() => {
193
+ const urls = parsed.thumbnailUrls;
194
+ if (!urls || Object.keys(urls).length === 0) {
195
+ setPoster(parsed.thumbnailSrc ?? void 0);
196
+ return;
197
+ }
198
+ const sorted = Object.entries(urls).map(([label, url]) => ({ px: parseInt(label.replace("w", ""), 10) || 0, url })).filter((e) => e.px > 0).sort((a, b) => a.px - b.px);
199
+ if (sorted.length === 0) {
200
+ setPoster(parsed.thumbnailSrc ?? void 0);
201
+ return;
202
+ }
203
+ const dpr = typeof window !== "undefined" ? window.devicePixelRatio ?? 1 : 1;
204
+ const conn = typeof navigator !== "undefined" ? navigator.connection : null;
205
+ const connFactor = conn?.effectiveType === "4g" ? 1 : conn?.effectiveType === "3g" ? 0.5 : 0.25;
206
+ const targetPx = (playerWidth || 640) * dpr * (conn ? connFactor : 1);
207
+ const best = sorted.find((e) => e.px >= targetPx) ?? sorted[sorted.length - 1];
208
+ setPoster(best.url);
209
+ }, [parsed.thumbnailUrls, parsed.thumbnailSrc, playerWidth]);
198
210
  const activeSource = parsed.sources[sourceIndex] ?? parsed.sources[0] ?? null;
199
211
  const progressPercent = duration ? currentTime / duration * 100 : 0;
200
212
  const bufferedPercent = duration ? bufferedTime / duration * 100 : 0;
@@ -443,40 +455,71 @@ function Video({
443
455
  async function loadStoryboard() {
444
456
  if (!parsed.storyboard) {
445
457
  setStoryboardCues([]);
458
+ setActiveStoryboardRes(null);
446
459
  return;
447
460
  }
448
461
  if (parsed.storyboard.frames.length > 0) {
449
462
  setStoryboardCues(parsed.storyboard.frames);
450
463
  return;
451
464
  }
465
+ const resolutions = parsed.storyboard.resolutions;
466
+ if (resolutions && Object.keys(resolutions).length > 0) {
467
+ storyboardCacheRef.current.clear();
468
+ preloadedSpritesRef.current.clear();
469
+ const RES_ORDER = ["180p", "240p", "360p", "480p", "default"];
470
+ const keys = Object.keys(resolutions).sort((a, b) => {
471
+ const ai = RES_ORDER.indexOf(a);
472
+ const bi = RES_ORDER.indexOf(b);
473
+ return (ai === -1 ? 99 : ai) - (bi === -1 ? 99 : bi);
474
+ });
475
+ const loadRes = async (key) => {
476
+ if (cancelled) return;
477
+ const res = resolutions[key];
478
+ if (!res) return;
479
+ let cues = [];
480
+ if (res.vttText) {
481
+ cues = parseStoryboardVtt(res.vttText, res.spriteUrl);
482
+ } else {
483
+ cues = [{ start: 0, end: Number.MAX_SAFE_INTEGER, image: res.spriteUrl, w: res.tileW, h: res.tileH }];
484
+ }
485
+ storyboardCacheRef.current.set(key, cues);
486
+ if (!preloadedSpritesRef.current.has(res.spriteUrl)) {
487
+ preloadedSpritesRef.current.add(res.spriteUrl);
488
+ const img = new window.Image();
489
+ img.src = res.spriteUrl;
490
+ }
491
+ return cues;
492
+ };
493
+ const firstKey = keys[0];
494
+ const firstCues = await loadRes(firstKey);
495
+ if (!cancelled && firstCues) {
496
+ setStoryboardCues(firstCues);
497
+ setActiveStoryboardRes(firstKey);
498
+ }
499
+ for (const key of keys.slice(1)) {
500
+ await loadRes(key);
501
+ }
502
+ return;
503
+ }
452
504
  if (!parsed.storyboard.src) {
453
505
  setStoryboardCues([]);
454
506
  return;
455
507
  }
456
508
  try {
457
509
  const response = await fetch(parsed.storyboard.src);
458
- if (!response.ok) {
459
- throw new Error("Storyboard not found");
460
- }
510
+ if (!response.ok) throw new Error("Storyboard not found");
461
511
  const text = await response.text();
462
- const cues = parseStoryboardVtt(
463
- text,
464
- new URL(parsed.storyboard.src, window.location.href).href
465
- );
466
- if (!cancelled) {
467
- setStoryboardCues(cues);
468
- }
512
+ const cues = parseStoryboardVtt(text, new URL(parsed.storyboard.src, window.location.href).href);
513
+ if (!cancelled) setStoryboardCues(cues);
469
514
  } catch {
470
515
  if (!cancelled && parsed.storyboard.fallbackImage) {
471
- setStoryboardCues([
472
- {
473
- start: 0,
474
- end: Number.MAX_SAFE_INTEGER,
475
- image: parsed.storyboard.fallbackImage,
476
- w: parsed.storyboard.width ?? 160,
477
- h: parsed.storyboard.height ?? 90
478
- }
479
- ]);
516
+ setStoryboardCues([{
517
+ start: 0,
518
+ end: Number.MAX_SAFE_INTEGER,
519
+ image: parsed.storyboard.fallbackImage,
520
+ w: parsed.storyboard.width ?? 160,
521
+ h: parsed.storyboard.height ?? 90
522
+ }]);
480
523
  }
481
524
  }
482
525
  }
@@ -787,6 +830,63 @@ function Video({
787
830
  },
788
831
  [duration]
789
832
  );
833
+ const upgradeStoryboardResolution = useCallback((hoverTime) => {
834
+ const resolutions = parsed.storyboard?.resolutions;
835
+ if (!resolutions || storyboardCacheRef.current.size === 0) return;
836
+ const RES_ORDER = ["480p", "360p", "240p", "180p", "default"];
837
+ const conn = typeof navigator !== "undefined" ? navigator.connection : null;
838
+ const maxRes = conn?.effectiveType === "2g" ? "180p" : conn?.effectiveType === "3g" ? "240p" : "480p";
839
+ const maxIdx = RES_ORDER.indexOf(maxRes);
840
+ for (let i = 0; i <= maxIdx; i++) {
841
+ const key = RES_ORDER[i];
842
+ if (storyboardCacheRef.current.has(key)) {
843
+ if (activeStoryboardRes !== key) {
844
+ setStoryboardCues(storyboardCacheRef.current.get(key));
845
+ setActiveStoryboardRes(key);
846
+ const spriteUrl = resolutions[key]?.spriteUrl;
847
+ if (spriteUrl && !preloadedSpritesRef.current.has(spriteUrl)) {
848
+ preloadedSpritesRef.current.add(spriteUrl);
849
+ const img = new window.Image();
850
+ img.src = spriteUrl;
851
+ }
852
+ }
853
+ break;
854
+ }
855
+ }
856
+ const bestKey = [...storyboardCacheRef.current.keys()].sort((a, b) => {
857
+ const order = ["480p", "360p", "240p", "180p", "default"];
858
+ return order.indexOf(a) - order.indexOf(b);
859
+ })[0];
860
+ if (bestKey) {
861
+ const cues = storyboardCacheRef.current.get(bestKey) ?? [];
862
+ const nearIdx = cues.findIndex((c) => c.start > hoverTime);
863
+ const window2 = cues.slice(Math.max(0, nearIdx - 2), nearIdx + 3);
864
+ for (const c of window2) {
865
+ if (!preloadedSpritesRef.current.has(c.image)) {
866
+ preloadedSpritesRef.current.add(c.image);
867
+ const img = new window.Image();
868
+ img.src = c.image;
869
+ }
870
+ }
871
+ }
872
+ }, [parsed.storyboard?.resolutions, activeStoryboardRes]);
873
+ useEffect(() => {
874
+ const resolutions = parsed.storyboard?.resolutions;
875
+ if (!resolutions || storyboardCacheRef.current.size === 0 || !duration) return;
876
+ const RES_ORDER = ["180p", "240p", "360p", "480p", "default"];
877
+ const lowestKey = RES_ORDER.find((k) => storyboardCacheRef.current.has(k));
878
+ if (!lowestKey) return;
879
+ const cues = storyboardCacheRef.current.get(lowestKey) ?? [];
880
+ const playheadIdx = cues.findIndex((c) => c.start > currentTime);
881
+ const nearby = cues.slice(Math.max(0, playheadIdx - 2), playheadIdx + 6);
882
+ for (const c of nearby) {
883
+ if (!preloadedSpritesRef.current.has(c.image)) {
884
+ preloadedSpritesRef.current.add(c.image);
885
+ const img = new window.Image();
886
+ img.src = c.image;
887
+ }
888
+ }
889
+ }, [Math.floor(currentTime / 10), parsed.storyboard?.resolutions, duration]);
790
890
  const updatePreview = useCallback(
791
891
  (clientX) => {
792
892
  const progress = progressRef.current;
@@ -797,6 +897,7 @@ function Video({
797
897
  const rect = progress.getBoundingClientRect();
798
898
  const x = Math.max(0, Math.min(clientX - rect.left, rect.width));
799
899
  const time = x / rect.width * duration;
900
+ upgradeStoryboardResolution(time);
800
901
  const cue = findStoryboardCue(storyboardCues, time);
801
902
  if (!cue) {
802
903
  setPreview(null);
@@ -808,7 +909,7 @@ function Video({
808
909
  left: Math.max(80, Math.min(x, rect.width - 80))
809
910
  });
810
911
  },
811
- [duration, storyboardCues]
912
+ [duration, storyboardCues, upgradeStoryboardResolution]
812
913
  );
813
914
  const handleProgressPointerMove = useCallback(
814
915
  (event) => {
@@ -822,8 +923,19 @@ function Video({
822
923
  }, []);
823
924
  const handleProgressPointerLeave = useCallback(() => {
824
925
  setIsHoveringProgress(false);
825
- if (!isDragging) setPreview(null);
826
- }, [isDragging]);
926
+ if (!isDragging) {
927
+ setPreview(null);
928
+ const resolutions = parsed.storyboard?.resolutions;
929
+ if (resolutions && storyboardCacheRef.current.size > 0) {
930
+ const RES_ORDER = ["180p", "240p", "360p", "480p", "default"];
931
+ const lowestKey = RES_ORDER.find((k) => storyboardCacheRef.current.has(k));
932
+ if (lowestKey && activeStoryboardRes !== lowestKey) {
933
+ setStoryboardCues(storyboardCacheRef.current.get(lowestKey));
934
+ setActiveStoryboardRes(lowestKey);
935
+ }
936
+ }
937
+ }
938
+ }, [isDragging, parsed.storyboard?.resolutions, activeStoryboardRes]);
827
939
  const handleProgressPointerDown = useCallback(
828
940
  (event) => {
829
941
  const progress = progressRef.current;
@@ -1972,7 +2084,8 @@ function parseVideoChildren(children) {
1972
2084
  }
1973
2085
  if (child.type === VideoThumbnail || name === "SiloVideoThumbnail") {
1974
2086
  const element = child;
1975
- parsed.thumbnailSrc = element.props.src;
2087
+ if (element.props.urls) parsed.thumbnailUrls = element.props.urls;
2088
+ if (element.props.src) parsed.thumbnailSrc = element.props.src;
1976
2089
  }
1977
2090
  if (child.type === AgeRating || name === "SiloAgeRating") {
1978
2091
  const element = child;
@@ -1995,15 +2108,10 @@ function parseVideoChildren(children) {
1995
2108
  });
1996
2109
  parsed.storyboard = {
1997
2110
  ...element.props.src !== void 0 && { src: element.props.src },
1998
- ...element.props.fallbackImage !== void 0 && {
1999
- fallbackImage: element.props.fallbackImage
2000
- },
2001
- ...element.props.width !== void 0 && {
2002
- width: element.props.width
2003
- },
2004
- ...element.props.height !== void 0 && {
2005
- height: element.props.height
2006
- },
2111
+ ...element.props.fallbackImage !== void 0 && { fallbackImage: element.props.fallbackImage },
2112
+ ...element.props.width !== void 0 && { width: element.props.width },
2113
+ ...element.props.height !== void 0 && { height: element.props.height },
2114
+ ...element.props.resolutions !== void 0 && { resolutions: element.props.resolutions },
2007
2115
  frames
2008
2116
  };
2009
2117
  }