@banou/media-player 0.6.0 → 0.6.2
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/README.md +6 -6
- package/package.json +51 -51
- package/src/assets/picture-in-picture.svg +5 -5
- package/src/components/chrome.tsx +95 -95
- package/src/components/control-bar.tsx +333 -333
- package/src/components/overlay.tsx +91 -91
- package/src/components/playback-slider.tsx +174 -174
- package/src/components/progress-bar.tsx +251 -251
- package/src/components/settings.tsx +321 -321
- package/src/components/sound.tsx +100 -100
- package/src/components/tooltip-display.tsx +83 -83
- package/src/components/volume-slider.tsx +156 -156
- package/src/index.tsx +195 -195
- package/src/main.tsx +169 -169
- package/src/state-machines/data-source.ts +84 -84
- package/src/state-machines/index.ts +5 -5
- package/src/state-machines/media-properties.ts +82 -82
- package/src/state-machines/media-source.ts +129 -129
- package/src/state-machines/media.ts +255 -255
- package/src/state-machines/subtitles.ts +285 -285
- package/src/state-machines/thumbnails.ts +123 -123
- package/src/state-machines/utils.ts +94 -94
- package/src/utils/actor-utils.ts +19 -19
- package/src/utils/colors.ts +8 -8
- package/src/utils/context.ts +23 -23
- package/src/utils/fonts.ts +159 -159
- package/src/utils/index.ts +229 -229
- package/src/utils/languages.ts +262 -262
- package/src/utils/mp4box.ts +74 -74
- package/src/utils/time.ts +15 -15
- package/src/utils/use-local-storage.ts +30 -30
- package/src/utils/use-scrub.ts +40 -40
- package/src/utils/volume-utils.ts +39 -39
- package/src/utils/window-height.ts +19 -19
- package/src/vite-env.d.ts +1 -1
- package/tsconfig.json +28 -28
- package/tsconfig.node.json +9 -9
|
@@ -1,123 +1,123 @@
|
|
|
1
|
-
import type { Index } from 'libav-wasm/build/worker'
|
|
2
|
-
|
|
3
|
-
import { makeRemuxer } from 'libav-wasm'
|
|
4
|
-
import PQueue from 'p-queue'
|
|
5
|
-
|
|
6
|
-
import { fromAsyncCallback } from './utils'
|
|
7
|
-
import { toStreamChunkSize } from '../utils'
|
|
8
|
-
import { DownloadedRange } from '../utils/context'
|
|
9
|
-
|
|
10
|
-
type ExtendedIndex = Index & { duration?: number }
|
|
11
|
-
|
|
12
|
-
export type Thumbnail = {
|
|
13
|
-
url: string
|
|
14
|
-
blob: Blob
|
|
15
|
-
timestamp: number
|
|
16
|
-
duration: number
|
|
17
|
-
index: ExtendedIndex
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
type DataSourceEvents =
|
|
21
|
-
| { type: 'DOWNLOADED_RANGES_UPDATED', downloadedRanges: DownloadedRange[] }
|
|
22
|
-
|
|
23
|
-
type DataSourceEmittedEvents =
|
|
24
|
-
| { type: 'NEW_THUMBNAIL', thumbnail: Thumbnail }
|
|
25
|
-
|
|
26
|
-
type DataSourceInput = {
|
|
27
|
-
remuxerOptions: Parameters<typeof makeRemuxer>[0]
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export default fromAsyncCallback<DataSourceEvents, DataSourceInput, DataSourceEmittedEvents>(async ({ sendBack, receive, input, self, emit }) => {
|
|
31
|
-
const { remuxerOptions } = input
|
|
32
|
-
const { publicPath, workerUrl, bufferSize, length, read } = remuxerOptions
|
|
33
|
-
|
|
34
|
-
let resolve: (value: void) => void
|
|
35
|
-
const readyPromise = new Promise<void>(_resolve => {
|
|
36
|
-
resolve = _resolve
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
receive(async (event) => {
|
|
40
|
-
await readyPromise
|
|
41
|
-
if (event.type === 'DOWNLOADED_RANGES_UPDATED') {
|
|
42
|
-
// take 1 index every 5seconds
|
|
43
|
-
const selectedIndexes =
|
|
44
|
-
metadata
|
|
45
|
-
.indexes
|
|
46
|
-
.reduce((acc, index) => {
|
|
47
|
-
const lastIndex = acc.at(-1)
|
|
48
|
-
if (lastIndex && index.timestamp - lastIndex.timestamp < 5) {
|
|
49
|
-
return acc
|
|
50
|
-
} else {
|
|
51
|
-
const nextValidIndex =
|
|
52
|
-
metadata
|
|
53
|
-
.indexes
|
|
54
|
-
.slice(index.index + 1)
|
|
55
|
-
.find(nextIndex => nextIndex.timestamp > index.timestamp + 5)
|
|
56
|
-
if (!nextValidIndex) return [...acc, index]
|
|
57
|
-
return [...acc, { ...index, duration: nextValidIndex.timestamp - index.timestamp }]
|
|
58
|
-
}
|
|
59
|
-
}, [] as Index[])
|
|
60
|
-
|
|
61
|
-
const readyIndexes =
|
|
62
|
-
selectedIndexes
|
|
63
|
-
.filter((index) => {
|
|
64
|
-
const nextIndex = metadata.indexes.at(index.index + 1)
|
|
65
|
-
const endByte = nextIndex ? nextIndex.pos : remuxerOptions.length
|
|
66
|
-
const startByte = index.pos
|
|
67
|
-
const isWithinDownloadedRange =
|
|
68
|
-
event
|
|
69
|
-
.downloadedRanges
|
|
70
|
-
.some(({ startByteOffset, endByteOffset }) =>
|
|
71
|
-
startByteOffset <= startByte && endByte <=endByteOffset
|
|
72
|
-
)
|
|
73
|
-
return isWithinDownloadedRange
|
|
74
|
-
})
|
|
75
|
-
.sort((a, b) => a.timestamp - b.timestamp)
|
|
76
|
-
|
|
77
|
-
readyIndexes.forEach(index => loadMore(index))
|
|
78
|
-
}
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
const remuxer = await makeRemuxer({
|
|
82
|
-
publicPath,
|
|
83
|
-
workerUrl,
|
|
84
|
-
bufferSize,
|
|
85
|
-
length,
|
|
86
|
-
read
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
const metadata = await remuxer.init()
|
|
90
|
-
|
|
91
|
-
const queue = new PQueue({ concurrency: 1 })
|
|
92
|
-
|
|
93
|
-
let thumbnails: Thumbnail[] = []
|
|
94
|
-
const loadMore = (index: ExtendedIndex) =>
|
|
95
|
-
queue.add(async () => {
|
|
96
|
-
if (thumbnails.find(thumbnail => thumbnail.index.index === index.index)) return
|
|
97
|
-
const buffer = await remuxer.readKeyframe(index.timestamp)
|
|
98
|
-
|
|
99
|
-
const blob = new Blob([buffer], { type: 'image/png' })
|
|
100
|
-
const url = URL.createObjectURL(blob)
|
|
101
|
-
const nextIndex = metadata.indexes.at(index.index + 1)
|
|
102
|
-
const duration =
|
|
103
|
-
nextIndex
|
|
104
|
-
? nextIndex.timestamp - index.timestamp
|
|
105
|
-
: metadata.info.input.duration - index.timestamp
|
|
106
|
-
const thumbnail = {
|
|
107
|
-
blob,
|
|
108
|
-
url,
|
|
109
|
-
timestamp: index.timestamp,
|
|
110
|
-
duration: index.duration ?? duration,
|
|
111
|
-
index
|
|
112
|
-
} satisfies Thumbnail
|
|
113
|
-
thumbnails.push(thumbnail)
|
|
114
|
-
sendBack({ type: 'NEW_THUMBNAIL', thumbnail })
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
// @ts-expect-error
|
|
118
|
-
resolve()
|
|
119
|
-
|
|
120
|
-
return () => {
|
|
121
|
-
remuxer.destroy()
|
|
122
|
-
}
|
|
123
|
-
})
|
|
1
|
+
import type { Index } from 'libav-wasm/build/worker'
|
|
2
|
+
|
|
3
|
+
import { makeRemuxer } from 'libav-wasm'
|
|
4
|
+
import PQueue from 'p-queue'
|
|
5
|
+
|
|
6
|
+
import { fromAsyncCallback } from './utils'
|
|
7
|
+
import { toStreamChunkSize } from '../utils'
|
|
8
|
+
import { DownloadedRange } from '../utils/context'
|
|
9
|
+
|
|
10
|
+
type ExtendedIndex = Index & { duration?: number }
|
|
11
|
+
|
|
12
|
+
export type Thumbnail = {
|
|
13
|
+
url: string
|
|
14
|
+
blob: Blob
|
|
15
|
+
timestamp: number
|
|
16
|
+
duration: number
|
|
17
|
+
index: ExtendedIndex
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type DataSourceEvents =
|
|
21
|
+
| { type: 'DOWNLOADED_RANGES_UPDATED', downloadedRanges: DownloadedRange[] }
|
|
22
|
+
|
|
23
|
+
type DataSourceEmittedEvents =
|
|
24
|
+
| { type: 'NEW_THUMBNAIL', thumbnail: Thumbnail }
|
|
25
|
+
|
|
26
|
+
type DataSourceInput = {
|
|
27
|
+
remuxerOptions: Parameters<typeof makeRemuxer>[0]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default fromAsyncCallback<DataSourceEvents, DataSourceInput, DataSourceEmittedEvents>(async ({ sendBack, receive, input, self, emit }) => {
|
|
31
|
+
const { remuxerOptions } = input
|
|
32
|
+
const { publicPath, workerUrl, bufferSize, length, read } = remuxerOptions
|
|
33
|
+
|
|
34
|
+
let resolve: (value: void) => void
|
|
35
|
+
const readyPromise = new Promise<void>(_resolve => {
|
|
36
|
+
resolve = _resolve
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
receive(async (event) => {
|
|
40
|
+
await readyPromise
|
|
41
|
+
if (event.type === 'DOWNLOADED_RANGES_UPDATED') {
|
|
42
|
+
// take 1 index every 5seconds
|
|
43
|
+
const selectedIndexes =
|
|
44
|
+
metadata
|
|
45
|
+
.indexes
|
|
46
|
+
.reduce((acc, index) => {
|
|
47
|
+
const lastIndex = acc.at(-1)
|
|
48
|
+
if (lastIndex && index.timestamp - lastIndex.timestamp < 5) {
|
|
49
|
+
return acc
|
|
50
|
+
} else {
|
|
51
|
+
const nextValidIndex =
|
|
52
|
+
metadata
|
|
53
|
+
.indexes
|
|
54
|
+
.slice(index.index + 1)
|
|
55
|
+
.find(nextIndex => nextIndex.timestamp > index.timestamp + 5)
|
|
56
|
+
if (!nextValidIndex) return [...acc, index]
|
|
57
|
+
return [...acc, { ...index, duration: nextValidIndex.timestamp - index.timestamp }]
|
|
58
|
+
}
|
|
59
|
+
}, [] as Index[])
|
|
60
|
+
|
|
61
|
+
const readyIndexes =
|
|
62
|
+
selectedIndexes
|
|
63
|
+
.filter((index) => {
|
|
64
|
+
const nextIndex = metadata.indexes.at(index.index + 1)
|
|
65
|
+
const endByte = nextIndex ? nextIndex.pos : remuxerOptions.length
|
|
66
|
+
const startByte = index.pos
|
|
67
|
+
const isWithinDownloadedRange =
|
|
68
|
+
event
|
|
69
|
+
.downloadedRanges
|
|
70
|
+
.some(({ startByteOffset, endByteOffset }) =>
|
|
71
|
+
startByteOffset <= startByte && endByte <=endByteOffset
|
|
72
|
+
)
|
|
73
|
+
return isWithinDownloadedRange
|
|
74
|
+
})
|
|
75
|
+
.sort((a, b) => a.timestamp - b.timestamp)
|
|
76
|
+
|
|
77
|
+
readyIndexes.forEach(index => loadMore(index))
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
const remuxer = await makeRemuxer({
|
|
82
|
+
publicPath,
|
|
83
|
+
workerUrl,
|
|
84
|
+
bufferSize,
|
|
85
|
+
length,
|
|
86
|
+
read
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
const metadata = await remuxer.init()
|
|
90
|
+
|
|
91
|
+
const queue = new PQueue({ concurrency: 1 })
|
|
92
|
+
|
|
93
|
+
let thumbnails: Thumbnail[] = []
|
|
94
|
+
const loadMore = (index: ExtendedIndex) =>
|
|
95
|
+
queue.add(async () => {
|
|
96
|
+
if (thumbnails.find(thumbnail => thumbnail.index.index === index.index)) return
|
|
97
|
+
const buffer = await remuxer.readKeyframe(index.timestamp)
|
|
98
|
+
|
|
99
|
+
const blob = new Blob([buffer], { type: 'image/png' })
|
|
100
|
+
const url = URL.createObjectURL(blob)
|
|
101
|
+
const nextIndex = metadata.indexes.at(index.index + 1)
|
|
102
|
+
const duration =
|
|
103
|
+
nextIndex
|
|
104
|
+
? nextIndex.timestamp - index.timestamp
|
|
105
|
+
: metadata.info.input.duration - index.timestamp
|
|
106
|
+
const thumbnail = {
|
|
107
|
+
blob,
|
|
108
|
+
url,
|
|
109
|
+
timestamp: index.timestamp,
|
|
110
|
+
duration: index.duration ?? duration,
|
|
111
|
+
index
|
|
112
|
+
} satisfies Thumbnail
|
|
113
|
+
thumbnails.push(thumbnail)
|
|
114
|
+
sendBack({ type: 'NEW_THUMBNAIL', thumbnail })
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
// @ts-expect-error
|
|
118
|
+
resolve()
|
|
119
|
+
|
|
120
|
+
return () => {
|
|
121
|
+
remuxer.destroy()
|
|
122
|
+
}
|
|
123
|
+
})
|
|
@@ -1,94 +1,94 @@
|
|
|
1
|
-
import type { AnyActorSystem } from 'xstate/dist/declarations/src/system'
|
|
2
|
-
import type { AnyEventObject, CallbackActorLogic, CallbackActorRef, EventObject, NonReducibleUnknown } from 'xstate'
|
|
3
|
-
|
|
4
|
-
import PQueue from 'p-queue'
|
|
5
|
-
import { fromCallback } from 'xstate'
|
|
6
|
-
|
|
7
|
-
type Receiver<TEvent extends EventObject> = (listener: {
|
|
8
|
-
bivarianceHack(event: TEvent): void
|
|
9
|
-
}['bivarianceHack']) => void
|
|
10
|
-
|
|
11
|
-
export type CallbackLogicFunction<TEvent extends EventObject = AnyEventObject, TSentEvent extends EventObject = AnyEventObject, TInput = NonReducibleUnknown, TEmitted extends EventObject = EventObject> = ({ input, system, self, sendBack, receive, emit }: {
|
|
12
|
-
input: TInput
|
|
13
|
-
system: AnyActorSystem
|
|
14
|
-
self: CallbackActorRef<TEvent>
|
|
15
|
-
sendBack: (event: TSentEvent) => void
|
|
16
|
-
receive: Receiver<TEvent>
|
|
17
|
-
emit: (emitted: TEmitted) => void
|
|
18
|
-
}) => Promise<(() => void) | void>
|
|
19
|
-
|
|
20
|
-
type FromAsyncCallback = <TEvent extends EventObject, TInput = NonReducibleUnknown, TEmitted extends EventObject = EventObject>(callback: CallbackLogicFunction<TEvent, AnyEventObject, TInput, TEmitted>) => CallbackActorLogic<TEvent, TInput, TEmitted>
|
|
21
|
-
|
|
22
|
-
export const fromAsyncCallback =
|
|
23
|
-
((callback: (...args: any[]) => Promise<() => any>) =>
|
|
24
|
-
fromCallback((...args: any[]) => {
|
|
25
|
-
const callbackPromise = callback(...args)
|
|
26
|
-
return () => {
|
|
27
|
-
callbackPromise
|
|
28
|
-
.then(callbackResult => callbackResult?.())
|
|
29
|
-
}
|
|
30
|
-
})) as FromAsyncCallback
|
|
31
|
-
|
|
32
|
-
export const getTimeRanges = (sourceBuffer: SourceBuffer) =>
|
|
33
|
-
Array(sourceBuffer.buffered.length)
|
|
34
|
-
.fill(undefined)
|
|
35
|
-
.map((_, index) => ({
|
|
36
|
-
index,
|
|
37
|
-
start: sourceBuffer.buffered.start(index),
|
|
38
|
-
end: sourceBuffer.buffered.end(index)
|
|
39
|
-
}))
|
|
40
|
-
|
|
41
|
-
const setupListeners = (sourceBuffer: SourceBuffer, resolve: (value: Event) => void, reject: (reason: Event) => void) => {
|
|
42
|
-
const updateEndListener = (ev: Event) => {
|
|
43
|
-
resolve(ev)
|
|
44
|
-
unregisterListeners()
|
|
45
|
-
}
|
|
46
|
-
const abortListener = (ev: Event) => {
|
|
47
|
-
resolve(ev)
|
|
48
|
-
unregisterListeners()
|
|
49
|
-
}
|
|
50
|
-
const errorListener = (ev: Event) => {
|
|
51
|
-
console.error(ev)
|
|
52
|
-
reject(ev)
|
|
53
|
-
unregisterListeners()
|
|
54
|
-
}
|
|
55
|
-
const unregisterListeners = () => {
|
|
56
|
-
sourceBuffer.removeEventListener('updateend', updateEndListener)
|
|
57
|
-
sourceBuffer.removeEventListener('abort', abortListener)
|
|
58
|
-
sourceBuffer.removeEventListener('error', errorListener)
|
|
59
|
-
}
|
|
60
|
-
sourceBuffer.addEventListener('updateend', updateEndListener, { once: true })
|
|
61
|
-
sourceBuffer.addEventListener('abort', abortListener, { once: true })
|
|
62
|
-
sourceBuffer.addEventListener('error', errorListener, { once: true })
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export const updateSourceBuffer = (sourceBuffer: SourceBuffer) => {
|
|
66
|
-
const queue = new PQueue({ concurrency: 1 })
|
|
67
|
-
|
|
68
|
-
const appendBuffer = (buffer: ArrayBuffer) =>
|
|
69
|
-
queue.add(() =>
|
|
70
|
-
new Promise<Event>((resolve, reject) => {
|
|
71
|
-
setupListeners(sourceBuffer, resolve, reject)
|
|
72
|
-
sourceBuffer.appendBuffer(buffer)
|
|
73
|
-
})
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
const unbufferRange = async (start: number, end: number) =>
|
|
77
|
-
queue.add(() =>
|
|
78
|
-
new Promise((resolve, reject) => {
|
|
79
|
-
setupListeners(sourceBuffer, resolve, reject)
|
|
80
|
-
sourceBuffer.remove(start, end)
|
|
81
|
-
})
|
|
82
|
-
)
|
|
83
|
-
|
|
84
|
-
const updateTimestampOffset = (timestampOffset: number) =>
|
|
85
|
-
queue.add(() => {
|
|
86
|
-
sourceBuffer.timestampOffset = timestampOffset
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
return {
|
|
90
|
-
appendBuffer,
|
|
91
|
-
unbufferRange,
|
|
92
|
-
updateTimestampOffset
|
|
93
|
-
}
|
|
94
|
-
}
|
|
1
|
+
import type { AnyActorSystem } from 'xstate/dist/declarations/src/system'
|
|
2
|
+
import type { AnyEventObject, CallbackActorLogic, CallbackActorRef, EventObject, NonReducibleUnknown } from 'xstate'
|
|
3
|
+
|
|
4
|
+
import PQueue from 'p-queue'
|
|
5
|
+
import { fromCallback } from 'xstate'
|
|
6
|
+
|
|
7
|
+
type Receiver<TEvent extends EventObject> = (listener: {
|
|
8
|
+
bivarianceHack(event: TEvent): void
|
|
9
|
+
}['bivarianceHack']) => void
|
|
10
|
+
|
|
11
|
+
export type CallbackLogicFunction<TEvent extends EventObject = AnyEventObject, TSentEvent extends EventObject = AnyEventObject, TInput = NonReducibleUnknown, TEmitted extends EventObject = EventObject> = ({ input, system, self, sendBack, receive, emit }: {
|
|
12
|
+
input: TInput
|
|
13
|
+
system: AnyActorSystem
|
|
14
|
+
self: CallbackActorRef<TEvent>
|
|
15
|
+
sendBack: (event: TSentEvent) => void
|
|
16
|
+
receive: Receiver<TEvent>
|
|
17
|
+
emit: (emitted: TEmitted) => void
|
|
18
|
+
}) => Promise<(() => void) | void>
|
|
19
|
+
|
|
20
|
+
type FromAsyncCallback = <TEvent extends EventObject, TInput = NonReducibleUnknown, TEmitted extends EventObject = EventObject>(callback: CallbackLogicFunction<TEvent, AnyEventObject, TInput, TEmitted>) => CallbackActorLogic<TEvent, TInput, TEmitted>
|
|
21
|
+
|
|
22
|
+
export const fromAsyncCallback =
|
|
23
|
+
((callback: (...args: any[]) => Promise<() => any>) =>
|
|
24
|
+
fromCallback((...args: any[]) => {
|
|
25
|
+
const callbackPromise = callback(...args)
|
|
26
|
+
return () => {
|
|
27
|
+
callbackPromise
|
|
28
|
+
.then(callbackResult => callbackResult?.())
|
|
29
|
+
}
|
|
30
|
+
})) as FromAsyncCallback
|
|
31
|
+
|
|
32
|
+
export const getTimeRanges = (sourceBuffer: SourceBuffer) =>
|
|
33
|
+
Array(sourceBuffer.buffered.length)
|
|
34
|
+
.fill(undefined)
|
|
35
|
+
.map((_, index) => ({
|
|
36
|
+
index,
|
|
37
|
+
start: sourceBuffer.buffered.start(index),
|
|
38
|
+
end: sourceBuffer.buffered.end(index)
|
|
39
|
+
}))
|
|
40
|
+
|
|
41
|
+
const setupListeners = (sourceBuffer: SourceBuffer, resolve: (value: Event) => void, reject: (reason: Event) => void) => {
|
|
42
|
+
const updateEndListener = (ev: Event) => {
|
|
43
|
+
resolve(ev)
|
|
44
|
+
unregisterListeners()
|
|
45
|
+
}
|
|
46
|
+
const abortListener = (ev: Event) => {
|
|
47
|
+
resolve(ev)
|
|
48
|
+
unregisterListeners()
|
|
49
|
+
}
|
|
50
|
+
const errorListener = (ev: Event) => {
|
|
51
|
+
console.error(ev)
|
|
52
|
+
reject(ev)
|
|
53
|
+
unregisterListeners()
|
|
54
|
+
}
|
|
55
|
+
const unregisterListeners = () => {
|
|
56
|
+
sourceBuffer.removeEventListener('updateend', updateEndListener)
|
|
57
|
+
sourceBuffer.removeEventListener('abort', abortListener)
|
|
58
|
+
sourceBuffer.removeEventListener('error', errorListener)
|
|
59
|
+
}
|
|
60
|
+
sourceBuffer.addEventListener('updateend', updateEndListener, { once: true })
|
|
61
|
+
sourceBuffer.addEventListener('abort', abortListener, { once: true })
|
|
62
|
+
sourceBuffer.addEventListener('error', errorListener, { once: true })
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const updateSourceBuffer = (sourceBuffer: SourceBuffer) => {
|
|
66
|
+
const queue = new PQueue({ concurrency: 1 })
|
|
67
|
+
|
|
68
|
+
const appendBuffer = (buffer: ArrayBuffer) =>
|
|
69
|
+
queue.add(() =>
|
|
70
|
+
new Promise<Event>((resolve, reject) => {
|
|
71
|
+
setupListeners(sourceBuffer, resolve, reject)
|
|
72
|
+
sourceBuffer.appendBuffer(buffer)
|
|
73
|
+
})
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
const unbufferRange = async (start: number, end: number) =>
|
|
77
|
+
queue.add(() =>
|
|
78
|
+
new Promise((resolve, reject) => {
|
|
79
|
+
setupListeners(sourceBuffer, resolve, reject)
|
|
80
|
+
sourceBuffer.remove(start, end)
|
|
81
|
+
})
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
const updateTimestampOffset = (timestampOffset: number) =>
|
|
85
|
+
queue.add(() => {
|
|
86
|
+
sourceBuffer.timestampOffset = timestampOffset
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
appendBuffer,
|
|
91
|
+
unbufferRange,
|
|
92
|
+
updateTimestampOffset
|
|
93
|
+
}
|
|
94
|
+
}
|
package/src/utils/actor-utils.ts
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import { Actor, AnyActorLogic } from "xstate"
|
|
2
|
-
|
|
3
|
-
export const togglePlay = (
|
|
4
|
-
mediaActor: Actor<AnyActorLogic>,
|
|
5
|
-
isPaused: boolean,
|
|
6
|
-
duration: number | undefined,
|
|
7
|
-
currentTime: number
|
|
8
|
-
) => {
|
|
9
|
-
if (!mediaActor) throw new Error('Media actor not found')
|
|
10
|
-
if (!duration) throw new Error('Duration not found')
|
|
11
|
-
if (duration === currentTime) {
|
|
12
|
-
mediaActor.send({ type: 'SET_TIME', value: 0 })
|
|
13
|
-
mediaActor.send({ type: 'PLAY' })
|
|
14
|
-
} else if (isPaused) {
|
|
15
|
-
mediaActor.send({ type: 'PLAY' })
|
|
16
|
-
} else {
|
|
17
|
-
mediaActor.send({ type: 'PAUSE' })
|
|
18
|
-
}
|
|
19
|
-
}
|
|
1
|
+
import { Actor, AnyActorLogic } from "xstate"
|
|
2
|
+
|
|
3
|
+
export const togglePlay = (
|
|
4
|
+
mediaActor: Actor<AnyActorLogic>,
|
|
5
|
+
isPaused: boolean,
|
|
6
|
+
duration: number | undefined,
|
|
7
|
+
currentTime: number
|
|
8
|
+
) => {
|
|
9
|
+
if (!mediaActor) throw new Error('Media actor not found')
|
|
10
|
+
if (!duration) throw new Error('Duration not found')
|
|
11
|
+
if (duration === currentTime) {
|
|
12
|
+
mediaActor.send({ type: 'SET_TIME', value: 0 })
|
|
13
|
+
mediaActor.send({ type: 'PLAY' })
|
|
14
|
+
} else if (isPaused) {
|
|
15
|
+
mediaActor.send({ type: 'PLAY' })
|
|
16
|
+
} else {
|
|
17
|
+
mediaActor.send({ type: 'PAUSE' })
|
|
18
|
+
}
|
|
19
|
+
}
|
package/src/utils/colors.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
const colors = {
|
|
2
|
-
primary: '#EAEBEE',
|
|
3
|
-
secondary: '#D0D0D9',
|
|
4
|
-
hover: 'rgba(255, 255, 255, 0.13)',
|
|
5
|
-
borderPrimary: '#384F70',
|
|
6
|
-
backgroundTooltip: '#222222',
|
|
7
|
-
}
|
|
8
|
-
|
|
1
|
+
const colors = {
|
|
2
|
+
primary: '#EAEBEE',
|
|
3
|
+
secondary: '#D0D0D9',
|
|
4
|
+
hover: 'rgba(255, 255, 255, 0.13)',
|
|
5
|
+
borderPrimary: '#384F70',
|
|
6
|
+
backgroundTooltip: '#222222',
|
|
7
|
+
}
|
|
8
|
+
|
|
9
9
|
export default colors
|
package/src/utils/context.ts
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import { createContext } from 'react'
|
|
2
|
-
|
|
3
|
-
export type DownloadedRange =
|
|
4
|
-
// | {
|
|
5
|
-
// startTimestamp: number
|
|
6
|
-
// endTimestamp: number
|
|
7
|
-
// }
|
|
8
|
-
| {
|
|
9
|
-
startByteOffset: number
|
|
10
|
-
endByteOffset: number
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export type MediaPlayerContextType = {
|
|
14
|
-
videoElement?: HTMLVideoElement
|
|
15
|
-
title?: string
|
|
16
|
-
subtitle?: string
|
|
17
|
-
size?: number
|
|
18
|
-
downloadedRanges?: DownloadedRange[]
|
|
19
|
-
hideUI: boolean
|
|
20
|
-
update: (context: Omit<MediaPlayerContextType, 'update'>) => void
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export const MediaPlayerContext = createContext<MediaPlayerContextType>({} as MediaPlayerContextType)
|
|
1
|
+
import { createContext } from 'react'
|
|
2
|
+
|
|
3
|
+
export type DownloadedRange =
|
|
4
|
+
// | {
|
|
5
|
+
// startTimestamp: number
|
|
6
|
+
// endTimestamp: number
|
|
7
|
+
// }
|
|
8
|
+
| {
|
|
9
|
+
startByteOffset: number
|
|
10
|
+
endByteOffset: number
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type MediaPlayerContextType = {
|
|
14
|
+
videoElement?: HTMLVideoElement
|
|
15
|
+
title?: string
|
|
16
|
+
subtitle?: string
|
|
17
|
+
size?: number
|
|
18
|
+
downloadedRanges?: DownloadedRange[]
|
|
19
|
+
hideUI: boolean
|
|
20
|
+
update: (context: Omit<MediaPlayerContextType, 'update'>) => void
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const MediaPlayerContext = createContext<MediaPlayerContextType>({} as MediaPlayerContextType)
|