🐛 (audioClip) Fix audio clip UI on Safari
This commit is contained in:
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@typebot.io/js",
|
||||
"version": "0.3.10",
|
||||
"version": "0.3.11",
|
||||
"description": "Javascript library to display typebots on your website",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
|
@ -294,7 +294,6 @@ const BotContent = (props: BotContentProps) => {
|
||||
|
||||
onMount(() => {
|
||||
if (!botContainerElement) return
|
||||
console.log('yes')
|
||||
setBotContainer(botContainerElement)
|
||||
resizeObserver.observe(botContainerElement)
|
||||
setBotContainerHeight(`${botContainerElement.clientHeight}px`)
|
||||
|
@ -59,7 +59,6 @@ export const InputChatBlock = (props: Props) => {
|
||||
})
|
||||
|
||||
const handleSubmit = async (content: InputSubmitContent) => {
|
||||
console.log(content)
|
||||
setAnswer(content)
|
||||
props.onSubmit(content)
|
||||
}
|
||||
|
@ -127,12 +127,12 @@ const TextGuestBubble = (props: { answer: TextInputSubmitContent }) => {
|
||||
|
||||
const AudioGuestBubble = (props: { answer: RecordingInputSubmitContent }) => {
|
||||
return (
|
||||
<div class="flex flex-col gap-1 items-end w-full">
|
||||
<div class="flex flex-col gap-1 items-end">
|
||||
<div
|
||||
class="p-2 w-full whitespace-pre-wrap typebot-guest-bubble flex flex-col max-w-[316px]"
|
||||
class="p-2 w-full whitespace-pre-wrap typebot-guest-bubble flex flex-col"
|
||||
data-testid="guest-bubble"
|
||||
>
|
||||
<audio controls src={props.answer.url} class="w-full h-[54px]" />
|
||||
<audio controls src={props.answer.url} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
@ -216,7 +216,9 @@ export const TextInput = (props: Props) => {
|
||||
<div
|
||||
class={clsx(
|
||||
'typebot-input-form flex w-full gap-2 items-end',
|
||||
props.block.options?.isLong ? 'max-w-full' : 'max-w-[350px]'
|
||||
props.block.options?.isLong && recordingStatus() !== 'started'
|
||||
? 'max-w-full'
|
||||
: 'max-w-[350px]'
|
||||
)}
|
||||
onKeyDown={submitWhenEnter}
|
||||
onDrop={handleDropFile}
|
||||
|
@ -8,9 +8,10 @@ import { defaultButtonsBackgroundColor } from '@typebot.io/schemas/features/type
|
||||
|
||||
const barWidth = 3
|
||||
const barGap = 3
|
||||
const dx = 53.5
|
||||
let offset = 0
|
||||
const dx = 60
|
||||
const initBarsHeightPercent = 10
|
||||
const minDetectedVolumePercent = 5
|
||||
const maxDetectedVolumePercent = 90
|
||||
|
||||
type Props = {
|
||||
recordingStatus: 'asking' | 'started' | 'stopped'
|
||||
@ -30,13 +31,14 @@ export const VoiceRecorder = (props: Props) => {
|
||||
let stream: MediaStream | undefined
|
||||
let bars: number[] = []
|
||||
let recordTimeInterval: NodeJS.Timer | undefined
|
||||
let lastFrameTime: DOMHighResTimeStamp | undefined
|
||||
let lastFrameTime: DOMHighResTimeStamp | undefined = undefined
|
||||
let offset = 0
|
||||
|
||||
const fillRgb = hexToRgb(
|
||||
props.buttonsTheme?.backgroundColor ?? defaultButtonsBackgroundColor
|
||||
).join(', ')
|
||||
|
||||
const animate = () => {
|
||||
const draw = () => {
|
||||
if (!ctx || !canvasElement || !lastFrameTime) return
|
||||
|
||||
const currentTime = performance.now()
|
||||
@ -72,25 +74,22 @@ export const VoiceRecorder = (props: Props) => {
|
||||
|
||||
offset += dx * (deltaTime / 1000)
|
||||
|
||||
animationFrameId = requestAnimationFrame(animate)
|
||||
animationFrameId = requestAnimationFrame(draw)
|
||||
}
|
||||
|
||||
const startRecording = async () => {
|
||||
if (!canvasElement) return
|
||||
if (!ctx) ctx = canvasElement.getContext('2d') ?? undefined
|
||||
|
||||
lastFrameTime = performance.now()
|
||||
|
||||
animate()
|
||||
|
||||
recordTimeInterval = setInterval(() => {
|
||||
setRecordingTime((prev) => (prev += 1))
|
||||
}, 1000)
|
||||
|
||||
stream = await navigator.mediaDevices.getUserMedia({ audio: true })
|
||||
|
||||
props.onRecordingConfirmed(stream)
|
||||
|
||||
if (!ctx) ctx = canvasElement.getContext('2d') ?? undefined
|
||||
|
||||
recordTimeInterval = setInterval(() => {
|
||||
setRecordingTime((prev) => (prev += 1))
|
||||
}, 1000)
|
||||
|
||||
audioContext = new AudioContext()
|
||||
volumeNode = await loadVolumeProcessorWorklet(audioContext)
|
||||
|
||||
@ -100,8 +99,21 @@ export const VoiceRecorder = (props: Props) => {
|
||||
volumeNode.connect(audioContext.destination)
|
||||
|
||||
volumeNode.port.onmessage = (event) => {
|
||||
bars.push(event.data)
|
||||
const initBars = (canvasElement.width + barGap) / (barWidth + barGap)
|
||||
const shouldAddNewBar =
|
||||
(initBars + bars.length) * (barWidth + barGap) <
|
||||
canvasElement.width + offset
|
||||
if (shouldAddNewBar)
|
||||
bars.push(
|
||||
Math.min(
|
||||
Math.max(event.data, minDetectedVolumePercent),
|
||||
maxDetectedVolumePercent
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
lastFrameTime = performance.now()
|
||||
animationFrameId = requestAnimationFrame(draw)
|
||||
}
|
||||
|
||||
const stopRecording = () => {
|
||||
@ -156,9 +168,11 @@ export const VoiceRecorder = (props: Props) => {
|
||||
<div class="relative flex w-full">
|
||||
<canvas ref={canvasElement} class="w-full h-[56px]" />
|
||||
<div class="absolute left-gradient w-2 left-0 h-[56px]" />
|
||||
<div class="absolute right-gradient w-2 right-0 h-[56px]" />
|
||||
<div class="absolute right-gradient w-3 right-0 h-[56px]" />
|
||||
</div>
|
||||
<span class="font-bold text-sm">{formatTimeLabel(recordingTime())}</span>
|
||||
<span class="time-container flex-none w-[35px] font-bold text-sm">
|
||||
{formatTimeLabel(recordingTime())}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -1,19 +1,13 @@
|
||||
export const volumeProcessorCode = `
|
||||
const throttleMs = 110;
|
||||
const maxVolumePercent = 80;
|
||||
const volumeMultiplier = 3;
|
||||
const gainFactor = 3;
|
||||
|
||||
class VolumeProcessor extends AudioWorkletProcessor {
|
||||
constructor() {
|
||||
super();
|
||||
this.lastUpdateTime = 0;
|
||||
this.volumeSum = 0;
|
||||
this.volumeCount = 1;
|
||||
}
|
||||
|
||||
process(inputs) {
|
||||
const input = inputs[0];
|
||||
const currentTime = new Date().getTime();
|
||||
if (input.length > 0) {
|
||||
const channelData = input[0];
|
||||
let sum = 0;
|
||||
@ -21,16 +15,7 @@ class VolumeProcessor extends AudioWorkletProcessor {
|
||||
sum += channelData[i] * channelData[i];
|
||||
}
|
||||
const rms = Math.sqrt(sum / channelData.length);
|
||||
const volumePercent = rms * 100;
|
||||
this.volumeSum += volumePercent;
|
||||
this.volumeCount += 1;
|
||||
}
|
||||
if (currentTime - this.lastUpdateTime >= throttleMs) {
|
||||
const averageVolume = 1 + this.volumeSum / this.volumeCount;
|
||||
this.port.postMessage(Math.min(averageVolume * volumeMultiplier, maxVolumePercent));
|
||||
this.volumeSum = 0;
|
||||
this.volumeCount = 1;
|
||||
this.lastUpdateTime = currentTime;
|
||||
this.port.postMessage(rms * 100 * gainFactor)
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@typebot.io/nextjs",
|
||||
"version": "0.3.10",
|
||||
"version": "0.3.11",
|
||||
"description": "Convenient library to display typebots on your Next.js website",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@typebot.io/react",
|
||||
"version": "0.3.10",
|
||||
"version": "0.3.11",
|
||||
"description": "Convenient library to display typebots on your React app",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
Reference in New Issue
Block a user