@banou/media-player 0.6.2 → 0.7.0

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 (119) hide show
  1. package/README.md +148 -3
  2. package/dist/engine/index.d.ts +10 -0
  3. package/dist/engine/index.js +2 -0
  4. package/dist/engine/picture-in-picture.d.ts +27 -0
  5. package/dist/engine/playback.d.ts +45 -0
  6. package/dist/engine/source-buffer.d.ts +12 -0
  7. package/dist/engine/subtitles.d.ts +36 -0
  8. package/dist/engine/thumbnails.d.ts +22 -0
  9. package/dist/engine-Dmc7JSd9.js +470 -0
  10. package/dist/index.d.ts +12 -0
  11. package/dist/index.js +1566 -0
  12. package/dist/react/components/chrome.d.ts +9 -0
  13. package/dist/react/components/control-bar.d.ts +2 -0
  14. package/dist/react/components/overlay.d.ts +4 -0
  15. package/dist/react/components/playback-slider.d.ts +2 -0
  16. package/dist/react/components/progress-bar.d.ts +2 -0
  17. package/dist/react/components/settings.d.ts +2 -0
  18. package/dist/react/components/sound.d.ts +7 -0
  19. package/{build → dist/react}/components/tooltip-display.d.ts +8 -8
  20. package/dist/react/components/volume-slider.d.ts +6 -0
  21. package/dist/react/hooks/use-drag-value.d.ts +23 -0
  22. package/dist/react/hooks/use-picture-in-picture.d.ts +2 -0
  23. package/dist/react/hooks/use-playback.d.ts +6 -0
  24. package/dist/react/hooks/use-thumbnails.d.ts +11 -0
  25. package/dist/react/player.d.ts +25 -0
  26. package/dist/react/source-feature.d.ts +84 -0
  27. package/dist/react/video-player.d.ts +43 -0
  28. package/dist/utils/source.d.ts +30 -0
  29. package/dist/utils/track-label.d.ts +19 -0
  30. package/dist/utils/volume-utils.d.ts +6 -0
  31. package/package.json +49 -37
  32. package/src/lib/engine/index.ts +14 -0
  33. package/src/lib/engine/picture-in-picture.ts +250 -0
  34. package/src/lib/engine/playback.ts +352 -0
  35. package/src/lib/engine/source-buffer.ts +61 -0
  36. package/src/lib/engine/subtitles.ts +210 -0
  37. package/src/lib/engine/thumbnails.ts +137 -0
  38. package/src/lib/globals.d.ts +8 -0
  39. package/src/lib/index.tsx +36 -0
  40. package/src/lib/react/components/chrome.tsx +126 -0
  41. package/src/{components → lib/react/components}/control-bar.tsx +74 -96
  42. package/src/lib/react/components/overlay.tsx +121 -0
  43. package/src/{components → lib/react/components}/playback-slider.tsx +30 -52
  44. package/src/{components → lib/react/components}/progress-bar.tsx +128 -41
  45. package/src/lib/react/components/settings.tsx +391 -0
  46. package/src/lib/react/components/sound.tsx +133 -0
  47. package/src/{components → lib/react/components}/tooltip-display.tsx +11 -8
  48. package/src/{components → lib/react/components}/volume-slider.tsx +32 -46
  49. package/src/lib/react/hooks/use-drag-value.ts +73 -0
  50. package/src/lib/react/hooks/use-picture-in-picture.ts +36 -0
  51. package/src/lib/react/hooks/use-playback.ts +134 -0
  52. package/src/lib/react/hooks/use-thumbnails.ts +77 -0
  53. package/src/lib/react/player.ts +20 -0
  54. package/src/lib/react/source-feature.ts +90 -0
  55. package/src/lib/react/video-player.tsx +119 -0
  56. package/src/lib/utils/source.ts +60 -0
  57. package/src/lib/utils/track-label.ts +36 -0
  58. package/src/lib/utils/volume-utils.ts +13 -0
  59. package/build/components/chrome.d.ts +0 -7
  60. package/build/components/control-bar.d.ts +0 -6
  61. package/build/components/overlay.d.ts +0 -4
  62. package/build/components/playback-slider.d.ts +0 -2
  63. package/build/components/progress-bar.d.ts +0 -1
  64. package/build/components/settings.d.ts +0 -2
  65. package/build/components/sound.d.ts +0 -4
  66. package/build/components/volume-slider.d.ts +0 -6
  67. package/build/index.d.ts +0 -26
  68. package/build/index.js +0 -3416
  69. package/build/main.d.ts +0 -1
  70. package/build/state-machines/data-source.d.ts +0 -27
  71. package/build/state-machines/index.d.ts +0 -34038
  72. package/build/state-machines/media-properties.d.ts +0 -45
  73. package/build/state-machines/media-source.d.ts +0 -34
  74. package/build/state-machines/media.d.ts +0 -8558
  75. package/build/state-machines/subtitles.d.ts +0 -56
  76. package/build/state-machines/thumbnails.d.ts +0 -24
  77. package/build/state-machines/utils.d.ts +0 -26
  78. package/build/utils/actor-utils.d.ts +0 -2
  79. package/build/utils/context.d.ts +0 -14
  80. package/build/utils/index.d.ts +0 -4
  81. package/build/utils/languages.d.ts +0 -260
  82. package/build/utils/mp4box.d.ts +0 -61
  83. package/build/utils/use-local-storage.d.ts +0 -3
  84. package/build/utils/use-scrub.d.ts +0 -11
  85. package/build/utils/volume-utils.d.ts +0 -17
  86. package/build/utils/window-height.d.ts +0 -2
  87. package/src/components/chrome.tsx +0 -95
  88. package/src/components/overlay.tsx +0 -91
  89. package/src/components/settings.tsx +0 -322
  90. package/src/components/sound.tsx +0 -101
  91. package/src/index.tsx +0 -195
  92. package/src/main.tsx +0 -169
  93. package/src/state-machines/data-source.ts +0 -84
  94. package/src/state-machines/index.ts +0 -5
  95. package/src/state-machines/media-properties.ts +0 -82
  96. package/src/state-machines/media-source.ts +0 -129
  97. package/src/state-machines/media.ts +0 -255
  98. package/src/state-machines/subtitles.ts +0 -285
  99. package/src/state-machines/thumbnails.ts +0 -123
  100. package/src/state-machines/utils.ts +0 -94
  101. package/src/utils/actor-utils.ts +0 -19
  102. package/src/utils/context.ts +0 -23
  103. package/src/utils/index.ts +0 -229
  104. package/src/utils/languages.ts +0 -262
  105. package/src/utils/mp4box.ts +0 -74
  106. package/src/utils/use-local-storage.ts +0 -30
  107. package/src/utils/use-scrub.ts +0 -40
  108. package/src/utils/volume-utils.ts +0 -39
  109. package/src/utils/window-height.ts +0 -19
  110. package/src/vite-env.d.ts +0 -1
  111. package/tsconfig.json +0 -28
  112. package/tsconfig.node.json +0 -9
  113. /package/{build → dist}/utils/colors.d.ts +0 -0
  114. /package/{build → dist}/utils/fonts.d.ts +0 -0
  115. /package/{build → dist}/utils/time.d.ts +0 -0
  116. /package/src/{assets → lib/assets}/picture-in-picture.svg +0 -0
  117. /package/src/{utils → lib/utils}/colors.ts +0 -0
  118. /package/src/{utils → lib/utils}/fonts.ts +0 -0
  119. /package/src/{utils → lib/utils}/time.ts +0 -0
package/src/main.tsx DELETED
@@ -1,169 +0,0 @@
1
- /// <reference types="@emotion/react/types/css-prop" />
2
- import { useCallback, useEffect, useMemo, useState } from 'react'
3
- import { createRoot } from 'react-dom/client'
4
- import { css, Global } from '@emotion/react'
5
-
6
- import MediaPlayer from './index'
7
- import { DownloadedRange } from './utils/context'
8
-
9
- const mountStyle = css`
10
- display: grid;
11
- height: 100vh;
12
- width: 100vw;
13
- `
14
-
15
- const BASE_BUFFER_SIZE = 2_500_000
16
- const url = '/video.mkv'
17
-
18
- const Mount = () => {
19
- const [contentLength, setContentLength] = useState<number>()
20
-
21
- const read = useCallback(
22
- (offset: number, size: number) => {
23
- if (contentLength === undefined) return Promise.resolve(new Uint8Array(0).buffer)
24
- if (offset >= contentLength) return Promise.resolve(new Uint8Array(0).buffer)
25
- return (
26
- fetch(url, { headers: { Range: `bytes=${offset}-${Math.min(offset + size, contentLength) - 1}` } })
27
- .then(res => res.arrayBuffer())
28
- )
29
- },
30
- [contentLength]
31
- )
32
-
33
- useEffect(() => {
34
- fetch(url, { headers: { Range: `bytes=${0}-${1}` } })
35
- .then(async ({ headers, body }) => {
36
- if (!body) throw new Error('no body')
37
- const contentRangeContentLength = headers.get('Content-Range')?.split('/').at(1)
38
- const contentLength =
39
- contentRangeContentLength
40
- ? Number(contentRangeContentLength)
41
- : Number(headers.get('Content-Length'))
42
- setContentLength(contentLength)
43
- })
44
- }, [])
45
-
46
- const jassubWorkerUrl = useMemo(() => {
47
- const workerUrl = new URL('/build/jassub-worker.js', import.meta.url).toString()
48
- const blob = new Blob([`importScripts(${JSON.stringify(workerUrl)})`], { type: 'application/javascript' })
49
- return URL.createObjectURL(blob)
50
- }, [])
51
-
52
- const libavWorkerUrl = useMemo(() => {
53
- const workerUrl = new URL('/build/libav.js', new URL(window.location.toString()).origin).toString()
54
- const blob = new Blob([`importScripts(${JSON.stringify(workerUrl)})`], { type: 'application/javascript' })
55
- return URL.createObjectURL(blob)
56
- }, [])
57
-
58
- const jassubWasmUrl = useMemo(() => {
59
- return new URL('/build/jassub-worker.wasm', new URL(window.location.toString()).origin).toString()
60
- }, [])
61
-
62
- const jassubModernWasmUrl = useMemo(() => {
63
- return new URL('/build/jassub-modern-worker.wasm', new URL(window.location.toString()).origin).toString()
64
- }, [])
65
-
66
- const [downloadedRanges, setDownloadedRanges] = useState<DownloadedRange[]>([])
67
-
68
- useEffect(() => {
69
- if (!contentLength) return
70
- let i = 0
71
- const increaseDownloadedRanges = () => {
72
- setDownloadedRanges(() => [
73
- {
74
- startByteOffset: 0,
75
- endByteOffset: contentLength * i
76
- }
77
- ])
78
- i += 0.1
79
- if (i < 1) {
80
- setTimeout(increaseDownloadedRanges, 1000)
81
- }
82
- }
83
- increaseDownloadedRanges()
84
- }, [contentLength])
85
-
86
- return (
87
- <div css={mountStyle}>
88
- <MediaPlayer
89
- title={'video.mkv'}
90
- downloadedRanges={contentLength ? downloadedRanges : undefined}
91
- bufferSize={BASE_BUFFER_SIZE}
92
- read={read}
93
- size={contentLength}
94
- autoplay={true}
95
- publicPath={new URL('/build/', new URL(import.meta.url).origin).toString()}
96
- jassubModernWasmUrl={jassubModernWasmUrl}
97
- jassubWorkerUrl={jassubWorkerUrl}
98
- jassubWasmUrl={jassubWasmUrl}
99
- libavWorkerUrl={libavWorkerUrl}
100
- />
101
- </div>
102
- )
103
- }
104
-
105
- const globalStyle = css`
106
- @import url('https://fonts.googleapis.com/css2?family=Fira+Code:wght@300;400;500;600;700&family=Fira+Sans:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,500;1,600;1,700;1,800;1,900&family=Montserrat:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap');
107
-
108
- *, *::before, *::after {
109
- box-sizing: border-box;
110
- margin: 0;
111
- padding: 0;
112
- }
113
-
114
- html {
115
- font-size: 62.5%;
116
- height: 100%;
117
- width: 100%;
118
- }
119
-
120
- body {
121
- margin: 0;
122
- height: 100%;
123
- width: 100%;
124
- font-size: 1.6rem;
125
- font-family: Fira Sans;
126
- color: #fff;
127
-
128
- font-family: Montserrat;
129
- }
130
-
131
- body > div {
132
- height: 100%;
133
- width: 100%;
134
- }
135
-
136
- a {
137
- color: #777777;
138
- text-decoration: none;
139
- }
140
-
141
- a:hover {
142
- color: #fff;
143
- text-decoration: underline;
144
- }
145
-
146
- ul {
147
- list-style: none;
148
- }
149
- `
150
-
151
- const mountElement = document.createElement('div')
152
-
153
- const root = createRoot(
154
- document.body.appendChild(mountElement)
155
- )
156
-
157
- root.render(
158
- <>
159
- <Global styles={globalStyle}/>
160
- <Mount/>
161
- </>
162
- )
163
-
164
- if (import.meta.hot) {
165
- import.meta.hot.dispose(() => {
166
- root.unmount()
167
- mountElement.remove()
168
- })
169
- }
@@ -1,84 +0,0 @@
1
- import { makeRemuxer } from 'libav-wasm'
2
-
3
- import { fromAsyncCallback } from './utils'
4
- import { queuedThrottleWithLastCall, toStreamChunkSize } from '../utils'
5
- import { Attachment, SubtitleFragment } from 'libav-wasm/build/worker'
6
-
7
- type DataSourceEvents =
8
- | { type: 'METADATA', mimeType: string, duration: number }
9
- | { type: 'SEEKING', currentTime: number }
10
- | { type: 'NEED_DATA' }
11
-
12
- type DataSourceEmittedEvents =
13
- | { type: 'DATA', data: Uint8Array }
14
- | { type: 'NEW_SUBTITLE_FRAGMENTS', subtitles: SubtitleFragment[] }
15
- | { type: 'NEW_ATTACHMENTS', attachments: Attachment[] }
16
-
17
- type DataSourceInput = {
18
- remuxerOptions: Parameters<typeof makeRemuxer>[0]
19
- }
20
-
21
- export default fromAsyncCallback<DataSourceEvents, DataSourceInput, DataSourceEmittedEvents>(async ({ sendBack, receive, input, self, emit }) => {
22
- const { remuxerOptions } = input
23
- const { publicPath, workerUrl, bufferSize, length, read } = remuxerOptions
24
-
25
- const remuxer = await makeRemuxer({
26
- publicPath,
27
- workerUrl,
28
- bufferSize,
29
- length,
30
- read
31
- })
32
-
33
- const metadata = await remuxer.init()
34
- if (metadata.indexes) sendBack({ type: 'INDEXES', indexes: metadata.indexes })
35
- if (metadata.attachments?.length) sendBack({ type: 'NEW_ATTACHMENTS', attachments: metadata.attachments })
36
- if (metadata.subtitles?.length) sendBack({ type: 'NEW_SUBTITLE_FRAGMENTS', subtitles: metadata.subtitles })
37
- sendBack({ type: 'METADATA', ...metadata })
38
-
39
- let isFinished = false
40
- let currentSeeks: { currentTime: number }[] = []
41
- const loadMore = queuedThrottleWithLastCall(100, async () => {
42
- if (currentSeeks.length || isFinished) return
43
- try {
44
- const { data, subtitles, finished } = await remuxer.read()
45
- if (finished) {
46
- isFinished = true
47
- }
48
- if (subtitles.length) {
49
- sendBack({ type: 'NEW_SUBTITLE_FRAGMENTS', subtitles })
50
- }
51
- sendBack({ type: 'DATA', data })
52
- } catch (err: any) {
53
- if (err.message === 'Cancelled') return
54
- console.error(err)
55
- }
56
- })
57
-
58
- receive(async (event) => {
59
- if (event.type === 'NEED_DATA') {
60
- loadMore()
61
- } else if (event.type === 'SEEKING') {
62
- isFinished = false
63
- const { currentTime } = event
64
- const seekObject = { currentTime }
65
- currentSeeks = [...currentSeeks, seekObject]
66
- try {
67
- const { data, pts } = await remuxer
68
- .seek(currentTime)
69
- .finally(() => {
70
- currentSeeks = currentSeeks.filter(seekObj => seekObj !== seekObject)
71
- })
72
- sendBack({ type: 'TIMESTAMP_OFFSET', timestampOffset: pts })
73
- sendBack({ type: 'DATA', data })
74
- } catch (err: any) {
75
- if (err.message === 'Cancelled') return
76
- console.error(err)
77
- }
78
- }
79
- })
80
-
81
- return () => {
82
- remuxer.destroy()
83
- }
84
- })
@@ -1,5 +0,0 @@
1
- import { createActorContext } from '@xstate/react'
2
-
3
- import mediaMachine from './media'
4
-
5
- export const MediaMachineContext = createActorContext(mediaMachine)
@@ -1,82 +0,0 @@
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 +0,0 @@
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
- })