@geekapps/silo-elements-nextjs 0.3.7 → 0.3.8

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
@@ -2,6 +2,7 @@ import React, { useState, useRef, useCallback, useMemo, useEffect } from 'react'
2
2
  import { useMultipartUpload, useBatchUpload, useSignedUrl, useFileStatus } from '@geekapps/silo-nextjs';
3
3
  export { SiloProvider, useBatchUpload, useFileStatus, useMultipartUpload, useSignedUrl } from '@geekapps/silo-nextjs';
4
4
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
5
+ import MediaInfoFactory from 'mediainfo.js';
5
6
  import gsap from 'gsap';
6
7
  import { Pause, Play, Captions as Captions$1, Settings, Minimize, Maximize, VolumeX, Volume2 } from 'lucide-react';
7
8
 
@@ -163,7 +164,7 @@ var FORMATS = [
163
164
  { value: "png", label: "PNG", hint: "Sem perda" }
164
165
  ];
165
166
  function ImageOptions({ value, onChange, style }) {
166
- const fmt = value.format ?? "webp";
167
+ const fmt2 = value.format ?? "webp";
167
168
  const optimize = value.optimize ?? true;
168
169
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2.5", style, children: [
169
170
  /* @__PURE__ */ jsxs("div", { children: [
@@ -174,7 +175,7 @@ function ImageOptions({ value, onChange, style }) {
174
175
  type: "button",
175
176
  onClick: () => onChange({ ...value, format: f.value }),
176
177
  title: f.hint,
177
- style: fmt === f.value ? { border: "1px solid #6366f1", background: "#6366f1", color: "#fff", padding: "4px 12px", borderRadius: 6, fontSize: 12, fontWeight: 600, cursor: "pointer" } : { border: "1px solid rgba(255,255,255,0.2)", background: "rgba(255,255,255,0.06)", color: "rgba(255,255,255,0.8)", padding: "4px 12px", borderRadius: 6, fontSize: 12, fontWeight: 600, cursor: "pointer" },
178
+ style: fmt2 === f.value ? { border: "1px solid #6366f1", background: "#6366f1", color: "#fff", padding: "4px 12px", borderRadius: 6, fontSize: 12, fontWeight: 600, cursor: "pointer" } : { border: "1px solid rgba(255,255,255,0.2)", background: "rgba(255,255,255,0.06)", color: "rgba(255,255,255,0.8)", padding: "4px 12px", borderRadius: 6, fontSize: 12, fontWeight: 600, cursor: "pointer" },
178
179
  children: f.label
179
180
  },
180
181
  f.value
@@ -698,6 +699,125 @@ function VideoOptions({ value, onChange, style }) {
698
699
  )
699
700
  ] });
700
701
  }
702
+ var CODEC_MESSAGES = {
703
+ AV1: { title: "AV1 n\xE3o \xE9 suportado como entrada", detail: "O pipeline de transcodifica\xE7\xE3o n\xE3o aceita arquivos AV1. Converta para H.264 ou H.265 antes de enviar." },
704
+ VP9: { title: "VP9 n\xE3o \xE9 suportado como entrada", detail: "Arquivos WebM/VP9 n\xE3o s\xE3o aceitos. Converta para H.264 ou H.265 antes de enviar." },
705
+ VP8: { title: "VP8 n\xE3o \xE9 suportado como entrada", detail: "Arquivos WebM/VP8 n\xE3o s\xE3o aceitos. Converta para H.264 antes de enviar." },
706
+ "MPEG-1 Video": { title: "MPEG-1 n\xE3o \xE9 suportado", detail: "Formato muito antigo. Converta para H.264 antes de enviar." },
707
+ "MPEG-2 Video": { title: "MPEG-2 n\xE3o \xE9 suportado", detail: "Converta para H.264 antes de enviar." }
708
+ };
709
+ var CONTAINER_MESSAGES = {
710
+ FLV: { title: "Formato FLV n\xE3o suportado", detail: "Arquivos Flash Video n\xE3o s\xE3o aceitos. Converta para MP4 (H.264) antes de enviar." },
711
+ "Flash Video": { title: "Formato FLV n\xE3o suportado", detail: "Arquivos Flash Video n\xE3o s\xE3o aceitos. Converta para MP4 (H.264) antes de enviar." },
712
+ RealMedia: { title: "Formato RealMedia n\xE3o suportado", detail: "Converta para MP4 (H.264) antes de enviar." },
713
+ ASF: { title: "Formato ASF/WMV n\xE3o suportado", detail: "Converta para MP4 (H.264) antes de enviar." }
714
+ };
715
+ function issueMessage(issue) {
716
+ if (issue.code === "unsupported_codec") {
717
+ return CODEC_MESSAGES[issue.codec] ?? { title: `Codec ${issue.codec} n\xE3o suportado`, detail: "Converta para H.264 ou H.265 antes de enviar." };
718
+ }
719
+ const key = Object.keys(CONTAINER_MESSAGES).find((k) => issue.container.includes(k));
720
+ return (key ? CONTAINER_MESSAGES[key] : null) ?? { title: `Container ${issue.container} n\xE3o suportado`, detail: "Converta para MP4 (H.264) antes de enviar." };
721
+ }
722
+ function fmt(n, unit) {
723
+ return `${n.toLocaleString("pt-BR")}${unit}`;
724
+ }
725
+ function fmtDuration(secs) {
726
+ const h = Math.floor(secs / 3600);
727
+ const m = Math.floor(secs % 3600 / 60);
728
+ const s = Math.floor(secs % 60);
729
+ if (h > 0) return `${h}h ${m}m ${s}s`;
730
+ if (m > 0) return `${m}m ${s}s`;
731
+ return `${s}s`;
732
+ }
733
+ function VideoCompatAlert({ info, issues, analyzing }) {
734
+ if (analyzing) {
735
+ return /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8, padding: "10px 14px", background: "rgba(99,102,241,0.06)", border: "1px solid rgba(99,102,241,0.18)", borderRadius: 10 }, children: [
736
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 14 }, children: "\u{1F50D}" }),
737
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 12, color: "rgba(165,180,252,0.9)" }, children: "Analisando arquivo\u2026" })
738
+ ] });
739
+ }
740
+ const v = info.video;
741
+ return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: [
742
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexWrap: "wrap", gap: 6 }, children: [
743
+ v && /* @__PURE__ */ jsxs(Fragment, { children: [
744
+ /* @__PURE__ */ jsx(Chip, { label: v.codec || "?", color: "neutral" }),
745
+ v.width > 0 && v.height > 0 && /* @__PURE__ */ jsx(Chip, { label: `${v.width}\xD7${v.height}`, color: "neutral" }),
746
+ v.duration > 0 && /* @__PURE__ */ jsx(Chip, { label: fmtDuration(v.duration), color: "neutral" }),
747
+ v.frameRate > 0 && /* @__PURE__ */ jsx(Chip, { label: fmt(Math.round(v.frameRate), " fps"), color: "neutral" }),
748
+ v.hdr && /* @__PURE__ */ jsx(Chip, { label: "HDR", color: "purple" })
749
+ ] }),
750
+ info.audio && /* @__PURE__ */ jsx(Chip, { label: info.audio.codec || "sem \xE1udio", color: "neutral" }),
751
+ /* @__PURE__ */ jsx(Chip, { label: formatBytes(info.fileSize), color: "neutral" })
752
+ ] }),
753
+ issues.map((issue, i) => {
754
+ const msg = issueMessage(issue);
755
+ return /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8, padding: "10px 14px", background: "rgba(239,68,68,0.07)", border: "1px solid rgba(239,68,68,0.22)", borderRadius: 10 }, children: [
756
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 14, flexShrink: 0 }, children: "\u{1F6AB}" }),
757
+ /* @__PURE__ */ jsxs("div", { children: [
758
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 12, fontWeight: 700, color: "#f87171", marginBottom: 2 }, children: msg.title }),
759
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 11, color: "rgba(255,255,255,0.5)", lineHeight: 1.55 }, children: msg.detail })
760
+ ] })
761
+ ] }, i);
762
+ })
763
+ ] });
764
+ }
765
+ function Chip({ label, color }) {
766
+ const s = color === "purple" ? { background: "rgba(168,85,247,0.12)", border: "1px solid rgba(168,85,247,0.25)", color: "#c084fc" } : { background: "rgba(255,255,255,0.05)", border: "1px solid rgba(255,255,255,0.1)", color: "rgba(255,255,255,0.6)" };
767
+ return /* @__PURE__ */ jsx("span", { style: { ...s, fontSize: 11, fontWeight: 600, borderRadius: 6, padding: "2px 8px", display: "inline-block" }, children: label });
768
+ }
769
+ var UNSUPPORTED_CODECS = ["AV1", "VP8", "VP9", "MPEG-1 Video", "MPEG-2 Video", "Theora", "RealVideo"];
770
+ var UNSUPPORTED_CONTAINERS = ["FLV", "Flash Video", "RealMedia", "ASF"];
771
+ function getCompatIssues(info) {
772
+ const issues = [];
773
+ if (info.video && UNSUPPORTED_CODECS.includes(info.video.codec)) {
774
+ issues.push({ code: "unsupported_codec", codec: info.video.codec });
775
+ }
776
+ if (UNSUPPORTED_CONTAINERS.some((c) => info.container.includes(c))) {
777
+ issues.push({ code: "unsupported_container", container: info.container });
778
+ }
779
+ return issues;
780
+ }
781
+ async function analyzeVideoFile(file) {
782
+ const wasmUrl = `https://cdn.jsdelivr.net/npm/mediainfo.js@0.3.7/dist/MediaInfoModule.wasm`;
783
+ const mediainfo = await MediaInfoFactory({ format: "object", locateFile: () => wasmUrl });
784
+ const getSize = () => file.size;
785
+ const readChunk = (chunkSize, offset) => new Promise((resolve, reject) => {
786
+ const reader = new FileReader();
787
+ reader.onload = (e) => resolve(new Uint8Array(e.target.result));
788
+ reader.onerror = reject;
789
+ reader.readAsArrayBuffer(file.slice(offset, offset + chunkSize));
790
+ });
791
+ const result = await mediainfo.analyzeData(getSize, readChunk);
792
+ mediainfo.close();
793
+ const tracks = result.media?.track ?? [];
794
+ const general = tracks.find((t) => t["@type"] === "General");
795
+ const video = tracks.find((t) => t["@type"] === "Video");
796
+ const audio = tracks.find((t) => t["@type"] === "Audio");
797
+ const str = (v) => typeof v === "string" ? v : "";
798
+ const num = (v) => parseFloat(str(v)) || 0;
799
+ const hdrFormats = str(video?.["HDR_Format"] ?? video?.["transfer_characteristics"]);
800
+ const isHdr = hdrFormats.length > 0 && !hdrFormats.toLowerCase().includes("bt.709") && !hdrFormats.toLowerCase().includes("sdr");
801
+ return {
802
+ container: str(general?.["Format"]) || file.type || "Unknown",
803
+ fileSize: file.size,
804
+ video: video ? {
805
+ codec: str(video["Format"]),
806
+ width: num(video["Width"]),
807
+ height: num(video["Height"]),
808
+ duration: num(video["Duration"]) / 1e3,
809
+ bitrate: num(video["BitRate"]),
810
+ frameRate: num(video["FrameRate"]),
811
+ hdr: isHdr,
812
+ colorSpace: str(video["colour_primaries"]) || str(video["ColorSpace"])
813
+ } : null,
814
+ audio: audio ? {
815
+ codec: str(audio["Format"]),
816
+ channels: num(audio["Channels"]),
817
+ sampleRate: num(audio["SamplingRate"])
818
+ } : null
819
+ };
820
+ }
701
821
  var DEFAULT_VIDEO_OPTS = {
702
822
  thumbnails: true,
703
823
  storyboard: false,
@@ -742,6 +862,9 @@ function VideoUploader({
742
862
  const [preview, setPreview] = useState(null);
743
863
  const [videoOpts, setVideoOpts] = useState(() => createInitialVideoOpts(video));
744
864
  const [stagedFile, setStagedFile] = useState(null);
865
+ const [videoInfo, setVideoInfo] = useState(null);
866
+ const [videoIssues, setVideoIssues] = useState([]);
867
+ const [analyzing, setAnalyzing] = useState(false);
745
868
  const t = resolveTheme(theme);
746
869
  const vars = themeToVars(t);
747
870
  const doUpload = useCallback(async (file, opts) => {
@@ -758,8 +881,19 @@ function VideoUploader({
758
881
  if (!file) return;
759
882
  if (showVideoOptions) {
760
883
  setVideoOpts(createInitialVideoOpts(video));
884
+ setVideoInfo(null);
885
+ setVideoIssues([]);
761
886
  setStagedFile(file);
762
887
  if (showPreview) setPreview(URL.createObjectURL(file));
888
+ setAnalyzing(true);
889
+ try {
890
+ const info = await analyzeVideoFile(file);
891
+ setVideoInfo(info);
892
+ setVideoIssues(getCompatIssues(info));
893
+ } catch {
894
+ } finally {
895
+ setAnalyzing(false);
896
+ }
763
897
  } else {
764
898
  await doUpload(file, video ?? videoOpts);
765
899
  }
@@ -802,7 +936,11 @@ function VideoUploader({
802
936
  ),
803
937
  showVideoOptions && stagedFile && !isUploading && state.status !== "done" && /* @__PURE__ */ jsxs(Fragment, { children: [
804
938
  preview && /* @__PURE__ */ jsx("div", { className: "border border-slate-200 rounded-xl overflow-hidden bg-black", children: /* @__PURE__ */ jsx("video", { src: preview, className: "w-full max-h-[200px] block", muted: true, playsInline: true, controls: true }) }),
805
- /* @__PURE__ */ jsxs("div", { className: "border border-slate-200 rounded-xl overflow-hidden", children: [
939
+ (analyzing || videoInfo) && /* @__PURE__ */ jsxs("div", { className: "border border-slate-200 rounded-xl overflow-hidden", children: [
940
+ /* @__PURE__ */ jsx("div", { className: "px-3.5 py-2 bg-slate-100 text-[12px] font-bold text-slate-500 tracking-[0.04em]", children: "\u{1F4CB} Informa\xE7\xF5es do arquivo" }),
941
+ /* @__PURE__ */ jsx("div", { className: "p-3.5", children: /* @__PURE__ */ jsx(VideoCompatAlert, { info: videoInfo, issues: videoIssues, analyzing }) })
942
+ ] }),
943
+ videoIssues.length === 0 && /* @__PURE__ */ jsxs("div", { className: "border border-slate-200 rounded-xl overflow-hidden", children: [
806
944
  /* @__PURE__ */ jsx("div", { className: "px-3.5 py-2 bg-slate-100 text-[12px] font-bold text-slate-500 tracking-[0.04em]", children: "\u{1F3AC} Configura\xE7\xF5es de v\xEDdeo" }),
807
945
  /* @__PURE__ */ jsx(VideoOptions, { value: videoOpts, onChange: setVideoOpts, style: { padding: "12px 14px" } })
808
946
  ] }),
@@ -814,6 +952,8 @@ function VideoUploader({
814
952
  onClick: () => {
815
953
  setStagedFile(null);
816
954
  setPreview(null);
955
+ setVideoInfo(null);
956
+ setVideoIssues([]);
817
957
  setVideoOpts(createInitialVideoOpts(video));
818
958
  },
819
959
  children: "Cancelar"
@@ -822,10 +962,13 @@ function VideoUploader({
822
962
  /* @__PURE__ */ jsx(
823
963
  "button",
824
964
  {
825
- className: "flex-1 inline-flex items-center justify-center gap-1.5 text-sm font-bold py-2.5 px-4 rounded-xl border-transparent bg-indigo-500 text-white cursor-pointer hover:opacity-90",
965
+ disabled: videoIssues.length > 0 || analyzing,
966
+ className: "flex-1 inline-flex items-center justify-center gap-1.5 text-sm font-bold py-2.5 px-4 rounded-xl border-transparent bg-indigo-500 text-white cursor-pointer hover:opacity-90 disabled:opacity-40 disabled:cursor-not-allowed",
826
967
  onClick: () => {
827
968
  const f = stagedFile;
828
969
  setStagedFile(null);
970
+ setVideoInfo(null);
971
+ setVideoIssues([]);
829
972
  void doUpload(f, videoOpts);
830
973
  setVideoOpts(createInitialVideoOpts(video));
831
974
  },
@@ -2168,15 +2311,23 @@ function Video({
2168
2311
  });
2169
2312
  let mediaErrorAttempts = 0;
2170
2313
  hls.on(Hls.Events.ERROR, (_, data) => {
2171
- console.debug("[Silo/hls] ERROR fatal=", data.fatal, "type=", data.type, "details=", data.details, "url=", (data.frag?.url ?? data.url ?? "").slice(-80));
2314
+ const fragUrl = (data.frag?.url ?? data.url ?? "").slice(-80);
2315
+ const fragType = data.frag?.type ?? "?";
2316
+ console.debug("[Silo/hls] ERROR fatal=", data.fatal, "type=", data.type, "details=", data.details, "fragType=", fragType, "url=", fragUrl);
2172
2317
  if (!data.fatal) return;
2318
+ if (data.details === Hls.ErrorDetails.FRAG_PARSING_ERROR && fragType === "audio") {
2319
+ console.warn("[Silo/hls] audio frag parse failed \u2014 disabling separate audio tracks and retrying");
2320
+ setAudioTracks([]);
2321
+ hls.recoverMediaError();
2322
+ return;
2323
+ }
2173
2324
  if (data.type === Hls.ErrorTypes.MEDIA_ERROR && mediaErrorAttempts < 2) {
2174
2325
  mediaErrorAttempts += 1;
2175
2326
  console.debug("[Silo/hls] recoverMediaError attempt", mediaErrorAttempts);
2176
2327
  hls.recoverMediaError();
2177
2328
  return;
2178
2329
  }
2179
- console.error("[Silo] HLS playback failed", data.details);
2330
+ console.error("[Silo] HLS playback failed", data.details, "fragType=", fragType, "url=", fragUrl);
2180
2331
  hls.destroy();
2181
2332
  hlsRef.current = null;
2182
2333
  setIsLoading(false);