audiocard
Version:
Opinionated, responsive, audio player compatible with Twitter Cards - Fully RSC compliant
372 lines (365 loc) • 10.2 kB
JavaScript
'use client';
import { jsxs, jsx } from 'react/jsx-runtime';
import * as React from 'react';
const SkipBack = ({
seconds,
...rest
}) => jsxs("svg", {
viewBox: "0 0 71 71",
...rest,
children: [jsx("defs", {
children: jsx("clipPath", {
id: "prefix__a",
children: jsx("path", {
d: "M.5 71.5V.5h71v71z"
})
})
}), jsxs("g", {
clipPath: "url(#prefix__a)",
transform: "matrix(-1 0 0 1 71 0)",
children: [jsx("path", {
d: "M61.5 35.5c0 7-2 14-8 20-10 11-28 11-39 0-10-11-10-28 0-39 6-6 14-8 21-8h1",
fill: "none",
strokeWidth: 3,
stroke: "currentColor",
strokeLinecap: "square"
}), jsx("path", {
d: "M61.5 35.5h-6M12.5 35.5h-6M34.5 57.5v6",
fill: "none",
strokeWidth: 3,
stroke: "currentColor"
}), jsx("path", {
d: "M34 1l11 7-11 7v-1V0zM45 1l12 7-12 7v-1V0z",
fill: "currentColor"
})]
}), jsx("text", {
x: 23,
y: 44,
fontSize: 23,
fill: "currentColor",
className: "prefix__svgcentertext",
children: seconds
})]
});
const SkipForward = ({
seconds,
...rest
}) => jsxs("svg", {
viewBox: "0 0 71 71",
...rest,
children: [jsx("defs", {
children: jsx("clipPath", {
id: "prefix__a",
children: jsx("path", {
d: "M.5 71.5V.5h71v71z"
})
})
}), jsxs("g", {
clipPath: "url(#prefix__a)",
children: [jsx("path", {
d: "M61.5 35.5c0 7-2 14-8 20-10 11-28 11-39 0-10-11-10-28 0-39 6-6 14-8 21-8h1",
fill: "none",
strokeWidth: 3,
stroke: "currentColor",
strokeLinecap: "square"
}), jsx("path", {
d: "M61.5 35.5h-6M12.5 35.5h-6M34.5 57.5v6",
fill: "none",
strokeWidth: 3,
stroke: "currentColor"
}), jsx("path", {
d: "M34 1l11 7-11 7v-1V0zM45 1l12 7-12 7v-1V0z",
fill: "currentColor"
})]
}), jsx("text", {
x: 22,
y: 44,
fontSize: 23,
fill: "currentColor",
className: "prefix__svgcentertext",
children: seconds
})]
});
const Play = props => jsx("svg", {
viewBox: "0 0 1.25 1.25",
display: "block",
...props,
children: jsx("path", {
fill: "currentColor",
d: "M.25.125l1 .5-1 .5z"
})
});
const Pause = props => jsx("svg", {
viewBox: "0 0 1 1",
display: "block",
...props,
children: jsx("g", {
fill: "currentColor",
children: jsx("path", {
d: "M.15.15h.262v.7H.15zM.588.15H.85v.7H.588z"
})
})
});
const canonicalWidth = 750;
const canonicalHeight = 225;
const aspectRatio = canonicalWidth / canonicalHeight;
function AudioCard({
art,
background,
className,
color = '#666',
link,
linkText,
progressBarBackground = '#ddd',
progressBarCompleteBackground = '#aaa',
skipBackSeconds,
skipForwardSeconds,
source,
title,
duration = 0,
currentTime: initialCurrentTime = 0,
width = canonicalWidth,
autoplay = false,
preload = 'auto'
}) {
const height = width / aspectRatio;
const h = value => value * height / canonicalHeight;
const w = value => value * width / canonicalWidth;
const audioRef = React.useRef(null);
const [isPlaying, setIsPlaying] = React.useState(false);
const [currentTime, setCurrentTime] = React.useState(initialCurrentTime);
const [internalDuration, setInternalDuration] = React.useState(duration || 0);
React.useEffect(() => {
setCurrentTime(initialCurrentTime);
}, [initialCurrentTime, setCurrentTime]);
React.useEffect(() => {
if (autoplay && audioRef.current) {
audioRef.current.play();
}
}, [autoplay]);
const handlePlayPause = () => {
const audio = audioRef.current;
if (!audio) return;
if (isPlaying) {
audio.pause();
} else {
audio.play();
}
};
const handleSkip = seconds => {
const audio = audioRef.current;
if (!audio) return;
audio.currentTime = Math.max(0, Math.min(internalDuration || duration || 0, audio.currentTime + seconds));
};
const handleAudioTimeUpdate = () => {
if (audioRef.current) {
setCurrentTime(audioRef.current.currentTime);
}
};
const handleAudioPlay = () => setIsPlaying(true);
const handleAudioPause = () => setIsPlaying(false);
const handleAudioLoadedMetadata = () => {
if (audioRef.current) {
setInternalDuration(audioRef.current.duration);
}
};
const handleProgressBarClick = e => {
const audio = audioRef.current;
if (!audio) return;
const rect = e.currentTarget.getBoundingClientRect();
const x = e.clientX - rect.left;
const percent = x / rect.width;
const seekTime = percent * (internalDuration || duration || 0);
audio.currentTime = seekTime;
};
const progressPercentage = (internalDuration || duration || 0) > 0 ? Math.min(100, Math.max(0, currentTime / (internalDuration || duration || 0) * 100)) : 0;
const containerStyle = {
width: '100%',
maxWidth: '100%',
display: 'flex',
flexFlow: 'row nowrap',
fontFamily: "'San Francisco', 'Helvetica Neue', Helvetica, sans-serif",
lineHeight: '1em',
height: height,
color: color,
userSelect: 'none'
};
if (background) {
containerStyle.backgroundColor = background;
}
const contentStyle = {
flex: 1,
display: 'flex',
flexFlow: 'column nowrap',
padding: 0
};
const controlsStyle = {
flex: 1,
display: 'flex',
flexFlow: 'row nowrap',
justifyContent: 'space-between',
fontSize: h(16)
};
const timesStyle = {
display: 'flex',
flexFlow: 'row nowrap',
justifyContent: 'space-between',
margin: '0 5px 5px 5px',
fontSize: h(16)
};
const controlStyle = {
textDecoration: 'none',
display: 'flex',
flexFlow: 'column nowrap',
justifyContent: 'center',
margin: '5px',
width: '15%',
color: color,
cursor: 'pointer'
};
const artStyle = {
height: height,
width: height,
minHeight: height,
minWidth: height
};
const titleStyle = {
textAlign: 'center',
overflow: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
fontSize: h(24),
width: w(canonicalWidth - (art ? canonicalHeight : 0) - 20),
margin: h(12) + 'px ' + w(10) + 'px ' + h(12) + 'px ' + w(10) + 'px',
minHeight: h(30)
};
const linkStyle = {
display: 'block',
textAlign: 'center',
fontSize: h(20),
color: color,
textDecoration: 'none'
};
const progressContainerStyle = {
position: 'relative',
height: h(20),
minHeight: h(20),
cursor: 'pointer'
};
const progressBarStyle = {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
width: '100%',
height: '100%',
backgroundColor: progressBarBackground,
boxShadow: 'inset 1px 1px 2px rgba(0, 0, 0, 0.3)',
WebkitAppearance: 'none',
MozAppearance: 'none'
};
const progressFillStyle = {
position: 'absolute',
top: 0,
left: 0,
height: '100%',
width: progressPercentage + '%',
backgroundColor: progressBarCompleteBackground,
transition: 'width 0.1s ease'
};
const formatTime = seconds => {
if (!Number.isFinite(seconds) || seconds < 0) return '0:00';
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
const secsStr = secs < 10 ? '0' + secs : String(secs);
return mins + ':' + secsStr;
};
return jsxs("div", {
className: className,
style: containerStyle,
children: [art && jsx("img", {
src: art,
alt: title || 'Audio artwork',
style: artStyle
}), jsxs("div", {
style: contentStyle,
children: [title && jsx("div", {
style: titleStyle,
children: title
}), jsx("audio", {
ref: audioRef,
src: source,
preload: String(preload),
autoPlay: autoplay,
onTimeUpdate: handleAudioTimeUpdate,
onPlay: handleAudioPlay,
onPause: handleAudioPause,
onLoadedMetadata: handleAudioLoadedMetadata,
style: {
display: 'none'
}
}), jsxs("div", {
style: controlsStyle,
children: [skipBackSeconds !== undefined ? jsx("div", {
style: controlStyle,
onClick: () => handleSkip(-skipBackSeconds),
title: `Skip Back ${skipBackSeconds}s`,
children: jsx(SkipBack, {
seconds: skipBackSeconds
})
}) : jsx("div", {
style: controlStyle
}), jsx("div", {
style: controlStyle,
onClick: handlePlayPause,
title: isPlaying ? 'Pause' : 'Play',
children: isPlaying ? jsx(Pause, {}) : jsx(Play, {})
}), skipForwardSeconds !== undefined ? jsx("div", {
style: controlStyle,
onClick: () => handleSkip(skipForwardSeconds),
title: `Skip Forward ${skipForwardSeconds}s`,
children: jsx(SkipForward, {
seconds: skipForwardSeconds
})
}) : jsx("div", {
style: controlStyle
})]
}), link && linkText && jsx("a", {
href: link,
target: "_blank",
rel: "noopener noreferrer",
style: linkStyle,
children: linkText
}), jsxs("div", {
style: timesStyle,
children: [jsx("span", {
children: Number.isFinite(currentTime) && currentTime >= 0 && Number.isFinite(internalDuration || duration || 0) && (internalDuration || duration || 0) > 0 ? formatTime(currentTime) : jsx("span", {
style: {
visibility: 'hidden'
},
children: "0:00"
})
}), jsx("span", {
children: Number.isFinite(internalDuration || duration || 0) && (internalDuration || duration || 0) > 0 ? formatTime(internalDuration || duration || 0) : jsx("span", {
style: {
visibility: 'hidden'
},
children: "0:00"
})
})]
}), jsx("div", {
style: progressContainerStyle,
onClick: handleProgressBarClick,
children: jsx("div", {
style: progressBarStyle,
children: jsx("div", {
style: progressFillStyle
})
})
})]
})]
});
}
export { AudioCard as default };
//# sourceMappingURL=audiocard.esm.js.map