@pproenca/node-webcodecs 0.1.0 → 0.1.1-alpha.5

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.
Files changed (103) hide show
  1. package/README.md +78 -206
  2. package/binding.gyp +123 -0
  3. package/dist/audio-decoder.js +1 -2
  4. package/dist/audio-encoder.d.ts +4 -0
  5. package/dist/audio-encoder.js +28 -2
  6. package/dist/binding.d.ts +0 -2
  7. package/dist/binding.js +43 -124
  8. package/dist/control-message-queue.js +0 -1
  9. package/dist/demuxer.d.ts +7 -0
  10. package/dist/demuxer.js +9 -0
  11. package/dist/encoded-chunks.d.ts +16 -0
  12. package/dist/encoded-chunks.js +82 -2
  13. package/dist/image-decoder.js +4 -0
  14. package/dist/index.d.ts +17 -3
  15. package/dist/index.js +9 -4
  16. package/dist/is.d.ts +18 -0
  17. package/dist/is.js +14 -0
  18. package/dist/native-types.d.ts +20 -0
  19. package/dist/platform.d.ts +1 -10
  20. package/dist/platform.js +1 -39
  21. package/dist/resource-manager.d.ts +1 -2
  22. package/dist/resource-manager.js +3 -17
  23. package/dist/types.d.ts +46 -0
  24. package/dist/video-decoder.d.ts +21 -0
  25. package/dist/video-decoder.js +74 -2
  26. package/dist/video-encoder.d.ts +22 -0
  27. package/dist/video-encoder.js +83 -8
  28. package/dist/video-frame.d.ts +6 -3
  29. package/dist/video-frame.js +36 -4
  30. package/lib/audio-decoder.ts +1 -2
  31. package/lib/audio-encoder.ts +31 -2
  32. package/lib/binding.ts +45 -104
  33. package/lib/control-message-queue.ts +0 -1
  34. package/lib/demuxer.ts +10 -0
  35. package/lib/encoded-chunks.ts +90 -2
  36. package/lib/image-decoder.ts +5 -0
  37. package/lib/index.ts +9 -3
  38. package/lib/is.ts +32 -0
  39. package/lib/native-types.ts +22 -0
  40. package/lib/platform.ts +1 -41
  41. package/lib/resource-manager.ts +3 -19
  42. package/lib/types.ts +52 -1
  43. package/lib/video-decoder.ts +84 -2
  44. package/lib/video-encoder.ts +90 -8
  45. package/lib/video-frame.ts +52 -7
  46. package/package.json +49 -32
  47. package/src/addon.cc +57 -0
  48. package/src/async_decode_worker.cc +243 -36
  49. package/src/async_decode_worker.h +55 -4
  50. package/src/async_encode_worker.cc +155 -44
  51. package/src/async_encode_worker.h +38 -12
  52. package/src/audio_data.cc +38 -15
  53. package/src/audio_data.h +1 -0
  54. package/src/audio_decoder.cc +24 -3
  55. package/src/audio_encoder.cc +55 -4
  56. package/src/common.cc +125 -17
  57. package/src/common.h +34 -4
  58. package/src/demuxer.cc +16 -2
  59. package/src/encoded_audio_chunk.cc +10 -0
  60. package/src/encoded_audio_chunk.h +2 -0
  61. package/src/encoded_video_chunk.h +1 -0
  62. package/src/error_builder.cc +0 -4
  63. package/src/image_decoder.cc +127 -90
  64. package/src/image_decoder.h +11 -4
  65. package/src/muxer.cc +1 -0
  66. package/src/test_video_generator.cc +3 -2
  67. package/src/video_decoder.cc +169 -19
  68. package/src/video_decoder.h +9 -11
  69. package/src/video_encoder.cc +428 -35
  70. package/src/video_encoder.h +16 -0
  71. package/src/video_filter.cc +22 -11
  72. package/src/video_frame.cc +160 -5
  73. package/src/warnings.cc +0 -4
  74. package/dist/audio-data.js.map +0 -1
  75. package/dist/audio-decoder.js.map +0 -1
  76. package/dist/audio-encoder.js.map +0 -1
  77. package/dist/binding.js.map +0 -1
  78. package/dist/codec-base.js.map +0 -1
  79. package/dist/control-message-queue.js.map +0 -1
  80. package/dist/demuxer.js.map +0 -1
  81. package/dist/encoded-chunks.js.map +0 -1
  82. package/dist/errors.js.map +0 -1
  83. package/dist/ffmpeg.d.ts +0 -21
  84. package/dist/ffmpeg.js +0 -112
  85. package/dist/image-decoder.js.map +0 -1
  86. package/dist/image-track-list.js.map +0 -1
  87. package/dist/image-track.js.map +0 -1
  88. package/dist/index.js.map +0 -1
  89. package/dist/is.js.map +0 -1
  90. package/dist/muxer.js.map +0 -1
  91. package/dist/native-types.js.map +0 -1
  92. package/dist/platform.js.map +0 -1
  93. package/dist/resource-manager.js.map +0 -1
  94. package/dist/test-video-generator.js.map +0 -1
  95. package/dist/transfer.js.map +0 -1
  96. package/dist/types.js.map +0 -1
  97. package/dist/video-decoder.js.map +0 -1
  98. package/dist/video-encoder.js.map +0 -1
  99. package/dist/video-filter.js.map +0 -1
  100. package/dist/video-frame.js.map +0 -1
  101. package/install/build.js +0 -51
  102. package/install/check.js +0 -192
  103. package/lib/ffmpeg.ts +0 -78
@@ -3,6 +3,7 @@
3
3
 
4
4
  #include "src/audio_encoder.h"
5
5
 
6
+ #include <cstdio>
6
7
  #include <string>
7
8
  #include <vector>
8
9
 
@@ -43,6 +44,8 @@ AudioEncoder::AudioEncoder(const Napi::CallbackInfo& info)
43
44
  number_of_channels_(0),
44
45
  timestamp_(0),
45
46
  frame_count_(0) {
47
+ // Track active encoder instance
48
+ webcodecs::counterAudioEncoders++;
46
49
  Napi::Env env = info.Env();
47
50
 
48
51
  if (info.Length() < 1 || !info[0].IsObject()) {
@@ -62,9 +65,27 @@ AudioEncoder::AudioEncoder(const Napi::CallbackInfo& info)
62
65
  error_callback_ = Napi::Persistent(init.Get("error").As<Napi::Function>());
63
66
  }
64
67
 
65
- AudioEncoder::~AudioEncoder() { Cleanup(); }
68
+ AudioEncoder::~AudioEncoder() {
69
+ // CRITICAL: Call Cleanup() first to ensure codec context is properly
70
+ // flushed before any further cleanup.
71
+ Cleanup();
72
+
73
+ // Now safe to disable FFmpeg logging.
74
+ webcodecs::ShutdownFFmpegLogging();
75
+
76
+ webcodecs::counterAudioEncoders--;
77
+ }
66
78
 
67
79
  void AudioEncoder::Cleanup() {
80
+ // Flush codec internal buffers BEFORE destroying resources.
81
+ // Audio codecs (opus, aac, mp3) may have internal queued samples. Flushing
82
+ // ensures they're drained before context destruction.
83
+ // CRITICAL: Only flush if codec was successfully opened. avcodec_flush_buffers
84
+ // crashes on an unopened codec context (the internal codec pointer is NULL).
85
+ if (codec_context_ && avcodec_is_open(codec_context_.get())) {
86
+ avcodec_flush_buffers(codec_context_.get());
87
+ }
88
+
68
89
  frame_.reset();
69
90
  packet_.reset();
70
91
  swr_context_.reset();
@@ -94,6 +115,12 @@ Napi::Value AudioEncoder::Configure(const Napi::CallbackInfo& info) {
94
115
  codec_id = AV_CODEC_ID_OPUS;
95
116
  } else if (codec_str.find("mp4a.40") == 0) {
96
117
  codec_id = AV_CODEC_ID_AAC;
118
+ } else if (codec_str == "flac") {
119
+ codec_id = AV_CODEC_ID_FLAC;
120
+ } else if (codec_str == "mp3") {
121
+ codec_id = AV_CODEC_ID_MP3;
122
+ } else if (codec_str == "vorbis") {
123
+ codec_id = AV_CODEC_ID_VORBIS;
97
124
  }
98
125
 
99
126
  // Find encoder.
@@ -133,10 +160,19 @@ Napi::Value AudioEncoder::Configure(const Napi::CallbackInfo& info) {
133
160
  codec_context_->bit_rate = webcodecs::AttrAsInt64(config, "bitrate", 128000);
134
161
 
135
162
  // Set sample format based on codec.
136
- // Opus uses non-planar float (flt), AAC uses planar float (fltp).
163
+ // Different codecs require different sample formats:
164
+ // - Opus: non-planar float (flt)
165
+ // - AAC/Vorbis: planar float (fltp)
166
+ // - MP3: planar signed 16-bit (s16p) or planar float (fltp)
167
+ // - FLAC: signed 16-bit (s16) or signed 32-bit (s32)
137
168
  if (codec_id == AV_CODEC_ID_OPUS) {
138
169
  codec_context_->sample_fmt = AV_SAMPLE_FMT_FLT;
170
+ } else if (codec_id == AV_CODEC_ID_FLAC) {
171
+ codec_context_->sample_fmt = AV_SAMPLE_FMT_S16;
172
+ } else if (codec_id == AV_CODEC_ID_MP3) {
173
+ codec_context_->sample_fmt = AV_SAMPLE_FMT_S16P;
139
174
  } else {
175
+ // AAC and Vorbis use planar float
140
176
  codec_context_->sample_fmt = AV_SAMPLE_FMT_FLTP;
141
177
  }
142
178
 
@@ -298,9 +334,9 @@ void AudioEncoder::Close(const Napi::CallbackInfo& info) {
298
334
  Napi::Value AudioEncoder::Reset(const Napi::CallbackInfo& info) {
299
335
  Napi::Env env = info.Env();
300
336
 
337
+ // W3C spec: reset() is a no-op when closed (don't throw)
301
338
  if (state_ == "closed") {
302
- throw Napi::Error::New(env,
303
- "InvalidStateError: Cannot reset closed encoder");
339
+ return env.Undefined();
304
340
  }
305
341
 
306
342
  Cleanup();
@@ -554,6 +590,21 @@ Napi::Value AudioEncoder::IsConfigSupported(const Napi::CallbackInfo& info) {
554
590
  if (!c) {
555
591
  supported = false;
556
592
  }
593
+ } else if (codec == "flac") {
594
+ const AVCodec* c = avcodec_find_encoder(AV_CODEC_ID_FLAC);
595
+ if (!c) {
596
+ supported = false;
597
+ }
598
+ } else if (codec == "mp3") {
599
+ const AVCodec* c = avcodec_find_encoder(AV_CODEC_ID_MP3);
600
+ if (!c) {
601
+ supported = false;
602
+ }
603
+ } else if (codec == "vorbis") {
604
+ const AVCodec* c = avcodec_find_encoder(AV_CODEC_ID_VORBIS);
605
+ if (!c) {
606
+ supported = false;
607
+ }
557
608
  } else {
558
609
  supported = false;
559
610
  }
package/src/common.cc CHANGED
@@ -13,10 +13,70 @@
13
13
 
14
14
  namespace webcodecs {
15
15
 
16
- // Global counters
17
- std::atomic<int> counterQueue{0};
18
- std::atomic<int> counterProcess{0};
19
- std::atomic<int> counterFrames{0};
16
+ // STATIC DESTRUCTION ORDER FIX: Use heap-allocated "immortal" counters.
17
+ // During process exit, static destructors may run in unpredictable order.
18
+ // Codec destructors access these counters, so we must ensure they're never
19
+ // destroyed. We trade a tiny memory leak at exit for crash-free shutdown.
20
+ // This matches the pattern used for FFmpeg logging queue/mutex.
21
+
22
+ // Per-class instance counters for deterministic leak detection
23
+ static std::atomic<int64_t>& GetCounterVideoFrames() {
24
+ static auto* counter = new std::atomic<int64_t>(0);
25
+ return *counter;
26
+ }
27
+ static std::atomic<int64_t>& GetCounterAudioData() {
28
+ static auto* counter = new std::atomic<int64_t>(0);
29
+ return *counter;
30
+ }
31
+ static std::atomic<int64_t>& GetCounterVideoEncoders() {
32
+ static auto* counter = new std::atomic<int64_t>(0);
33
+ return *counter;
34
+ }
35
+ static std::atomic<int64_t>& GetCounterVideoDecoders() {
36
+ static auto* counter = new std::atomic<int64_t>(0);
37
+ return *counter;
38
+ }
39
+ static std::atomic<int64_t>& GetCounterAudioEncoders() {
40
+ static auto* counter = new std::atomic<int64_t>(0);
41
+ return *counter;
42
+ }
43
+ static std::atomic<int64_t>& GetCounterAudioDecoders() {
44
+ static auto* counter = new std::atomic<int64_t>(0);
45
+ return *counter;
46
+ }
47
+
48
+ // Legacy counters (maintained for backwards compatibility)
49
+ static std::atomic<int>& GetCounterQueue() {
50
+ static auto* counter = new std::atomic<int>(0);
51
+ return *counter;
52
+ }
53
+ static std::atomic<int>& GetCounterProcess() {
54
+ static auto* counter = new std::atomic<int>(0);
55
+ return *counter;
56
+ }
57
+ static std::atomic<int>& GetCounterFrames() {
58
+ static auto* counter = new std::atomic<int>(0);
59
+ return *counter;
60
+ }
61
+
62
+ // References to immortal counters for extern linkage
63
+ std::atomic<int64_t>& counterVideoFrames = GetCounterVideoFrames();
64
+ std::atomic<int64_t>& counterAudioData = GetCounterAudioData();
65
+ std::atomic<int64_t>& counterVideoEncoders = GetCounterVideoEncoders();
66
+ std::atomic<int64_t>& counterVideoDecoders = GetCounterVideoDecoders();
67
+ std::atomic<int64_t>& counterAudioEncoders = GetCounterAudioEncoders();
68
+ std::atomic<int64_t>& counterAudioDecoders = GetCounterAudioDecoders();
69
+
70
+ std::atomic<int>& counterQueue = GetCounterQueue();
71
+ std::atomic<int>& counterProcess = GetCounterProcess();
72
+ std::atomic<int>& counterFrames = GetCounterFrames();
73
+
74
+ // FreeCallback for consistent buffer deallocation (following sharp pattern).
75
+ // Default implementation uses delete[]. Can be overridden for platform-specific
76
+ // memory management (e.g., Windows mixed runtime scenarios).
77
+ std::function<void(void*, uint8_t*)> FreeCallback = [](void*, uint8_t* data) {
78
+ delete[] data;
79
+ };
20
80
 
21
81
  //==============================================================================
22
82
  // Attribute Helpers
@@ -276,8 +336,14 @@ Napi::Error FFmpegError(Napi::Env env, const std::string& operation,
276
336
  }
277
337
 
278
338
  std::string FFmpegErrorString(int errnum) {
279
- char errbuf[AV_ERROR_MAX_STRING_SIZE];
280
- av_strerror(errnum, errbuf, sizeof(errbuf));
339
+ char errbuf[AV_ERROR_MAX_STRING_SIZE] = {0};
340
+ int ret = av_strerror(errnum, errbuf, sizeof(errbuf));
341
+ // Check for explicit failure OR empty buffer (ABI mismatch with strerror_r).
342
+ // FFmpeg built on musl expects XSI strerror_r (returns int, writes to buffer).
343
+ // When running on glibc, GNU strerror_r returns char* without writing to buffer.
344
+ if (ret < 0 || errbuf[0] == '\0') {
345
+ snprintf(errbuf, sizeof(errbuf), "Error code %d", errnum);
346
+ }
281
347
  return std::string(errbuf);
282
348
  }
283
349
 
@@ -333,6 +399,15 @@ std::string PixelFormatToString(AVPixelFormat format) {
333
399
  }
334
400
  }
335
401
 
402
+ //==============================================================================
403
+ // String Utilities
404
+ //==============================================================================
405
+
406
+ std::string TrimEnd(const std::string& str) {
407
+ size_t end = str.find_last_not_of(" \t\n\r\f\v");
408
+ return (end == std::string::npos) ? "" : str.substr(0, end + 1);
409
+ }
410
+
336
411
  //==============================================================================
337
412
  // FFmpeg Initialization
338
413
  //==============================================================================
@@ -351,13 +426,31 @@ void InitFFmpeg() {
351
426
  // FFmpeg Logging
352
427
  //==============================================================================
353
428
 
354
- static std::queue<std::string> ffmpegWarnings;
355
- static std::mutex ffmpegWarningsMutex;
429
+ // STATIC DESTRUCTION ORDER FIX: Use heap-allocated "immortal" objects to prevent
430
+ // crashes during process exit. When vitest worker processes exit, static
431
+ // destructors may run in unpredictable order, causing FFmpeg's log callback
432
+ // to access destroyed mutex/queue. By never destroying these, we trade a tiny
433
+ // memory leak at exit for crash-free shutdown.
434
+ static std::queue<std::string>& GetWarningsQueue() {
435
+ static auto* queue = new std::queue<std::string>();
436
+ return *queue;
437
+ }
438
+ static std::mutex& GetWarningsMutex() {
439
+ static auto* mutex = new std::mutex();
440
+ return *mutex;
441
+ }
442
+ static std::atomic<bool> ffmpegLoggingActive{false};
356
443
 
357
444
  void InitFFmpegLogging() {
358
445
  static std::once_flag log_init_once;
359
446
  std::call_once(log_init_once, []() {
447
+ ffmpegLoggingActive.store(true, std::memory_order_release);
360
448
  av_log_set_callback([](void* ptr, int level, const char* fmt, va_list vl) {
449
+ // Guard against callbacks during/after shutdown to prevent
450
+ // static destruction order fiasco on process exit.
451
+ if (!ffmpegLoggingActive.load(std::memory_order_acquire)) {
452
+ return;
453
+ }
361
454
  if (level <= AV_LOG_WARNING) {
362
455
  char buf[1024];
363
456
  vsnprintf(buf, sizeof(buf), fmt, vl);
@@ -369,28 +462,43 @@ void InitFFmpegLogging() {
369
462
  // Skip empty messages
370
463
  if (strlen(buf) == 0) return;
371
464
 
372
- std::lock_guard<std::mutex> lock(ffmpegWarningsMutex);
373
- ffmpegWarnings.push(buf);
465
+ std::lock_guard<std::mutex> lock(GetWarningsMutex());
466
+ GetWarningsQueue().push(buf);
374
467
  }
375
468
  });
376
469
  av_log_set_level(AV_LOG_WARNING);
377
470
  });
378
471
  }
379
472
 
473
+ void ShutdownFFmpegLogging() {
474
+ // Ensure shutdown runs exactly once - multiple concurrent calls from
475
+ // encoder/decoder destructors and cleanup hook could race on av_log_set_callback.
476
+ static std::once_flag shutdown_once;
477
+ std::call_once(shutdown_once, []() {
478
+ // Disable logging callback before static destructors run.
479
+ // This prevents the callback from accessing destroyed statics
480
+ // during process exit (static destruction order fiasco).
481
+ ffmpegLoggingActive.store(false, std::memory_order_release);
482
+ av_log_set_callback(nullptr);
483
+ });
484
+ }
485
+
380
486
  std::vector<std::string> GetFFmpegWarnings() {
381
- std::lock_guard<std::mutex> lock(ffmpegWarningsMutex);
487
+ std::lock_guard<std::mutex> lock(GetWarningsMutex());
382
488
  std::vector<std::string> result;
383
- while (!ffmpegWarnings.empty()) {
384
- result.push_back(ffmpegWarnings.front());
385
- ffmpegWarnings.pop();
489
+ auto& queue = GetWarningsQueue();
490
+ while (!queue.empty()) {
491
+ result.push_back(queue.front());
492
+ queue.pop();
386
493
  }
387
494
  return result;
388
495
  }
389
496
 
390
497
  void ClearFFmpegWarnings() {
391
- std::lock_guard<std::mutex> lock(ffmpegWarningsMutex);
392
- while (!ffmpegWarnings.empty()) {
393
- ffmpegWarnings.pop();
498
+ std::lock_guard<std::mutex> lock(GetWarningsMutex());
499
+ auto& queue = GetWarningsQueue();
500
+ while (!queue.empty()) {
501
+ queue.pop();
394
502
  }
395
503
  }
396
504
 
package/src/common.h CHANGED
@@ -14,6 +14,7 @@ extern "C" {
14
14
  #include <napi.h>
15
15
 
16
16
  #include <atomic>
17
+ #include <functional>
17
18
  #include <mutex>
18
19
  #include <string>
19
20
  #include <tuple>
@@ -109,12 +110,40 @@ AVPixelFormat PixelFormatFromString(const std::string& format);
109
110
  std::string PixelFormatToString(AVPixelFormat format);
110
111
 
111
112
  //==============================================================================
112
- // Global Counters (for monitoring, following sharp pattern)
113
+ // Global Counters (for monitoring and leak detection)
113
114
  //==============================================================================
114
115
 
115
- extern std::atomic<int> counterQueue;
116
- extern std::atomic<int> counterProcess;
117
- extern std::atomic<int> counterFrames;
116
+ // Per-class instance counters for deterministic leak detection.
117
+ // Increment in constructor, decrement in destructor.
118
+ // NOTE: These are references to heap-allocated "immortal" atomics to prevent
119
+ // static destruction order issues during process exit (darwin-x64 fix).
120
+ extern std::atomic<int64_t>& counterVideoFrames;
121
+ extern std::atomic<int64_t>& counterAudioData;
122
+ extern std::atomic<int64_t>& counterVideoEncoders;
123
+ extern std::atomic<int64_t>& counterVideoDecoders;
124
+ extern std::atomic<int64_t>& counterAudioEncoders;
125
+ extern std::atomic<int64_t>& counterAudioDecoders;
126
+
127
+ // Legacy counters (maintained for backwards compatibility)
128
+ extern std::atomic<int>& counterQueue;
129
+ extern std::atomic<int>& counterProcess;
130
+ extern std::atomic<int>& counterFrames; // Legacy frame counter (use counterVideoFrames)
131
+
132
+ //==============================================================================
133
+ // Memory Management (following sharp pattern for Windows compatibility)
134
+ //==============================================================================
135
+
136
+ // FreeCallback for consistent buffer deallocation across platforms.
137
+ // Windows mixed runtime libraries can have issues with different allocators,
138
+ // so using a consistent deallocation function avoids potential crashes.
139
+ extern std::function<void(void*, uint8_t*)> FreeCallback;
140
+
141
+ //==============================================================================
142
+ // String Utilities
143
+ //==============================================================================
144
+
145
+ // Trim whitespace from end of string (following sharp pattern)
146
+ std::string TrimEnd(const std::string& str);
118
147
 
119
148
  //==============================================================================
120
149
  // FFmpeg Initialization
@@ -127,6 +156,7 @@ void InitFFmpeg();
127
156
  //==============================================================================
128
157
 
129
158
  void InitFFmpegLogging();
159
+ void ShutdownFFmpegLogging();
130
160
  std::vector<std::string> GetFFmpegWarnings();
131
161
  void ClearFFmpegWarnings();
132
162
 
package/src/demuxer.cc CHANGED
@@ -20,6 +20,7 @@ Napi::Object Demuxer::Init(Napi::Env env, Napi::Object exports) {
20
20
  {
21
21
  InstanceMethod("open", &Demuxer::Open),
22
22
  InstanceMethod("demux", &Demuxer::DemuxPackets),
23
+ InstanceMethod("demuxPackets", &Demuxer::DemuxPackets),
23
24
  InstanceMethod("close", &Demuxer::Close),
24
25
  InstanceMethod("getVideoTrack", &Demuxer::GetVideoTrack),
25
26
  InstanceMethod("getAudioTrack", &Demuxer::GetAudioTrack),
@@ -65,6 +66,11 @@ void Demuxer::Cleanup() {
65
66
  tracks_.clear();
66
67
  video_stream_index_ = -1;
67
68
  audio_stream_index_ = -1;
69
+
70
+ // Clear callback references
71
+ on_track_callback_.Reset();
72
+ on_chunk_callback_.Reset();
73
+ on_error_callback_.Reset();
68
74
  }
69
75
 
70
76
  Napi::Value Demuxer::Open(const Napi::CallbackInfo& info) {
@@ -175,6 +181,11 @@ Napi::Value Demuxer::DemuxPackets(const Napi::CallbackInfo& info) {
175
181
  return env.Undefined();
176
182
  }
177
183
 
184
+ int max_packets = 0; // 0 = unlimited (backwards compatible)
185
+ if (info.Length() > 0 && info[0].IsNumber()) {
186
+ max_packets = info[0].As<Napi::Number>().Int32Value();
187
+ }
188
+
178
189
  ffmpeg::AVPacketPtr packet = ffmpeg::make_packet();
179
190
  if (!packet) {
180
191
  Napi::Error::New(env, "Failed to allocate packet")
@@ -182,15 +193,18 @@ Napi::Value Demuxer::DemuxPackets(const Napi::CallbackInfo& info) {
182
193
  return env.Undefined();
183
194
  }
184
195
 
185
- while (av_read_frame(format_context_.get(), packet.get()) >= 0) {
196
+ int packets_read = 0;
197
+ while ((max_packets == 0 || packets_read < max_packets) &&
198
+ av_read_frame(format_context_.get(), packet.get()) >= 0) {
186
199
  if (packet->stream_index == video_stream_index_ ||
187
200
  packet->stream_index == audio_stream_index_) {
188
201
  EmitChunk(env, packet.get(), packet->stream_index);
202
+ packets_read++;
189
203
  }
190
204
  av_packet_unref(packet.get());
191
205
  }
192
206
 
193
- return env.Undefined();
207
+ return Napi::Number::New(env, packets_read);
194
208
  }
195
209
 
196
210
  void Demuxer::EmitChunk(Napi::Env env, AVPacket* packet, int track_index) {
@@ -26,6 +26,7 @@ Napi::Object EncodedAudioChunk::Init(Napi::Env env, Napi::Object exports) {
26
26
  InstanceAccessor("byteLength", &EncodedAudioChunk::GetByteLength,
27
27
  nullptr),
28
28
  InstanceMethod("copyTo", &EncodedAudioChunk::CopyTo),
29
+ InstanceMethod("close", &EncodedAudioChunk::Close),
29
30
  });
30
31
 
31
32
  constructor_ = Napi::Persistent(func);
@@ -173,3 +174,12 @@ void EncodedAudioChunk::CopyTo(const Napi::CallbackInfo& info) {
173
174
 
174
175
  std::memcpy(dest_data, data_.data(), data_.size());
175
176
  }
177
+
178
+ void EncodedAudioChunk::Close(const Napi::CallbackInfo& info) {
179
+ if (!closed_) {
180
+ // clear() + shrink_to_fit() actually releases memory.
181
+ data_.clear();
182
+ data_.shrink_to_fit();
183
+ closed_ = true;
184
+ }
185
+ }
@@ -30,6 +30,7 @@ class EncodedAudioChunk : public Napi::ObjectWrap<EncodedAudioChunk> {
30
30
 
31
31
  // Methods.
32
32
  void CopyTo(const Napi::CallbackInfo& info);
33
+ void Close(const Napi::CallbackInfo& info);
33
34
 
34
35
  // Internal access.
35
36
  const std::vector<uint8_t>& GetData() const { return data_; }
@@ -41,6 +42,7 @@ class EncodedAudioChunk : public Napi::ObjectWrap<EncodedAudioChunk> {
41
42
  int64_t timestamp_;
42
43
  int64_t duration_;
43
44
  std::vector<uint8_t> data_;
45
+ bool closed_ = false;
44
46
  };
45
47
 
46
48
  #endif // SRC_ENCODED_AUDIO_CHUNK_H_
@@ -28,6 +28,7 @@ class EncodedVideoChunk : public Napi::ObjectWrap<EncodedVideoChunk> {
28
28
  const uint8_t* GetData() const { return data_.data(); }
29
29
  size_t GetDataSize() const { return data_.size(); }
30
30
  int64_t GetTimestampValue() const { return timestamp_; }
31
+ int64_t GetDurationValue() const { return has_duration_ ? duration_ : 0; }
31
32
  const std::string& GetTypeValue() const { return type_; }
32
33
 
33
34
  private:
@@ -20,10 +20,6 @@ Napi::Object ErrorBuilder::Init(Napi::Env env, Napi::Object exports) {
20
20
  InstanceMethod("throwError", &ErrorBuilder::ThrowErrorJS),
21
21
  });
22
22
 
23
- Napi::FunctionReference* constructor = new Napi::FunctionReference();
24
- *constructor = Napi::Persistent(func);
25
- env.SetInstanceData(constructor);
26
-
27
23
  exports.Set("ErrorBuilder", func);
28
24
  return exports;
29
25
  }