@siteed/expo-audio-stream 1.0.3 → 1.0.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 (114) hide show
  1. package/.size-limit.json +4 -4
  2. package/README.md +18 -176
  3. package/android/src/main/java/net/siteed/audiostream/AudioRecorderManager.kt +1 -0
  4. package/app.plugin.js +1 -1
  5. package/build/AudioAnalysis/AudioAnalysis.types.d.ts +3 -5
  6. package/build/AudioAnalysis/AudioAnalysis.types.d.ts.map +1 -1
  7. package/build/AudioAnalysis/AudioAnalysis.types.js.map +1 -1
  8. package/build/AudioAnalysis/extractAudioAnalysis.d.ts +19 -3
  9. package/build/AudioAnalysis/extractAudioAnalysis.d.ts.map +1 -1
  10. package/build/AudioAnalysis/extractAudioAnalysis.js +14 -27
  11. package/build/AudioAnalysis/extractAudioAnalysis.js.map +1 -1
  12. package/build/AudioAnalysis/extractWaveform.d.ts.map +1 -1
  13. package/build/AudioAnalysis/extractWaveform.js +3 -3
  14. package/build/AudioAnalysis/extractWaveform.js.map +1 -1
  15. package/build/AudioRecorder.provider.d.ts +6 -6
  16. package/build/AudioRecorder.provider.d.ts.map +1 -1
  17. package/build/AudioRecorder.provider.js +9 -9
  18. package/build/AudioRecorder.provider.js.map +1 -1
  19. package/build/ExpoAudioStream.native.d.ts.map +1 -1
  20. package/build/ExpoAudioStream.native.js +2 -2
  21. package/build/ExpoAudioStream.native.js.map +1 -1
  22. package/build/ExpoAudioStream.types.d.ts +20 -18
  23. package/build/ExpoAudioStream.types.d.ts.map +1 -1
  24. package/build/ExpoAudioStream.types.js.map +1 -1
  25. package/build/ExpoAudioStream.web.d.ts +8 -8
  26. package/build/ExpoAudioStream.web.d.ts.map +1 -1
  27. package/build/ExpoAudioStream.web.js +40 -22
  28. package/build/ExpoAudioStream.web.js.map +1 -1
  29. package/build/ExpoAudioStreamModule.d.ts.map +1 -1
  30. package/build/ExpoAudioStreamModule.js +8 -7
  31. package/build/ExpoAudioStreamModule.js.map +1 -1
  32. package/build/WebRecorder.web.d.ts +8 -8
  33. package/build/WebRecorder.web.d.ts.map +1 -1
  34. package/build/WebRecorder.web.js +60 -50
  35. package/build/WebRecorder.web.js.map +1 -1
  36. package/build/constants.d.ts +1 -1
  37. package/build/constants.d.ts.map +1 -1
  38. package/build/constants.js +3 -3
  39. package/build/constants.js.map +1 -1
  40. package/build/events.d.ts +16 -4
  41. package/build/events.d.ts.map +1 -1
  42. package/build/events.js +8 -8
  43. package/build/events.js.map +1 -1
  44. package/build/index.d.ts +8 -8
  45. package/build/index.d.ts.map +1 -1
  46. package/build/index.js +6 -6
  47. package/build/index.js.map +1 -1
  48. package/build/logger.d.ts +2 -2
  49. package/build/logger.d.ts.map +1 -1
  50. package/build/logger.js +7 -11
  51. package/build/logger.js.map +1 -1
  52. package/build/useAudioRecorder.d.ts +4 -21
  53. package/build/useAudioRecorder.d.ts.map +1 -1
  54. package/build/useAudioRecorder.js +33 -33
  55. package/build/useAudioRecorder.js.map +1 -1
  56. package/build/utils/BlobFix.d.ts +9 -0
  57. package/build/utils/BlobFix.d.ts.map +1 -0
  58. package/build/utils/BlobFix.js +494 -0
  59. package/build/utils/BlobFix.js.map +1 -0
  60. package/build/utils/concatenateBuffers.d.ts +8 -0
  61. package/build/utils/concatenateBuffers.d.ts.map +1 -0
  62. package/build/utils/concatenateBuffers.js +21 -0
  63. package/build/utils/concatenateBuffers.js.map +1 -0
  64. package/build/utils/convertPCMToFloat32.d.ts +2 -2
  65. package/build/utils/convertPCMToFloat32.d.ts.map +1 -1
  66. package/build/utils/convertPCMToFloat32.js +49 -36
  67. package/build/utils/convertPCMToFloat32.js.map +1 -1
  68. package/build/utils/encodingToBitDepth.d.ts +1 -1
  69. package/build/utils/encodingToBitDepth.d.ts.map +1 -1
  70. package/build/utils/encodingToBitDepth.js +3 -3
  71. package/build/utils/encodingToBitDepth.js.map +1 -1
  72. package/build/utils/getWavFileInfo.d.ts +4 -3
  73. package/build/utils/getWavFileInfo.d.ts.map +1 -1
  74. package/build/utils/getWavFileInfo.js +18 -15
  75. package/build/utils/getWavFileInfo.js.map +1 -1
  76. package/build/utils/writeWavHeader.d.ts.map +1 -1
  77. package/build/utils/writeWavHeader.js +4 -4
  78. package/build/utils/writeWavHeader.js.map +1 -1
  79. package/build/workers/InlineFeaturesExtractor.web.d.ts.map +1 -1
  80. package/build/workers/InlineFeaturesExtractor.web.js.map +1 -1
  81. package/build/workers/inlineAudioWebWorker.web.d.ts.map +1 -1
  82. package/build/workers/inlineAudioWebWorker.web.js.map +1 -1
  83. package/expo-module.config.json +8 -17
  84. package/ios/AudioStreamManager.swift +1 -0
  85. package/ios/ExpoAudioStreamModule.swift +1 -0
  86. package/ios/RecordingResult.swift +1 -0
  87. package/package.json +72 -65
  88. package/plugin/build/index.d.ts +1 -1
  89. package/plugin/build/index.js +7 -7
  90. package/plugin/src/index.ts +47 -47
  91. package/plugin/tsconfig.json +8 -13
  92. package/src/AudioAnalysis/AudioAnalysis.types.ts +59 -60
  93. package/src/AudioAnalysis/extractAudioAnalysis.ts +132 -121
  94. package/src/AudioAnalysis/extractWaveform.ts +18 -18
  95. package/src/AudioRecorder.provider.tsx +53 -53
  96. package/src/ExpoAudioStream.native.ts +2 -2
  97. package/src/ExpoAudioStream.types.ts +56 -53
  98. package/src/ExpoAudioStream.web.ts +232 -205
  99. package/src/ExpoAudioStreamModule.ts +17 -16
  100. package/src/WebRecorder.web.ts +407 -390
  101. package/src/constants.ts +11 -11
  102. package/src/events.ts +27 -13
  103. package/src/index.ts +15 -15
  104. package/src/logger.ts +15 -18
  105. package/src/useAudioRecorder.tsx +394 -389
  106. package/src/utils/BlobFix.ts +550 -0
  107. package/src/utils/concatenateBuffers.ts +24 -0
  108. package/src/utils/convertPCMToFloat32.ts +72 -45
  109. package/src/utils/encodingToBitDepth.ts +14 -14
  110. package/src/utils/getWavFileInfo.ts +106 -99
  111. package/src/utils/writeWavHeader.ts +45 -45
  112. package/src/workers/InlineFeaturesExtractor.web.tsx +1 -1
  113. package/src/workers/inlineAudioWebWorker.web.tsx +1 -1
  114. package/tsconfig.json +12 -7
@@ -0,0 +1,550 @@
1
+ /*
2
+ * There is a bug where `navigator.mediaDevices.getUserMedia` + `MediaRecorder`
3
+ * creates WEBM files without duration metadata. See:
4
+ * - https://bugs.chromium.org/p/chromium/issues/detail?id=642012
5
+ * - https://stackoverflow.com/a/39971175/13989043
6
+ *
7
+ * This file contains a function that fixes the duration metadata of a WEBM file.
8
+ * - Answer found: https://stackoverflow.com/a/75218309/13989043
9
+ * - Code adapted from: https://github.com/mat-sz/webm-fix-duration
10
+ * (forked from https://github.com/yusitnikov/fix-webm-duration)
11
+ */
12
+
13
+ /*
14
+ * This is the list of possible WEBM file sections by their IDs.
15
+ * Possible types: Container, Binary, Uint, Int, String, Float, Date
16
+ */
17
+ interface Section {
18
+ name: string;
19
+ type: string;
20
+ }
21
+
22
+ const sections: Record<number, Section> = {
23
+ 0xa45dfa3: { name: "EBML", type: "Container" },
24
+ 0x286: { name: "EBMLVersion", type: "Uint" },
25
+ 0x2f7: { name: "EBMLReadVersion", type: "Uint" },
26
+ 0x2f2: { name: "EBMLMaxIDLength", type: "Uint" },
27
+ 0x2f3: { name: "EBMLMaxSizeLength", type: "Uint" },
28
+ 0x282: { name: "DocType", type: "String" },
29
+ 0x287: { name: "DocTypeVersion", type: "Uint" },
30
+ 0x285: { name: "DocTypeReadVersion", type: "Uint" },
31
+ 0x6c: { name: "Void", type: "Binary" },
32
+ 0x3f: { name: "CRC-32", type: "Binary" },
33
+ 0xb538667: { name: "SignatureSlot", type: "Container" },
34
+ 0x3e8a: { name: "SignatureAlgo", type: "Uint" },
35
+ 0x3e9a: { name: "SignatureHash", type: "Uint" },
36
+ 0x3ea5: { name: "SignaturePublicKey", type: "Binary" },
37
+ 0x3eb5: { name: "Signature", type: "Binary" },
38
+ 0x3e5b: { name: "SignatureElements", type: "Container" },
39
+ 0x3e7b: { name: "SignatureElementList", type: "Container" },
40
+ 0x2532: { name: "SignedElement", type: "Binary" },
41
+ 0x8538067: { name: "Segment", type: "Container" },
42
+ 0x14d9b74: { name: "SeekHead", type: "Container" },
43
+ 0xdbb: { name: "Seek", type: "Container" },
44
+ 0x13ab: { name: "SeekID", type: "Binary" },
45
+ 0x13ac: { name: "SeekPosition", type: "Uint" },
46
+ 0x549a966: { name: "Info", type: "Container" },
47
+ 0x33a4: { name: "SegmentUID", type: "Binary" },
48
+ 0x3384: { name: "SegmentFilename", type: "String" },
49
+ 0x1cb923: { name: "PrevUID", type: "Binary" },
50
+ 0x1c83ab: { name: "PrevFilename", type: "String" },
51
+ 0x1eb923: { name: "NextUID", type: "Binary" },
52
+ 0x1e83bb: { name: "NextFilename", type: "String" },
53
+ 0x444: { name: "SegmentFamily", type: "Binary" },
54
+ 0x2924: { name: "ChapterTranslate", type: "Container" },
55
+ 0x29fc: { name: "ChapterTranslateEditionUID", type: "Uint" },
56
+ 0x29bf: { name: "ChapterTranslateCodec", type: "Uint" },
57
+ 0x29a5: { name: "ChapterTranslateID", type: "Binary" },
58
+ 0xad7b1: { name: "TimecodeScale", type: "Uint" },
59
+ 0x489: { name: "Duration", type: "Float" },
60
+ 0x461: { name: "DateUTC", type: "Date" },
61
+ 0x3ba9: { name: "Title", type: "String" },
62
+ 0xd80: { name: "MuxingApp", type: "String" },
63
+ 0x1741: { name: "WritingApp", type: "String" },
64
+ // 0xf43b675: { name: 'Cluster', type: 'Container' },
65
+ 0x67: { name: "Timecode", type: "Uint" },
66
+ 0x1854: { name: "SilentTracks", type: "Container" },
67
+ 0x18d7: { name: "SilentTrackNumber", type: "Uint" },
68
+ 0x27: { name: "Position", type: "Uint" },
69
+ 0x2b: { name: "PrevSize", type: "Uint" },
70
+ 0x23: { name: "SimpleBlock", type: "Binary" },
71
+ 0x20: { name: "BlockGroup", type: "Container" },
72
+ 0x21: { name: "Block", type: "Binary" },
73
+ 0x22: { name: "BlockVirtual", type: "Binary" },
74
+ 0x35a1: { name: "BlockAdditions", type: "Container" },
75
+ 0x26: { name: "BlockMore", type: "Container" },
76
+ 0x6e: { name: "BlockAddID", type: "Uint" },
77
+ 0x25: { name: "BlockAdditional", type: "Binary" },
78
+ 0x1b: { name: "BlockDuration", type: "Uint" },
79
+ 0x7a: { name: "ReferencePriority", type: "Uint" },
80
+ 0x7b: { name: "ReferenceBlock", type: "Int" },
81
+ 0x7d: { name: "ReferenceVirtual", type: "Int" },
82
+ 0x24: { name: "CodecState", type: "Binary" },
83
+ 0x35a2: { name: "DiscardPadding", type: "Int" },
84
+ 0xe: { name: "Slices", type: "Container" },
85
+ 0x68: { name: "TimeSlice", type: "Container" },
86
+ 0x4c: { name: "LaceNumber", type: "Uint" },
87
+ 0x4d: { name: "FrameNumber", type: "Uint" },
88
+ 0x4b: { name: "BlockAdditionID", type: "Uint" },
89
+ 0x4e: { name: "Delay", type: "Uint" },
90
+ 0x4f: { name: "SliceDuration", type: "Uint" },
91
+ 0x48: { name: "ReferenceFrame", type: "Container" },
92
+ 0x49: { name: "ReferenceOffset", type: "Uint" },
93
+ 0x4a: { name: "ReferenceTimeCode", type: "Uint" },
94
+ 0x2f: { name: "EncryptedBlock", type: "Binary" },
95
+ 0x654ae6b: { name: "Tracks", type: "Container" },
96
+ 0x2e: { name: "TrackEntry", type: "Container" },
97
+ 0x57: { name: "TrackNumber", type: "Uint" },
98
+ 0x33c5: { name: "TrackUID", type: "Uint" },
99
+ 0x3: { name: "TrackType", type: "Uint" },
100
+ 0x39: { name: "FlagEnabled", type: "Uint" },
101
+ 0x8: { name: "FlagDefault", type: "Uint" },
102
+ 0x15aa: { name: "FlagForced", type: "Uint" },
103
+ 0x1c: { name: "FlagLacing", type: "Uint" },
104
+ 0x2de7: { name: "MinCache", type: "Uint" },
105
+ 0x2df8: { name: "MaxCache", type: "Uint" },
106
+ 0x3e383: { name: "DefaultDuration", type: "Uint" },
107
+ 0x34e7a: { name: "DefaultDecodedFieldDuration", type: "Uint" },
108
+ 0x3314f: { name: "TrackTimecodeScale", type: "Float" },
109
+ 0x137f: { name: "TrackOffset", type: "Int" },
110
+ 0x15ee: { name: "MaxBlockAdditionID", type: "Uint" },
111
+ 0x136e: { name: "Name", type: "String" },
112
+ 0x2b59c: { name: "Language", type: "String" },
113
+ 0x6: { name: "CodecID", type: "String" },
114
+ 0x23a2: { name: "CodecPrivate", type: "Binary" },
115
+ 0x58688: { name: "CodecName", type: "String" },
116
+ 0x3446: { name: "AttachmentLink", type: "Uint" },
117
+ 0x1a9697: { name: "CodecSettings", type: "String" },
118
+ 0x1b4040: { name: "CodecInfoURL", type: "String" },
119
+ 0x6b240: { name: "CodecDownloadURL", type: "String" },
120
+ 0x2a: { name: "CodecDecodeAll", type: "Uint" },
121
+ 0x2fab: { name: "TrackOverlay", type: "Uint" },
122
+ 0x16aa: { name: "CodecDelay", type: "Uint" },
123
+ 0x16bb: { name: "SeekPreRoll", type: "Uint" },
124
+ 0x2624: { name: "TrackTranslate", type: "Container" },
125
+ 0x26fc: { name: "TrackTranslateEditionUID", type: "Uint" },
126
+ 0x26bf: { name: "TrackTranslateCodec", type: "Uint" },
127
+ 0x26a5: { name: "TrackTranslateTrackID", type: "Binary" },
128
+ 0x60: { name: "Video", type: "Container" },
129
+ 0x1a: { name: "FlagInterlaced", type: "Uint" },
130
+ 0x13b8: { name: "StereoMode", type: "Uint" },
131
+ 0x13c0: { name: "AlphaMode", type: "Uint" },
132
+ 0x13b9: { name: "OldStereoMode", type: "Uint" },
133
+ 0x30: { name: "PixelWidth", type: "Uint" },
134
+ 0x3a: { name: "PixelHeight", type: "Uint" },
135
+ 0x14aa: { name: "PixelCropBottom", type: "Uint" },
136
+ 0x14bb: { name: "PixelCropTop", type: "Uint" },
137
+ 0x14cc: { name: "PixelCropLeft", type: "Uint" },
138
+ 0x14dd: { name: "PixelCropRight", type: "Uint" },
139
+ 0x14b0: { name: "DisplayWidth", type: "Uint" },
140
+ 0x14ba: { name: "DisplayHeight", type: "Uint" },
141
+ 0x14b2: { name: "DisplayUnit", type: "Uint" },
142
+ 0x14b3: { name: "AspectRatioType", type: "Uint" },
143
+ 0xeb524: { name: "ColourSpace", type: "Binary" },
144
+ 0xfb523: { name: "GammaValue", type: "Float" },
145
+ 0x383e3: { name: "FrameRate", type: "Float" },
146
+ 0x61: { name: "Audio", type: "Container" },
147
+ 0x35: { name: "SamplingFrequency", type: "Float" },
148
+ 0x38b5: { name: "OutputSamplingFrequency", type: "Float" },
149
+ 0x1f: { name: "Channels", type: "Uint" },
150
+ 0x3d7b: { name: "ChannelPositions", type: "Binary" },
151
+ 0x2264: { name: "BitDepth", type: "Uint" },
152
+ 0x62: { name: "TrackOperation", type: "Container" },
153
+ 0x63: { name: "TrackCombinePlanes", type: "Container" },
154
+ 0x64: { name: "TrackPlane", type: "Container" },
155
+ 0x65: { name: "TrackPlaneUID", type: "Uint" },
156
+ 0x66: { name: "TrackPlaneType", type: "Uint" },
157
+ 0x69: { name: "TrackJoinBlocks", type: "Container" },
158
+ 0x6d: { name: "TrackJoinUID", type: "Uint" },
159
+ 0x40: { name: "TrickTrackUID", type: "Uint" },
160
+ 0x41: { name: "TrickTrackSegmentUID", type: "Binary" },
161
+ 0x46: { name: "TrickTrackFlag", type: "Uint" },
162
+ 0x47: { name: "TrickMasterTrackUID", type: "Uint" },
163
+ 0x44: { name: "TrickMasterTrackSegmentUID", type: "Binary" },
164
+ 0x2d80: { name: "ContentEncodings", type: "Container" },
165
+ 0x2240: { name: "ContentEncoding", type: "Container" },
166
+ 0x1031: { name: "ContentEncodingOrder", type: "Uint" },
167
+ 0x1032: { name: "ContentEncodingScope", type: "Uint" },
168
+ 0x1033: { name: "ContentEncodingType", type: "Uint" },
169
+ 0x1034: { name: "ContentCompression", type: "Container" },
170
+ 0x254: { name: "ContentCompAlgo", type: "Uint" },
171
+ 0x255: { name: "ContentCompSettings", type: "Binary" },
172
+ 0x1035: { name: "ContentEncryption", type: "Container" },
173
+ 0x7e1: { name: "ContentEncAlgo", type: "Uint" },
174
+ 0x7e2: { name: "ContentEncKeyID", type: "Binary" },
175
+ 0x7e3: { name: "ContentSignature", type: "Binary" },
176
+ 0x7e4: { name: "ContentSigKeyID", type: "Binary" },
177
+ 0x7e5: { name: "ContentSigAlgo", type: "Uint" },
178
+ 0x7e6: { name: "ContentSigHashAlgo", type: "Uint" },
179
+ 0xc53bb6b: { name: "Cues", type: "Container" },
180
+ 0x3b: { name: "CuePoint", type: "Container" },
181
+ 0x33: { name: "CueTime", type: "Uint" },
182
+ 0x37: { name: "CueTrackPositions", type: "Container" },
183
+ 0x77: { name: "CueTrack", type: "Uint" },
184
+ 0x71: { name: "CueClusterPosition", type: "Uint" },
185
+ 0x70: { name: "CueRelativePosition", type: "Uint" },
186
+ 0x32: { name: "CueDuration", type: "Uint" },
187
+ 0x1378: { name: "CueBlockNumber", type: "Uint" },
188
+ 0x6a: { name: "CueCodecState", type: "Uint" },
189
+ 0x5b: { name: "CueReference", type: "Container" },
190
+ 0x16: { name: "CueRefTime", type: "Uint" },
191
+ 0x17: { name: "CueRefCluster", type: "Uint" },
192
+ 0x135f: { name: "CueRefNumber", type: "Uint" },
193
+ 0x6b: { name: "CueRefCodecState", type: "Uint" },
194
+ 0x941a469: { name: "Attachments", type: "Container" },
195
+ 0x21a7: { name: "AttachedFile", type: "Container" },
196
+ 0x67e: { name: "FileDescription", type: "String" },
197
+ 0x66e: { name: "FileName", type: "String" },
198
+ 0x660: { name: "FileMimeType", type: "String" },
199
+ 0x65c: { name: "FileData", type: "Binary" },
200
+ 0x6ae: { name: "FileUID", type: "Uint" },
201
+ 0x675: { name: "FileReferral", type: "Binary" },
202
+ 0x661: { name: "FileUsedStartTime", type: "Uint" },
203
+ 0x662: { name: "FileUsedEndTime", type: "Uint" },
204
+ 0x43a770: { name: "Chapters", type: "Container" },
205
+ 0x5b9: { name: "EditionEntry", type: "Container" },
206
+ 0x5bc: { name: "EditionUID", type: "Uint" },
207
+ 0x5bd: { name: "EditionFlagHidden", type: "Uint" },
208
+ 0x5db: { name: "EditionFlagDefault", type: "Uint" },
209
+ 0x5dd: { name: "EditionFlagOrdered", type: "Uint" },
210
+ 0x36: { name: "ChapterAtom", type: "Container" },
211
+ 0x33c4: { name: "ChapterUID", type: "Uint" },
212
+ 0x1654: { name: "ChapterStringUID", type: "String" },
213
+ 0x11: { name: "ChapterTimeStart", type: "Uint" },
214
+ 0x12: { name: "ChapterTimeEnd", type: "Uint" },
215
+ 0x18: { name: "ChapterFlagHidden", type: "Uint" },
216
+ 0x598: { name: "ChapterFlagEnabled", type: "Uint" },
217
+ 0x2e67: { name: "ChapterSegmentUID", type: "Binary" },
218
+ 0x2ebc: { name: "ChapterSegmentEditionUID", type: "Uint" },
219
+ 0x23c3: { name: "ChapterPhysicalEquiv", type: "Uint" },
220
+ 0xf: { name: "ChapterTrack", type: "Container" },
221
+ 0x9: { name: "ChapterTrackNumber", type: "Uint" },
222
+ 0x0: { name: "ChapterDisplay", type: "Container" },
223
+ 0x5: { name: "ChapString", type: "String" },
224
+ 0x37c: { name: "ChapLanguage", type: "String" },
225
+ 0x37e: { name: "ChapCountry", type: "String" },
226
+ 0x2944: { name: "ChapProcess", type: "Container" },
227
+ 0x2955: { name: "ChapProcessCodecID", type: "Uint" },
228
+ 0x50d: { name: "ChapProcessPrivate", type: "Binary" },
229
+ 0x2911: { name: "ChapProcessCommand", type: "Container" },
230
+ 0x2922: { name: "ChapProcessTime", type: "Uint" },
231
+ 0x2933: { name: "ChapProcessData", type: "Binary" },
232
+ 0x254c367: { name: "Tags", type: "Container" },
233
+ 0x3373: { name: "Tag", type: "Container" },
234
+ 0x23c0: { name: "Targets", type: "Container" },
235
+ 0x28ca: { name: "TargetTypeValue", type: "Uint" },
236
+ 0x23ca: { name: "TargetType", type: "String" },
237
+ 0x23c5: { name: "TagTrackUID", type: "Uint" },
238
+ 0x23c9: { name: "TagEditionUID", type: "Uint" },
239
+ 0x23c4: { name: "TagChapterUID", type: "Uint" },
240
+ 0x23c6: { name: "TagAttachmentUID", type: "Uint" },
241
+ 0x27c8: { name: "SimpleTag", type: "Container" },
242
+ 0x5a3: { name: "TagName", type: "String" },
243
+ 0x47a: { name: "TagLanguage", type: "String" },
244
+ 0x484: { name: "TagDefault", type: "Uint" },
245
+ 0x487: { name: "TagString", type: "String" },
246
+ 0x485: { name: "TagBinary", type: "Binary" },
247
+ };
248
+
249
+ class WebmBase<T> {
250
+ source?: Uint8Array;
251
+ data?: T;
252
+
253
+ constructor(private name = "Unknown", private type = "Unknown") {}
254
+
255
+ updateBySource() {}
256
+
257
+ setSource(source: Uint8Array) {
258
+ this.source = source;
259
+ this.updateBySource();
260
+ }
261
+
262
+ updateByData() {}
263
+
264
+ setData(data: T) {
265
+ this.data = data;
266
+ this.updateByData();
267
+ }
268
+ }
269
+
270
+ class WebmUint extends WebmBase<string> {
271
+ constructor(name: string, type: string) {
272
+ super(name, type || "Uint");
273
+ }
274
+
275
+ updateBySource() {
276
+ // use hex representation of a number instead of number value
277
+ this.data = "";
278
+ for (let i = 0; i < this.source!.length; i++) {
279
+ const hex = this.source![i].toString(16);
280
+ this.data += padHex(hex);
281
+ }
282
+ }
283
+
284
+ updateByData() {
285
+ const length = this.data!.length / 2;
286
+ this.source = new Uint8Array(length);
287
+ for (let i = 0; i < length; i++) {
288
+ const hex = this.data!.substr(i * 2, 2);
289
+ this.source[i] = parseInt(hex, 16);
290
+ }
291
+ }
292
+
293
+ getValue() {
294
+ return parseInt(this.data!, 16);
295
+ }
296
+
297
+ setValue(value: number) {
298
+ this.setData(padHex(value.toString(16)));
299
+ }
300
+ }
301
+
302
+ function padHex(hex: string) {
303
+ return hex.length % 2 === 1 ? "0" + hex : hex;
304
+ }
305
+
306
+ class WebmFloat extends WebmBase<number> {
307
+ constructor(name: string, type: string) {
308
+ super(name, type || "Float");
309
+ }
310
+
311
+ getFloatArrayType() {
312
+ return this.source && this.source.length === 4
313
+ ? Float32Array
314
+ : Float64Array;
315
+ }
316
+ updateBySource() {
317
+ const byteArray = this.source!.reverse();
318
+ const floatArrayType = this.getFloatArrayType();
319
+ const floatArray = new floatArrayType(byteArray.buffer);
320
+ this.data! = floatArray[0];
321
+ }
322
+ updateByData() {
323
+ const floatArrayType = this.getFloatArrayType();
324
+ const floatArray = new floatArrayType([this.data!]);
325
+ const byteArray = new Uint8Array(floatArray.buffer);
326
+ this.source = byteArray.reverse();
327
+ }
328
+ getValue() {
329
+ return this.data;
330
+ }
331
+ setValue(value: number) {
332
+ this.setData(value);
333
+ }
334
+ }
335
+
336
+ interface ContainerData {
337
+ id: number;
338
+ idHex?: string;
339
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
340
+ data: WebmBase<any>;
341
+ }
342
+
343
+ class WebmContainer extends WebmBase<ContainerData[]> {
344
+ offset: number = 0;
345
+ data: ContainerData[] = [];
346
+
347
+ constructor(name: string, type: string) {
348
+ super(name, type || "Container");
349
+ }
350
+
351
+ readByte() {
352
+ return this.source![this.offset++];
353
+ }
354
+ readUint() {
355
+ const firstByte = this.readByte();
356
+ const bytes = 8 - firstByte.toString(2).length;
357
+ let value = firstByte - (1 << (7 - bytes));
358
+ for (let i = 0; i < bytes; i++) {
359
+ // don't use bit operators to support x86
360
+ value *= 256;
361
+ value += this.readByte();
362
+ }
363
+ return value;
364
+ }
365
+ updateBySource() {
366
+ let end: number | undefined = undefined;
367
+ this.data = [];
368
+ for (
369
+ this.offset = 0;
370
+ this.offset < this.source!.length;
371
+ this.offset = end
372
+ ) {
373
+ const id = this.readUint();
374
+ const len = this.readUint();
375
+ end = Math.min(this.offset + len, this.source!.length);
376
+ const data = this.source!.slice(this.offset, end);
377
+
378
+ const info = sections[id] || { name: "Unknown", type: "Unknown" };
379
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
380
+ let ctr: any = WebmBase;
381
+ switch (info.type) {
382
+ case "Container":
383
+ ctr = WebmContainer;
384
+ break;
385
+ case "Uint":
386
+ ctr = WebmUint;
387
+ break;
388
+ case "Float":
389
+ ctr = WebmFloat;
390
+ break;
391
+ }
392
+ const section = new ctr(info.name, info.type);
393
+ section.setSource(data);
394
+ this.data.push({
395
+ id: id,
396
+ idHex: id.toString(16),
397
+ data: section,
398
+ });
399
+ }
400
+ }
401
+ writeUint(x: number, draft = false) {
402
+ let flag = 0x80;
403
+ let bytes = 1;
404
+ // eslint-disable-next-line no-empty
405
+ for (; x >= flag && bytes < 8; bytes++, flag *= 0x80) {}
406
+
407
+ if (!draft) {
408
+ let value = flag + x;
409
+ for (let i = bytes - 1; i >= 0; i--) {
410
+ // don't use bit operators to support x86
411
+ const c = value % 256;
412
+ this.source![this.offset! + i] = c;
413
+ value = (value - c) / 256;
414
+ }
415
+ }
416
+
417
+ this.offset += bytes;
418
+ }
419
+
420
+ writeSections(draft = false) {
421
+ this.offset = 0;
422
+ for (let i = 0; i < this.data.length; i++) {
423
+ const section = this.data[i],
424
+ content = section.data.source,
425
+ contentLength = content!.length;
426
+ this.writeUint(section.id, draft);
427
+ this.writeUint(contentLength, draft);
428
+ if (!draft) {
429
+ this.source!.set(content!, this.offset);
430
+ }
431
+ this.offset += contentLength;
432
+ }
433
+ return this.offset;
434
+ }
435
+
436
+ updateByData() {
437
+ // run without accessing this.source to determine total length - need to know it to create Uint8Array
438
+ const length = this.writeSections(true);
439
+ this.source = new Uint8Array(length);
440
+ // now really write data
441
+ this.writeSections();
442
+ }
443
+
444
+ getSectionById(id: number) {
445
+ for (let i = 0; i < this.data.length; i++) {
446
+ const section = this.data[i];
447
+ if (section.id === id) {
448
+ return section.data;
449
+ }
450
+ }
451
+
452
+ return undefined;
453
+ }
454
+ }
455
+
456
+ class WebmFile extends WebmContainer {
457
+ constructor(source: Uint8Array) {
458
+ super("File", "File");
459
+ this.setSource(source);
460
+ }
461
+
462
+ fixDuration(duration: number) {
463
+ const segmentSection = this.getSectionById(0x8538067) as WebmContainer;
464
+ if (!segmentSection) {
465
+ return false;
466
+ }
467
+
468
+ const infoSection = segmentSection.getSectionById(
469
+ 0x549a966,
470
+ ) as WebmContainer;
471
+ if (!infoSection) {
472
+ return false;
473
+ }
474
+
475
+ const timeScaleSection = infoSection.getSectionById(
476
+ 0xad7b1,
477
+ ) as WebmFloat;
478
+ if (!timeScaleSection) {
479
+ return false;
480
+ }
481
+
482
+ let durationSection = infoSection.getSectionById(0x489) as WebmFloat;
483
+ if (durationSection) {
484
+ if (durationSection.getValue()! <= 0) {
485
+ durationSection.setValue(duration);
486
+ } else {
487
+ return false;
488
+ }
489
+ } else {
490
+ // append Duration section
491
+ durationSection = new WebmFloat("Duration", "Float");
492
+ durationSection.setValue(duration);
493
+ infoSection.data.push({
494
+ id: 0x489,
495
+ data: durationSection,
496
+ });
497
+ }
498
+
499
+ // set default time scale to 1 millisecond (1000000 nanoseconds)
500
+ timeScaleSection.setValue(1000000);
501
+ infoSection.updateByData();
502
+ segmentSection.updateByData();
503
+ this.updateByData();
504
+
505
+ return true;
506
+ }
507
+
508
+ toBlob(type = "video/webm") {
509
+ return new Blob([this.source!.buffer], { type });
510
+ }
511
+ }
512
+
513
+ /**
514
+ * Fixes duration on MediaRecorder output.
515
+ * @param blob Input Blob with incorrect duration.
516
+ * @param duration Correct duration (in milliseconds).
517
+ * @param type Output blob mimetype (default: video/webm).
518
+ * @returns
519
+ */
520
+ export const webmFixDuration = (
521
+ blob: Blob,
522
+ duration: number,
523
+ type = "video/webm",
524
+ ): Promise<Blob> => {
525
+ return new Promise((resolve, reject) => {
526
+ try {
527
+ const reader = new FileReader();
528
+
529
+ reader.addEventListener("loadend", () => {
530
+ try {
531
+ const result = reader.result as ArrayBuffer;
532
+ const file = new WebmFile(new Uint8Array(result));
533
+ if (file.fixDuration(duration)) {
534
+ resolve(file.toBlob(type));
535
+ } else {
536
+ resolve(blob);
537
+ }
538
+ } catch (ex) {
539
+ reject(ex);
540
+ }
541
+ });
542
+
543
+ reader.addEventListener("error", () => reject());
544
+
545
+ reader.readAsArrayBuffer(blob);
546
+ } catch (ex) {
547
+ reject(ex);
548
+ }
549
+ });
550
+ };
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Concatenates an array of ArrayBuffers into a single ArrayBuffer.
3
+ *
4
+ * @param buffers - An array of ArrayBuffers to be concatenated.
5
+ * @returns A single ArrayBuffer containing the concatenated data.
6
+ */
7
+ export const concatenateBuffers = (buffers: ArrayBuffer[]): ArrayBuffer => {
8
+ // Filter out any undefined or null buffers
9
+ const validBuffers = buffers.filter((buffer) => buffer)
10
+ const totalLength = validBuffers.reduce(
11
+ (sum, buffer) => sum + buffer.byteLength,
12
+ 0
13
+ )
14
+ // Create a new Uint8Array to hold the concatenated result
15
+ const result = new Uint8Array(totalLength)
16
+ // Offset to keep track of the current position in the result array
17
+ let offset = 0
18
+
19
+ for (const buffer of validBuffers) {
20
+ result.set(new Uint8Array(buffer), offset)
21
+ offset += buffer.byteLength
22
+ }
23
+ return result.buffer
24
+ }