@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.
Files changed (37) hide show
  1. package/README.md +6 -6
  2. package/package.json +51 -51
  3. package/src/assets/picture-in-picture.svg +5 -5
  4. package/src/components/chrome.tsx +95 -95
  5. package/src/components/control-bar.tsx +333 -333
  6. package/src/components/overlay.tsx +91 -91
  7. package/src/components/playback-slider.tsx +174 -174
  8. package/src/components/progress-bar.tsx +251 -251
  9. package/src/components/settings.tsx +321 -321
  10. package/src/components/sound.tsx +100 -100
  11. package/src/components/tooltip-display.tsx +83 -83
  12. package/src/components/volume-slider.tsx +156 -156
  13. package/src/index.tsx +195 -195
  14. package/src/main.tsx +169 -169
  15. package/src/state-machines/data-source.ts +84 -84
  16. package/src/state-machines/index.ts +5 -5
  17. package/src/state-machines/media-properties.ts +82 -82
  18. package/src/state-machines/media-source.ts +129 -129
  19. package/src/state-machines/media.ts +255 -255
  20. package/src/state-machines/subtitles.ts +285 -285
  21. package/src/state-machines/thumbnails.ts +123 -123
  22. package/src/state-machines/utils.ts +94 -94
  23. package/src/utils/actor-utils.ts +19 -19
  24. package/src/utils/colors.ts +8 -8
  25. package/src/utils/context.ts +23 -23
  26. package/src/utils/fonts.ts +159 -159
  27. package/src/utils/index.ts +229 -229
  28. package/src/utils/languages.ts +262 -262
  29. package/src/utils/mp4box.ts +74 -74
  30. package/src/utils/time.ts +15 -15
  31. package/src/utils/use-local-storage.ts +30 -30
  32. package/src/utils/use-scrub.ts +40 -40
  33. package/src/utils/volume-utils.ts +39 -39
  34. package/src/utils/window-height.ts +19 -19
  35. package/src/vite-env.d.ts +1 -1
  36. package/tsconfig.json +28 -28
  37. 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
+ }
@@ -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
+ }
@@ -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
@@ -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)