@djangocfg/ui-nextjs 2.1.82 → 2.1.83
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -4
- package/src/tools/AudioPlayer/@refactoring3/00-IMPLEMENTATION-ROADMAP.md +1146 -0
- package/src/tools/AudioPlayer/@refactoring3/01-WAVESURFER-STREAMING-ANALYSIS.md +611 -0
- package/src/tools/AudioPlayer/@refactoring3/02-MEDIA-VIEWER-ANALYSIS.md +560 -0
- package/src/tools/AudioPlayer/@refactoring3/03-HYBRID-ARCHITECTURE-PROPOSAL.md +769 -0
- package/src/tools/AudioPlayer/@refactoring3/04-CRACKLING-ISSUE-DIAGNOSIS.md +373 -0
- package/src/tools/AudioPlayer/README.md +177 -205
- package/src/tools/AudioPlayer/components/AudioPlayer.tsx +9 -4
- package/src/tools/AudioPlayer/components/HybridAudioPlayer.tsx +251 -0
- package/src/tools/AudioPlayer/components/HybridSimplePlayer.tsx +291 -0
- package/src/tools/AudioPlayer/components/HybridWaveform.tsx +279 -0
- package/src/tools/AudioPlayer/components/SimpleAudioPlayer.tsx +16 -26
- package/src/tools/AudioPlayer/components/index.ts +6 -1
- package/src/tools/AudioPlayer/context/AudioProvider.tsx +8 -3
- package/src/tools/AudioPlayer/context/HybridAudioProvider.tsx +121 -0
- package/src/tools/AudioPlayer/context/index.ts +14 -2
- package/src/tools/AudioPlayer/hooks/index.ts +11 -0
- package/src/tools/AudioPlayer/hooks/useHybridAudio.ts +387 -0
- package/src/tools/AudioPlayer/hooks/useHybridAudioAnalysis.ts +95 -0
- package/src/tools/AudioPlayer/hooks/useSharedWebAudio.ts +6 -3
- package/src/tools/AudioPlayer/index.ts +31 -0
- package/src/tools/AudioPlayer/progressive/ProgressiveAudioPlayer.tsx +8 -0
- package/src/tools/index.ts +22 -0
- package/src/tools/AudioPlayer/@refactoring/00-PLAN.md +0 -148
- package/src/tools/AudioPlayer/@refactoring/01-TYPES.md +0 -301
- package/src/tools/AudioPlayer/@refactoring/02-HOOKS.md +0 -281
- package/src/tools/AudioPlayer/@refactoring/03-CONTEXT.md +0 -328
- package/src/tools/AudioPlayer/@refactoring/04-COMPONENTS.md +0 -251
- package/src/tools/AudioPlayer/@refactoring/05-EFFECTS.md +0 -427
- package/src/tools/AudioPlayer/@refactoring/06-UTILS-AND-INDEX.md +0 -193
- package/src/tools/AudioPlayer/@refactoring/07-EXECUTION-CHECKLIST.md +0 -146
- package/src/tools/AudioPlayer/@refactoring2/ISSUE_ANALYSIS.md +0 -187
- package/src/tools/AudioPlayer/@refactoring2/PLAN.md +0 -372
|
@@ -1,372 +0,0 @@
|
|
|
1
|
-
# Progressive Audio Player - План рефакторинга
|
|
2
|
-
|
|
3
|
-
## Проблема
|
|
4
|
-
|
|
5
|
-
WaveSurfer.js требует полной загрузки аудиофайла для рендеринга waveform. При HTTP Range streaming backend возвращает только 2MB chunks, что приводит к:
|
|
6
|
-
- Seek на позицию > 2 минут не работает
|
|
7
|
-
- После неудачного seek плеер "зависает"
|
|
8
|
-
|
|
9
|
-
## Решение
|
|
10
|
-
|
|
11
|
-
Создать собственный `ProgressiveAudioPlayer` который:
|
|
12
|
-
1. Использует HTML5 `<audio>` для воспроизведения (нативные Range requests)
|
|
13
|
-
2. Рисует waveform прогрессивно по мере загрузки
|
|
14
|
-
3. Визуально показывает загруженную/незагруженную область
|
|
15
|
-
4. Полный контроль над seek поведением
|
|
16
|
-
|
|
17
|
-
---
|
|
18
|
-
|
|
19
|
-
## Архитектура
|
|
20
|
-
|
|
21
|
-
```
|
|
22
|
-
┌──────────────────────────────────────────────────────────────┐
|
|
23
|
-
│ ProgressiveAudioPlayer │
|
|
24
|
-
├──────────────────────────────────────────────────────────────┤
|
|
25
|
-
│ │
|
|
26
|
-
│ ┌────────────────────────────────────────────────────────┐ │
|
|
27
|
-
│ │ WaveformCanvas │ │
|
|
28
|
-
│ │ ┌──────────────────┬─────────────────────────────────┐│ │
|
|
29
|
-
│ │ │ ████████████████ │ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ ││ │
|
|
30
|
-
│ │ │ (загружено) │ (placeholder/loading) ││ │
|
|
31
|
-
│ │ └──────────────────┴─────────────────────────────────┘│ │
|
|
32
|
-
│ │ ▲ cursor │ │
|
|
33
|
-
│ └────────────────────────────────────────────────────────┘ │
|
|
34
|
-
│ │
|
|
35
|
-
│ ┌────────────────────────────────────────────────────────┐ │
|
|
36
|
-
│ │ <audio> (hidden) - нативный HTML5 player │ │
|
|
37
|
-
│ │ - src={streamingUrl} │ │
|
|
38
|
-
│ │ - браузер сам делает Range requests │ │
|
|
39
|
-
│ └────────────────────────────────────────────────────────┘ │
|
|
40
|
-
│ │
|
|
41
|
-
│ ┌────────────────────────────────────────────────────────┐ │
|
|
42
|
-
│ │ Controls │ │
|
|
43
|
-
│ │ [⏮] [▶/⏸] [⏭] [🔊━━━━] [🔁] 00:00 / 03:45 │ │
|
|
44
|
-
│ └────────────────────────────────────────────────────────┘ │
|
|
45
|
-
│ │
|
|
46
|
-
└──────────────────────────────────────────────────────────────┘
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
---
|
|
50
|
-
|
|
51
|
-
## Компоненты
|
|
52
|
-
|
|
53
|
-
### 1. `useProgressiveWaveform` - хук для загрузки и декодирования
|
|
54
|
-
|
|
55
|
-
```typescript
|
|
56
|
-
interface UseProgressiveWaveformOptions {
|
|
57
|
-
url: string;
|
|
58
|
-
chunkSize?: number; // размер chunk для загрузки (default: 512KB)
|
|
59
|
-
samplesPerChunk?: number; // сколько peaks на chunk (default: 100)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
interface UseProgressiveWaveformReturn {
|
|
63
|
-
peaks: number[]; // массив peaks [0-1]
|
|
64
|
-
loadedPercent: number; // сколько загружено (0-100)
|
|
65
|
-
isLoading: boolean;
|
|
66
|
-
error: Error | null;
|
|
67
|
-
loadedRanges: [number, number][]; // загруженные диапазоны
|
|
68
|
-
}
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
**Алгоритм:**
|
|
72
|
-
1. Получить Content-Length через HEAD запрос
|
|
73
|
-
2. Разбить на chunks
|
|
74
|
-
3. Загружать chunks последовательно или параллельно
|
|
75
|
-
4. Декодировать через AudioContext.decodeAudioData()
|
|
76
|
-
5. Извлечь peaks из decoded buffer
|
|
77
|
-
6. Обновлять состояние по мере загрузки
|
|
78
|
-
|
|
79
|
-
### 2. `WaveformCanvas` - canvas компонент
|
|
80
|
-
|
|
81
|
-
```typescript
|
|
82
|
-
interface WaveformCanvasProps {
|
|
83
|
-
peaks: number[];
|
|
84
|
-
loadedPercent: number;
|
|
85
|
-
currentTime: number;
|
|
86
|
-
duration: number;
|
|
87
|
-
buffered: TimeRanges; // из audio.buffered
|
|
88
|
-
onSeek: (time: number) => void;
|
|
89
|
-
|
|
90
|
-
// Стилизация
|
|
91
|
-
waveColor?: string;
|
|
92
|
-
progressColor?: string;
|
|
93
|
-
loadingColor?: string; // цвет незагруженной области
|
|
94
|
-
cursorColor?: string;
|
|
95
|
-
barWidth?: number;
|
|
96
|
-
barGap?: number;
|
|
97
|
-
height?: number;
|
|
98
|
-
}
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
**Отрисовка:**
|
|
102
|
-
1. Фон - незагруженная область (полупрозрачная или placeholder pattern)
|
|
103
|
-
2. Загруженные peaks - основной цвет
|
|
104
|
-
3. Проигранная часть - progressColor
|
|
105
|
-
4. Курсор текущей позиции
|
|
106
|
-
5. Buffered ranges индикатор (тонкая полоса снизу)
|
|
107
|
-
|
|
108
|
-
### 3. `useAudioElement` - хук для управления audio
|
|
109
|
-
|
|
110
|
-
```typescript
|
|
111
|
-
interface UseAudioElementOptions {
|
|
112
|
-
src: string;
|
|
113
|
-
autoPlay?: boolean;
|
|
114
|
-
onTimeUpdate?: (time: number) => void;
|
|
115
|
-
onDurationChange?: (duration: number) => void;
|
|
116
|
-
onBufferUpdate?: (buffered: TimeRanges) => void;
|
|
117
|
-
onEnded?: () => void;
|
|
118
|
-
onError?: (error: Error) => void;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
interface UseAudioElementReturn {
|
|
122
|
-
audioRef: RefObject<HTMLAudioElement>;
|
|
123
|
-
isPlaying: boolean;
|
|
124
|
-
currentTime: number;
|
|
125
|
-
duration: number;
|
|
126
|
-
volume: number;
|
|
127
|
-
isMuted: boolean;
|
|
128
|
-
buffered: TimeRanges;
|
|
129
|
-
|
|
130
|
-
// Actions
|
|
131
|
-
play: () => Promise<void>;
|
|
132
|
-
pause: () => void;
|
|
133
|
-
seek: (time: number) => void;
|
|
134
|
-
setVolume: (vol: number) => void;
|
|
135
|
-
toggleMute: () => void;
|
|
136
|
-
}
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
### 4. `ProgressiveAudioPlayer` - главный компонент
|
|
140
|
-
|
|
141
|
-
```typescript
|
|
142
|
-
interface ProgressiveAudioPlayerProps {
|
|
143
|
-
src: string;
|
|
144
|
-
title?: string;
|
|
145
|
-
artist?: string;
|
|
146
|
-
coverArt?: string;
|
|
147
|
-
|
|
148
|
-
// Features
|
|
149
|
-
showWaveform?: boolean;
|
|
150
|
-
showControls?: boolean;
|
|
151
|
-
showTimer?: boolean;
|
|
152
|
-
showVolume?: boolean;
|
|
153
|
-
showLoop?: boolean;
|
|
154
|
-
|
|
155
|
-
// Callbacks
|
|
156
|
-
onPlay?: () => void;
|
|
157
|
-
onPause?: () => void;
|
|
158
|
-
onEnded?: () => void;
|
|
159
|
-
onError?: (error: Error) => void;
|
|
160
|
-
|
|
161
|
-
// Styling
|
|
162
|
-
className?: string;
|
|
163
|
-
waveformOptions?: WaveformOptions;
|
|
164
|
-
}
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
### 5. `ProgressiveAudioContext` - контекст (опционально)
|
|
168
|
-
|
|
169
|
-
Для совместимости с существующими хуками:
|
|
170
|
-
- `useAudioControls()`
|
|
171
|
-
- `useAudioState()`
|
|
172
|
-
- `useAudioElement()`
|
|
173
|
-
|
|
174
|
-
---
|
|
175
|
-
|
|
176
|
-
## Файловая структура
|
|
177
|
-
|
|
178
|
-
```
|
|
179
|
-
AudioPlayer/
|
|
180
|
-
├── @refactoring2/
|
|
181
|
-
│ └── PLAN.md (этот файл)
|
|
182
|
-
│
|
|
183
|
-
├── components/
|
|
184
|
-
│ ├── ProgressiveAudioPlayer.tsx # NEW - главный компонент
|
|
185
|
-
│ ├── WaveformCanvas.tsx # NEW - canvas waveform
|
|
186
|
-
│ ├── AudioPlayer.tsx # существующий (WaveSurfer)
|
|
187
|
-
│ └── SimpleAudioPlayer.tsx # существующий
|
|
188
|
-
│
|
|
189
|
-
├── hooks/
|
|
190
|
-
│ ├── useProgressiveWaveform.ts # NEW - загрузка + декодирование
|
|
191
|
-
│ ├── useAudioElement.ts # NEW - управление audio
|
|
192
|
-
│ ├── useWaveformRenderer.ts # NEW - рендеринг на canvas
|
|
193
|
-
│ └── useAudioHotkeys.ts # существующий (переиспользуем)
|
|
194
|
-
│
|
|
195
|
-
├── context/
|
|
196
|
-
│ ├── ProgressiveAudioProvider.tsx # NEW - контекст
|
|
197
|
-
│ └── AudioProvider.tsx # существующий (WaveSurfer)
|
|
198
|
-
│
|
|
199
|
-
├── utils/
|
|
200
|
-
│ ├── peaks.ts # NEW - извлечение peaks
|
|
201
|
-
│ ├── audioDecoder.ts # NEW - декодирование chunks
|
|
202
|
-
│ └── formatTime.ts # существующий
|
|
203
|
-
│
|
|
204
|
-
└── types/
|
|
205
|
-
└── progressive.ts # NEW - типы
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
---
|
|
209
|
-
|
|
210
|
-
## Этапы реализации
|
|
211
|
-
|
|
212
|
-
### Этап 1: Базовые хуки (core) ✅ DONE
|
|
213
|
-
|
|
214
|
-
**Файлы:**
|
|
215
|
-
- `progressive/useAudioElement.ts` ✅
|
|
216
|
-
- `progressive/peaks.ts` ✅
|
|
217
|
-
- `progressive/types.ts` ✅
|
|
218
|
-
|
|
219
|
-
**Задачи:**
|
|
220
|
-
1. [x] Создать `useAudioElement` - обёртка над HTMLAudioElement
|
|
221
|
-
2. [x] Создать `extractPeaks(audioBuffer)` - извлечение peaks из AudioBuffer
|
|
222
|
-
3. [x] Создать утилиты для peaks (merge, resample, smooth)
|
|
223
|
-
4. [x] Типы для всех новых компонентов
|
|
224
|
-
|
|
225
|
-
### Этап 2: Progressive загрузка ✅ DONE
|
|
226
|
-
|
|
227
|
-
**Файлы:**
|
|
228
|
-
- `progressive/useProgressiveWaveform.ts` ✅
|
|
229
|
-
|
|
230
|
-
**Задачи:**
|
|
231
|
-
1. [x] HEAD запрос для Content-Length
|
|
232
|
-
2. [x] Chunked fetch с Range headers
|
|
233
|
-
3. [x] Декодирование каждого chunk
|
|
234
|
-
4. [x] Накопление peaks в состоянии
|
|
235
|
-
5. [x] Обработка ошибок и retry
|
|
236
|
-
|
|
237
|
-
### Этап 3: Canvas рендеринг ✅ DONE
|
|
238
|
-
|
|
239
|
-
**Файлы:**
|
|
240
|
-
- `progressive/WaveformCanvas.tsx` ✅
|
|
241
|
-
|
|
242
|
-
**Задачи:**
|
|
243
|
-
1. [x] Canvas setup с правильным DPI
|
|
244
|
-
2. [x] Рендеринг peaks (bars)
|
|
245
|
-
3. [x] Progress overlay (проигранная часть)
|
|
246
|
-
4. [x] Loading indicator для незагруженной части
|
|
247
|
-
5. [x] Cursor (текущая позиция)
|
|
248
|
-
6. [x] Buffered ranges indicator
|
|
249
|
-
7. [x] Click/drag для seek
|
|
250
|
-
8. [x] Hover preview
|
|
251
|
-
|
|
252
|
-
### Этап 4: Главный компонент ✅ DONE
|
|
253
|
-
|
|
254
|
-
**Файлы:**
|
|
255
|
-
- `progressive/ProgressiveAudioPlayer.tsx` ✅
|
|
256
|
-
- `progressive/index.ts` ✅
|
|
257
|
-
|
|
258
|
-
**Задачи:**
|
|
259
|
-
1. [x] Собрать всё вместе
|
|
260
|
-
2. [x] Controls UI (play/pause, volume, etc.)
|
|
261
|
-
3. [x] Timer display
|
|
262
|
-
4. [x] Export из index.ts
|
|
263
|
-
5. [ ] Keyboard shortcuts (переиспользовать useAudioHotkeys) - TODO
|
|
264
|
-
|
|
265
|
-
### Этап 5: Интеграция и тесты 🔄 IN PROGRESS
|
|
266
|
-
|
|
267
|
-
**Задачи:**
|
|
268
|
-
1. [x] Export из AudioPlayer/index.ts
|
|
269
|
-
2. [ ] Обновить AudioViewer в cmdop - использовать ProgressiveAudioPlayer
|
|
270
|
-
3. [ ] Playground example
|
|
271
|
-
4. [ ] Тестирование с разными форматами/размерами
|
|
272
|
-
5. [ ] Добавить Context для совместимости с существующими хуками
|
|
273
|
-
|
|
274
|
-
---
|
|
275
|
-
|
|
276
|
-
## Технические детали
|
|
277
|
-
|
|
278
|
-
### Декодирование chunks
|
|
279
|
-
|
|
280
|
-
```typescript
|
|
281
|
-
async function decodeChunk(
|
|
282
|
-
chunk: ArrayBuffer,
|
|
283
|
-
audioContext: AudioContext
|
|
284
|
-
): Promise<Float32Array> {
|
|
285
|
-
// Для MP3/AAC нужен полный frame
|
|
286
|
-
// Возможно потребуется накопление данных
|
|
287
|
-
const audioBuffer = await audioContext.decodeAudioData(chunk);
|
|
288
|
-
return audioBuffer.getChannelData(0); // mono
|
|
289
|
-
}
|
|
290
|
-
```
|
|
291
|
-
|
|
292
|
-
**Проблема:** `decodeAudioData` требует валидный аудио-контейнер. Для streaming нужно:
|
|
293
|
-
- Либо накапливать достаточно данных для декодирования
|
|
294
|
-
- Либо использовать Web Codecs API (AudioDecoder)
|
|
295
|
-
- Либо генерировать peaks на backend
|
|
296
|
-
|
|
297
|
-
### Альтернатива: Backend peaks
|
|
298
|
-
|
|
299
|
-
Если декодирование на frontend сложное, можно:
|
|
300
|
-
1. Go генерирует peaks через FFmpeg при первом запросе
|
|
301
|
-
2. Кэширует в файл рядом с аудио
|
|
302
|
-
3. Frontend загружает peaks JSON отдельно
|
|
303
|
-
|
|
304
|
-
```bash
|
|
305
|
-
# FFmpeg генерация peaks
|
|
306
|
-
ffmpeg -i audio.mp3 -af "asetnsamples=n=1024,astats=metadata=1:reset=1" -f null - 2>&1 | grep lavfi.astats
|
|
307
|
-
```
|
|
308
|
-
|
|
309
|
-
Или использовать `audiowaveform`:
|
|
310
|
-
```bash
|
|
311
|
-
audiowaveform -i audio.mp3 -o peaks.json --pixels-per-second 10
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
---
|
|
315
|
-
|
|
316
|
-
## Миграция
|
|
317
|
-
|
|
318
|
-
### Для cmdop AudioViewer
|
|
319
|
-
|
|
320
|
-
```typescript
|
|
321
|
-
// Было:
|
|
322
|
-
<SimpleAudioPlayer src={streamUrl} showWaveform />
|
|
323
|
-
|
|
324
|
-
// Стало:
|
|
325
|
-
<ProgressiveAudioPlayer src={streamUrl} showWaveform />
|
|
326
|
-
```
|
|
327
|
-
|
|
328
|
-
### Backwards compatibility
|
|
329
|
-
|
|
330
|
-
- Существующие компоненты остаются для blob/local файлов
|
|
331
|
-
- `ProgressiveAudioPlayer` для streaming URLs
|
|
332
|
-
- `SimpleAudioPlayer` получает prop `progressive?: boolean`
|
|
333
|
-
|
|
334
|
-
---
|
|
335
|
-
|
|
336
|
-
## Оценка сложности
|
|
337
|
-
|
|
338
|
-
| Компонент | Сложность | Строки кода |
|
|
339
|
-
|-----------|-----------|-------------|
|
|
340
|
-
| useAudioElement | Низкая | ~80 |
|
|
341
|
-
| peaks utils | Низкая | ~50 |
|
|
342
|
-
| useProgressiveWaveform | Высокая | ~150 |
|
|
343
|
-
| WaveformCanvas | Средняя | ~200 |
|
|
344
|
-
| ProgressiveAudioPlayer | Средняя | ~150 |
|
|
345
|
-
| Context | Низкая | ~100 |
|
|
346
|
-
| **Итого** | | **~730** |
|
|
347
|
-
|
|
348
|
-
**Сравнение:** WaveSurfer.js = ~15,000 строк
|
|
349
|
-
|
|
350
|
-
---
|
|
351
|
-
|
|
352
|
-
## Риски и альтернативы
|
|
353
|
-
|
|
354
|
-
### Риск 1: decodeAudioData требует полный файл
|
|
355
|
-
|
|
356
|
-
**Решение:** Использовать Web Codecs API или генерировать peaks на backend
|
|
357
|
-
|
|
358
|
-
### Риск 2: Производительность canvas на длинных треках
|
|
359
|
-
|
|
360
|
-
**Решение:** Виртуализация - рендерить только видимую область
|
|
361
|
-
|
|
362
|
-
### Риск 3: Совместимость браузеров
|
|
363
|
-
|
|
364
|
-
**Решение:** AudioContext поддерживается везде, Web Codecs - fallback на backend peaks
|
|
365
|
-
|
|
366
|
-
---
|
|
367
|
-
|
|
368
|
-
## Следующий шаг
|
|
369
|
-
|
|
370
|
-
Начать с **Этапа 1** - базовые хуки и утилиты.
|
|
371
|
-
|
|
372
|
-
Или сначала проверить **backend peaks** подход - если Go может генерировать peaks, это упростит frontend.
|