Add chat hiding mentions follow counts waveform and post fixes

This commit is contained in:
boutmoun123
2026-06-15 15:05:30 +03:00
parent 20fe06b5ed
commit 3d2a41b22a
28 changed files with 1352 additions and 75 deletions
@@ -13,6 +13,8 @@ describe('post media response util', () => {
expect(media.mediaType).toBe(PostType.AUDIO);
expect(media.durationSeconds).toBe(42);
expect(media.waveformPeaks).toHaveLength(100);
expect(media.waveformPeaksPreview).toHaveLength(100);
expect(media.waveformPeaksDetailed.length).toBeGreaterThanOrEqual(100);
expect(media.waveformPeaks.every((value) => Number.isInteger(value))).toBe(true);
expect(media.waveformPeaks.every((value) => value >= 0 && value <= 100)).toBe(true);
});
@@ -26,5 +28,7 @@ describe('post media response util', () => {
expect(media.mediaType).toBe(PostType.IMAGE);
expect(media.waveformPeaks).toEqual([]);
expect(media.waveformPeaksPreview).toEqual([]);
expect(media.waveformPeaksDetailed).toEqual([]);
});
});
+17 -4
View File
@@ -1,5 +1,5 @@
import { PostType } from '../enums/post-type.enum';
import { normalizeWaveformPeaks } from './waveform.util';
import { buildWaveformPeakSet } from './waveform.util';
type VariantSet = {
originalUrl?: string;
@@ -19,6 +19,8 @@ type PostMediaInput = {
thumbnailUrl?: string;
thumbnailVariants?: VariantSet;
waveformPeaks?: number[];
waveformPeaksPreview?: number[];
waveformPeaksDetailed?: number[];
durationSeconds?: number | null;
};
@@ -88,8 +90,17 @@ export const buildPostMediaResponse = (post: PostMediaInput) => {
: mediaType === PostType.VIDEO || mediaType === PostType.AUDIO
? thumbnailUrl
: '';
const waveformPeaks =
mediaType === PostType.AUDIO ? normalizeWaveformPeaks(post.waveformPeaks ?? []) : [];
const waveformSet =
mediaType === PostType.AUDIO
? buildWaveformPeakSet(
post.waveformPeaksDetailed?.length
? post.waveformPeaksDetailed
: post.waveformPeaksPreview?.length
? post.waveformPeaksPreview
: post.waveformPeaks,
post.durationSeconds,
)
: { waveformPeaks: [], waveformPeaksPreview: [], waveformPeaksDetailed: [] };
return {
mediaType,
@@ -102,7 +113,9 @@ export const buildPostMediaResponse = (post: PostMediaInput) => {
audioUrl: post.audioUrl ?? '',
images,
durationSeconds: post.durationSeconds ?? null,
waveformPeaks,
waveformPeaks: waveformSet.waveformPeaks,
waveformPeaksPreview: waveformSet.waveformPeaksPreview,
waveformPeaksDetailed: waveformSet.waveformPeaksDetailed,
isPlayable: mediaType === PostType.VIDEO ? !!preferredPlaybackUrl : mediaType === PostType.AUDIO ? !!post.audioUrl : false,
};
};
+28
View File
@@ -1,6 +1,9 @@
import {
buildWaveformPeakSet,
generateWaveformPeakSetFromBuffer,
generateWaveformPeaksFromBuffer,
generateWaveformPeaksFromSeed,
getDetailedWaveformPeakCount,
normalizeWaveformPeaks,
} from './waveform.util';
@@ -109,4 +112,29 @@ describe('waveform util', () => {
expect(peaks.slice(0, 40).some((value) => value < 60)).toBe(true);
expect(peaks.slice(60).some((value) => value > 80)).toBe(true);
});
it('generates detailed peaks for long audio without changing preview size', () => {
const detailedCount = getDetailedWaveformPeakCount(180);
const waveformSet = buildWaveformPeakSet(
Array.from({ length: 1400 }, (_, index) => (index % 17) + 1),
180,
);
expect(detailedCount).toBeGreaterThanOrEqual(600);
expectDisplaySafeWaveform(waveformSet.waveformPeaksPreview, 100);
expectDisplaySafeWaveform(waveformSet.waveformPeaks, 100);
expect(waveformSet.waveformPeaksDetailed.length).toBeGreaterThanOrEqual(600);
expect(waveformSet.waveformPeaksDetailed.length).toBeLessThanOrEqual(1200);
expect(waveformSet.waveformPeaksDetailed.every((value) => value >= 0 && value <= 100)).toBe(true);
});
it('generates preview and detailed peak sets from buffers', () => {
const wavBuffer = createPcmWavBuffer((frame) => 0.15 + (frame / 7999) * 0.7);
const waveformSet = generateWaveformPeakSetFromBuffer(wavBuffer, 240);
expectDisplaySafeWaveform(waveformSet.waveformPeaksPreview, 100);
expect(waveformSet.waveformPeaksDetailed.length).toBeGreaterThanOrEqual(600);
expect(waveformSet.waveformPeaksDetailed.length).toBeLessThanOrEqual(1200);
expect(new Set(waveformSet.waveformPeaksDetailed).size).toBeGreaterThan(8);
});
});
+87 -6
View File
@@ -1,6 +1,16 @@
export const DEFAULT_WAVEFORM_PEAK_COUNT = 100;
export const DEFAULT_DETAILED_WAVEFORM_PEAK_COUNT = 800;
const MIN_DISPLAY_WAVEFORM_PEAKS = 80;
const MAX_DISPLAY_WAVEFORM_PEAKS = 120;
const MIN_DETAILED_WAVEFORM_PEAKS = 100;
const LONG_AUDIO_MIN_DETAILED_WAVEFORM_PEAKS = 600;
const MAX_DETAILED_WAVEFORM_PEAKS = 1200;
export type WaveformPeakSet = {
waveformPeaks: number[];
waveformPeaksPreview: number[];
waveformPeaksDetailed: number[];
};
export const getDefaultWaveformPeakCount = (): number => {
const configured = Number(process.env.AUDIO_WAVEFORM_PEAKS);
@@ -17,6 +27,19 @@ export const getDefaultWaveformPeakCount = (): number => {
const clampPeak = (value: number): number => Math.max(0, Math.min(100, Math.round(value)));
const clampSampleCount = (
targetCount: number,
fallback: number,
min: number,
max: number,
): number => {
if (!Number.isInteger(targetCount) || targetCount < min || targetCount > max) {
return fallback;
}
return targetCount;
};
const fallbackWaveform = (samples: number): number[] =>
Array.from({ length: samples }, (_, index) => {
const wave = Math.sin((index / Math.max(1, samples - 1)) * Math.PI * 6);
@@ -154,12 +177,12 @@ export const normalizeWaveformPeaks = (
input: unknown[] | undefined,
targetCount = getDefaultWaveformPeakCount(),
): number[] => {
const samples =
Number.isInteger(targetCount) &&
targetCount >= MIN_DISPLAY_WAVEFORM_PEAKS &&
targetCount <= MAX_DISPLAY_WAVEFORM_PEAKS
? targetCount
: DEFAULT_WAVEFORM_PEAK_COUNT;
const samples = clampSampleCount(
targetCount,
DEFAULT_WAVEFORM_PEAK_COUNT,
MIN_DISPLAY_WAVEFORM_PEAKS,
MAX_DETAILED_WAVEFORM_PEAKS,
);
if (!input?.length) {
return fallbackWaveform(samples);
@@ -255,3 +278,61 @@ export const generateWaveformPeaksFromSeed = (
return normalizeWaveformPeaks(peaks, samples);
};
export const getDetailedWaveformPeakCount = (durationSeconds?: number | null): number => {
if (!Number.isFinite(durationSeconds) || !durationSeconds || durationSeconds <= 0) {
return DEFAULT_DETAILED_WAVEFORM_PEAK_COUNT;
}
const byQuarterSecond = Math.ceil(durationSeconds / 0.25);
const minimum = durationSeconds >= 120 ? LONG_AUDIO_MIN_DETAILED_WAVEFORM_PEAKS : MIN_DETAILED_WAVEFORM_PEAKS;
return Math.max(minimum, Math.min(MAX_DETAILED_WAVEFORM_PEAKS, byQuarterSecond));
};
export const buildWaveformPeakSet = (
input: unknown[] | undefined,
durationSeconds?: number | null,
): WaveformPeakSet => {
const preview = normalizeWaveformPeaks(input, getDefaultWaveformPeakCount());
const detailed = normalizeWaveformPeaks(
input?.length ? input : preview,
getDetailedWaveformPeakCount(durationSeconds),
);
return {
waveformPeaks: preview,
waveformPeaksPreview: preview,
waveformPeaksDetailed: detailed,
};
};
export const generateWaveformPeakSetFromBuffer = (
buffer: Buffer,
durationSeconds?: number | null,
): WaveformPeakSet => {
const detailed = generateWaveformPeaksFromBuffer(
buffer,
getDetailedWaveformPeakCount(durationSeconds),
);
const preview = normalizeWaveformPeaks(detailed, getDefaultWaveformPeakCount());
return {
waveformPeaks: preview,
waveformPeaksPreview: preview,
waveformPeaksDetailed: detailed,
};
};
export const generateWaveformPeakSetFromSeed = (
seed: string,
durationSeconds?: number | null,
): WaveformPeakSet => {
const detailed = generateWaveformPeaksFromSeed(seed, getDetailedWaveformPeakCount(durationSeconds));
const preview = normalizeWaveformPeaks(detailed, getDefaultWaveformPeakCount());
return {
waveformPeaks: preview,
waveformPeaksPreview: preview,
waveformPeaksDetailed: detailed,
};
};