Improve waveform peaks for audio player
فشلت بعض الفحوصات
Deploy To Ghaymah / deploy (push) Has been cancelled

هذا الالتزام موجود في:
boutmoun123
2026-06-19 02:45:09 +03:00
الأصل 23c1a2d12b
التزام e1edc1c848
5 ملفات معدلة مع 63 إضافات و33 حذوفات

عرض الملف

@@ -12,11 +12,15 @@ 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).toHaveLength(160);
expect(media.waveformPeaksPreview).toHaveLength(160);
expect(media.waveformPeaksDetailed.length).toBeGreaterThanOrEqual(500);
expect(media.waveformPeaksDetailed.length).toBeLessThanOrEqual(1000);
expect(media.waveformPeaks).toEqual(media.waveformPeaksPreview);
expect(media.waveformPeaks.every((value) => Number.isInteger(value))).toBe(true);
expect(media.waveformPeaks.every((value) => value >= 0 && value <= 100)).toBe(true);
expect(media.waveformPeaksDetailed.every((value) => Number.isInteger(value))).toBe(true);
expect(media.waveformPeaksDetailed.every((value) => value >= 0 && value <= 100)).toBe(true);
});
it('does not attach waveformPeaks to non-audio media', () => {

عرض الملف

@@ -7,7 +7,7 @@ import {
normalizeWaveformPeaks,
} from './waveform.util';
const expectDisplaySafeWaveform = (peaks: number[], expectedLength = 100) => {
const expectDisplaySafeWaveform = (peaks: number[], expectedLength = 160) => {
expect(peaks).toHaveLength(expectedLength);
expect(peaks.every((value) => Number.isInteger(value))).toBe(true);
expect(peaks.every((value) => value >= 0 && value <= 100)).toBe(true);
@@ -61,7 +61,7 @@ describe('waveform util', () => {
process.env.AUDIO_WAVEFORM_PEAKS = originalAudioWaveformPeaks;
});
it('returns exactly 100 values by default', () => {
it('returns exactly 160 preview values by default', () => {
expectDisplaySafeWaveform(normalizeWaveformPeaks([10, 20, 30, 40]));
});
@@ -71,19 +71,19 @@ describe('waveform util', () => {
expectDisplaySafeWaveform(peaks);
});
it('resamples short input to 100 values', () => {
it('resamples short input to 160 values', () => {
const peaks = normalizeWaveformPeaks([5, 20, 60, 90, 30, 10]);
expectDisplaySafeWaveform(peaks);
});
it('downsamples long input to 100 values', () => {
it('downsamples long input to 160 values', () => {
const input = Array.from({ length: 200 }, (_, index) => index + 1);
expectDisplaySafeWaveform(normalizeWaveformPeaks(input));
});
it('returns fallback 100 values for empty input', () => {
it('returns fallback 160 values for empty input', () => {
const peaks = normalizeWaveformPeaks([]);
expectDisplaySafeWaveform(peaks);
@@ -91,11 +91,11 @@ describe('waveform util', () => {
});
it('uses configured values only within the Flutter display-safe range', () => {
process.env.AUDIO_WAVEFORM_PEAKS = '120';
expectDisplaySafeWaveform(normalizeWaveformPeaks([1, 2, 3]), 120);
process.env.AUDIO_WAVEFORM_PEAKS = '180';
expectDisplaySafeWaveform(normalizeWaveformPeaks([1, 2, 3]), 180);
process.env.AUDIO_WAVEFORM_PEAKS = '48';
expectDisplaySafeWaveform(normalizeWaveformPeaks([1, 2, 3]), 100);
process.env.AUDIO_WAVEFORM_PEAKS = '100';
expectDisplaySafeWaveform(normalizeWaveformPeaks([1, 2, 3]), 160);
});
it('generates display-safe peaks from audio buffers and seeds', () => {
@@ -113,6 +113,21 @@ describe('waveform util', () => {
expect(peaks.slice(60).some((value) => value > 80)).toBe(true);
});
it('keeps detailed peak counts within Flutter response limits', () => {
expect(getDetailedWaveformPeakCount(12)).toBe(500);
expect(getDetailedWaveformPeakCount(130)).toBeGreaterThanOrEqual(600);
expect(getDetailedWaveformPeakCount(130)).toBeLessThanOrEqual(1000);
expect(getDetailedWaveformPeakCount(600)).toBe(1000);
});
it('keeps silent WAV samples low instead of amplifying them into a fake waveform', () => {
const wavBuffer = createPcmWavBuffer(() => 0);
const peaks = generateWaveformPeaksFromBuffer(wavBuffer);
expectDisplaySafeWaveform(peaks);
expect(peaks.every((value) => value === 0)).toBe(true);
});
it('generates detailed peaks for long audio without changing preview size', () => {
const detailedCount = getDetailedWaveformPeakCount(180);
const waveformSet = buildWaveformPeakSet(
@@ -121,20 +136,26 @@ describe('waveform util', () => {
);
expect(detailedCount).toBeGreaterThanOrEqual(600);
expectDisplaySafeWaveform(waveformSet.waveformPeaksPreview, 100);
expectDisplaySafeWaveform(waveformSet.waveformPeaks, 100);
expect(detailedCount).toBeLessThanOrEqual(1000);
expectDisplaySafeWaveform(waveformSet.waveformPeaksPreview, 160);
expectDisplaySafeWaveform(waveformSet.waveformPeaks, 160);
expect(waveformSet.waveformPeaks).toEqual(waveformSet.waveformPeaksPreview);
expect(waveformSet.waveformPeaksDetailed.length).toBeGreaterThanOrEqual(600);
expect(waveformSet.waveformPeaksDetailed.length).toBeLessThanOrEqual(1200);
expect(waveformSet.waveformPeaksDetailed.length).toBeLessThanOrEqual(1000);
expect(waveformSet.waveformPeaksDetailed.every((value) => Number.isInteger(value))).toBe(true);
expect(waveformSet.waveformPeaksDetailed.every((value) => value >= 0 && value <= 100)).toBe(true);
expect(new Set(waveformSet.waveformPeaksDetailed).size).toBeGreaterThan(1);
});
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);
expectDisplaySafeWaveform(waveformSet.waveformPeaksPreview, 160);
expect(waveformSet.waveformPeaks).toEqual(waveformSet.waveformPeaksPreview);
expect(waveformSet.waveformPeaksDetailed.length).toBeGreaterThanOrEqual(600);
expect(waveformSet.waveformPeaksDetailed.length).toBeLessThanOrEqual(1200);
expect(waveformSet.waveformPeaksDetailed.length).toBeLessThanOrEqual(1000);
expect(waveformSet.waveformPeaksDetailed.every((value) => Number.isInteger(value))).toBe(true);
expect(new Set(waveformSet.waveformPeaksDetailed).size).toBeGreaterThan(8);
});
});

عرض الملف

@@ -1,10 +1,11 @@
export const DEFAULT_WAVEFORM_PEAK_COUNT = 100;
export const DEFAULT_WAVEFORM_PEAK_COUNT = 160;
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 MIN_DISPLAY_WAVEFORM_PEAKS = 120;
const MAX_DISPLAY_WAVEFORM_PEAKS = 180;
const MIN_DETAILED_WAVEFORM_PEAKS = 500;
const LONG_AUDIO_MIN_DETAILED_WAVEFORM_PEAKS = 600;
const MAX_DETAILED_WAVEFORM_PEAKS = 1200;
const MAX_DETAILED_WAVEFORM_PEAKS = 1000;
const NEAR_SILENCE_THRESHOLD = 1;
export type WaveformPeakSet = {
waveformPeaks: number[];
@@ -54,7 +55,7 @@ const scaleToRange = (values: number[]): number[] => {
const sorted = [...values].sort((left, right) => left - right);
const max = sorted[sorted.length - 1] ?? 0;
if (max <= 0) {
if (max <= NEAR_SILENCE_THRESHOLD) {
return values.map((value) => clampPeak(value));
}
@@ -267,13 +268,15 @@ export const generateWaveformPeaksFromSeed = (
hash = Math.imul(hash, 16777619);
}
const phase = (hash % 360) * (Math.PI / 180);
const peaks: number[] = [];
let state = hash >>> 0;
for (let i = 0; i < samples; i += 1) {
state = (Math.imul(state, 1664525) + 1013904223) >>> 0;
const base = 18 + (state % 65);
const accent = i % 6 === 0 ? 18 : i % 3 === 0 ? 10 : 0;
peaks.push(base + accent);
const progress = i / Math.max(1, samples - 1);
const primary = Math.sin(progress * Math.PI * 12 + phase) * 24;
const secondary = Math.sin(progress * Math.PI * 37 + phase / 2) * 11;
const accent = i % 11 === 0 ? 16 : i % 7 === 0 ? 8 : 0;
peaks.push(42 + primary + secondary + accent);
}
return normalizeWaveformPeaks(peaks, samples);

عرض الملف

@@ -128,7 +128,7 @@ export default () => ({
},
audioProcessing: {
enabled: (process.env.AUDIO_PROCESSING_ENABLED ?? 'false').toLowerCase() === 'true',
waveformPeaks: Number(process.env.AUDIO_WAVEFORM_PEAKS ?? 100),
waveformPeaks: Number(process.env.AUDIO_WAVEFORM_PEAKS ?? 160),
},
logging: {
level: process.env.LOG_LEVEL ?? 'log',

عرض الملف

@@ -142,12 +142,14 @@ describe('MediaService', () => {
expect(result.audioUrl).toBe('/uploads/ai-music/generated.wav');
expect(result.mimeType).toBe('audio/wav');
expect(result.sizeBytes).toBe(audioBuffer.length);
expect(result.waveformPeaks).toHaveLength(100);
expect(result.waveformPeaksPreview).toHaveLength(100);
expect(result.waveformPeaksDetailed.length).toBeGreaterThanOrEqual(100);
expect(result.waveformPeaksDetailed.length).toBeLessThanOrEqual(1200);
expect(result.waveformPeaks).toHaveLength(160);
expect(result.waveformPeaksPreview).toHaveLength(160);
expect(result.waveformPeaks).toEqual(result.waveformPeaksPreview);
expect(result.waveformPeaksDetailed.length).toBeGreaterThanOrEqual(500);
expect(result.waveformPeaksDetailed.length).toBeLessThanOrEqual(1000);
expect(result.waveformPeaksDetailed.every((value: number) => Number.isInteger(value))).toBe(true);
expect(result.waveformPeaksDetailed.every((value: number) => value >= 0 && value <= 100)).toBe(true);
expect(new Set(result.waveformPeaksDetailed).size).toBeGreaterThan(1);
expect(storageService.resolveLocalFilePath).toHaveBeenCalledWith(
'/uploads/ai-music/generated.wav',
);