@banou/media-player 0.5.1 → 0.6.1

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 (43) hide show
  1. package/README.md +6 -6
  2. package/build/components/playback-slider.d.ts +2 -0
  3. package/build/index.js +1159 -1012
  4. package/build/state-machines/index.d.ts +2503 -1838
  5. package/build/state-machines/media.d.ts +218 -51
  6. package/build/state-machines/subtitles.d.ts +3 -0
  7. package/build/utils/use-local-storage.d.ts +1 -1
  8. package/package.json +51 -51
  9. package/src/assets/picture-in-picture.svg +5 -5
  10. package/src/components/chrome.tsx +95 -95
  11. package/src/components/control-bar.tsx +333 -327
  12. package/src/components/overlay.tsx +91 -91
  13. package/src/components/playback-slider.tsx +174 -0
  14. package/src/components/progress-bar.tsx +251 -251
  15. package/src/components/settings.tsx +321 -268
  16. package/src/components/sound.tsx +100 -100
  17. package/src/components/tooltip-display.tsx +83 -83
  18. package/src/components/volume-slider.tsx +156 -156
  19. package/src/index.tsx +195 -195
  20. package/src/main.tsx +169 -169
  21. package/src/state-machines/data-source.ts +84 -84
  22. package/src/state-machines/index.ts +5 -5
  23. package/src/state-machines/media-properties.ts +82 -82
  24. package/src/state-machines/media-source.ts +129 -129
  25. package/src/state-machines/media.ts +255 -252
  26. package/src/state-machines/subtitles.ts +285 -269
  27. package/src/state-machines/thumbnails.ts +123 -123
  28. package/src/state-machines/utils.ts +94 -94
  29. package/src/utils/actor-utils.ts +19 -19
  30. package/src/utils/colors.ts +8 -8
  31. package/src/utils/context.ts +23 -23
  32. package/src/utils/fonts.ts +159 -159
  33. package/src/utils/index.ts +229 -229
  34. package/src/utils/languages.ts +262 -262
  35. package/src/utils/mp4box.ts +74 -74
  36. package/src/utils/time.ts +15 -15
  37. package/src/utils/use-local-storage.ts +30 -30
  38. package/src/utils/use-scrub.ts +40 -40
  39. package/src/utils/volume-utils.ts +39 -39
  40. package/src/utils/window-height.ts +19 -19
  41. package/src/vite-env.d.ts +1 -1
  42. package/tsconfig.json +28 -28
  43. package/tsconfig.node.json +9 -9
@@ -1,82 +1,82 @@
1
- import { fromCallback } from 'xstate'
2
-
3
- type MediaPropertiesEvents =
4
- | { type: 'PLAY' }
5
- | { type: 'PAUSE' }
6
- | { type: 'SET_TIME', value: number }
7
- | { type: 'SET_VOLUME', muted: boolean, volume: number }
8
- | { type: 'SET_PLAYBACK_RATE', playbackRate: number }
9
- | { type: 'DESTROY' }
10
-
11
- type MediaPropertiesEmittedEvents =
12
- | { type: 'PLAYING' }
13
- | { type: 'PAUSED' }
14
- | { type: 'TIME_UPDATE', currentTime: number }
15
- | { type: 'VOLUME_UPDATE', muted: boolean, volume: number }
16
- | { type: 'PLAYBACK_RATE_UPDATE', playbackRate: number }
17
- | { type: 'DURATION_UPDATE', duration: number }
18
- | { type: 'SEEKING', currentTime: number }
19
- | { type: 'ENDED' }
20
-
21
- type MediaPropertiesInput = { videoElement: HTMLVideoElement }
22
-
23
- export default fromCallback<MediaPropertiesEvents, MediaPropertiesInput, MediaPropertiesEmittedEvents>(({ sendBack, receive, input }) => {
24
- const { videoElement } = input
25
-
26
- receive((event) => {
27
- if (event.type === 'PLAY') {
28
- videoElement
29
- .play()
30
- .then(() => handlePlay())
31
- .catch((error) => {
32
- console.error(error)
33
- })
34
- }
35
- if (event.type === 'PAUSE') {
36
- videoElement.pause()
37
- handlePause()
38
- }
39
- if (event.type === 'SET_TIME') {
40
- videoElement.currentTime = event.value
41
- handleTimeUpdate()
42
- }
43
- if (event.type === 'SET_VOLUME') {
44
- videoElement.volume = event.volume
45
- videoElement.muted = event.muted
46
- handleVolumeUpdate()
47
- }
48
- if (event.type === 'SET_PLAYBACK_RATE') {
49
- videoElement.playbackRate = event.playbackRate
50
- handlePlaybackRateUpdate()
51
- }
52
- })
53
-
54
- const handlePlay = () => sendBack({ type: 'PLAYING' })
55
- const handlePause = () => sendBack({ type: 'PAUSED' })
56
- const handleEnded = () => sendBack({ type: 'ENDED' })
57
- const handleTimeUpdate = () => sendBack({ type: 'TIME_UPDATE', currentTime: videoElement.currentTime })
58
- const handleVolumeUpdate = () => sendBack({ type: 'VOLUME_UPDATE', muted: videoElement.muted, volume: videoElement.volume })
59
- const handlePlaybackRateUpdate = () => sendBack({ type: 'PLAYBACK_RATE_UPDATE', playbackRate: videoElement.playbackRate })
60
- const handleSeeking = () => sendBack({ type: 'SEEKING', currentTime: videoElement.currentTime })
61
- const handleDurationChange = () => sendBack({ type: 'DURATION_UPDATE', duration: videoElement.duration })
62
-
63
- videoElement.addEventListener('play', handlePlay)
64
- videoElement.addEventListener('pause', handlePause)
65
- videoElement.addEventListener('ended', handleEnded)
66
- videoElement.addEventListener('timeupdate', handleTimeUpdate)
67
- videoElement.addEventListener('volumechange', handleVolumeUpdate)
68
- videoElement.addEventListener('ratechange', handlePlaybackRateUpdate)
69
- videoElement.addEventListener('seeking', handleSeeking)
70
- videoElement.addEventListener('durationchange', handleDurationChange)
71
-
72
- return () => {
73
- videoElement.removeEventListener('play', handlePlay)
74
- videoElement.removeEventListener('pause', handlePause)
75
- videoElement.removeEventListener('ended', handleEnded)
76
- videoElement.removeEventListener('timeupdate', handleTimeUpdate)
77
- videoElement.removeEventListener('volumechange', handleVolumeUpdate)
78
- videoElement.removeEventListener('ratechange', handlePlaybackRateUpdate)
79
- videoElement.removeEventListener('seeking', handleSeeking)
80
- videoElement.removeEventListener('durationchange', handleDurationChange)
81
- }
82
- })
1
+ import { fromCallback } from 'xstate'
2
+
3
+ type MediaPropertiesEvents =
4
+ | { type: 'PLAY' }
5
+ | { type: 'PAUSE' }
6
+ | { type: 'SET_TIME', value: number }
7
+ | { type: 'SET_VOLUME', muted: boolean, volume: number }
8
+ | { type: 'SET_PLAYBACK_RATE', playbackRate: number }
9
+ | { type: 'DESTROY' }
10
+
11
+ type MediaPropertiesEmittedEvents =
12
+ | { type: 'PLAYING' }
13
+ | { type: 'PAUSED' }
14
+ | { type: 'TIME_UPDATE', currentTime: number }
15
+ | { type: 'VOLUME_UPDATE', muted: boolean, volume: number }
16
+ | { type: 'PLAYBACK_RATE_UPDATE', playbackRate: number }
17
+ | { type: 'DURATION_UPDATE', duration: number }
18
+ | { type: 'SEEKING', currentTime: number }
19
+ | { type: 'ENDED' }
20
+
21
+ type MediaPropertiesInput = { videoElement: HTMLVideoElement }
22
+
23
+ export default fromCallback<MediaPropertiesEvents, MediaPropertiesInput, MediaPropertiesEmittedEvents>(({ sendBack, receive, input }) => {
24
+ const { videoElement } = input
25
+
26
+ receive((event) => {
27
+ if (event.type === 'PLAY') {
28
+ videoElement
29
+ .play()
30
+ .then(() => handlePlay())
31
+ .catch((error) => {
32
+ console.error(error)
33
+ })
34
+ }
35
+ if (event.type === 'PAUSE') {
36
+ videoElement.pause()
37
+ handlePause()
38
+ }
39
+ if (event.type === 'SET_TIME') {
40
+ videoElement.currentTime = event.value
41
+ handleTimeUpdate()
42
+ }
43
+ if (event.type === 'SET_VOLUME') {
44
+ videoElement.volume = event.volume
45
+ videoElement.muted = event.muted
46
+ handleVolumeUpdate()
47
+ }
48
+ if (event.type === 'SET_PLAYBACK_RATE') {
49
+ videoElement.playbackRate = event.playbackRate
50
+ handlePlaybackRateUpdate()
51
+ }
52
+ })
53
+
54
+ const handlePlay = () => sendBack({ type: 'PLAYING' })
55
+ const handlePause = () => sendBack({ type: 'PAUSED' })
56
+ const handleEnded = () => sendBack({ type: 'ENDED' })
57
+ const handleTimeUpdate = () => sendBack({ type: 'TIME_UPDATE', currentTime: videoElement.currentTime })
58
+ const handleVolumeUpdate = () => sendBack({ type: 'VOLUME_UPDATE', muted: videoElement.muted, volume: videoElement.volume })
59
+ const handlePlaybackRateUpdate = () => sendBack({ type: 'PLAYBACK_RATE_UPDATE', playbackRate: videoElement.playbackRate })
60
+ const handleSeeking = () => sendBack({ type: 'SEEKING', currentTime: videoElement.currentTime })
61
+ const handleDurationChange = () => sendBack({ type: 'DURATION_UPDATE', duration: videoElement.duration })
62
+
63
+ videoElement.addEventListener('play', handlePlay)
64
+ videoElement.addEventListener('pause', handlePause)
65
+ videoElement.addEventListener('ended', handleEnded)
66
+ videoElement.addEventListener('timeupdate', handleTimeUpdate)
67
+ videoElement.addEventListener('volumechange', handleVolumeUpdate)
68
+ videoElement.addEventListener('ratechange', handlePlaybackRateUpdate)
69
+ videoElement.addEventListener('seeking', handleSeeking)
70
+ videoElement.addEventListener('durationchange', handleDurationChange)
71
+
72
+ return () => {
73
+ videoElement.removeEventListener('play', handlePlay)
74
+ videoElement.removeEventListener('pause', handlePause)
75
+ videoElement.removeEventListener('ended', handleEnded)
76
+ videoElement.removeEventListener('timeupdate', handleTimeUpdate)
77
+ videoElement.removeEventListener('volumechange', handleVolumeUpdate)
78
+ videoElement.removeEventListener('ratechange', handlePlaybackRateUpdate)
79
+ videoElement.removeEventListener('seeking', handleSeeking)
80
+ videoElement.removeEventListener('durationchange', handleDurationChange)
81
+ }
82
+ })
@@ -1,129 +1,129 @@
1
- import { makeRemuxer } from 'libav-wasm'
2
-
3
- import { fromAsyncCallback, getTimeRanges, updateSourceBuffer } from './utils'
4
-
5
- type MediaSourceEvents =
6
- | ({ type: 'METADATA' } & Awaited<ReturnType<Awaited<ReturnType<typeof makeRemuxer>>['init']>>)
7
- | { type: 'TIMESTAMP_OFFSET', timestampOffset: number }
8
- | { type: 'DATA', data: ArrayBuffer }
9
-
10
- type MediaSourceEmittedEvents =
11
- | { type: 'SOURCE_BUFFER_READY' }
12
- | { type: 'SOURCE_OPEN' }
13
- | { type: 'SOURCE_ENDED' }
14
- | { type: 'SOURCE_ERROR' }
15
- | { type: 'NEED_DATA' }
16
-
17
- export type MediaSourceOptions = {
18
- preEvictionTime?: number
19
- postEvictionTime?: number
20
- bufferTargetTime?: number
21
- }
22
-
23
- type MediaSourceInput = {
24
- videoElement: HTMLVideoElement
25
- preEvictionTime?: number
26
- postEvictionTime?: number
27
- bufferTargetTime?: number
28
- }
29
-
30
- const defaultPreEvictionTime = -20
31
- const defaultPostEvictionTime = 60
32
- const defaultBufferTargetTime = 30
33
-
34
- export default fromAsyncCallback<MediaSourceEvents, MediaSourceInput, MediaSourceEmittedEvents>(async ({ sendBack, receive, input, self, emit }) => {
35
- const { videoElement, preEvictionTime, postEvictionTime, bufferTargetTime } = input
36
-
37
- const handlError = () => console.error(videoElement.error)
38
- videoElement.addEventListener('error', handlError)
39
-
40
- const mediaSource = new MediaSource()
41
- const mediaSourceUrl = URL.createObjectURL(mediaSource)
42
- videoElement.src = mediaSourceUrl
43
-
44
- const getPreEvictionTime = () => videoElement.currentTime + (preEvictionTime ?? defaultPreEvictionTime)
45
- const getPostEvictionTime = () => videoElement.currentTime + (postEvictionTime ?? defaultPostEvictionTime)
46
- const getBufferTargetTime = () => videoElement.currentTime + (bufferTargetTime ?? defaultBufferTargetTime)
47
-
48
- let headerBuffer: ArrayBuffer | undefined
49
- const sourceBuffer =
50
- await new Promise<SourceBuffer>(resolve =>
51
- mediaSource.addEventListener(
52
- 'sourceopen',
53
- () => {
54
- let resolved = false
55
- receive((event) => {
56
- if (resolved) return
57
- if (event.type === 'METADATA') {
58
- const sourceBuffer = mediaSource.addSourceBuffer(`video/mp4; codecs="${event.info.input.videoMimeType},${event.info.input.audioMimeType}"`)
59
- sourceBuffer.mode = 'segments'
60
- mediaSource.duration = event.info.input.duration
61
- headerBuffer = event.data
62
- resolved = true
63
- resolve(sourceBuffer)
64
- }
65
- })
66
- },
67
- { once: true }
68
- )
69
- )
70
-
71
- if (!headerBuffer) throw new Error('No header buffer')
72
-
73
- const { appendBuffer, unbufferRange, updateTimestampOffset } = updateSourceBuffer(sourceBuffer)
74
- appendBuffer(headerBuffer)
75
-
76
- receive(async (event) => {
77
- if (event.type === 'DATA') {
78
- await appendBuffer(event.data)
79
- } else if (event.type === 'TIMESTAMP_OFFSET') {
80
- await updateTimestampOffset(event.timestampOffset)
81
- }
82
- })
83
-
84
- const unbuffer = async () => {
85
- const preEvictionTime = getPreEvictionTime()
86
- const postEvictionTime = getPostEvictionTime()
87
- const bufferedRanges = getTimeRanges(sourceBuffer)
88
- for (const { start, end } of bufferedRanges) {
89
- if (start < preEvictionTime) {
90
- await unbufferRange(
91
- start,
92
- preEvictionTime
93
- )
94
- }
95
- if (end > postEvictionTime) {
96
- await unbufferRange(
97
- postEvictionTime,
98
- end
99
- )
100
- }
101
- }
102
- }
103
-
104
- const interval = setInterval(async () => {
105
- await unbuffer()
106
- const timeRanges = getTimeRanges(sourceBuffer)
107
- const maxBufferedTime = Math.max(...timeRanges.map(({ end }) => end))
108
- if (maxBufferedTime < getBufferTargetTime()) {
109
- sendBack({ type: 'NEED_DATA' })
110
- }
111
- }, 100)
112
-
113
- const handleSourceOpen = () => sendBack({ type: 'SOURCE_OPEN' })
114
- const handleSourceEnded = () => sendBack({ type: 'SOURCE_ENDED' })
115
- const handleSourceError = () => sendBack({ type: 'SOURCE_ERROR' })
116
-
117
- mediaSource.addEventListener('sourceopen', handleSourceOpen)
118
- mediaSource.addEventListener('sourceended', handleSourceEnded)
119
- mediaSource.addEventListener('error', handleSourceError)
120
-
121
- return () => {
122
- URL.revokeObjectURL(mediaSourceUrl)
123
- clearInterval(interval)
124
- videoElement.removeEventListener('error', handlError)
125
- mediaSource.removeEventListener('sourceopen', handleSourceOpen)
126
- mediaSource.removeEventListener('sourceended', handleSourceEnded)
127
- mediaSource.removeEventListener('error', handleSourceError)
128
- }
129
- })
1
+ import { makeRemuxer } from 'libav-wasm'
2
+
3
+ import { fromAsyncCallback, getTimeRanges, updateSourceBuffer } from './utils'
4
+
5
+ type MediaSourceEvents =
6
+ | ({ type: 'METADATA' } & Awaited<ReturnType<Awaited<ReturnType<typeof makeRemuxer>>['init']>>)
7
+ | { type: 'TIMESTAMP_OFFSET', timestampOffset: number }
8
+ | { type: 'DATA', data: ArrayBuffer }
9
+
10
+ type MediaSourceEmittedEvents =
11
+ | { type: 'SOURCE_BUFFER_READY' }
12
+ | { type: 'SOURCE_OPEN' }
13
+ | { type: 'SOURCE_ENDED' }
14
+ | { type: 'SOURCE_ERROR' }
15
+ | { type: 'NEED_DATA' }
16
+
17
+ export type MediaSourceOptions = {
18
+ preEvictionTime?: number
19
+ postEvictionTime?: number
20
+ bufferTargetTime?: number
21
+ }
22
+
23
+ type MediaSourceInput = {
24
+ videoElement: HTMLVideoElement
25
+ preEvictionTime?: number
26
+ postEvictionTime?: number
27
+ bufferTargetTime?: number
28
+ }
29
+
30
+ const defaultPreEvictionTime = -20
31
+ const defaultPostEvictionTime = 60
32
+ const defaultBufferTargetTime = 30
33
+
34
+ export default fromAsyncCallback<MediaSourceEvents, MediaSourceInput, MediaSourceEmittedEvents>(async ({ sendBack, receive, input, self, emit }) => {
35
+ const { videoElement, preEvictionTime, postEvictionTime, bufferTargetTime } = input
36
+
37
+ const handlError = () => console.error(videoElement.error)
38
+ videoElement.addEventListener('error', handlError)
39
+
40
+ const mediaSource = new MediaSource()
41
+ const mediaSourceUrl = URL.createObjectURL(mediaSource)
42
+ videoElement.src = mediaSourceUrl
43
+
44
+ const getPreEvictionTime = () => videoElement.currentTime + (preEvictionTime ?? defaultPreEvictionTime)
45
+ const getPostEvictionTime = () => videoElement.currentTime + (postEvictionTime ?? defaultPostEvictionTime)
46
+ const getBufferTargetTime = () => videoElement.currentTime + (bufferTargetTime ?? defaultBufferTargetTime)
47
+
48
+ let headerBuffer: ArrayBuffer | undefined
49
+ const sourceBuffer =
50
+ await new Promise<SourceBuffer>(resolve =>
51
+ mediaSource.addEventListener(
52
+ 'sourceopen',
53
+ () => {
54
+ let resolved = false
55
+ receive((event) => {
56
+ if (resolved) return
57
+ if (event.type === 'METADATA') {
58
+ const sourceBuffer = mediaSource.addSourceBuffer(`video/mp4; codecs="${[event.info.output.videoMimeType, event.info.output.audioMimeType].filter(Boolean).join(',')}"`)
59
+ sourceBuffer.mode = 'segments'
60
+ mediaSource.duration = event.info.input.duration
61
+ headerBuffer = event.data
62
+ resolved = true
63
+ resolve(sourceBuffer)
64
+ }
65
+ })
66
+ },
67
+ { once: true }
68
+ )
69
+ )
70
+
71
+ if (!headerBuffer) throw new Error('No header buffer')
72
+
73
+ const { appendBuffer, unbufferRange, updateTimestampOffset } = updateSourceBuffer(sourceBuffer)
74
+ appendBuffer(headerBuffer)
75
+
76
+ receive(async (event) => {
77
+ if (event.type === 'DATA') {
78
+ await appendBuffer(event.data)
79
+ } else if (event.type === 'TIMESTAMP_OFFSET') {
80
+ await updateTimestampOffset(event.timestampOffset)
81
+ }
82
+ })
83
+
84
+ const unbuffer = async () => {
85
+ const preEvictionTime = getPreEvictionTime()
86
+ const postEvictionTime = getPostEvictionTime()
87
+ const bufferedRanges = getTimeRanges(sourceBuffer)
88
+ for (const { start, end } of bufferedRanges) {
89
+ if (start < preEvictionTime) {
90
+ await unbufferRange(
91
+ start,
92
+ preEvictionTime
93
+ )
94
+ }
95
+ if (end > postEvictionTime) {
96
+ await unbufferRange(
97
+ postEvictionTime,
98
+ end
99
+ )
100
+ }
101
+ }
102
+ }
103
+
104
+ const interval = setInterval(async () => {
105
+ await unbuffer()
106
+ const timeRanges = getTimeRanges(sourceBuffer)
107
+ const maxBufferedTime = Math.max(...timeRanges.map(({ end }) => end))
108
+ if (maxBufferedTime < getBufferTargetTime()) {
109
+ sendBack({ type: 'NEED_DATA' })
110
+ }
111
+ }, 100)
112
+
113
+ const handleSourceOpen = () => sendBack({ type: 'SOURCE_OPEN' })
114
+ const handleSourceEnded = () => sendBack({ type: 'SOURCE_ENDED' })
115
+ const handleSourceError = () => sendBack({ type: 'SOURCE_ERROR' })
116
+
117
+ mediaSource.addEventListener('sourceopen', handleSourceOpen)
118
+ mediaSource.addEventListener('sourceended', handleSourceEnded)
119
+ mediaSource.addEventListener('error', handleSourceError)
120
+
121
+ return () => {
122
+ URL.revokeObjectURL(mediaSourceUrl)
123
+ clearInterval(interval)
124
+ videoElement.removeEventListener('error', handlError)
125
+ mediaSource.removeEventListener('sourceopen', handleSourceOpen)
126
+ mediaSource.removeEventListener('sourceended', handleSourceEnded)
127
+ mediaSource.removeEventListener('error', handleSourceError)
128
+ }
129
+ })