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