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