@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,322 +1,322 @@
1
- import { useEffect, useMemo, useRef, useState } from "react"
2
- import { css } from "@emotion/react"
3
- import { ChevronLeft, ChevronRight, Settings } from "react-feather"
4
-
5
- import { MediaMachineContext } from "../state-machines"
6
- import { TooltipDisplay } from "./tooltip-display"
7
- import { fonts } from "../utils/fonts"
8
- import PlaybackSlider from "./playback-slider"
9
- import useLocalStorage, { booleanType } from "../utils/use-local-storage"
10
-
11
- const style = css`
12
- position: relative;
13
-
14
- .popover {
15
- position: absolute;
16
- right: 50%;
17
- transform: translateX(50%);
18
-
19
- overflow-y: auto;
20
- width: 180px;
21
- height: 160px;
22
- top: -180px;
23
- @media (min-width: 768px) {
24
- width: 250px;
25
- height: 160px;
26
- }
27
-
28
- display: flex;
29
- flex-direction: column;
30
-
31
- border-radius: 8px;
32
- background-color: rgba(28,28,28,0.95);
33
- ${fonts.bMedium.regular}
34
-
35
- z-index: 4;
36
-
37
- > div {
38
- display: flex;
39
- align-items: center;
40
- justify-content: space-between;
41
-
42
- color: #fff;
43
- outline: none;
44
-
45
- padding: 8px 6px 8px 12px;
46
-
47
- width: 100%;
48
-
49
- :first-of-type {
50
- border-radius: 8px 8px 0 0;
51
- }
52
- :last-of-type {
53
- border-radius: 0 0 8px 8px;
54
- }
55
- :not(&.no-hover) {
56
- cursor: pointer;
57
- }
58
- :not(&.no-hover):hover {
59
- background-color: rgba(255,255,255,.1);
60
- }
61
-
62
- > div {
63
- display: flex;
64
- align-items: center;
65
- justify-content: center;
66
-
67
- .secondary {
68
- color: #eee;
69
- ${fonts.bSmall.regular}
70
- }
71
- }
72
- }
73
-
74
- .back {
75
- display: flex;
76
- align-items: center;
77
- justify-content: flex-start;
78
- border-bottom: 1px solid #4D4D4E;
79
-
80
- svg {
81
- transform: translateX(-4px);
82
- transition: transform 0.2s ease-in-out;
83
- }
84
-
85
- :hover {
86
- svg {
87
- transform: translateX(-8px);
88
- }
89
- }
90
- }
91
- }
92
-
93
- .playback-rate {
94
- .slider {
95
- width: 100%;
96
- }
97
- .options {
98
- display: grid;
99
- grid-template-columns: 1fr 1fr 1fr 1fr;
100
- align-items: center;
101
-
102
- padding: 8px 12px;
103
-
104
- > div {
105
- display: flex;
106
- align-items: center;
107
- border-radius: 4px;
108
-
109
- padding: 8px 6px;
110
- height: 100%;
111
-
112
- cursor: pointer;
113
- :hover {
114
- background-color: rgba(255,255,255,.1);
115
- }
116
- }
117
- }
118
- }
119
-
120
- .subtitle {
121
- .description {
122
- word-break: break-word;
123
- }
124
- }
125
- `
126
-
127
- enum PopoverContent {
128
- Default,
129
- PlaybackRate,
130
- Advanced,
131
- SelectNewSources,
132
- Subtitles
133
- }
134
-
135
- const SettingsAction = () => {
136
- const mediaActor = MediaMachineContext.useActorRef()
137
- const playbackRate = MediaMachineContext.useSelector((state) => state.context.media.playbackRate)
138
- const subtitleStreams = MediaMachineContext.useSelector((state) => state.context.subtitleStreams)
139
- const selectedSubtitleStreamIndex = MediaMachineContext.useSelector((state) => state.context.selectedSubtitleStreamIndex)
140
- const [hideMediaStats, setHideMediaStats] = useLocalStorage('hideMediaStats', 'false') as [booleanType, (newValue: booleanType) => void]
141
-
142
- const [isOpenPopover, setIsOpenPopover] = useState(false)
143
- const [popoverContent, setPopoverContent] = useState(PopoverContent.Default)
144
- const settingsContainerRef = useRef<HTMLDivElement>(null)
145
-
146
- const togglePopover = () => {
147
- setIsOpenPopover(!isOpenPopover)
148
- setPopoverContent(PopoverContent.Default)
149
- }
150
-
151
- const setPlaybackRate = (rate: number) => {
152
- mediaActor.send({ type: 'SET_PLAYBACK_RATE', playbackRate: rate })
153
- setPopoverContent(PopoverContent.Default)
154
- }
155
-
156
- useEffect(() => {
157
- const handleClickOutside = (ev: MouseEvent) => {
158
- if (
159
- isOpenPopover &&
160
- settingsContainerRef.current &&
161
- !settingsContainerRef.current.contains(ev.target as Node)
162
- ) {
163
- togglePopover()
164
- }
165
- }
166
- document.addEventListener('mousedown', handleClickOutside)
167
- return () => document.removeEventListener('mousedown', handleClickOutside)
168
- }, [isOpenPopover])
169
-
170
- const languagesWithStreamIndex = useMemo(
171
- () => subtitleStreams.map(({ header }) => ({ streamIndex: header.streamIndex, language: header.parsed.info.Title })),
172
- [subtitleStreams.length]
173
- )
174
-
175
- const setLanguage = (languageWithStreamIndex: { streamIndex: number | undefined, language?: string }) => () => {
176
- mediaActor.send({ type: 'SELECT_SUBTITLE_STREAM', streamIndex: languageWithStreamIndex.streamIndex })
177
- togglePopover()
178
- }
179
-
180
- const changePopoverContent = (newPopoverContent: PopoverContent) => () => {
181
- setPopoverContent(newPopoverContent)
182
- }
183
-
184
- return (
185
- <div css={style} ref={settingsContainerRef}>
186
- <TooltipDisplay
187
- id='settings'
188
- disabled={isOpenPopover}
189
- text={
190
- <button
191
- className='settings'
192
- type='button'
193
- onClick={togglePopover}
194
- >
195
- <Settings />
196
- </button>
197
- }
198
- toolTipText={
199
- <span>
200
- Settings
201
- </span>
202
- }
203
- />
204
- {
205
- isOpenPopover && popoverContent === PopoverContent.Default && (
206
- <div className='popover menu'>
207
- <div onClick={changePopoverContent(PopoverContent.Advanced)}>
208
- <span>Advanced</span>
209
- <div>
210
- <ChevronRight />
211
- </div>
212
- </div>
213
- {/* <div onClick={changePopoverContent(PopoverContent.SelectNewSources)}>
214
- <div>Select new sources</div>
215
- <div>
216
- <ChevronRight />
217
- </div>
218
- </div> */}
219
- <div onClick={changePopoverContent(PopoverContent.Subtitles)}>
220
- <div>Subtitles</div>
221
- <div>
222
- <ChevronRight />
223
- </div>
224
- </div>
225
- <div onClick={changePopoverContent(PopoverContent.PlaybackRate)}>
226
- <div>Playback speed</div>
227
- <div>
228
- <span className='secondary'>
229
- {
230
- playbackRate === 1
231
- ? '(Default)'
232
- : playbackRate.toFixed(2)
233
- }
234
- </span>
235
- <ChevronRight />
236
- </div>
237
- </div>
238
- </div>
239
- )
240
- }
241
- {
242
- isOpenPopover && popoverContent === PopoverContent.PlaybackRate && (
243
- <div className='popover playback-rate'>
244
- <div className="back" onClick={changePopoverContent(PopoverContent.Default)}>
245
- <ChevronLeft />
246
- <span>Plackback speed</span>
247
- </div>
248
- <div className="slider no-hover">
249
- <PlaybackSlider />
250
- </div>
251
- <div className="options no-hover">
252
- <div onClick={() => setPlaybackRate(0.5)}>
253
- 0.5x
254
- </div>
255
- <div onClick={() => setPlaybackRate(1)}>
256
- 1x
257
- </div>
258
- <div onClick={() => setPlaybackRate(1.5)}>
259
- 1.5x
260
- </div>
261
- <div onClick={() => setPlaybackRate(2)}>
262
- 2x
263
- </div>
264
- </div>
265
- </div>
266
- )
267
- }
268
- {
269
- isOpenPopover && popoverContent === PopoverContent.Advanced && (
270
- <div className='popover advanced'>
271
- <div className="back" onClick={changePopoverContent(PopoverContent.Default)}>
272
- <ChevronLeft />
273
- <span>Advanced</span>
274
- </div>
275
- <div onClick={() => setHideMediaStats(hideMediaStats === 'true' ? 'false' : 'true')}>
276
- <span>Hide stats</span>
277
- <span>{hideMediaStats === 'true' ? '✓' : ''}</span>
278
- </div>
279
- </div>
280
- )
281
- }
282
- {
283
- isOpenPopover && popoverContent === PopoverContent.SelectNewSources && (
284
- <div className='popover sources'>
285
- <div className="back" onClick={changePopoverContent(PopoverContent.Default)}>
286
- <ChevronLeft />
287
- <span>Select new sources</span>
288
- </div>
289
- </div>
290
- )
291
- }
292
- {
293
- isOpenPopover && popoverContent === PopoverContent.Subtitles && (
294
- <div className='popover subtitle'>
295
- <div className="back" onClick={changePopoverContent(PopoverContent.Default)}>
296
- <ChevronLeft />
297
- <span>Subtitles</span>
298
- </div>
299
- <div onClick={setLanguage({ streamIndex: undefined })}>
300
- <span>Disable</span>
301
- <span>{selectedSubtitleStreamIndex === undefined ? '✓' : ''}</span>
302
- </div>
303
- {
304
- languagesWithStreamIndex.map((languageWithStreamIndex) => (
305
- <div
306
- key={languageWithStreamIndex.streamIndex}
307
- onClick={setLanguage(languageWithStreamIndex)}
308
- className="description"
309
- >
310
- <span>{languageWithStreamIndex.language}</span>
311
- <span>{selectedSubtitleStreamIndex === languageWithStreamIndex.streamIndex ? '✓' : ''}</span>
312
- </div>
313
- ))
314
- }
315
- </div>
316
- )
317
- }
318
- </div>
319
- )
320
- }
321
-
1
+ import { useEffect, useMemo, useRef, useState } from "react"
2
+ import { css } from "@emotion/react"
3
+ import { ChevronLeft, ChevronRight, Settings } from "react-feather"
4
+
5
+ import { MediaMachineContext } from "../state-machines"
6
+ import { TooltipDisplay } from "./tooltip-display"
7
+ import { fonts } from "../utils/fonts"
8
+ import PlaybackSlider from "./playback-slider"
9
+ import useLocalStorage, { booleanType } from "../utils/use-local-storage"
10
+
11
+ const style = css`
12
+ position: relative;
13
+
14
+ .popover {
15
+ position: absolute;
16
+ right: 50%;
17
+ transform: translateX(50%);
18
+
19
+ overflow-y: auto;
20
+ width: 180px;
21
+ height: 160px;
22
+ top: -180px;
23
+ @media (min-width: 768px) {
24
+ width: 250px;
25
+ height: 160px;
26
+ }
27
+
28
+ display: flex;
29
+ flex-direction: column;
30
+
31
+ border-radius: 8px;
32
+ background-color: rgba(28,28,28,0.95);
33
+ ${fonts.bMedium.regular}
34
+
35
+ z-index: 4;
36
+
37
+ > div {
38
+ display: flex;
39
+ align-items: center;
40
+ justify-content: space-between;
41
+
42
+ color: #fff;
43
+ outline: none;
44
+
45
+ padding: 8px 6px 8px 12px;
46
+
47
+ width: 100%;
48
+
49
+ :first-of-type {
50
+ border-radius: 8px 8px 0 0;
51
+ }
52
+ :last-of-type {
53
+ border-radius: 0 0 8px 8px;
54
+ }
55
+ :not(&.no-hover) {
56
+ cursor: pointer;
57
+ }
58
+ :not(&.no-hover):hover {
59
+ background-color: rgba(255,255,255,.1);
60
+ }
61
+
62
+ > div {
63
+ display: flex;
64
+ align-items: center;
65
+ justify-content: center;
66
+
67
+ .secondary {
68
+ color: #eee;
69
+ ${fonts.bSmall.regular}
70
+ }
71
+ }
72
+ }
73
+
74
+ .back {
75
+ display: flex;
76
+ align-items: center;
77
+ justify-content: flex-start;
78
+ border-bottom: 1px solid #4D4D4E;
79
+
80
+ svg {
81
+ transform: translateX(-4px);
82
+ transition: transform 0.2s ease-in-out;
83
+ }
84
+
85
+ :hover {
86
+ svg {
87
+ transform: translateX(-8px);
88
+ }
89
+ }
90
+ }
91
+ }
92
+
93
+ .playback-rate {
94
+ .slider {
95
+ width: 100%;
96
+ }
97
+ .options {
98
+ display: grid;
99
+ grid-template-columns: 1fr 1fr 1fr 1fr;
100
+ align-items: center;
101
+
102
+ padding: 8px 12px;
103
+
104
+ > div {
105
+ display: flex;
106
+ align-items: center;
107
+ border-radius: 4px;
108
+
109
+ padding: 8px 6px;
110
+ height: 100%;
111
+
112
+ cursor: pointer;
113
+ :hover {
114
+ background-color: rgba(255,255,255,.1);
115
+ }
116
+ }
117
+ }
118
+ }
119
+
120
+ .subtitle {
121
+ .description {
122
+ word-break: break-word;
123
+ }
124
+ }
125
+ `
126
+
127
+ enum PopoverContent {
128
+ Default,
129
+ PlaybackRate,
130
+ Advanced,
131
+ SelectNewSources,
132
+ Subtitles
133
+ }
134
+
135
+ const SettingsAction = () => {
136
+ const mediaActor = MediaMachineContext.useActorRef()
137
+ const playbackRate = MediaMachineContext.useSelector((state) => state.context.media.playbackRate)
138
+ const subtitleStreams = MediaMachineContext.useSelector((state) => state.context.subtitleStreams)
139
+ const selectedSubtitleStreamIndex = MediaMachineContext.useSelector((state) => state.context.selectedSubtitleStreamIndex)
140
+ const [hideMediaStats, setHideMediaStats] = useLocalStorage('hideMediaStats', 'false') as [booleanType, (newValue: booleanType) => void]
141
+
142
+ const [isOpenPopover, setIsOpenPopover] = useState(false)
143
+ const [popoverContent, setPopoverContent] = useState(PopoverContent.Default)
144
+ const settingsContainerRef = useRef<HTMLDivElement>(null)
145
+
146
+ const togglePopover = () => {
147
+ setIsOpenPopover(!isOpenPopover)
148
+ setPopoverContent(PopoverContent.Default)
149
+ }
150
+
151
+ const setPlaybackRate = (rate: number) => {
152
+ mediaActor.send({ type: 'SET_PLAYBACK_RATE', playbackRate: rate })
153
+ setPopoverContent(PopoverContent.Default)
154
+ }
155
+
156
+ useEffect(() => {
157
+ const handleClickOutside = (ev: MouseEvent) => {
158
+ if (
159
+ isOpenPopover &&
160
+ settingsContainerRef.current &&
161
+ !settingsContainerRef.current.contains(ev.target as Node)
162
+ ) {
163
+ togglePopover()
164
+ }
165
+ }
166
+ document.addEventListener('mousedown', handleClickOutside)
167
+ return () => document.removeEventListener('mousedown', handleClickOutside)
168
+ }, [isOpenPopover])
169
+
170
+ const languagesWithStreamIndex = useMemo(
171
+ () => subtitleStreams.map(({ header }) => ({ streamIndex: header.streamIndex, language: header.parsed.info.Title })),
172
+ [subtitleStreams.length]
173
+ )
174
+
175
+ const setLanguage = (languageWithStreamIndex: { streamIndex: number | undefined, language?: string }) => () => {
176
+ mediaActor.send({ type: 'SELECT_SUBTITLE_STREAM', streamIndex: languageWithStreamIndex.streamIndex })
177
+ togglePopover()
178
+ }
179
+
180
+ const changePopoverContent = (newPopoverContent: PopoverContent) => () => {
181
+ setPopoverContent(newPopoverContent)
182
+ }
183
+
184
+ return (
185
+ <div css={style} ref={settingsContainerRef}>
186
+ <TooltipDisplay
187
+ id='settings'
188
+ disabled={isOpenPopover}
189
+ text={
190
+ <button
191
+ className='settings'
192
+ type='button'
193
+ onClick={togglePopover}
194
+ >
195
+ <Settings />
196
+ </button>
197
+ }
198
+ toolTipText={
199
+ <span>
200
+ Settings
201
+ </span>
202
+ }
203
+ />
204
+ {
205
+ isOpenPopover && popoverContent === PopoverContent.Default && (
206
+ <div className='popover menu'>
207
+ <div onClick={changePopoverContent(PopoverContent.Advanced)}>
208
+ <span>Advanced</span>
209
+ <div>
210
+ <ChevronRight />
211
+ </div>
212
+ </div>
213
+ {/* <div onClick={changePopoverContent(PopoverContent.SelectNewSources)}>
214
+ <div>Select new sources</div>
215
+ <div>
216
+ <ChevronRight />
217
+ </div>
218
+ </div> */}
219
+ <div onClick={changePopoverContent(PopoverContent.Subtitles)}>
220
+ <div>Subtitles</div>
221
+ <div>
222
+ <ChevronRight />
223
+ </div>
224
+ </div>
225
+ <div onClick={changePopoverContent(PopoverContent.PlaybackRate)}>
226
+ <div>Playback speed</div>
227
+ <div>
228
+ <span className='secondary'>
229
+ {
230
+ playbackRate === 1
231
+ ? '(Default)'
232
+ : playbackRate.toFixed(2)
233
+ }
234
+ </span>
235
+ <ChevronRight />
236
+ </div>
237
+ </div>
238
+ </div>
239
+ )
240
+ }
241
+ {
242
+ isOpenPopover && popoverContent === PopoverContent.PlaybackRate && (
243
+ <div className='popover playback-rate'>
244
+ <div className="back" onClick={changePopoverContent(PopoverContent.Default)}>
245
+ <ChevronLeft />
246
+ <span>Plackback speed</span>
247
+ </div>
248
+ <div className="slider no-hover">
249
+ <PlaybackSlider />
250
+ </div>
251
+ <div className="options no-hover">
252
+ <div onClick={() => setPlaybackRate(0.5)}>
253
+ 0.5x
254
+ </div>
255
+ <div onClick={() => setPlaybackRate(1)}>
256
+ 1x
257
+ </div>
258
+ <div onClick={() => setPlaybackRate(1.5)}>
259
+ 1.5x
260
+ </div>
261
+ <div onClick={() => setPlaybackRate(2)}>
262
+ 2x
263
+ </div>
264
+ </div>
265
+ </div>
266
+ )
267
+ }
268
+ {
269
+ isOpenPopover && popoverContent === PopoverContent.Advanced && (
270
+ <div className='popover advanced'>
271
+ <div className="back" onClick={changePopoverContent(PopoverContent.Default)}>
272
+ <ChevronLeft />
273
+ <span>Advanced</span>
274
+ </div>
275
+ <div onClick={() => setHideMediaStats(hideMediaStats === 'true' ? 'false' : 'true')}>
276
+ <span>Hide stats</span>
277
+ <span>{hideMediaStats === 'true' ? '✓' : ''}</span>
278
+ </div>
279
+ </div>
280
+ )
281
+ }
282
+ {
283
+ isOpenPopover && popoverContent === PopoverContent.SelectNewSources && (
284
+ <div className='popover sources'>
285
+ <div className="back" onClick={changePopoverContent(PopoverContent.Default)}>
286
+ <ChevronLeft />
287
+ <span>Select new sources</span>
288
+ </div>
289
+ </div>
290
+ )
291
+ }
292
+ {
293
+ isOpenPopover && popoverContent === PopoverContent.Subtitles && (
294
+ <div className='popover subtitle'>
295
+ <div className="back" onClick={changePopoverContent(PopoverContent.Default)}>
296
+ <ChevronLeft />
297
+ <span>Subtitles</span>
298
+ </div>
299
+ <div onClick={setLanguage({ streamIndex: undefined })}>
300
+ <span>Disable</span>
301
+ <span>{selectedSubtitleStreamIndex === undefined ? '✓' : ''}</span>
302
+ </div>
303
+ {
304
+ languagesWithStreamIndex.map((languageWithStreamIndex) => (
305
+ <div
306
+ key={languageWithStreamIndex.streamIndex}
307
+ onClick={setLanguage(languageWithStreamIndex)}
308
+ className="description"
309
+ >
310
+ <span>{languageWithStreamIndex.language}</span>
311
+ <span>{selectedSubtitleStreamIndex === languageWithStreamIndex.streamIndex ? '✓' : ''}</span>
312
+ </div>
313
+ ))
314
+ }
315
+ </div>
316
+ )
317
+ }
318
+ </div>
319
+ )
320
+ }
321
+
322
322
  export default SettingsAction