@geekapps/silo-elements-nextjs 0.1.21 → 0.1.22

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.
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import { useMultipartUpload, useBatchUpload, useSignedUrl, useFileStatus } from
3
3
  export { SiloProvider, useBatchUpload, useFileStatus, useMultipartUpload, useSignedUrl, useSiloClient } from '@geekapps/silo-nextjs';
4
4
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
5
5
  import gsap from 'gsap';
6
- import { Play, Pause, FastForward, VolumeX, Volume2, AudioLines, Captions, Settings, Minimize, Maximize } from 'lucide-react';
6
+ import { Play, Rewind, Pause, FastForward, VolumeX, Volume2, Settings, Minimize, Maximize } from 'lucide-react';
7
7
 
8
8
  // src/ImageUploader.tsx
9
9
 
@@ -1208,6 +1208,7 @@ var AUTO_QUALITY = {
1208
1208
  label: "Auto",
1209
1209
  type: "auto"
1210
1210
  };
1211
+ var PLAYBACK_SPEEDS = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 2];
1211
1212
  function Sources(_props) {
1212
1213
  return null;
1213
1214
  }
@@ -1258,7 +1259,9 @@ function Video({
1258
1259
  const [selectedQuality, setSelectedQuality] = useState("auto");
1259
1260
  const [audioTracks, setAudioTracks] = useState([]);
1260
1261
  const [selectedAudio, setSelectedAudio] = useState(0);
1261
- const [openMenu, setOpenMenu] = useState(null);
1262
+ const [settingsOpen, setSettingsOpen] = useState(false);
1263
+ const [settingsTab, setSettingsTab] = useState("quality");
1264
+ const [playbackRate, setPlaybackRate] = useState(1);
1262
1265
  const [subtitleMode, setSubtitleMode] = useState(initialSubtitleMode);
1263
1266
  const [activeCue, setActiveCue] = useState(null);
1264
1267
  const [storyboardCues, setStoryboardCues] = useState(
@@ -1270,6 +1273,8 @@ function Video({
1270
1273
  const [bufferedTime, setBufferedTime] = useState(0);
1271
1274
  const [isPlaying, setIsPlaying] = useState(false);
1272
1275
  const [hasPlayed, setHasPlayed] = useState(false);
1276
+ const [clickIcon, setClickIcon] = useState(null);
1277
+ const clickIconTimerRef = useRef(null);
1273
1278
  const [isLoading, setIsLoading] = useState(true);
1274
1279
  const [controlsVisible, setControlsVisible] = useState(true);
1275
1280
  const [volume, setVolume] = useState(defaultVolume);
@@ -1293,6 +1298,7 @@ function Video({
1293
1298
  const video = videoRef.current;
1294
1299
  if (!video) return;
1295
1300
  Array.from(video.textTracks).forEach((track) => {
1301
+ if (track.kind === "metadata") return;
1296
1302
  track.mode = mode !== "off" && track.language === mode ? "hidden" : "disabled";
1297
1303
  });
1298
1304
  if (mode === "off") setActiveCue(null);
@@ -1395,6 +1401,11 @@ function Video({
1395
1401
  applySubtitleMode(subtitleMode);
1396
1402
  if (subtitleMode === "off") setActiveCue(null);
1397
1403
  }, [subtitleMode, applySubtitleMode]);
1404
+ useEffect(() => {
1405
+ const video = videoRef.current;
1406
+ if (!video) return;
1407
+ video.playbackRate = playbackRate;
1408
+ }, [playbackRate]);
1398
1409
  const subtitleModeRef = useRef(subtitleMode);
1399
1410
  useEffect(() => {
1400
1411
  subtitleModeRef.current = subtitleMode;
@@ -1408,7 +1419,7 @@ function Video({
1408
1419
  setActiveCue(null);
1409
1420
  return;
1410
1421
  }
1411
- const track = Array.from(video.textTracks).find((t) => t.language === mode);
1422
+ const track = Array.from(video.textTracks).find((t) => t.language === mode && t.kind !== "metadata");
1412
1423
  if (!track || !track.activeCues || track.activeCues.length === 0) {
1413
1424
  setActiveCue(null);
1414
1425
  return;
@@ -1418,6 +1429,7 @@ function Video({
1418
1429
  };
1419
1430
  const bindTracks = () => {
1420
1431
  Array.from(video.textTracks).forEach((t) => {
1432
+ if (t.kind === "metadata") return;
1421
1433
  t.removeEventListener("cuechange", onCueChange);
1422
1434
  t.addEventListener("cuechange", onCueChange);
1423
1435
  });
@@ -1511,7 +1523,7 @@ function Video({
1511
1523
  setQualities([AUTO_QUALITY]);
1512
1524
  setAudioTracks([]);
1513
1525
  setSelectedAudio(0);
1514
- setOpenMenu(null);
1526
+ setSettingsOpen(false);
1515
1527
  video.pause();
1516
1528
  video.removeAttribute("src");
1517
1529
  video.load();
@@ -1653,11 +1665,15 @@ function Video({
1653
1665
  const video = videoRef.current;
1654
1666
  if (!video) return;
1655
1667
  try {
1656
- if (video.paused) {
1668
+ const wasPaused = video.paused;
1669
+ if (wasPaused) {
1657
1670
  await video.play();
1658
1671
  } else {
1659
1672
  video.pause();
1660
1673
  }
1674
+ if (clickIconTimerRef.current) window.clearTimeout(clickIconTimerRef.current);
1675
+ setClickIcon(wasPaused ? "play" : "pause");
1676
+ clickIconTimerRef.current = window.setTimeout(() => setClickIcon(null), 600);
1661
1677
  } catch {
1662
1678
  setError("O navegador bloqueou a reprodu\xE7\xE3o autom\xE1tica.");
1663
1679
  }
@@ -1716,7 +1732,7 @@ function Video({
1716
1732
  }, []);
1717
1733
  const changeAudio = useCallback((trackId) => {
1718
1734
  setSelectedAudio(trackId);
1719
- setOpenMenu(null);
1735
+ setSettingsOpen(false);
1720
1736
  if (hlsRef.current) {
1721
1737
  hlsRef.current.audioTrack = trackId;
1722
1738
  }
@@ -1726,7 +1742,7 @@ function Video({
1726
1742
  const option = qualities.find((quality) => quality.id === qualityId);
1727
1743
  if (!option) return;
1728
1744
  setSelectedQuality(qualityId);
1729
- setOpenMenu(null);
1745
+ setSettingsOpen(false);
1730
1746
  if (option.type === "auto") {
1731
1747
  if (hlsRef.current) {
1732
1748
  hlsRef.current.currentLevel = -1;
@@ -1856,7 +1872,7 @@ function Video({
1856
1872
  className: "relative w-full overflow-hidden rounded-[14px] bg-black shadow-[0_30px_90px_rgba(15,15,15,0.22)] outline-none ring-1 ring-black/5",
1857
1873
  style: maxHeight ? { maxHeight: typeof maxHeight === "number" ? `${maxHeight}px` : maxHeight, aspectRatio: "16/9" } : { aspectRatio: "16/9" },
1858
1874
  children: [
1859
- /* @__PURE__ */ jsx(
1875
+ /* @__PURE__ */ jsxs(
1860
1876
  "video",
1861
1877
  {
1862
1878
  ref: videoRef,
@@ -1864,17 +1880,27 @@ function Video({
1864
1880
  playsInline: true,
1865
1881
  preload: "metadata",
1866
1882
  crossOrigin: "anonymous",
1867
- children: parsed.subtitles.map((subtitle) => /* @__PURE__ */ jsx(
1868
- "track",
1869
- {
1870
- kind: "subtitles",
1871
- src: subtitle.src,
1872
- srcLang: subtitle.srclang,
1873
- label: subtitle.label,
1874
- default: subtitle.default
1875
- },
1876
- `${activeSource.src}-${subtitle.srclang}`
1877
- ))
1883
+ children: [
1884
+ parsed.subtitles.map((subtitle) => /* @__PURE__ */ jsx(
1885
+ "track",
1886
+ {
1887
+ kind: "subtitles",
1888
+ src: subtitle.src,
1889
+ srcLang: subtitle.srclang,
1890
+ label: subtitle.label,
1891
+ default: subtitle.default
1892
+ },
1893
+ `${activeSource.src}-${subtitle.srclang}`
1894
+ )),
1895
+ parsed.storyboard?.src && /* @__PURE__ */ jsx(
1896
+ "track",
1897
+ {
1898
+ kind: "metadata",
1899
+ label: "thumbnails",
1900
+ src: parsed.storyboard.src
1901
+ }
1902
+ )
1903
+ ]
1878
1904
  }
1879
1905
  ),
1880
1906
  poster && !hasPlayed && /* @__PURE__ */ jsx(
@@ -1882,7 +1908,7 @@ function Video({
1882
1908
  {
1883
1909
  src: poster,
1884
1910
  "aria-hidden": true,
1885
- className: "pointer-events-none absolute inset-0 h-full w-full object-cover"
1911
+ className: "pointer-events-none absolute inset-0 h-full w-full object-contain bg-black"
1886
1912
  }
1887
1913
  ),
1888
1914
  /* @__PURE__ */ jsx(
@@ -1920,6 +1946,81 @@ function Video({
1920
1946
  )
1921
1947
  ] }),
1922
1948
  /* @__PURE__ */ jsxs("footer", { onClick: (e) => e.stopPropagation(), className: "relative z-10 px-3 pb-3 text-white @sm:px-5 @sm:pb-5 @lg:px-9 @lg:pb-8", children: [
1949
+ settingsOpen && /* @__PURE__ */ jsxs(Fragment, { children: [
1950
+ /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-40", onClick: () => setSettingsOpen(false) }),
1951
+ /* @__PURE__ */ jsxs("div", { className: "absolute bottom-full left-0 right-0 z-50 mb-2 mx-0 overflow-hidden rounded-xl border border-white/10 bg-black/90 shadow-2xl backdrop-blur-xl", children: [
1952
+ /* @__PURE__ */ jsx("div", { className: "flex border-b border-white/10", children: ["quality", "subtitles", ...audioTracks.length > 1 ? ["audio"] : [], "playback"].map((tab) => /* @__PURE__ */ jsx(
1953
+ "button",
1954
+ {
1955
+ type: "button",
1956
+ onClick: () => setSettingsTab(tab),
1957
+ className: `flex-1 px-3 py-2.5 text-xs font-semibold capitalize transition ${settingsTab === tab ? "text-white border-b-2 border-white -mb-px" : "text-white/50 hover:text-white/80"}`,
1958
+ children: tab
1959
+ },
1960
+ tab
1961
+ )) }),
1962
+ /* @__PURE__ */ jsxs("div", { className: "max-h-48 overflow-y-auto py-1", children: [
1963
+ settingsTab === "quality" && /* @__PURE__ */ jsx(Fragment, { children: [...qualities].reverse().map((quality) => /* @__PURE__ */ jsxs(
1964
+ SettingsItem,
1965
+ {
1966
+ active: selectedQuality === quality.id,
1967
+ onClick: () => changeQuality(quality.id),
1968
+ children: [
1969
+ quality.label,
1970
+ quality.id === "auto" && /* @__PURE__ */ jsx("span", { className: "ml-1 text-[10px] text-white/40", children: "ABR" })
1971
+ ]
1972
+ },
1973
+ quality.id
1974
+ )) }),
1975
+ settingsTab === "subtitles" && /* @__PURE__ */ jsxs(Fragment, { children: [
1976
+ /* @__PURE__ */ jsx(
1977
+ SettingsItem,
1978
+ {
1979
+ active: subtitleMode === "off",
1980
+ onClick: () => {
1981
+ setSubtitleMode("off");
1982
+ setSettingsOpen(false);
1983
+ },
1984
+ children: "Off"
1985
+ }
1986
+ ),
1987
+ parsed.subtitles.map((subtitle) => /* @__PURE__ */ jsx(
1988
+ SettingsItem,
1989
+ {
1990
+ active: subtitleMode === subtitle.srclang,
1991
+ onClick: () => {
1992
+ setSubtitleMode(subtitle.srclang);
1993
+ setSettingsOpen(false);
1994
+ },
1995
+ children: subtitle.label
1996
+ },
1997
+ subtitle.srclang
1998
+ ))
1999
+ ] }),
2000
+ settingsTab === "audio" && audioTracks.length > 1 && /* @__PURE__ */ jsx(Fragment, { children: audioTracks.map((track) => /* @__PURE__ */ jsx(
2001
+ SettingsItem,
2002
+ {
2003
+ active: selectedAudio === track.id,
2004
+ onClick: () => changeAudio(track.id),
2005
+ children: track.label
2006
+ },
2007
+ track.id
2008
+ )) }),
2009
+ settingsTab === "playback" && /* @__PURE__ */ jsx(Fragment, { children: PLAYBACK_SPEEDS.map((speed) => /* @__PURE__ */ jsx(
2010
+ SettingsItem,
2011
+ {
2012
+ active: playbackRate === speed,
2013
+ onClick: () => {
2014
+ setPlaybackRate(speed);
2015
+ setSettingsOpen(false);
2016
+ },
2017
+ children: speed === 1 ? "Normal" : `${speed}x`
2018
+ },
2019
+ speed
2020
+ )) })
2021
+ ] })
2022
+ ] })
2023
+ ] }),
1923
2024
  /* @__PURE__ */ jsxs(
1924
2025
  "div",
1925
2026
  {
@@ -1974,6 +2075,16 @@ function Video({
1974
2075
  ),
1975
2076
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 @sm:gap-4 @lg:gap-5", children: [
1976
2077
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 @sm:gap-4 @lg:gap-5", children: [
2078
+ /* @__PURE__ */ jsx(
2079
+ "button",
2080
+ {
2081
+ type: "button",
2082
+ onClick: () => seekRelative(-10),
2083
+ className: "grid size-6 place-items-center text-white transition hover:scale-105 hover:text-white/80 @sm:size-8",
2084
+ "aria-label": "Rewind 10 seconds",
2085
+ children: /* @__PURE__ */ jsx(Rewind, { className: "size-5 @sm:size-7" })
2086
+ }
2087
+ ),
1977
2088
  /* @__PURE__ */ jsx(
1978
2089
  "button",
1979
2090
  {
@@ -1989,7 +2100,7 @@ function Video({
1989
2100
  {
1990
2101
  type: "button",
1991
2102
  onClick: () => seekRelative(10),
1992
- className: "hidden size-6 place-items-center text-white transition hover:scale-105 hover:text-white/80 @sm:grid @sm:size-8",
2103
+ className: "grid size-6 place-items-center text-white transition hover:scale-105 hover:text-white/80 @sm:size-8",
1993
2104
  "aria-label": "Forward 10 seconds",
1994
2105
  children: /* @__PURE__ */ jsx(FastForward, { className: "size-5 @sm:size-7" })
1995
2106
  }
@@ -2029,91 +2140,19 @@ function Video({
2029
2140
  }
2030
2141
  ),
2031
2142
  /* @__PURE__ */ jsx("div", { className: "mx-0.5 hidden h-4 w-px bg-white/20 @md:mx-1 @md:block" }),
2032
- audioTracks.length > 1 && /* @__PURE__ */ jsxs("div", { className: "relative", children: [
2033
- /* @__PURE__ */ jsx(
2034
- "button",
2035
- {
2036
- type: "button",
2037
- onClick: () => setOpenMenu(openMenu === "audio" ? null : "audio"),
2038
- className: `grid size-6 place-items-center rounded transition hover:text-white/80 @sm:size-8 ${openMenu === "audio" ? "text-white" : "text-white/60"}`,
2039
- "aria-label": "Audio track",
2040
- children: /* @__PURE__ */ jsx(AudioLines, { className: "size-4 @sm:size-5" })
2041
- }
2042
- ),
2043
- openMenu === "audio" && /* @__PURE__ */ jsx(PopoverMenu, { onClose: () => setOpenMenu(null), children: audioTracks.map((track) => /* @__PURE__ */ jsx(
2044
- PopoverItem,
2045
- {
2046
- active: selectedAudio === track.id,
2047
- onClick: () => changeAudio(track.id),
2048
- children: track.label
2049
- },
2050
- track.id
2051
- )) })
2052
- ] }),
2053
- parsed.subtitles.length > 0 && /* @__PURE__ */ jsxs("div", { className: "relative", children: [
2054
- /* @__PURE__ */ jsx(
2055
- "button",
2056
- {
2057
- type: "button",
2058
- onClick: () => setOpenMenu(openMenu === "captions" ? null : "captions"),
2059
- className: `grid size-6 place-items-center rounded transition hover:text-white/80 @sm:size-8 ${subtitleMode !== "off" || openMenu === "captions" ? "text-white" : "text-white/60"}`,
2060
- "aria-label": "Captions",
2061
- children: /* @__PURE__ */ jsx(Captions, { className: "size-4 @sm:size-5" })
2062
- }
2063
- ),
2064
- openMenu === "captions" && /* @__PURE__ */ jsxs(PopoverMenu, { onClose: () => setOpenMenu(null), children: [
2065
- /* @__PURE__ */ jsx(
2066
- PopoverItem,
2067
- {
2068
- active: subtitleMode === "off",
2069
- onClick: () => {
2070
- setSubtitleMode("off");
2071
- setOpenMenu(null);
2072
- },
2073
- children: "Off"
2074
- }
2075
- ),
2076
- parsed.subtitles.map((subtitle) => /* @__PURE__ */ jsx(
2077
- PopoverItem,
2078
- {
2079
- active: subtitleMode === subtitle.srclang,
2080
- onClick: () => {
2081
- setSubtitleMode(subtitle.srclang);
2082
- setOpenMenu(null);
2083
- },
2084
- children: subtitle.label
2085
- },
2086
- subtitle.srclang
2087
- ))
2088
- ] })
2089
- ] }),
2090
- /* @__PURE__ */ jsxs("div", { className: "relative", children: [
2091
- /* @__PURE__ */ jsxs(
2092
- "button",
2093
- {
2094
- type: "button",
2095
- onClick: () => setOpenMenu(openMenu === "quality" ? null : "quality"),
2096
- className: `flex h-6 items-center gap-1 rounded px-1.5 text-xs font-semibold transition hover:text-white/80 @sm:h-8 @sm:px-2 ${openMenu === "quality" ? "text-white" : "text-white/60"}`,
2097
- "aria-label": "Quality",
2098
- children: [
2099
- /* @__PURE__ */ jsx(Settings, { className: "size-3.5 @sm:size-4" }),
2100
- /* @__PURE__ */ jsx("span", { className: "hidden @sm:inline", children: qualities.find((q) => q.id === selectedQuality)?.label ?? "Auto" })
2101
- ]
2102
- }
2103
- ),
2104
- openMenu === "quality" && /* @__PURE__ */ jsx(PopoverMenu, { onClose: () => setOpenMenu(null), children: [...qualities].reverse().map((quality) => /* @__PURE__ */ jsxs(
2105
- PopoverItem,
2106
- {
2107
- active: selectedQuality === quality.id,
2108
- onClick: () => changeQuality(quality.id),
2109
- children: [
2110
- quality.label,
2111
- quality.id === "auto" && /* @__PURE__ */ jsx("span", { className: "ml-1 text-[10px] text-white/40", children: "ABR" })
2112
- ]
2113
- },
2114
- quality.id
2115
- )) })
2116
- ] }),
2143
+ /* @__PURE__ */ jsx("div", { className: "relative", children: /* @__PURE__ */ jsxs(
2144
+ "button",
2145
+ {
2146
+ type: "button",
2147
+ onClick: () => setSettingsOpen((v) => !v),
2148
+ className: `flex h-6 items-center gap-1 rounded px-1.5 text-xs font-semibold transition hover:text-white/80 @sm:h-8 @sm:px-2 ${settingsOpen ? "text-white" : "text-white/60"}`,
2149
+ "aria-label": "Settings",
2150
+ children: [
2151
+ /* @__PURE__ */ jsx(Settings, { className: "size-3.5 @sm:size-4" }),
2152
+ /* @__PURE__ */ jsx("span", { className: "hidden @sm:inline", children: qualities.find((q) => q.id === selectedQuality)?.label ?? "Auto" })
2153
+ ]
2154
+ }
2155
+ ) }),
2117
2156
  /* @__PURE__ */ jsx(
2118
2157
  "button",
2119
2158
  {
@@ -2130,6 +2169,14 @@ function Video({
2130
2169
  ]
2131
2170
  }
2132
2171
  ),
2172
+ /* @__PURE__ */ jsx(
2173
+ "div",
2174
+ {
2175
+ className: "pointer-events-none absolute inset-0 z-50 grid place-items-center",
2176
+ style: { opacity: clickIcon ? 1 : 0, transition: clickIcon ? "opacity 0.08s ease-in" : "opacity 0.45s ease-out" },
2177
+ children: /* @__PURE__ */ jsx("span", { className: "grid size-14 place-items-center rounded-full bg-black/40 text-white backdrop-blur-sm ring-1 ring-white/20 @sm:size-18 @lg:size-22", children: clickIcon === "pause" ? /* @__PURE__ */ jsx(Pause, { className: "size-6 @sm:size-8 @lg:size-10" }) : /* @__PURE__ */ jsx(Play, { className: "ml-1 size-6 @sm:size-8 @lg:size-10" }) })
2178
+ }
2179
+ ),
2133
2180
  activeCue && /* @__PURE__ */ jsx(
2134
2181
  "div",
2135
2182
  {
@@ -2151,13 +2198,7 @@ function Video({
2151
2198
  );
2152
2199
  }
2153
2200
  var VideoPlayer = Video;
2154
- function PopoverMenu({ children, onClose }) {
2155
- return /* @__PURE__ */ jsxs(Fragment, { children: [
2156
- /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-40", onClick: onClose }),
2157
- /* @__PURE__ */ jsx("div", { className: "absolute bottom-full right-0 z-50 mb-2 min-w-35 overflow-hidden rounded-xl border border-white/10 bg-black/85 py-1 shadow-2xl backdrop-blur-xl", children })
2158
- ] });
2159
- }
2160
- function PopoverItem({
2201
+ function SettingsItem({
2161
2202
  children,
2162
2203
  active,
2163
2204
  onClick