@banou/media-player 0.6.2 → 0.8.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.
- package/README.md +149 -3
- package/dist/engine/index.d.ts +10 -0
- package/dist/engine/index.js +2 -0
- package/dist/engine/picture-in-picture.d.ts +27 -0
- package/dist/engine/playback.d.ts +45 -0
- package/dist/engine/source-buffer.d.ts +12 -0
- package/dist/engine/subtitles.d.ts +36 -0
- package/dist/engine/thumbnails.d.ts +22 -0
- package/dist/engine-Dmc7JSd9.js +470 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +1610 -0
- package/dist/react/components/chrome.d.ts +12 -0
- package/dist/react/components/control-bar.d.ts +2 -0
- package/dist/react/components/overlay.d.ts +4 -0
- package/dist/react/components/playback-slider.d.ts +2 -0
- package/dist/react/components/progress-bar.d.ts +2 -0
- package/dist/react/components/settings.d.ts +2 -0
- package/dist/react/components/sound.d.ts +7 -0
- package/{build → dist/react}/components/tooltip-display.d.ts +8 -8
- package/dist/react/components/volume-slider.d.ts +6 -0
- package/dist/react/hooks/use-drag-value.d.ts +23 -0
- package/dist/react/hooks/use-picture-in-picture.d.ts +2 -0
- package/dist/react/hooks/use-playback.d.ts +8 -0
- package/dist/react/hooks/use-thumbnails.d.ts +12 -0
- package/dist/react/media.d.ts +98 -0
- package/dist/react/player.d.ts +26 -0
- package/dist/react/source-feature.d.ts +106 -0
- package/dist/react/video-player.d.ts +83 -0
- package/dist/utils/source.d.ts +30 -0
- package/dist/utils/track-label.d.ts +31 -0
- package/dist/utils/volume-utils.d.ts +6 -0
- package/package.json +58 -37
- package/src/lib/engine/index.ts +14 -0
- package/src/lib/engine/picture-in-picture.ts +250 -0
- package/src/lib/engine/playback.ts +352 -0
- package/src/lib/engine/source-buffer.ts +61 -0
- package/src/lib/engine/subtitles.ts +210 -0
- package/src/lib/engine/thumbnails.ts +137 -0
- package/src/lib/globals.d.ts +8 -0
- package/src/lib/index.tsx +51 -0
- package/src/lib/react/components/chrome.tsx +130 -0
- package/src/{components → lib/react/components}/control-bar.tsx +74 -96
- package/src/lib/react/components/overlay.tsx +125 -0
- package/src/{components → lib/react/components}/playback-slider.tsx +30 -52
- package/src/lib/react/components/progress-bar.tsx +341 -0
- package/src/lib/react/components/settings.tsx +384 -0
- package/src/lib/react/components/sound.tsx +133 -0
- package/src/{components → lib/react/components}/tooltip-display.tsx +15 -12
- package/src/{components → lib/react/components}/volume-slider.tsx +32 -46
- package/src/lib/react/hooks/use-drag-value.ts +73 -0
- package/src/lib/react/hooks/use-picture-in-picture.ts +36 -0
- package/src/lib/react/hooks/use-playback.ts +146 -0
- package/src/lib/react/hooks/use-thumbnails.ts +78 -0
- package/src/lib/react/media.ts +124 -0
- package/src/lib/react/player.ts +20 -0
- package/src/lib/react/source-feature.ts +108 -0
- package/src/lib/react/video-player.tsx +223 -0
- package/src/lib/utils/fonts.ts +160 -0
- package/src/lib/utils/source.ts +60 -0
- package/src/lib/utils/track-label.ts +51 -0
- package/src/lib/utils/volume-utils.ts +13 -0
- package/build/components/chrome.d.ts +0 -7
- package/build/components/control-bar.d.ts +0 -6
- package/build/components/overlay.d.ts +0 -4
- package/build/components/playback-slider.d.ts +0 -2
- package/build/components/progress-bar.d.ts +0 -1
- package/build/components/settings.d.ts +0 -2
- package/build/components/sound.d.ts +0 -4
- package/build/components/volume-slider.d.ts +0 -6
- package/build/index.d.ts +0 -26
- package/build/index.js +0 -3416
- package/build/main.d.ts +0 -1
- package/build/state-machines/data-source.d.ts +0 -27
- package/build/state-machines/index.d.ts +0 -34038
- package/build/state-machines/media-properties.d.ts +0 -45
- package/build/state-machines/media-source.d.ts +0 -34
- package/build/state-machines/media.d.ts +0 -8558
- package/build/state-machines/subtitles.d.ts +0 -56
- package/build/state-machines/thumbnails.d.ts +0 -24
- package/build/state-machines/utils.d.ts +0 -26
- package/build/utils/actor-utils.d.ts +0 -2
- package/build/utils/context.d.ts +0 -14
- package/build/utils/index.d.ts +0 -4
- package/build/utils/languages.d.ts +0 -260
- package/build/utils/mp4box.d.ts +0 -61
- package/build/utils/use-local-storage.d.ts +0 -3
- package/build/utils/use-scrub.d.ts +0 -11
- package/build/utils/volume-utils.d.ts +0 -17
- package/build/utils/window-height.d.ts +0 -2
- package/src/components/chrome.tsx +0 -95
- package/src/components/overlay.tsx +0 -91
- package/src/components/progress-bar.tsx +0 -251
- package/src/components/settings.tsx +0 -322
- package/src/components/sound.tsx +0 -101
- package/src/index.tsx +0 -195
- package/src/main.tsx +0 -169
- package/src/state-machines/data-source.ts +0 -84
- package/src/state-machines/index.ts +0 -5
- package/src/state-machines/media-properties.ts +0 -82
- package/src/state-machines/media-source.ts +0 -129
- package/src/state-machines/media.ts +0 -255
- package/src/state-machines/subtitles.ts +0 -285
- package/src/state-machines/thumbnails.ts +0 -123
- package/src/state-machines/utils.ts +0 -94
- package/src/utils/actor-utils.ts +0 -19
- package/src/utils/context.ts +0 -23
- package/src/utils/fonts.ts +0 -160
- package/src/utils/index.ts +0 -229
- package/src/utils/languages.ts +0 -262
- package/src/utils/mp4box.ts +0 -74
- package/src/utils/use-local-storage.ts +0 -30
- package/src/utils/use-scrub.ts +0 -40
- package/src/utils/volume-utils.ts +0 -39
- package/src/utils/window-height.ts +0 -19
- package/src/vite-env.d.ts +0 -1
- package/tsconfig.json +0 -28
- package/tsconfig.node.json +0 -9
- /package/{build → dist}/utils/colors.d.ts +0 -0
- /package/{build → dist}/utils/fonts.d.ts +0 -0
- /package/{build → dist}/utils/time.d.ts +0 -0
- /package/src/{assets → lib/assets}/picture-in-picture.svg +0 -0
- /package/src/{utils → lib/utils}/colors.ts +0 -0
- /package/src/{utils → lib/utils}/time.ts +0 -0
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
/// <reference types="@emotion/react/types/css-prop" />
|
|
2
|
+
import type { TrackChoice } from '../source-feature'
|
|
3
|
+
|
|
4
|
+
import { useEffect, useRef, useState } from 'react'
|
|
5
|
+
import { css } from '@emotion/react'
|
|
6
|
+
import { ChevronLeft, ChevronRight, Settings } from 'react-feather'
|
|
7
|
+
|
|
8
|
+
import { usePlayer } from '../player'
|
|
9
|
+
import { TooltipDisplay } from './tooltip-display'
|
|
10
|
+
import { fonts } from '../../utils/fonts'
|
|
11
|
+
import PlaybackSlider from './playback-slider'
|
|
12
|
+
|
|
13
|
+
const style = css`
|
|
14
|
+
position: relative;
|
|
15
|
+
|
|
16
|
+
.settings {
|
|
17
|
+
/* the icon keeps its size, the pressable box grows around it */
|
|
18
|
+
@media (pointer: coarse) {
|
|
19
|
+
box-sizing: border-box;
|
|
20
|
+
justify-content: center;
|
|
21
|
+
|
|
22
|
+
min-width: 44px;
|
|
23
|
+
min-height: 44px;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.popover {
|
|
28
|
+
position: absolute;
|
|
29
|
+
/* the gear sits a couple of buttons in from the right edge, so a centred popover hangs off that
|
|
30
|
+
edge on a phone. It anchors to the button instead until the viewport has room to centre it. */
|
|
31
|
+
right: 0;
|
|
32
|
+
transform: none;
|
|
33
|
+
|
|
34
|
+
overflow-y: auto;
|
|
35
|
+
width: 180px;
|
|
36
|
+
max-width: calc(100vw - 24px);
|
|
37
|
+
height: 160px;
|
|
38
|
+
top: -180px;
|
|
39
|
+
@media (min-width: 768px) {
|
|
40
|
+
right: 50%;
|
|
41
|
+
transform: translateX(50%);
|
|
42
|
+
width: 250px;
|
|
43
|
+
height: 160px;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
display: flex;
|
|
47
|
+
flex-direction: column;
|
|
48
|
+
|
|
49
|
+
border-radius: 8px;
|
|
50
|
+
background-color: rgba(28,28,28,0.95);
|
|
51
|
+
${fonts.bMedium.regular}
|
|
52
|
+
|
|
53
|
+
z-index: 4;
|
|
54
|
+
|
|
55
|
+
> div {
|
|
56
|
+
display: flex;
|
|
57
|
+
align-items: center;
|
|
58
|
+
justify-content: space-between;
|
|
59
|
+
|
|
60
|
+
color: #fff;
|
|
61
|
+
outline: none;
|
|
62
|
+
|
|
63
|
+
padding: 8px 6px 8px 12px;
|
|
64
|
+
|
|
65
|
+
width: 100%;
|
|
66
|
+
/* rows keep their own height inside the fixed height column, so a long page scrolls rather than
|
|
67
|
+
squashing every row into it */
|
|
68
|
+
flex-shrink: 0;
|
|
69
|
+
@media (pointer: coarse) {
|
|
70
|
+
box-sizing: border-box;
|
|
71
|
+
min-height: 44px;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
:first-of-type {
|
|
75
|
+
border-radius: 8px 8px 0 0;
|
|
76
|
+
}
|
|
77
|
+
:last-of-type {
|
|
78
|
+
border-radius: 0 0 8px 8px;
|
|
79
|
+
}
|
|
80
|
+
:not(&.no-hover) {
|
|
81
|
+
cursor: pointer;
|
|
82
|
+
}
|
|
83
|
+
:not(&.no-hover):hover {
|
|
84
|
+
background-color: rgba(255,255,255,.1);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
> div {
|
|
88
|
+
display: flex;
|
|
89
|
+
align-items: center;
|
|
90
|
+
justify-content: center;
|
|
91
|
+
|
|
92
|
+
.secondary {
|
|
93
|
+
color: #eee;
|
|
94
|
+
${fonts.bSmall.regular}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.back {
|
|
100
|
+
display: flex;
|
|
101
|
+
align-items: center;
|
|
102
|
+
justify-content: flex-start;
|
|
103
|
+
border-bottom: 1px solid #4D4D4E;
|
|
104
|
+
|
|
105
|
+
svg {
|
|
106
|
+
transform: translateX(-4px);
|
|
107
|
+
transition: transform 0.2s ease-in-out;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
:hover {
|
|
111
|
+
svg {
|
|
112
|
+
transform: translateX(-8px);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.playback-rate {
|
|
119
|
+
.slider {
|
|
120
|
+
width: 100%;
|
|
121
|
+
}
|
|
122
|
+
.options {
|
|
123
|
+
display: grid;
|
|
124
|
+
grid-template-columns: 1fr 1fr 1fr 1fr;
|
|
125
|
+
align-items: center;
|
|
126
|
+
|
|
127
|
+
padding: 8px 12px;
|
|
128
|
+
/* four presets across a 180px popover leave targets 34px wide, so a finger gets two rows of two
|
|
129
|
+
until the popover is wide enough for the row of four */
|
|
130
|
+
@media (pointer: coarse) {
|
|
131
|
+
grid-template-columns: 1fr 1fr;
|
|
132
|
+
}
|
|
133
|
+
@media (pointer: coarse) and (min-width: 768px) {
|
|
134
|
+
grid-template-columns: 1fr 1fr 1fr 1fr;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
> div {
|
|
138
|
+
display: flex;
|
|
139
|
+
align-items: center;
|
|
140
|
+
border-radius: 4px;
|
|
141
|
+
|
|
142
|
+
padding: 8px 6px;
|
|
143
|
+
height: 100%;
|
|
144
|
+
@media (pointer: coarse) {
|
|
145
|
+
box-sizing: border-box;
|
|
146
|
+
min-height: 44px;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
cursor: pointer;
|
|
150
|
+
:hover {
|
|
151
|
+
background-color: rgba(255,255,255,.1);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.track-list {
|
|
158
|
+
.description {
|
|
159
|
+
word-break: break-word;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
`
|
|
163
|
+
|
|
164
|
+
enum PopoverContent {
|
|
165
|
+
Default,
|
|
166
|
+
PlaybackRate,
|
|
167
|
+
Subtitles,
|
|
168
|
+
Audio
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** One track picker, shared by the subtitle and audio menus. */
|
|
172
|
+
const TrackMenu = (
|
|
173
|
+
{ title, tracks, selected, onSelect, onBack, allowDisable }: {
|
|
174
|
+
title: string
|
|
175
|
+
tracks: TrackChoice[]
|
|
176
|
+
selected: string | number | undefined
|
|
177
|
+
onSelect: (id: string | number | undefined) => void
|
|
178
|
+
onBack: () => void
|
|
179
|
+
allowDisable?: boolean
|
|
180
|
+
}
|
|
181
|
+
) => (
|
|
182
|
+
<div className='popover track-list'>
|
|
183
|
+
<div className="back" onClick={onBack}>
|
|
184
|
+
<ChevronLeft />
|
|
185
|
+
<span>{title}</span>
|
|
186
|
+
</div>
|
|
187
|
+
{
|
|
188
|
+
allowDisable
|
|
189
|
+
? (
|
|
190
|
+
<div onClick={() => onSelect(undefined)}>
|
|
191
|
+
<span>Disable</span>
|
|
192
|
+
<span>{selected === undefined ? '✓' : ''}</span>
|
|
193
|
+
</div>
|
|
194
|
+
)
|
|
195
|
+
: null
|
|
196
|
+
}
|
|
197
|
+
{
|
|
198
|
+
tracks.map(({ id, label }) => (
|
|
199
|
+
<div
|
|
200
|
+
key={id}
|
|
201
|
+
onClick={() => onSelect(id)}
|
|
202
|
+
className="description"
|
|
203
|
+
>
|
|
204
|
+
<span>{label}</span>
|
|
205
|
+
<span>{selected === id ? '✓' : ''}</span>
|
|
206
|
+
</div>
|
|
207
|
+
))
|
|
208
|
+
}
|
|
209
|
+
</div>
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
export const SettingsAction = () => {
|
|
213
|
+
const player = usePlayer()
|
|
214
|
+
const playbackRate = usePlayer((state) => state.playbackRate)
|
|
215
|
+
const subtitleTracks = usePlayer((state) => state.subtitleTracks)
|
|
216
|
+
const selectedSubtitleTrack = usePlayer((state) => state.selectedSubtitleTrack)
|
|
217
|
+
const selectSubtitleTrack = usePlayer((state) => state.selectSubtitleTrack)
|
|
218
|
+
const audioTracks = usePlayer((state) => state.audioTracks)
|
|
219
|
+
const selectedAudioTrack = usePlayer((state) => state.selectedAudioTrack)
|
|
220
|
+
const selectAudioTrack = usePlayer((state) => state.selectAudioTrack)
|
|
221
|
+
|
|
222
|
+
const [isOpenPopover, setIsOpenPopover] = useState(false)
|
|
223
|
+
const [popoverContent, setPopoverContent] = useState(PopoverContent.Default)
|
|
224
|
+
const settingsContainerRef = useRef<HTMLDivElement>(null)
|
|
225
|
+
|
|
226
|
+
const togglePopover = () => {
|
|
227
|
+
setIsOpenPopover(!isOpenPopover)
|
|
228
|
+
setPopoverContent(PopoverContent.Default)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const setPlaybackRate = (rate: number) => {
|
|
232
|
+
player.setPlaybackRate(rate)
|
|
233
|
+
setPopoverContent(PopoverContent.Default)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
useEffect(() => {
|
|
237
|
+
const handleClickOutside = (ev: PointerEvent) => {
|
|
238
|
+
if (
|
|
239
|
+
isOpenPopover &&
|
|
240
|
+
settingsContainerRef.current &&
|
|
241
|
+
!settingsContainerRef.current.contains(ev.target as Node)
|
|
242
|
+
) {
|
|
243
|
+
togglePopover()
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
// pointerdown, because a touch device synthesizes mousedown too late to close reliably
|
|
247
|
+
document.addEventListener('pointerdown', handleClickOutside)
|
|
248
|
+
return () => document.removeEventListener('pointerdown', handleClickOutside)
|
|
249
|
+
}, [isOpenPopover])
|
|
250
|
+
|
|
251
|
+
const chooseSubtitle = (id: string | number | undefined) => {
|
|
252
|
+
selectSubtitleTrack(id)
|
|
253
|
+
togglePopover()
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const chooseAudio = (id: string | number | undefined) => {
|
|
257
|
+
// the audio menu never offers "Disable"
|
|
258
|
+
if (id !== undefined) selectAudioTrack(id)
|
|
259
|
+
togglePopover()
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const changePopoverContent = (newPopoverContent: PopoverContent) => () => {
|
|
263
|
+
setPopoverContent(newPopoverContent)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return (
|
|
267
|
+
<div css={style} ref={settingsContainerRef}>
|
|
268
|
+
<TooltipDisplay
|
|
269
|
+
id='settings'
|
|
270
|
+
disabled={isOpenPopover}
|
|
271
|
+
text={
|
|
272
|
+
<button
|
|
273
|
+
className='settings'
|
|
274
|
+
type='button'
|
|
275
|
+
onClick={togglePopover}
|
|
276
|
+
>
|
|
277
|
+
<Settings />
|
|
278
|
+
</button>
|
|
279
|
+
}
|
|
280
|
+
toolTipText={
|
|
281
|
+
<span>
|
|
282
|
+
Settings
|
|
283
|
+
</span>
|
|
284
|
+
}
|
|
285
|
+
/>
|
|
286
|
+
{
|
|
287
|
+
isOpenPopover && popoverContent === PopoverContent.Default && (
|
|
288
|
+
<div className='popover menu'>
|
|
289
|
+
{/*
|
|
290
|
+
A row is offered only where there is more than one thing behind it to choose between.
|
|
291
|
+
Audio needs two tracks for that; subtitles need one, because "Disable" is always a
|
|
292
|
+
second option, and a file with no subtitles at all should not offer the menu.
|
|
293
|
+
*/}
|
|
294
|
+
{audioTracks.length > 1
|
|
295
|
+
? (
|
|
296
|
+
<div onClick={changePopoverContent(PopoverContent.Audio)}>
|
|
297
|
+
<div>Audio</div>
|
|
298
|
+
<div>
|
|
299
|
+
<ChevronRight />
|
|
300
|
+
</div>
|
|
301
|
+
</div>
|
|
302
|
+
)
|
|
303
|
+
: null}
|
|
304
|
+
{subtitleTracks.length > 0
|
|
305
|
+
? (
|
|
306
|
+
<div onClick={changePopoverContent(PopoverContent.Subtitles)}>
|
|
307
|
+
<div>Subtitles</div>
|
|
308
|
+
<div>
|
|
309
|
+
<ChevronRight />
|
|
310
|
+
</div>
|
|
311
|
+
</div>
|
|
312
|
+
)
|
|
313
|
+
: null}
|
|
314
|
+
<div onClick={changePopoverContent(PopoverContent.PlaybackRate)}>
|
|
315
|
+
<div>Playback speed</div>
|
|
316
|
+
<div>
|
|
317
|
+
<span className='secondary'>
|
|
318
|
+
{
|
|
319
|
+
playbackRate === 1
|
|
320
|
+
? '(Default)'
|
|
321
|
+
: playbackRate.toFixed(2)
|
|
322
|
+
}
|
|
323
|
+
</span>
|
|
324
|
+
<ChevronRight />
|
|
325
|
+
</div>
|
|
326
|
+
</div>
|
|
327
|
+
</div>
|
|
328
|
+
)
|
|
329
|
+
}
|
|
330
|
+
{
|
|
331
|
+
isOpenPopover && popoverContent === PopoverContent.PlaybackRate && (
|
|
332
|
+
<div className='popover playback-rate'>
|
|
333
|
+
<div className="back" onClick={changePopoverContent(PopoverContent.Default)}>
|
|
334
|
+
<ChevronLeft />
|
|
335
|
+
<span>Playback speed</span>
|
|
336
|
+
</div>
|
|
337
|
+
<div className="slider no-hover">
|
|
338
|
+
<PlaybackSlider />
|
|
339
|
+
</div>
|
|
340
|
+
<div className="options no-hover">
|
|
341
|
+
<div onClick={() => setPlaybackRate(0.5)}>
|
|
342
|
+
0.5x
|
|
343
|
+
</div>
|
|
344
|
+
<div onClick={() => setPlaybackRate(1)}>
|
|
345
|
+
1x
|
|
346
|
+
</div>
|
|
347
|
+
<div onClick={() => setPlaybackRate(1.5)}>
|
|
348
|
+
1.5x
|
|
349
|
+
</div>
|
|
350
|
+
<div onClick={() => setPlaybackRate(2)}>
|
|
351
|
+
2x
|
|
352
|
+
</div>
|
|
353
|
+
</div>
|
|
354
|
+
</div>
|
|
355
|
+
)
|
|
356
|
+
}
|
|
357
|
+
{
|
|
358
|
+
isOpenPopover && popoverContent === PopoverContent.Subtitles && (
|
|
359
|
+
<TrackMenu
|
|
360
|
+
title='Subtitles'
|
|
361
|
+
tracks={subtitleTracks}
|
|
362
|
+
selected={selectedSubtitleTrack}
|
|
363
|
+
onSelect={chooseSubtitle}
|
|
364
|
+
onBack={changePopoverContent(PopoverContent.Default)}
|
|
365
|
+
allowDisable
|
|
366
|
+
/>
|
|
367
|
+
)
|
|
368
|
+
}
|
|
369
|
+
{
|
|
370
|
+
isOpenPopover && popoverContent === PopoverContent.Audio && (
|
|
371
|
+
<TrackMenu
|
|
372
|
+
title='Audio'
|
|
373
|
+
tracks={audioTracks}
|
|
374
|
+
selected={selectedAudioTrack}
|
|
375
|
+
onSelect={chooseAudio}
|
|
376
|
+
onBack={changePopoverContent(PopoverContent.Default)}
|
|
377
|
+
/>
|
|
378
|
+
)
|
|
379
|
+
}
|
|
380
|
+
</div>
|
|
381
|
+
)
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
export default SettingsAction
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/// <reference types="@emotion/react/types/css-prop" />
|
|
2
|
+
import type { Ref } from 'react'
|
|
3
|
+
|
|
4
|
+
import { useMemo } from 'react'
|
|
5
|
+
import { css } from '@emotion/react'
|
|
6
|
+
import { Volume1, Volume2, VolumeX } from 'react-feather'
|
|
7
|
+
|
|
8
|
+
import { usePlayer } from '../player'
|
|
9
|
+
import { TooltipDisplay } from './tooltip-display'
|
|
10
|
+
import { fonts } from '../../utils/fonts'
|
|
11
|
+
import VolumeSlider from './volume-slider'
|
|
12
|
+
import { linearToLogVolume, logToLinearVolume } from '../../utils/volume-utils'
|
|
13
|
+
|
|
14
|
+
const style = css`
|
|
15
|
+
position: relative;
|
|
16
|
+
display: flex;
|
|
17
|
+
align-items: center;
|
|
18
|
+
|
|
19
|
+
.volume-slider-container {
|
|
20
|
+
display: flex;
|
|
21
|
+
align-items: center;
|
|
22
|
+
|
|
23
|
+
width: 0;
|
|
24
|
+
margin-right: 0;
|
|
25
|
+
overflow: hidden;
|
|
26
|
+
pointer-events: none;
|
|
27
|
+
|
|
28
|
+
transition: margin .2s cubic-bezier(0.4,0,1,1), width .2s cubic-bezier(0.4,0,1,1);
|
|
29
|
+
-webkit-transition: margin .2s cubic-bezier(0.4,0,1,1), width .2s cubic-bezier(0.4,0,1,1);
|
|
30
|
+
|
|
31
|
+
.volume-slider-background {
|
|
32
|
+
display: flex;
|
|
33
|
+
flex-direction: row;
|
|
34
|
+
align-items: center;
|
|
35
|
+
|
|
36
|
+
border-radius: 4px;
|
|
37
|
+
|
|
38
|
+
.volume-value {
|
|
39
|
+
${fonts.bMedium.regular};
|
|
40
|
+
color: #ffffff;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
&:hover .volume-slider-container {
|
|
46
|
+
width: calc(9 * var(--mp-unit));
|
|
47
|
+
|
|
48
|
+
transition: margin .2s cubic-bezier(0,0,0.2,1), width .2s cubic-bezier(0,0,0.2,1);
|
|
49
|
+
-webkit-transition: margin .2s cubic-bezier(0,0,0.2,1), width .2s cubic-bezier(0,0,0.2,1);
|
|
50
|
+
|
|
51
|
+
pointer-events: auto;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* a touch screen has no hover, so the slider stays open there and the button only mutes */
|
|
55
|
+
@media (pointer: coarse) {
|
|
56
|
+
/* auto, not a fixed rem: the container keeps overflow: hidden, so a narrower fixed width
|
|
57
|
+
silently clips the track and makes the clipped part unhittable */
|
|
58
|
+
.volume-slider-container,
|
|
59
|
+
&:hover .volume-slider-container {
|
|
60
|
+
width: auto;
|
|
61
|
+
|
|
62
|
+
pointer-events: auto;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/* a 44px press target around an 18px icon, floored here because the control bar sets the
|
|
66
|
+
padding at a higher specificity */
|
|
67
|
+
.sound {
|
|
68
|
+
/* border-box explicitly, since a consumer reset cannot be assumed */
|
|
69
|
+
box-sizing: border-box;
|
|
70
|
+
justify-content: center;
|
|
71
|
+
|
|
72
|
+
min-width: 44px;
|
|
73
|
+
min-height: 44px;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
`
|
|
78
|
+
|
|
79
|
+
export type SoundProps = {
|
|
80
|
+
/** Lands on the button, which is what the control bar attaches its wheel listener to. */
|
|
81
|
+
ref?: Ref<HTMLButtonElement> | ((element: HTMLButtonElement | null) => void)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export const Sound = ({ ref }: SoundProps) => {
|
|
85
|
+
const player = usePlayer()
|
|
86
|
+
const volume = usePlayer((state) => state.volume)
|
|
87
|
+
const muted = usePlayer((state) => state.muted)
|
|
88
|
+
|
|
89
|
+
// the store holds gain, the slider holds linear position, so every read and write converts
|
|
90
|
+
const linearVolume = useMemo(() => logToLinearVolume(volume), [volume])
|
|
91
|
+
|
|
92
|
+
// linearToLogVolume floors its input, so the slider never produces a zero and the store's own
|
|
93
|
+
// unmute-above-zero is enough
|
|
94
|
+
const setVolume = (newLinearVolume: number) => {
|
|
95
|
+
player.setVolume(linearToLogVolume(newLinearVolume))
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<div css={style}>
|
|
100
|
+
<TooltipDisplay
|
|
101
|
+
id='sound'
|
|
102
|
+
text={
|
|
103
|
+
<button
|
|
104
|
+
className='sound'
|
|
105
|
+
type='button'
|
|
106
|
+
onClick={() => player.toggleMuted()}
|
|
107
|
+
ref={ref}
|
|
108
|
+
>
|
|
109
|
+
{muted || volume === 0
|
|
110
|
+
? <VolumeX size={18} color='#fff' />
|
|
111
|
+
: volume <= 0.5
|
|
112
|
+
? <Volume1 size={18} color='#fff' />
|
|
113
|
+
: <Volume2 size={18} color='#fff' />
|
|
114
|
+
}
|
|
115
|
+
</button>
|
|
116
|
+
}
|
|
117
|
+
toolTipText={
|
|
118
|
+
<span>{muted ? 'Unmute (m)' : 'Mute (m)'}</span>
|
|
119
|
+
}
|
|
120
|
+
/>
|
|
121
|
+
<div className='volume-slider-container'>
|
|
122
|
+
<div className='volume-slider-background'>
|
|
123
|
+
<VolumeSlider
|
|
124
|
+
value={muted ? 0 : linearVolume}
|
|
125
|
+
onChange={setVolume}
|
|
126
|
+
/>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export default Sound
|
|
@@ -1,14 +1,21 @@
|
|
|
1
|
-
import { ReactNode } from 'react'
|
|
1
|
+
import type { ReactNode } from 'react'
|
|
2
|
+
|
|
2
3
|
import { css } from '@emotion/react'
|
|
3
4
|
import { PlacesType, Tooltip } from 'react-tooltip'
|
|
4
5
|
|
|
5
|
-
import { fonts } from '
|
|
6
|
+
import { fonts } from '../../utils/fonts'
|
|
7
|
+
|
|
8
|
+
export enum buttonSize {
|
|
9
|
+
sm = 'sm',
|
|
10
|
+
md = 'md',
|
|
11
|
+
lg = 'lg'
|
|
12
|
+
}
|
|
6
13
|
|
|
7
14
|
const style = (size: buttonSize) => css`
|
|
8
15
|
display: flex;
|
|
9
16
|
justify-content: flex-end;
|
|
10
17
|
|
|
11
|
-
border-radius: 0.
|
|
18
|
+
border-radius: calc(0.4 * var(--mp-unit));
|
|
12
19
|
user-select: none;
|
|
13
20
|
|
|
14
21
|
z-index: 3;
|
|
@@ -18,13 +25,13 @@ const style = (size: buttonSize) => css`
|
|
|
18
25
|
}
|
|
19
26
|
|
|
20
27
|
${size === buttonSize.sm && css`
|
|
21
|
-
padding: 0.
|
|
28
|
+
padding: calc(0.4 * var(--mp-unit))!important;
|
|
22
29
|
`}
|
|
23
30
|
${size === buttonSize.md && css`
|
|
24
|
-
padding: 0.
|
|
31
|
+
padding: calc(0.6 * var(--mp-unit))!important;
|
|
25
32
|
`}
|
|
26
33
|
${size === buttonSize.lg && css`
|
|
27
|
-
padding: 1.
|
|
34
|
+
padding: calc(1.2 * var(--mp-unit))!important;
|
|
28
35
|
`}
|
|
29
36
|
`
|
|
30
37
|
|
|
@@ -40,12 +47,6 @@ interface TooltipDisplayProps {
|
|
|
40
47
|
disabled?: boolean
|
|
41
48
|
}
|
|
42
49
|
|
|
43
|
-
export enum buttonSize {
|
|
44
|
-
sm = 'sm',
|
|
45
|
-
md = 'md',
|
|
46
|
-
lg = 'lg'
|
|
47
|
-
}
|
|
48
|
-
|
|
49
50
|
export const TooltipDisplay = ({
|
|
50
51
|
id,
|
|
51
52
|
toolTipText,
|
|
@@ -81,3 +82,5 @@ export const TooltipDisplay = ({
|
|
|
81
82
|
}
|
|
82
83
|
</>
|
|
83
84
|
)
|
|
85
|
+
|
|
86
|
+
export default TooltipDisplay
|