@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,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.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
- })
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
+ })