@grundyjs/canvas-take 0.4.1 → 0.5.0
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/CHANGELOG.md +11 -0
- package/README.md +85 -6
- package/dist/aac-PROVENANCE.md +171 -0
- package/dist/aac-licenses/COPYING.LGPLv2.1 +502 -0
- package/dist/aac-licenses/LICENSE.md +127 -0
- package/dist/aac-licenses/MPL-2.0.txt +373 -0
- package/dist/aac-licenses/bridge.c +191 -0
- package/dist/gif.d.ts +6 -0
- package/dist/gif.js +16 -2
- package/dist/index.d.ts +8 -1
- package/dist/index.js +90 -6
- package/dist/mediabunny-LICENSE.txt +373 -0
- package/dist/video-aac-worker.d.ts +1 -0
- package/dist/video-aac-worker.js +3356 -0
- package/dist/video-aac-worker.js.map +7 -0
- package/dist/video-aac.d.ts +8 -0
- package/dist/video-aac.js +174 -0
- package/dist/video-mp4.d.ts +3 -0
- package/dist/video-mp4.js +38 -0
- package/dist/video-mp4.js.map +7 -0
- package/dist/video-webcodecs.d.ts +3 -0
- package/dist/video-webcodecs.js +149 -0
- package/dist/video.d.ts +215 -0
- package/dist/video.js +503 -0
- package/docs/drafts/offline-video-api.d.ts +2 -0
- package/docs/drafts/offline-video-example.ts +38 -0
- package/docs/drafts/tsconfig.json +12 -0
- package/docs/gif-benchmark.md +129 -0
- package/docs/gif-encoder.md +4 -0
- package/docs/offline-aac.md +87 -0
- package/docs/offline-video-adapters.md +84 -0
- package/docs/offline-video-api.md +154 -0
- package/docs/video-recording.md +55 -0
- package/package.json +42 -6
- package/ru/README.md +156 -8
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@grundyjs/canvas-take",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Record
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "Record canvas video with audio and export offline H.264/AAC MP4 or GIF with pluggable encoders, Worker support and explicit resource cleanup",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
7
7
|
"dist",
|
|
@@ -9,9 +9,36 @@
|
|
|
9
9
|
"ru/README.md",
|
|
10
10
|
"LICENSE",
|
|
11
11
|
"CHANGELOG.md",
|
|
12
|
-
"docs/gif-encoder.md"
|
|
12
|
+
"docs/gif-encoder.md",
|
|
13
|
+
"docs/gif-benchmark.md",
|
|
14
|
+
"docs/video-recording.md",
|
|
15
|
+
"docs/offline-video-api.md",
|
|
16
|
+
"docs/offline-video-adapters.md",
|
|
17
|
+
"docs/offline-aac.md",
|
|
18
|
+
"docs/drafts"
|
|
13
19
|
],
|
|
14
20
|
"exports": {
|
|
21
|
+
"./video/aac": {
|
|
22
|
+
"types": "./dist/video-aac.d.ts",
|
|
23
|
+
"import": "./dist/video-aac.js",
|
|
24
|
+
"default": "./dist/video-aac.js"
|
|
25
|
+
},
|
|
26
|
+
"./video/aac-worker": "./dist/video-aac-worker.js",
|
|
27
|
+
"./video/webcodecs": {
|
|
28
|
+
"types": "./dist/video-webcodecs.d.ts",
|
|
29
|
+
"import": "./dist/video-webcodecs.js",
|
|
30
|
+
"default": "./dist/video-webcodecs.js"
|
|
31
|
+
},
|
|
32
|
+
"./video/mp4": {
|
|
33
|
+
"types": "./dist/video-mp4.d.ts",
|
|
34
|
+
"import": "./dist/video-mp4.js",
|
|
35
|
+
"default": "./dist/video-mp4.js"
|
|
36
|
+
},
|
|
37
|
+
"./video": {
|
|
38
|
+
"types": "./dist/video.d.ts",
|
|
39
|
+
"import": "./dist/video.js",
|
|
40
|
+
"default": "./dist/video.js"
|
|
41
|
+
},
|
|
15
42
|
".": {
|
|
16
43
|
"types": "./dist/index.d.ts",
|
|
17
44
|
"import": "./dist/index.js",
|
|
@@ -35,19 +62,24 @@
|
|
|
35
62
|
"./gif/worker-runtime": "./dist/gif-worker-runtime.js"
|
|
36
63
|
},
|
|
37
64
|
"scripts": {
|
|
38
|
-
"build": "tsc -p tsconfig.json && node scripts/build-
|
|
65
|
+
"build": "tsc -p tsconfig.json && node scripts/build-adapters.mjs",
|
|
39
66
|
"test": "npm run build && node --test test/*.test.mjs",
|
|
40
67
|
"prepack": "npm run build",
|
|
41
68
|
"prepublishOnly": "npm test",
|
|
42
69
|
"test:package": "node scripts/test-package.mjs",
|
|
70
|
+
"test:release-video": "node scripts/test-release-video.mjs",
|
|
43
71
|
"test:next": "node scripts/test-next.mjs",
|
|
44
|
-
"test:browser": "npm run build && playwright test"
|
|
72
|
+
"test:browser": "npm run build && playwright test",
|
|
73
|
+
"benchmark:gif": "npm run build && node scripts/benchmark-gif.mjs"
|
|
45
74
|
},
|
|
46
75
|
"devDependencies": {
|
|
76
|
+
"@mediabunny/aac-encoder": "1.65.0",
|
|
47
77
|
"@playwright/test": "1.64.1",
|
|
48
78
|
"esbuild": "0.28.2",
|
|
79
|
+
"ffmpeg-static": "5.3.0",
|
|
49
80
|
"gifenc": "1.0.3",
|
|
50
81
|
"gifuct-js": "2.1.2",
|
|
82
|
+
"mediabunny": "1.65.0",
|
|
51
83
|
"typescript": "6.0.3"
|
|
52
84
|
},
|
|
53
85
|
"license": "MIT",
|
|
@@ -64,7 +96,11 @@
|
|
|
64
96
|
"gif",
|
|
65
97
|
"audio",
|
|
66
98
|
"web-worker",
|
|
67
|
-
"typescript"
|
|
99
|
+
"typescript",
|
|
100
|
+
"webcodecs",
|
|
101
|
+
"mp4",
|
|
102
|
+
"aac",
|
|
103
|
+
"offline-video"
|
|
68
104
|
],
|
|
69
105
|
"sideEffects": false,
|
|
70
106
|
"publishConfig": {
|
package/ru/README.md
CHANGED
|
@@ -5,9 +5,11 @@
|
|
|
5
5
|
[Сайт](https://grundyjs.ru/canvas-take/) · [npm](https://www.npmjs.com/package/@grundyjs/canvas-take) · [Ошибки и предложения](https://github.com/urffin/canvas-take/issues)
|
|
6
6
|
|
|
7
7
|
Независимая TypeScript-библиотека для записи видео с HTML canvas с необязательными
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
с декларациями TypeScript.
|
|
8
|
+
аудиодорожками, офлайн-экспорта MP4 H.264/AAC и GIF через отдельные точки входа.
|
|
9
|
+
Без внешних runtime npm-зависимостей и без зависимости от AlgiViz. Только ESM,
|
|
10
|
+
с декларациями TypeScript. Код CanvasTake — MIT; опциональные адаптеры включают
|
|
11
|
+
[Mediabunny MPL-2.0](../docs/offline-video-adapters.md#distribution-and-sources) и
|
|
12
|
+
[AAC WASM / FFmpeg LGPL](../docs/offline-aac.md#distribution-and-source). Пакет не запускает и не рисует вашу анимацию.
|
|
11
13
|
|
|
12
14
|
## Установка
|
|
13
15
|
|
|
@@ -39,11 +41,11 @@ recorder.dispose();
|
|
|
39
41
|
При записи со звуком варианты VP9 и VP8 включают Opus. Явно заданный неподдерживаемый
|
|
40
42
|
MIME вызывает ошибку без скрытой смены формата. Результат содержит фактический MIME
|
|
41
43
|
и соответствующее расширение. FPS и битрейт — целевые значения, а не гарантии.
|
|
42
|
-
durationMs измеряет
|
|
44
|
+
`durationMs` измеряет активное время записи без пауз, а не длительность, прочитанную из файла.
|
|
43
45
|
|
|
44
46
|
Не меняйте размеры битовой карты canvas от start до stop. Масштабирование через CSS
|
|
45
47
|
допускается. Изменение размеров завершает запись ошибкой. Canvas должен оставаться
|
|
46
|
-
origin-clean. Захват экрана,
|
|
48
|
+
origin-clean. Захват экрана, покадровое кодирование видео и отправка
|
|
47
49
|
на сервер не поддерживаются. Фрагменты хранятся в памяти: ограничивайте длительность.
|
|
48
50
|
Если замедление фоновой вкладки недопустимо, вызывайте dispose при скрытии страницы.
|
|
49
51
|
Поддержка кодеков зависит от браузера и доступных ресурсов.
|
|
@@ -67,11 +69,65 @@ WebM от MediaRecorder может не содержать метаданные
|
|
|
67
69
|
| `videoBitsPerSecond` | Необязательный положительный конечный целевой битрейт кодировщика |
|
|
68
70
|
| `mimeType` | Необязательный явно заданный поддерживаемый MIME `video/webm` или `video/mp4`, включая кодеки |
|
|
69
71
|
| `audioTracks` | Необязательный список только для чтения из активных звуковых `MediaStreamTrack`; без него или с пустым списком запись идёт без звука |
|
|
72
|
+
| `maxDurationMs` | С 0.5.0: положительное безопасное целое, лимит активного времени в мс без пауз |
|
|
70
73
|
|
|
71
|
-
`stop()` возвращает `{ blob, mimeType, extension, durationMs }
|
|
74
|
+
`stop()` возвращает `{ blob, mimeType, extension, durationMs, stopReason }` (поле `stopReason` доступно с 0.5.0). Используйте рекордер
|
|
72
75
|
в try/finally и вызывайте `dispose()`, даже если анимация завершилась ошибкой.
|
|
73
76
|
Освобождайте object URL, когда они больше не нужны.
|
|
74
77
|
|
|
78
|
+
## Пауза, продолжение и лимит времени (с версии 0.5.0)
|
|
79
|
+
|
|
80
|
+
```js
|
|
81
|
+
const recorder = createCanvasRecorder(canvas, { fps: 30, maxDurationMs: 30_000 });
|
|
82
|
+
try {
|
|
83
|
+
await recorder.start();
|
|
84
|
+
// В обработчиках UI: await recorder.pause(); await recorder.resume();
|
|
85
|
+
const result = await recorder.finished;
|
|
86
|
+
console.log(result.stopReason, result.durationMs);
|
|
87
|
+
} finally {
|
|
88
|
+
recorder.dispose();
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`pause()` и `resume()` завершаются после соответствующих событий MediaRecorder.
|
|
93
|
+
Состояния `recording` и `paused` меняются при этих событиях; внутренний MediaRecorder
|
|
94
|
+
может изменить состояние раньше. Повтор в уже достигнутом состоянии успешен,
|
|
95
|
+
повтор ожидающей операции возвращает тот же promise, а противоположная операция
|
|
96
|
+
до её завершения отклоняется. До завершения старта, во время остановки и после
|
|
97
|
+
завершения или dispose эти операции отклоняются. Остановка из паузы разрешена.
|
|
98
|
+
Остановка, ошибка и dispose отклоняют ожидающий pause/resume; обычная остановка
|
|
99
|
+
при этом возвращает финальное видео.
|
|
100
|
+
|
|
101
|
+
`finished` — стабильный promise с момента создания: он ждёт результата без команды
|
|
102
|
+
остановки, разрешается при ручном или автоматическом завершении и отклоняется при
|
|
103
|
+
ошибке или dispose. Повторные `stop()` возвращают тот же результат.
|
|
104
|
+
`stopReason` равен `manual` или `duration-limit`; сохраняется причина первого запроса остановки.
|
|
105
|
+
|
|
106
|
+
`maxDurationMs` проверяется до захвата и фиксируется при создании. Без него автоматической
|
|
107
|
+
остановки нет. Бюджет считается от события start до pause и от resume до следующей
|
|
108
|
+
паузы/остановки. Превышение вызывает обычную остановку с сохранением финального фрагмента.
|
|
109
|
+
Из-за задержек таймеров, событий и кодирования это не покадрово точный предел файла;
|
|
110
|
+
`durationMs` может оказаться больше лимита под нагрузкой.
|
|
111
|
+
|
|
112
|
+
Исходные аудиодорожки и анимация продолжают работать во время паузы: их временной
|
|
113
|
+
шкалой управляет приложение. Рекордер освобождает только принадлежащие ему дорожки
|
|
114
|
+
захвата и аудиоклоны, таймеры, обработчики и накопленные фрагменты. Размер canvas
|
|
115
|
+
должен оставаться постоянным и во время паузы.
|
|
116
|
+
|
|
117
|
+
`examples/video.html` демонстрирует выбор формата, звук, паузу, продолжение,
|
|
118
|
+
ручную/автоматическую остановку, превью и скачивание. Браузерные тесты записывают
|
|
119
|
+
и воспроизводят WebM и поддерживаемый MP4 H.264/AAC, проверяют звук, исключение пауз
|
|
120
|
+
и владение дорожками; неподдерживаемые кодеки явно пропускаются.
|
|
121
|
+
|
|
122
|
+
[Проверка синхронизации видео и звука](../docs/video-recording.md) использует шесть
|
|
123
|
+
вспышек/сигналов до и после двух пауз, независимое декодирование FFmpeg с временными
|
|
124
|
+
метками контейнера и контрольную копию с намеренно задержанным звуком.
|
|
125
|
+
FFmpeg используется только в инструментах разработки и не включается в библиотеку.
|
|
126
|
+
|
|
127
|
+
Первые видеокадры могут предшествовать событию start, особенно при запуске аудиодвижка.
|
|
128
|
+
Лимит активного времени не обрезает этот стартовый материал: длительность файла может
|
|
129
|
+
включать дополнительное время запуска и завершения кодировщика.
|
|
130
|
+
|
|
75
131
|
## Запись с озвучкой
|
|
76
132
|
|
|
77
133
|
Сведите звук в одну выходную дорожку Web Audio: несколько звуковых дорожек
|
|
@@ -139,6 +195,43 @@ CanvasTake клонирует дорожки при start и при заверш
|
|
|
139
195
|
для завершения записи вызовите `recorder.stop()`. Окончание AudioBufferSource
|
|
140
196
|
также не завершает выходную дорожку.
|
|
141
197
|
|
|
198
|
+
## Контроллер офлайн-видео (с версии 0.5.0)
|
|
199
|
+
|
|
200
|
+
`@grundyjs/canvas-take/video` содержит `createOfflineVideoExporter` и контракты
|
|
201
|
+
подключаемых видео-/аудиокодировщиков и упаковщика контейнера. Отдельные импорты
|
|
202
|
+
`/video/webcodecs` (`createWebCodecsVideoEncoder`), `/video/aac` (`createAacAudioEncoder`)
|
|
203
|
+
и `/video/mp4` (`createMp4Muxer`) создают реальный **MP4 H.264 Baseline / AAC-LC**.
|
|
204
|
+
Видео требует WebCodecs; AAC кодируется в отдельном Worker через встроенный
|
|
205
|
+
libavcodec WASM, включая браузеры без нативного AAC-кодировщика. Подготовленный PCM
|
|
206
|
+
необязателен. Файлы, задержка, обрезка и A/V проверяются независимым FFmpeg-декодером.
|
|
207
|
+
|
|
208
|
+
```js
|
|
209
|
+
import { createOfflineVideoExporter } from '@grundyjs/canvas-take/video';
|
|
210
|
+
// options содержит video.encoder/config, необязательное подготовленное аудио и muxer.
|
|
211
|
+
const exporter = await createOfflineVideoExporter(options);
|
|
212
|
+
try {
|
|
213
|
+
drawFrame(0);
|
|
214
|
+
await exporter.addFrame(canvas, { timestampUs: 0, durationUs: 33_333 });
|
|
215
|
+
const result = await exporter.finish();
|
|
216
|
+
} finally {
|
|
217
|
+
exporter.dispose();
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Временная шкала непрерывна и начинается с нуля, единица времени — целая микросекунда.
|
|
222
|
+
Длительности кадров и позиции аудиосэмплов не зависят от скорости кодирования.
|
|
223
|
+
Контроллер копирует входные данные, ограничивает очереди, последовательно передаёт
|
|
224
|
+
пакеты упаковщику и освобождает свои адаптеры при успехе, ошибке и отмене.
|
|
225
|
+
Политика несовпадения длительности аудио задаётся явно: ошибка либо обрезка/тишина.
|
|
226
|
+
[Описание API и протокола](../docs/offline-video-api.md) включает владение ресурсами,
|
|
227
|
+
flush, отмену и лимиты. [Реальные адаптеры и точность временной шкалы](../docs/offline-video-adapters.md)
|
|
228
|
+
содержат рабочий пример API; [браузерный пример](../examples/offline-video.html) позволяет
|
|
229
|
+
экспортировать, отменить и скачать MP4. Упаковщик округляет абсолютные границы кадров
|
|
230
|
+
до шкалы 57 600 Гц: дробная частота и финальное удержание проверены без накопления
|
|
231
|
+
ошибки. [AAC: задержка и число сэмплов](../docs/offline-aac.md) описывает проверку
|
|
232
|
+
44,1/48 кГц, mono/stereo, priming и padding. Длительность представления MP4 исключает
|
|
233
|
+
AAC padding; при выводе полных AAC-кадров в сырой PCM нужно применять конечную границу контейнера.
|
|
234
|
+
|
|
142
235
|
## Покадровый экспорт GIF
|
|
143
236
|
|
|
144
237
|
Доступен с версии 0.3.0.
|
|
@@ -308,11 +401,66 @@ AlgiViz и не изменяет тот проект.
|
|
|
308
401
|
`npm test` запускает модульные тесты и проверки Node Worker, а `npm run test:package`
|
|
309
402
|
проверяет установку упакованной библиотеки в отдельный проект. Для настоящих браузеров
|
|
310
403
|
сначала выполните `npx playwright install chromium firefox`, затем `npm run test:browser`.
|
|
311
|
-
Проверяются
|
|
312
|
-
и независимое декодирование
|
|
404
|
+
Проверяются запись видео, офлайн MP4/AAC и GIF, отмена, повторный экспорт, освобождение
|
|
405
|
+
ресурсов и независимое декодирование звука/временной шкалы. `npm run test:next` проверяет сборку
|
|
313
406
|
упакованной библиотеки с Next.js/Turbopack. Подробности — в
|
|
314
407
|
[описании проверок GIF](../docs/gif-encoder.md#verification).
|
|
315
408
|
|
|
409
|
+
`npm run test:release-video` устанавливает tarball кандидата и из его ассетов экспортирует
|
|
410
|
+
10-секундный MP4 H.264/AAC, 1080×1920. Проверяет все 300 декодированных кадров,
|
|
411
|
+
480 000 аудиосэмплов представления, воспроизведение, размеры и четыре A/V-маркера.
|
|
412
|
+
MP4 и метрики сохраняются в `test-results/release-video/`.
|
|
413
|
+
|
|
414
|
+
Для сравнения производительности GIF-адаптеров выполните `npm run benchmark:gif`
|
|
415
|
+
(короткий прогон: `npm run benchmark:gif -- --quick`). Матрица разрешений и длительностей,
|
|
416
|
+
границы измерений и ограничения описаны в [методике бенчмарка](../docs/gif-benchmark.md).
|
|
417
|
+
|
|
418
|
+
Команда `npm run benchmark:gif -- --compare-opacity` сравнивает код до оптимизации
|
|
419
|
+
с текущим на непрозрачных кадрах, смешанной прозрачности и полупрозрачных градиентах.
|
|
420
|
+
|
|
421
|
+
## Лимиты GIF (с версии 0.5.0)
|
|
422
|
+
|
|
423
|
+
Пример `examples/gif.html` позволяет задать все три лимита. Значения по умолчанию
|
|
424
|
+
соответствуют его анимации: 800×450 пикселей, 121 кадр и 8 000 мс с финальной паузой.
|
|
425
|
+
Пустое поле отключает дополнительный лимит. При превышении пример объясняет, что
|
|
426
|
+
увеличить, и возвращает настройки для повтора. Новый запуск убирает предыдущие
|
|
427
|
+
ссылку скачивания и превью, чтобы старый результат не показывался после ошибки.
|
|
428
|
+
|
|
429
|
+
В `createGifExporter` можно задать ограничения, одинаковые для синхронного и Worker-адаптеров:
|
|
430
|
+
|
|
431
|
+
```js
|
|
432
|
+
const gif = createGifExporter({
|
|
433
|
+
encoder: createGifWorkerEncoder(),
|
|
434
|
+
maxFramePixels: 1920 * 1080,
|
|
435
|
+
maxFrames: 600,
|
|
436
|
+
maxDurationMs: 30_000
|
|
437
|
+
});
|
|
438
|
+
try {
|
|
439
|
+
await gif.addFrame(canvas, 50);
|
|
440
|
+
const blob = await gif.finish();
|
|
441
|
+
} catch (error) {
|
|
442
|
+
// При превышении лимита addFrame отклоняется с RangeError и именем лимита.
|
|
443
|
+
console.error(error);
|
|
444
|
+
} finally {
|
|
445
|
+
gif.dispose();
|
|
446
|
+
}
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
Каждое значение — положительное безопасное целое число. Без параметра дополнительного
|
|
450
|
+
ограничения по нему нет; ограничения формата GIF сохраняются. Границы включительны:
|
|
451
|
+
`maxFramePixels` ограничивает `width * height`, `maxFrames` — число успешно закодированных
|
|
452
|
+
кадров, `maxDurationMs` — сумму длительностей после округления каждого кадра до 10 мс.
|
|
453
|
+
Два кадра по 54 мс занимают 100 мс, кадр на 55 мс — 60 мс. Повторное воспроизведение
|
|
454
|
+
не умножает длительность. `totalFrames` остаётся подсказкой для прогресса и не связан с `maxFrames`.
|
|
455
|
+
|
|
456
|
+
Неверное значение вызывает `RangeError` при создании, до выделения кодировщика или Worker.
|
|
457
|
+
При превышении лимита `addFrame` отклоняется до чтения пикселей и передачи кадра кодировщику.
|
|
458
|
+
Экспортер переходит в конечное состояние `failed`, сообщает один снимок прогресса ошибки
|
|
459
|
+
с ранее завершёнными кадрами, удаляет обработчик отмены и освобождает кодировщик, включая
|
|
460
|
+
завершение принадлежащего ему Worker. Получить частичный GIF через `finish` после ошибки
|
|
461
|
+
нельзя; для повтора нужен новый экспортер. Исходный canvas сохраняется. Лимиты ограничивают
|
|
462
|
+
входную работу, но не задают точный предел памяти или размера файла.
|
|
463
|
+
|
|
316
464
|
## Выпуск версии
|
|
317
465
|
|
|
318
466
|
Запустите `npm test` и `npm run test:package`: последняя команда упаковывает архив,
|