2
0

🐛 (audioClip) Fix audio clip UI on Safari

This commit is contained in:
Baptiste Arnaud
2024-08-21 14:32:47 +02:00
parent 37ef8fe240
commit b2c8ef941b
9 changed files with 42 additions and 43 deletions

View File

@ -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",

View File

@ -294,7 +294,6 @@ const BotContent = (props: BotContentProps) => {
onMount(() => {
if (!botContainerElement) return
console.log('yes')
setBotContainer(botContainerElement)
resizeObserver.observe(botContainerElement)
setBotContainerHeight(`${botContainerElement.clientHeight}px`)

View File

@ -59,7 +59,6 @@ export const InputChatBlock = (props: Props) => {
})
const handleSubmit = async (content: InputSubmitContent) => {
console.log(content)
setAnswer(content)
props.onSubmit(content)
}

View File

@ -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>
)

View File

@ -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}

View File

@ -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>
)
}

View File

@ -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;
}

View File

@ -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",

View File

@ -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",