b0cdbf5eff
* Make shared component build work in isolation * Add deps that were missing because they were getting picked up from element-web main but shared-components needs itself * Exclude test files from dts generation * Bump version * Change all the shared-component import to be the built artifact * Don't randomly inhale eslint configs in parent dirs please * maybe we don't need this anymore? * maybe fix build * Maybe fix docker build * More build faff * build:res on the parent as part of shared component prepare * link shared component repo inn docker build * 💅 * 💅x2 * Try converting the translation keys to a .d.ts file manually so it gets bundled rather than left as a relative import to the json file * add the script * Add this back for 2nd time now I think * Shouldn't need this anymore * patch-package on prepare because we're patching a dev dependency so it won't be there if we're installed as a dependency * Unused import * Prettier compliance * Only use counterpart from shared components as per comment * Import shared components CSS * Prettier * Call the one from shared components rather than recurse infinitely * Hopefully make tests work * wake up, comment goes before import * Fix lint errors * Fix dupe TranslationKey export * Update compound-web to fix type error An update to @types.react adds the 'hint' value to the enum of the 'popover' attribute and this version of compound-web uses the maching verson of @types/react so they don't conflict. * Maybe, hopefully, get the types working? Please? * Add copyright header to i18nkeys as eslint complains otherwise since it's now in src * prettier * stop running shared-component tests in EW * update snapshots because flex is now from an external stylesheet I guess * More snapshots * Manual class update * Avoid bundling compound bits Because a) it's silly and b) it means we end up bundling a copy of floating-ui too which causes absolute madness with its useDelayGroup contexts. * ignore test util files for coverage * Add !important because the styles are being applied in a different order now * Another !important because css order has changed * Try adding it here to make the test files ignored * More !important * commit yarn lock change * Add shared components coverage file * Update snapshots Because the line height was being overridden to 22.5px somehow by something I can't find, and now isn't: surely the normal 1.5rem is more sensible. * Update snapshots, attempt 2 * Another !important * More snapshot updates * Add test for i18n wrappers & add test script * lint * Prettier * Hopefully run shared component tests * don't need this bit for non-matrix * install ew deps * rigfht coverage location * Rename job here too * Try different coverage filename * Fix copyrights & comment * Typo Co-authored-by: Michael Telatynski <7t3chguy@gmail.com> --------- Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
86 lines
3.9 KiB
TypeScript
86 lines
3.9 KiB
TypeScript
/*
|
|
Copyright 2024 New Vector Ltd.
|
|
Copyright 2021 The Matrix.org Foundation C.I.C.
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
|
Please see LICENSE files in the repository root for full details.
|
|
*/
|
|
|
|
import { percentageOf } from "@element-hq/web-shared-components";
|
|
|
|
import { type IAmplitudePayload, type ITimingPayload, PayloadEvent, WORKLET_NAME } from "./consts";
|
|
|
|
// from AudioWorkletGlobalScope: https://developer.mozilla.org/en-US/docs/Web/API/AudioWorkletGlobalScope
|
|
declare const currentTime: number;
|
|
// declare const currentFrame: number;
|
|
// declare const sampleRate: number;
|
|
|
|
// We rate limit here to avoid overloading downstream consumers with amplitude information.
|
|
// The two major consumers are the voice message waveform thumbnail (resampled down to an
|
|
// appropriate length) and the live waveform shown to the user. Effectively, this controls
|
|
// the refresh rate of that live waveform and the number of samples the thumbnail has to
|
|
// work with.
|
|
const TARGET_AMPLITUDE_FREQUENCY = 16; // Hz
|
|
|
|
function roundTimeToTargetFreq(seconds: number): number {
|
|
// Epsilon helps avoid floating point rounding issues (1 + 1 = 1.999999, etc)
|
|
return Math.round((seconds + Number.EPSILON) * TARGET_AMPLITUDE_FREQUENCY) / TARGET_AMPLITUDE_FREQUENCY;
|
|
}
|
|
|
|
function nextTimeForTargetFreq(roundedSeconds: number): number {
|
|
// The extra round is just to make sure we cut off any floating point issues
|
|
return roundTimeToTargetFreq(roundedSeconds + 1 / TARGET_AMPLITUDE_FREQUENCY);
|
|
}
|
|
|
|
class MxVoiceWorklet extends AudioWorkletProcessor {
|
|
private nextAmplitudeSecond = 0;
|
|
private amplitudeIndex = 0;
|
|
|
|
public process(
|
|
inputs: Float32Array[][],
|
|
outputs: Float32Array[][],
|
|
parameters: Record<string, Float32Array>,
|
|
): boolean {
|
|
const currentSecond = roundTimeToTargetFreq(currentTime);
|
|
// We special case the first ping because there's a fairly good chance that we'll miss the zeroth
|
|
// update. Firefox for instance takes 0.06 seconds (roughly) to call this function for the first
|
|
// time. Edge and Chrome occasionally lag behind too, but for the most part are on time.
|
|
//
|
|
// When this doesn't work properly we end up producing a waveform of nulls and no live preview
|
|
// of the recorded message.
|
|
if (currentSecond === this.nextAmplitudeSecond || this.nextAmplitudeSecond === 0) {
|
|
// We're expecting exactly one mono input source, so just grab the very first frame of
|
|
// samples for the analysis.
|
|
const monoChan = inputs[0][0];
|
|
|
|
// The amplitude of the frame's samples is effectively the loudness of the frame. This
|
|
// translates into a bar which can be rendered as part of the whole recording clip's
|
|
// waveform.
|
|
//
|
|
// We translate the amplitude down to 0-1 for sanity's sake.
|
|
const minVal = Math.min(...monoChan);
|
|
const maxVal = Math.max(...monoChan);
|
|
const amplitude = percentageOf(maxVal, -1, 1) - percentageOf(minVal, -1, 1);
|
|
|
|
this.port.postMessage(<IAmplitudePayload>{
|
|
ev: PayloadEvent.AmplitudeMark,
|
|
amplitude: amplitude,
|
|
forIndex: this.amplitudeIndex++,
|
|
});
|
|
this.nextAmplitudeSecond = nextTimeForTargetFreq(currentSecond);
|
|
}
|
|
|
|
// We mostly use this worklet to fire regular clock updates through to components
|
|
this.port.postMessage(<ITimingPayload>{ ev: PayloadEvent.Timekeep, timeSeconds: currentTime });
|
|
|
|
// We're supposed to return false when we're "done" with the audio clip, but seeing as
|
|
// we are acting as a passive processor we are never truly "done". The browser will clean
|
|
// us up when it is done with us.
|
|
return true;
|
|
}
|
|
}
|
|
|
|
registerProcessor(WORKLET_NAME, MxVoiceWorklet);
|
|
|
|
export default ""; // to appease module loaders (we never use the export)
|