"use client"; import type React from "react"; import { forwardRef, useEffect, useRef, useState } from "react"; import { CarbonDocumentAudio, CarbonDocumentUnknown, CarbonPauseFilled, CarbonPlayFilledAlt, } from "./icons.js"; import type { MediaRendererProps } from "./types.js"; import { useResolvedMediaType } from "./useResolvedMediaType.js"; /** * Component that renders any asset stored on IPFS (or anywhere else), given the IPFS URI / URL. * * If an IPFS url is given, the asset is fetched from IPFS through the thirdweb IPFS gateway by default. You can also specify a custom gateway URL using the `gatewayUrl` prop. * * The [mime type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types) of the * asset is determined and the appropriate component is rendered on the UI. * * For example, if the URI points to an image, the `img` tag will be used. If it is a video, the `video` tag will be used, etc. * The component currently supports: * * - Images * - Videos * - Audio files * - SVGs (for [on-chain NFTs](https://blog.thirdweb.com/guides/how-to-create-on-chain-nfts-with-thirdweb/)) * - `iframe` and `HTML` * - If none of these are appropriate, the fallback is a link to the asset * * The default size of rendered media is 300px x 300px, but this can be changed using the `width` and `height` props. * * You can use thirdweb CLI to upload any file to IPFS and get the IPFS URI * * Note: This component no longer supports 3D models as of v5.92.0! * * `npx thirdweb upload ` * @example * ```tsx * import { MediaRenderer } from "thirdweb/react"; * * const client = createThirdwebClient({ clientId: "..." }); * * function Home() { * return ( * * ); * } * ``` * @param props - Refer to [`MediaRendererProps`](https://portal.thirdweb.com/references/typescript/v5/MediaRendererProps) to see the available props. */ export const MediaRenderer = /* @__PURE__ */ (() => forwardRef(function Media_Renderer( { src, poster, alt, gatewayUrl, requireInteraction = false, width = "300px", height = "300px", style, mimeType, client, controls, className, }, ref, ) { const mergedStyle: React.CSSProperties = { objectFit: "contain", ...style, }; const { mediaInfo, isFetched: mediaInfoIsFetched } = useResolvedMediaType( client, src ?? undefined, mimeType, gatewayUrl, ); const { mediaInfo: possiblePosterSrc } = useResolvedMediaType( client, poster ?? undefined, undefined, gatewayUrl, ); if (!mediaInfoIsFetched || !src) { return
; } if (mediaInfo.mimeType) { // html content if (mediaInfo.mimeType.startsWith("text/html")) { return ( } requireInteraction={requireInteraction} src={mediaInfo.url} style={mergedStyle} /> ); } // 3d model if (mediaInfo.mimeType.startsWith("model")) { console.error( "Encountered an unsupported media type. 3D model support was removed in v5.92.0. To add a 3D model to your app, use @google/model-viewer and use the ModelViewer component.", ); // show poster if (possiblePosterSrc.mimeType?.startsWith("image/")) { return ( } src={possiblePosterSrc.url} style={mergedStyle} width={width} /> ); } } // video if (mediaInfo.mimeType.startsWith("video")) { return ( } requireInteraction={requireInteraction} src={mediaInfo.url} style={mergedStyle} /> ); } // audio if (mediaInfo.mimeType.startsWith("audio")) { return ( } src={mediaInfo.url} style={mergedStyle} width={width} /> ); } // image if (mediaInfo.mimeType.startsWith("image/")) { return ( } src={mediaInfo.url} style={mergedStyle} width={width} /> ); } } // unknown mime types or no mime type return ( } src={mediaInfo.url} style={mergedStyle} /> ); }))(); interface PlayButtonProps { onClick: () => void; isPlaying: boolean; } const PlayButton: React.FC = ({ onClick, isPlaying }) => { const [isHovering, setIsHovering] = useState(false); const onMouseEnter = () => setIsHovering(true); const onMouseLeave = () => setIsHovering(false); const onMouseDown = () => setIsHovering(false); const onMouseUp = () => setIsHovering(true); return ( ); }; const ImageRenderer = /* @__PURE__ */ (() => forwardRef< HTMLImageElement, Pick< MediaRendererProps, "src" | "style" | "alt" | "className" | "height" | "width" > >(function Image_Renderer(props, ref) { const { style, src, alt, className, height, width } = props; const [error, setError] = useState(false); if (error) { return ( } src={src} style={style} /> ); } return ( {alt} { setError(true); }} ref={ref} src={src ?? undefined} style={style} width={width} /> ); }))(); const VideoPlayer = /* @__PURE__ */ (() => forwardRef< HTMLVideoElement, Pick< MediaRendererProps, | "alt" | "src" | "poster" | "requireInteraction" | "style" | "width" | "height" | "controls" | "className" > >(function Video_Player( { src, alt, poster, requireInteraction, style, width, height, controls, className, }, ref, ) { const videoRef = useRef(null); const [playing, setPlaying] = useState(!requireInteraction); const [muted, setMuted] = useState(true); const [error, setError] = useState(false); useEffect(() => { if (videoRef.current) { if (playing) { try { videoRef.current.play(); } catch (err) { console.error("Error playing video", err); } } else { try { videoRef.current.pause(); videoRef.current.currentTime = 0; } catch (err) { console.error("Error pausing video", err); } } } }, [playing]); if (error) { return ( } src={src} style={style} /> ); } return (
); }))(); const AudioPlayer = /* @__PURE__ */ (() => forwardRef< HTMLAudioElement, Pick< MediaRendererProps, | "src" | "alt" | "poster" | "style" | "height" | "width" | "className" | "controls" > >(function Audio_Player( { src, alt, poster, style, height, width, className, controls }, ref, ) { const audioRef = useRef(null); const [playing, setPlaying] = useState(false); const [muted, setMuted] = useState(true); const [error, setError] = useState(false); useEffect(() => { if (audioRef.current) { if (playing) { audioRef.current.play(); } else { audioRef.current.pause(); audioRef.current.currentTime = 0; } } }, [playing]); if (error) { return ( } src={src} style={style} /> ); } return (
{poster ? ( {alt} ) : (
)} { setPlaying((prev) => !prev); setMuted(false); }} />
); }))(); /** * @internal Exported for tests */ export const IframePlayer = /* @__PURE__ */ (() => forwardRef< HTMLIFrameElement, Omit< MediaRendererProps, | "client" | "gatewayUrl" | "mimeType" | "controls" | "height" | "width" | "children" > >(function Iframe_Player( { src, alt, poster, requireInteraction, style, ...restProps }, ref, ) { const [playing, setPlaying] = useState(!requireInteraction); return (