@banou/media-player 0.0.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/src/utils.ts ADDED
@@ -0,0 +1,385 @@
1
+ export type Chunk = {
2
+ offset: number
3
+ buffer: Uint8Array
4
+ pts: number
5
+ duration: number
6
+ pos: number
7
+ buffered: boolean
8
+ }
9
+
10
+ export const queuedDebounceWithLastCall = <T2 extends any[], T extends (...args: T2) => any>(time: number, func: T) => {
11
+ let runningFunction: Promise<ReturnType<T>> | undefined
12
+ let lastCall: Promise<ReturnType<T>> | undefined
13
+ let lastCallArguments: T2 | undefined
14
+
15
+ const checkForLastCall = (
16
+ timeStart: number,
17
+ resolve: (value: ReturnType<T> | PromiseLike<ReturnType<T>>) => void,
18
+ reject: (reason?: any) => void
19
+ ) =>
20
+ (result: ReturnType<T>) => {
21
+ const currentTime = performance.now()
22
+ setTimeout(() => {
23
+ if (!lastCallArguments) {
24
+ runningFunction = undefined
25
+ lastCall = undefined
26
+ return
27
+ }
28
+ const funcResult = (async () => (func(...lastCallArguments)))()
29
+ lastCallArguments = undefined
30
+ funcResult
31
+ .then(resolve)
32
+ .catch((err) => {
33
+ console.error(err)
34
+ reject(err)
35
+ })
36
+
37
+ let _resolve: (value: ReturnType<T> | PromiseLike<ReturnType<T>>) => void
38
+ let _reject: (reason?: any) => void
39
+ lastCall = new Promise((resolve, reject) => {
40
+ _resolve = resolve
41
+ _reject = reject
42
+ })
43
+
44
+ runningFunction =
45
+ funcResult
46
+ // @ts-ignore
47
+ .then(checkForLastCall(currentTime, _resolve, _reject))
48
+ // @ts-ignore
49
+ .catch(err => {
50
+ console.error(err)
51
+ return checkForLastCall(timeStart, _resolve, _reject)(err)
52
+ })
53
+ }, time - (currentTime - timeStart))
54
+ return result
55
+ }
56
+
57
+ return (...args: Parameters<T>) => {
58
+ lastCallArguments = args
59
+ if (!runningFunction) {
60
+ const timeStart = performance.now()
61
+ const funcResult = (async () => (func(...args)))()
62
+ lastCallArguments = undefined
63
+ let _resolve: (value: ReturnType<T> | PromiseLike<ReturnType<T>>) => void
64
+ let _reject: (reason?: any) => void
65
+ lastCall = new Promise((resolve, reject) => {
66
+ _resolve = resolve
67
+ _reject = reject
68
+ })
69
+
70
+ runningFunction =
71
+ funcResult
72
+ // @ts-ignore
73
+ .then(checkForLastCall(timeStart, _resolve, _reject))
74
+ // @ts-ignore
75
+ .catch(err => {
76
+ console.error(err)
77
+ return checkForLastCall(timeStart, _resolve, _reject)(err)
78
+ })
79
+
80
+ return funcResult
81
+ } else {
82
+ return lastCall
83
+ }
84
+ }
85
+ }
86
+
87
+ // todo: reimplement this into a ReadableByteStream https://web.dev/streams/ once FF gets support
88
+ // todo: remove firefox workaround once https://bugzilla.mozilla.org/show_bug.cgi?id=1808868 ships
89
+ export const bufferStream = ({ stream, size: SIZE }: { stream: ReadableStream, size: number }) =>
90
+ new ReadableStream<Uint8Array>({
91
+ start() {
92
+ // @ts-expect-error
93
+ this.reader = stream.getReader()
94
+ },
95
+ async pull(controller) {
96
+ try {
97
+ // @ts-expect-error
98
+ const { leftOverData }: { leftOverData: Uint8Array | undefined } = this
99
+
100
+ const accumulate = async ({ buffer = new Uint8Array(SIZE), currentSize = 0 } = {}): Promise<{ buffer?: Uint8Array, currentSize?: number, done: boolean }> => {
101
+ // @ts-expect-error
102
+ const { value: newBuffer, done } = await this.reader.read()
103
+
104
+ if (currentSize === 0 && leftOverData) {
105
+ // console.log('currentSize', currentSize)
106
+ // console.log('buffer', buffer)
107
+ // console.log('leftOverData', leftOverData)
108
+ buffer.set(leftOverData)
109
+ currentSize += leftOverData.byteLength
110
+ // @ts-expect-error
111
+ this.leftOverData = undefined
112
+ }
113
+
114
+ if (done) {
115
+ return { buffer: buffer.slice(0, currentSize), currentSize, done }
116
+ }
117
+
118
+ let newSize
119
+ const slicedBuffer = newBuffer.slice(0, SIZE - currentSize)
120
+ newSize = currentSize + slicedBuffer.byteLength
121
+ buffer.set(slicedBuffer, currentSize)
122
+
123
+ if (newSize === SIZE) {
124
+ // @ts-expect-error
125
+ this.leftOverData = newBuffer.slice(SIZE - currentSize)
126
+ return { buffer, currentSize: newSize, done: false }
127
+ }
128
+
129
+ return accumulate({ buffer, currentSize: newSize })
130
+ }
131
+ const { buffer, done } = await accumulate()
132
+ if (buffer?.byteLength) controller.enqueue(buffer)
133
+ if (done) controller.close()
134
+ } catch (err) {
135
+ console.error(err)
136
+ }
137
+ },
138
+ cancel() {
139
+ // @ts-expect-error
140
+ this.reader.cancel()
141
+ }
142
+ })
143
+
144
+ const getTimeRanges = (sourceBuffer: SourceBuffer) =>
145
+ Array(sourceBuffer.buffered.length)
146
+ .fill(undefined)
147
+ .map((_, index) => ({
148
+ index,
149
+ start: sourceBuffer.buffered.start(index),
150
+ end: sourceBuffer.buffered.end(index)
151
+ }))
152
+
153
+ const getTimeRange =
154
+ (sourceBuffer: SourceBuffer) =>
155
+ (time: number) =>
156
+ getTimeRanges(sourceBuffer)
157
+ .find(({ start, end }) => time >= start && time <= end)
158
+
159
+ // const setupListeners = (sourceBuffer: SourceBuffer, resolve: (value: Event) => void, reject: (reason: Event) => void) => {
160
+ // const updateEndListener = (ev: Event) => {
161
+ // resolve(ev)
162
+ // unregisterListeners()
163
+ // }
164
+ // const abortListener = (ev: Event) => {
165
+ // resolve(ev)
166
+ // unregisterListeners()
167
+ // }
168
+ // const errorListener = (ev: Event) => {
169
+ // reject(ev),
170
+ // unregisterListeners()
171
+ // }
172
+ // const unregisterListeners = () => {
173
+ // sourceBuffer.removeEventListener('updateend', updateEndListener)
174
+ // sourceBuffer.removeEventListener('abort', abortListener)
175
+ // sourceBuffer.removeEventListener('error', errorListener)
176
+ // }
177
+ // sourceBuffer.addEventListener('updateend', updateEndListener, { once: true })
178
+ // sourceBuffer.addEventListener('abort', abortListener, { once: true })
179
+ // sourceBuffer.addEventListener('error', errorListener, { once: true })
180
+ // }
181
+
182
+ // const appendBuffer = (queue: PQueue, sourceBuffer: SourceBuffer, buffer: ArrayBuffer) =>
183
+ // queue.add(() =>
184
+ // new Promise<Event>((resolve, reject) => {
185
+ // setupListeners(sourceBuffer, resolve, reject)
186
+ // sourceBuffer.appendBuffer(buffer)
187
+ // })
188
+ // )
189
+
190
+ // const bufferChunk = async (queue: PQueue, sourceBuffer: SourceBuffer, chunk: Chunk) => {
191
+ // await appendBuffer(queue, sourceBuffer, chunk.buffer.buffer)
192
+ // chunk.buffered = true
193
+ // }
194
+
195
+ // const unbufferChunk = async (queue: PQueue, sourceBuffer: SourceBuffer, chunks: Chunk[], chunk: Chunk) =>
196
+ // queue.add(() =>
197
+ // new Promise((resolve, reject) => {
198
+ // setupListeners(sourceBuffer, resolve, reject)
199
+
200
+ // const chunkIndex = chunks.indexOf(chunk)
201
+ // if (chunkIndex === -1) return reject('No chunk found')
202
+ // sourceBuffer.remove(chunk.pts, chunk.pts + chunk.duration)
203
+ // chunk.buffered = false
204
+ // })
205
+ // )
206
+
207
+
208
+
209
+
210
+
211
+
212
+
213
+
214
+
215
+
216
+
217
+ // const listenForOperationResult =
218
+ // (sourceBuffer: SourceBuffer) =>
219
+ // (resolve: (value: Event) => void, reject: (reason: Event) => void) => {
220
+ // const updateEndListener = (ev: Event) => {
221
+ // resolve(ev)
222
+ // unregisterListeners()
223
+ // }
224
+ // const abortListener = (ev: Event) => {
225
+ // resolve(ev)
226
+ // unregisterListeners()
227
+ // }
228
+ // const errorListener = (ev: Event) => {
229
+ // reject(ev),
230
+ // unregisterListeners()
231
+ // }
232
+ // const unregisterListeners = () => {
233
+ // sourceBuffer.removeEventListener('updateend', updateEndListener)
234
+ // sourceBuffer.removeEventListener('abort', abortListener)
235
+ // sourceBuffer.removeEventListener('error', errorListener)
236
+ // }
237
+ // sourceBuffer.addEventListener('updateend', updateEndListener, { once: true })
238
+ // sourceBuffer.addEventListener('abort', abortListener, { once: true })
239
+ // sourceBuffer.addEventListener('error', errorListener, { once: true })
240
+ // }
241
+
242
+ // export const appendBuffer =
243
+ // (sourceBuffer: SourceBuffer) =>
244
+ // (buffer: ArrayBuffer) =>
245
+ // new Promise<Event>((resolve, reject) => {
246
+ // listenForOperationResult(sourceBuffer)(resolve, reject)
247
+ // sourceBuffer.appendBuffer(buffer)
248
+ // })
249
+
250
+ // export const removeRange =
251
+ // (sourceBuffer: SourceBuffer) =>
252
+ // ({ start, end, index }: { start: number, end: number, index: number }) =>
253
+ // listenForOperationResult(sourceBuffer)(() => {
254
+ // sourceBuffer.remove(
255
+ // Math.max(sourceBuffer.buffered.start(index), start),
256
+ // Math.min(sourceBuffer.buffered.end(index), end)
257
+ // )
258
+ // })
259
+
260
+ // export const abort =
261
+ // (sourceBuffer: SourceBuffer) =>
262
+ // listenForOperationResult(sourceBuffer)(() => {
263
+ // sourceBuffer.abort()
264
+ // })
265
+
266
+ // export const appendChunk =
267
+ // (sourceBuffer: SourceBuffer) =>
268
+ // async (chunk: Chunk) => {
269
+ // await appendBuffer(sourceBuffer)(chunk.arrayBuffer.buffer)
270
+ // }
271
+
272
+ // export const removeChunk =
273
+ // (sourceBuffer: SourceBuffer) =>
274
+ // async (chunk: Chunk) => {
275
+ // if (chunk.keyframeIndex < 0) return console.log('skipped remove', chunk)
276
+ // const range = getTimeRange(sourceBuffer)(chunk.startTime) ?? getTimeRange(sourceBuffer)(chunk.endTime)
277
+ // if (!range) throw new RangeError('No TimeRange found with this chunk')
278
+ // await removeRange(sourceBuffer)({ start: chunk.startTime, end: chunk.endTime, index: range.index })
279
+ // }
280
+
281
+ // const PRE_SEEK_NEEDED_BUFFERS_IN_SECONDS = 15
282
+ // const POST_SEEK_NEEDED_BUFFERS_IN_SECONDS = 30
283
+
284
+ // // todo: fix issue with last chunk not properly loaded
285
+ // export const updateSourceBuffer =
286
+ // (sourceBuffer: SourceBuffer, getChunkArrayBuffer: (id: number) => Promise<Uint8Array | undefined>) => {
287
+ // const cache: Map<number, Uint8Array | Promise<Uint8Array | undefined>> = new Map()
288
+ // const bufferedChunksMap: Map<number, Uint8Array> = new Map()
289
+ // let renderCount = 0
290
+
291
+ // const update = async ({ currentTime, chunks, retry = false }: { currentTime: number, chunks: Chunk[], retry: boolean }) => {
292
+ // const currentRenderCount = renderCount
293
+ // renderCount++
294
+
295
+ // const neededChunks =
296
+ // [
297
+ // // header chunk
298
+ // { keyframeIndex: 0, startTime: 0, endTime: 0 },
299
+ // ...chunks
300
+ // .filter(({ startTime, endTime }) =>
301
+ // currentTime - PRE_SEEK_NEEDED_BUFFERS_IN_SECONDS < startTime
302
+ // && currentTime + POST_SEEK_NEEDED_BUFFERS_IN_SECONDS > endTime
303
+ // )
304
+ // ]
305
+
306
+ // // todo: improve perf by making this loop inclusive of bufferedChunksMap instead of chunks
307
+ // const shouldUnbufferChunks =
308
+ // chunks
309
+ // .filter(chunk => !neededChunks.includes(chunk))
310
+
311
+ // for (const chunk of shouldUnbufferChunks) {
312
+ // if (cache.has(chunk.keyframeIndex)) cache.delete(chunk.keyframeIndex)
313
+ // }
314
+
315
+ // if (sourceBuffer.updating) {
316
+ // await abort(sourceBuffer).catch((err) => {
317
+ // if (!(err instanceof Event)) throw err
318
+ // })
319
+ // }
320
+ // for (const chunk of shouldUnbufferChunks) {
321
+ // if (!bufferedChunksMap.has(chunk.keyframeIndex)) continue
322
+ // try {
323
+ // await removeChunk(sourceBuffer)(chunk)
324
+ // bufferedChunksMap.delete(chunk.keyframeIndex)
325
+ // } catch (err) {
326
+ // if (err.message === 'No TimeRange found with this chunk') {
327
+ // bufferedChunksMap.delete(chunk.keyframeIndex)
328
+ // }
329
+ // if (err.message !== 'No TimeRange found with this chunk') throw err
330
+ // }
331
+ // }
332
+ // const bufferedOutOfRangeRanges =
333
+ // getTimeRanges(sourceBuffer)
334
+ // .filter(({ start, end }) =>
335
+ // currentTime - PRE_SEEK_NEEDED_BUFFERS_IN_SECONDS > start && currentTime - PRE_SEEK_NEEDED_BUFFERS_IN_SECONDS > end ||
336
+ // currentTime + POST_SEEK_NEEDED_BUFFERS_IN_SECONDS < start && currentTime + POST_SEEK_NEEDED_BUFFERS_IN_SECONDS < end
337
+ // )
338
+ // for (const range of bufferedOutOfRangeRanges) {
339
+ // try {
340
+ // await removeRange(sourceBuffer)(range)
341
+ // } catch (err) {
342
+ // throw err
343
+ // }
344
+ // }
345
+ // for (const chunk of neededChunks) {
346
+ // // console.log('chunk', chunk, neededChunks, bufferedChunksMap.has(chunk.keyframeIndex), chunk.keyframeIndex + 1 === chunks.length)
347
+ // if (bufferedChunksMap.has(chunk.keyframeIndex) || (chunk.keyframeIndex !== 0 && chunk.keyframeIndex + 1 === chunks.length)) continue
348
+ // // console.log('check PASSED', chunk, neededChunks)
349
+ // try {
350
+ // const cachedBuffer = cache.get(chunk.keyframeIndex)
351
+ // if (!cachedBuffer) {
352
+ // const fetchBuffer =
353
+ // getChunkArrayBuffer(chunk.keyframeIndex)
354
+ // .then(arrayBuffer => {
355
+ // if (arrayBuffer) cache.set(chunk.keyframeIndex, arrayBuffer)
356
+ // return arrayBuffer
357
+ // })
358
+ // cache.set(chunk.keyframeIndex, fetchBuffer)
359
+ // }
360
+ // const buffer = await cache.get(chunk.keyframeIndex)
361
+ // if (renderCount !== currentRenderCount + 1) return
362
+ // if (!buffer) continue
363
+ // const _chunk = { ...chunk, arrayBuffer: buffer }
364
+ // await appendChunk(sourceBuffer)(_chunk).catch(err => {
365
+ // bufferedChunksMap.delete(chunk.keyframeIndex)
366
+ // throw err
367
+ // })
368
+ // bufferedChunksMap.set(chunk.keyframeIndex, buffer)
369
+ // } catch (err) {
370
+ // if (!(err instanceof Event)) {
371
+ // for (const range of getTimeRanges(sourceBuffer)) {
372
+ // await removeRange(sourceBuffer)(range)
373
+ // // todo: remove cached buffers from Maps
374
+ // }
375
+ // if (!retry) await update({ currentTime, chunks, retry: true })
376
+ // else throw err
377
+ // }
378
+ // // if (err.message !== 'Failed to execute \'appendBuffer\' on \'SourceBuffer\': This SourceBuffer is still processing an \'appendBuffer\' or \'remove\' operation.') throw err
379
+ // break
380
+ // }
381
+ // }
382
+ // }
383
+
384
+ // return update
385
+ // }
@@ -0,0 +1 @@
1
+ /// <reference types="vite/client" />
package/tsconfig.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "outDir": "build",
5
+ "lib": ["DOM", "DOM.Iterable", "ESNext"],
6
+ "allowJs": false,
7
+ "skipLibCheck": true,
8
+ "esModuleInterop": false,
9
+ "allowSyntheticDefaultImports": true,
10
+ "strict": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "module": "ESNext",
13
+ "moduleResolution": "Node",
14
+ "resolveJsonModule": true,
15
+ "isolatedModules": true,
16
+ "noEmit": false,
17
+ "declaration": true,
18
+ "emitDeclarationOnly": true,
19
+ "jsx": "react-jsx",
20
+ "paths": {
21
+ "mp4box": ["./src/mp4box.ts"]
22
+ }
23
+ },
24
+ "include": ["src"],
25
+ "references": [{ "path": "./tsconfig.node.json" }]
26
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "compilerOptions": {
3
+ "composite": true,
4
+ "module": "ESNext",
5
+ "moduleResolution": "Node",
6
+ "allowSyntheticDefaultImports": true
7
+ },
8
+ "include": ["vite.config.ts"]
9
+ }