@ai-matrx/media

中文文档
0.15.189发布于 2026/10/31MITTypeScriptESM + CJS近 30 天下载 8.7万

The AI Matrx media components kit: durable-ref-only renderers for images, video, audio and file thumbnails plus live TTS playback that just work with the Matrx file system — headless core hooks plus DOM bindings, wired to an injected MediaClient. A signed

原始使用文档
文档目录12 节
  1. The rules the types enforce
  2. Quick start
  3. Headless hooks ( /core )
  4. Components ( /react )
  5. Actions & sharing
  6. The complete share body ( /share )
  7. Diagnostics — a terminal failure is never silent
  8. File viewers ( /viewers )
  9. Next.js ( /next )
  10. Styling
  11. SSR / RSC
  12. License

@ai-matrx/media

Media components that just work with a durable file system. Renderers for images, video, audio and file thumbnails that take a durable reference — a file_id or a permanent public URL — and own the whole URL lifecycle: resolution, auth transport, load-error recovery, typed unavailable states, upload progress, and share/download/copy/open actions.

The package ships zero transport. Everything data-shaped arrives through one injected port, the MediaClient. That is the point: every media breakage we ever shipped came from code that hand-built a URL or an <img>; here the correct path is the only path the types allow.

@ai-matrx/media          types only — MediaRef, DurableSrc, MediaClient, host ports
@ai-matrx/media/core     headless React hooks (platform-free state machines)
@ai-matrx/media/react    DOM components (Tailwind classes, inlined SVG icons)
@ai-matrx/media/share    the complete share popover body (optional peer: @ai-matrx/associations)

The rules the types enforce

  1. The package never fetches a media URL itself. Bytes exist only via MediaClient.getBlob.
  2. Every component takes a ref, never a src. Even a caller-supplied URL string is classified by resolve(), never trusted.
  3. A signed URL is a handoff, never an identity. The only string a media element binds is the branded DurableSrc, which only a MediaClient implementation can mint. An expiring URL cannot pass the types without an unsafe cast.
  4. Share affordances fail closed. shareableUrl returns null rather than ever emitting a signed URL; unwired actions render disabled.
  5. One retry policy. Load errors route through MediaClient.recoverLoadError (session refresh → same-URL retry → terminal). The package has no retry logic of its own.

Quick start

import { MediaProvider } from "@ai-matrx/media/core";
import { InlineMediaRef, MediaThumbnail, MediaLightbox } from "@ai-matrx/media/react";

// `client` implements the MediaClient interface from "@ai-matrx/media".
// Matrx apps wire the canonical implementation from @ai-matrx/files/client;
// any structural implementation works.
<MediaProvider client={client} ports={{ ImageComponent, actions, playbackSession }}>
  <InlineMediaRef ref={{ file_id }} size="md" fit="cover" />
  <MediaThumbnail mediaRef={{ file_id }} fileName="report.pdf" />
</MediaProvider>

InlineMediaRef accepts a MediaRef ({ file_id, url?, mime_type? }), a bare fileId string, or an external public URL string — and renders a correctly-sized <img> / <video> / <audio> with fallback and informative error states. ref is a content prop (React 19); use mediaElementRef for DOM access.

Headless hooks (/core)

HookWhat it owns
useMediaResolution(ref)ref → MediaResolution as state (empty / ready / unavailable with a typed reason)
useMediaLoadRecovery(src)the retry-key/error machine over the client's ONE recovery policy
useMediaBlob(ref)object-URL bytes with lifecycle + release
useMediaUpload()single + batch upload, progress, and a per-file tray (entries)
useThumbnailSource(ref, opts)the 4-tier thumbnail decision (rendered thumb → variant → live source → icon)
useMediaActions(ref)share/download/copy/open availability + execution over the injected actions port
getFileKindDetails(name, mime)filename/MIME → category, icon name, color

Components (/react)

InlineMediaRef · MediaThumbnail · FileIcon · FileUploadDropzone (+ UploadProgressList, useMediaDropzone) · MediaActionToolbar · MediaLightbox — plus the package's own inlined SVG icon set (no icon library dependency).

Actions & sharing

Hosts inject a MediaActionsPort (share, download, copy, open handlers and/or a rich SharePopover component). The toolbar and lightbox render whatever is wired and disable the rest — sharing integration plugs in without touching this package.

<MediaProvider client={client} ports={{ actions: { download, share, SharePopover } }}>
  <MediaActionToolbar mediaRef={{ file_id }} fileName="photo.png" />
</MediaProvider>

The complete share body (/share)

/share ships the popover BODY the slot was reserved for — the canonical Matrx share experience:

  • Copy public link — one click asks MediaClient.shareableUrl(ref) for the truly-permanent URL (the host's implementation reuses or mints a no-expiry share link, or returns the permanent CDN URL for public files) and copies it. null fails closed — never a signed URL.
  • External refs get the simplified surface: the safe external URL or a disabled row.
  • Manage all links / who-can-see-this are host slots (manageLinks callback, AccessSummary component) — link inventory and reachability live with the host's iam surfaces.
  • Linked to / Attach to… — the association affordances, riding @ai-matrx/associations through its public hooks. They light up when an AssociationsProvider is mounted above and degrade to absent when not.
import { createMediaSharePopover } from "@ai-matrx/media/share";

const SharePopover = createMediaSharePopover({
  manageLinks: (ctx) => openShareLinkDialog(ctx),   // optional
  AccessSummary,                                    // optional host panel
  notify: { success: toast.success, error: toast.error }, // optional
});

<MediaProvider client={client} ports={{ actions: { SharePopover, download, copy } }}>
  {/* every toolbar/lightbox now opens the full share experience */}
</MediaProvider>

Only /share imports @ai-matrx/associations — a REAL dependency, installed automatically (it brings @ai-matrx/design-system with it). Nothing extra to resolve; association affordances still degrade to absent at runtime when no AssociationsProvider wraps the tree. The other entries never touch it.

Diagnostics — a terminal failure is never silent

Every dead render (a refused resolve(), failed private-pixel bytes, a load error the ONE recovery retry couldn't fix, a failed session mint) emits exactly one strictly-typed MediaFailureInfo — phase, durable mediaRef identity (never a signed URL), status?, retryOutcome?, terminal, message — latched per media identity so re-renders never spam. Bind the sink to your error system:

The retry allowance advances only after React commits the retry-key remount; duplicate browser error events from the original element while the session refresh is in flight are ignored and never consume the real retry.

<MediaProvider
  client={client}
  ports={{ diagnostics: { capture: (info) => myErrorCapture(info) } }}
>

Without a bound sink the package console.errors the same payload — loud by default, never silent. If your MediaClient.recoverLoadError returns a MediaRecoveryReport carrying the session mint's secret_source: "ephemeral", the package immediately captures an app-wide warning that private renders will break across server restarts/instances.

File viewers (/viewers)

Full-pane rendering of ONE file — a preview pane, a file tab, a code editor's binary-file fallback. MediaFileViewer dispatches on the file kind and ships working bodies for text, code, SVG, HTML and unknown files.

import { MediaFileViewer } from "@ai-matrx/media/viewers";

<MediaFileViewer
  source={{ kind: "ref", ref: { file_id } }}
  fileName={file.name}
  fileSize={file.size}
  generic={{ onDownload }}
/>;

Kinds whose rendering IS a heavy browser-only engine — PDF, Markdown, spreadsheets, Office documents — are yours to register, so a consumer that only shows text files never carries a PDF engine:

import { registerMediaViewer, registerMediaCodeHighlighter } from "@ai-matrx/media/viewers";

registerMediaViewer("pdf", MyPdfViewer);          // gets MediaViewerProps
registerMediaCodeHighlighter(MyPrismHighlighter); // optional: color for code

Until you register one, that kind renders an announcing default: it names the missing engine and the remedy and still offers download / view-as-text. It is never a blank pane. (Code is different: with no highlighter registered it renders complete — monospace, line-numbered, copyable — just without color, so it announces nothing.)

Registering a kind the package already renders replaces the built-in, which is how you plug in a domain-specific viewer of your own.

The blob source lane ({ kind: "blob", blob, url }) renders bytes you already have without touching the MediaClient at all.

Next.js (/next)

The ImageComponent port, implemented over next/image so you never write that wrapper again. next is an optional peer — nothing installs it for you, and no other entry imports it.

import { NextMediaImage } from "@ai-matrx/media/next";

const ports: MediaHostPorts = { ImageComponent: NextMediaImage };
// or: createNextMediaImageComponent({ unoptimized: false, quality: 90 })

It defaults to unoptimized, which is correct for Matrx media: durable file URLs are session-cookie authenticated and Next's optimizer fetches them from the server, where there is no user session.

Styling

/react uses Tailwind utility classes with semantic tokens (bg-muted, text-destructive, …). Tailwind v4 hosts register the package as a source:

@source "../node_modules/@ai-matrx/media";

SSR / RSC

The root entry is server-safe (types + one pure function). /core, /react, /share, /viewers and /next are client modules ("use client" is stamped per entry). Nothing touches window at import time.

License

MIT. Inlined icon paths copied from Lucide (ISC).

版本与发布记录

@ai-matrx/media 共发布 315 个版本,首次发布于 2026/08/30;过去 12 个月发布了 315 个版本。

版本发布日期距今
0.15.189latest2026/10/316 天前
0.15.1882026/10/306 天前
0.15.1872026/10/298 天前
0.15.1862026/10/288 天前
0.15.1852026/10/288 天前
0.15.1842026/10/2710 天前

关于 @ai-matrx/media 的常见问题

@ai-matrx/media 是什么?

The AI Matrx media components kit: durable-ref-only renderers for images, video, audio and file thumbnails plus live TTS playback that just work with the Matrx file system — headless core hooks plus DOM bindings, wired to an injected MediaClient. A signed

如何安装 @ai-matrx/media?

在项目目录运行 npm install @ai-matrx/media,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:react(>=18.0.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@ai-matrx/media 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@ai-matrx/media 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@ai-matrx/media 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=20。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@ai-matrx/media 可以商用吗?

@ai-matrx/media 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@ai-matrx/media 的下载量有多少?

根据 npm 官方下载统计,@ai-matrx/media 在 2026/10/05 – 2026/11/03 这 30 天内被下载 86,975 次,日均约 2,899 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@ai-matrx/media 还在维护吗?

最新版本 0.15.189 发布于 2026/10/31(6 天前);至今共发布 315 个版本,其中过去 12 个月发布了 315 个;首次发布于 2026/08/30。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。