-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Record and send audio via Chrome microphone #1996
Changes from 11 commits
0ac7d03
03f1fe1
ec5c7dd
45cc045
59f0e59
c5fe7f6
714fa29
fa1e217
ce075d7
9d48268
def5310
5c31751
a724ea8
3cb2f2d
96afbbf
8aa06fd
9c3bba3
a075b30
5406b4d
bd82d05
dc00c6b
ca005e1
a11d543
8837793
16f1a5d
f93ad11
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
.vercel |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
import { FernButton, FernButtonGroup } from "@fern-docs/components"; | ||
import { Download, Play, Pause } from "iconoir-react"; | ||
import { useEffect, useRef, useState } from "react"; | ||
|
||
interface PlaygroundAudioControlsProps { | ||
audioUrl: string | null; | ||
fileName?: string; | ||
} | ||
|
||
export function PlaygroundAudioControls({ | ||
audioUrl, | ||
fileName = "recording.webm", | ||
}: PlaygroundAudioControlsProps) { | ||
const [isPlaying, setIsPlaying] = useState(false); | ||
const [currentTime, setCurrentTime] = useState(0); | ||
const [duration, setDuration] = useState(0); | ||
const [isLoaded, setIsLoaded] = useState(false); | ||
const audioRef = useRef<HTMLAudioElement | null>(null); | ||
|
||
useEffect(() => { | ||
if (audioRef.current) { | ||
audioRef.current.onended = () => setIsPlaying(false); | ||
audioRef.current.onloadedmetadata = () => { | ||
const audioDuration = audioRef.current?.duration; | ||
if (audioDuration && !isNaN(audioDuration) && isFinite(audioDuration)) { | ||
setDuration(Math.round(audioDuration)); | ||
setIsLoaded(true); | ||
} | ||
}; | ||
audioRef.current.ontimeupdate = () => { | ||
const currentTime = audioRef.current?.currentTime; | ||
if (currentTime && !isNaN(currentTime) && isFinite(currentTime)) { | ||
setCurrentTime(Math.round(currentTime)); | ||
} | ||
}; | ||
} | ||
}, []); | ||
|
||
const formatTime = (seconds: number) => { | ||
const mins = Math.floor(seconds / 60) | ||
.toString() | ||
.padStart(2, "0"); | ||
const secs = (seconds % 60).toString().padStart(2, "0"); | ||
return mins === "00" ? `${secs}s` : `${mins}:${secs}`; | ||
}; | ||
|
||
const handlePlayPause = async () => { | ||
if (!audioRef.current || !audioUrl) return; | ||
|
||
if (isPlaying) { | ||
audioRef.current.pause(); | ||
} else { | ||
await audioRef.current.play(); | ||
} | ||
setIsPlaying(!isPlaying); | ||
}; | ||
|
||
const handleDownload = () => { | ||
if (!audioUrl) return; | ||
const a = document.createElement("a"); | ||
a.href = audioUrl; | ||
a.download = fileName; | ||
document.body.appendChild(a); | ||
a.click(); | ||
document.body.removeChild(a); | ||
}; | ||
|
||
if (!audioUrl) return null; | ||
|
||
return ( | ||
<div className="flex items-center gap-2"> | ||
<audio ref={audioRef} src={audioUrl} preload="metadata" /> | ||
{isLoaded && ( | ||
<span className="font-mono text-xs"> | ||
{`${formatTime(currentTime)}/${formatTime(duration)}`} | ||
</span> | ||
)} | ||
<FernButtonGroup> | ||
<FernButton | ||
icon={isPlaying ? <Pause /> : <Play />} | ||
onClick={handlePlayPause} | ||
size="small" | ||
variant="minimal" | ||
disabled={!audioUrl} | ||
/> | ||
<FernButton | ||
icon={<Download />} | ||
onClick={handleDownload} | ||
size="small" | ||
variant="minimal" | ||
disabled={!audioUrl} | ||
/> | ||
</FernButtonGroup> | ||
</div> | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tiny package that helps w/ the blob data from the media recorder api — without this, the
<audio>
element after recording has incorrect metadata (duration: Infinity) which messes with the playback