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