@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,91 +1,91 @@
1
- /// <reference types="@emotion/react/types/css-prop" />
2
-
3
- import { ClassAttributes, ReactNode, useCallback, useContext, useEffect, useState } from 'react'
4
- import { css } from '@emotion/react'
5
-
6
- import { MediaMachineContext } from '../state-machines'
7
- import { MediaPlayerContext } from '../utils/context'
8
- import { fonts } from '../utils/fonts'
9
-
10
- const style = css`
11
- top: unset !important;
12
- left: unset !important;
13
- width: 100%;
14
- height: 100%;
15
- margin: auto;
16
- pointer-events: none;
17
- `
18
-
19
- const titleStyle = css`
20
- position: absolute;
21
- top: 0;
22
- left: 0;
23
- width: 100%;
24
- padding: 2.4rem;
25
- ${fonts.headings.small}
26
- color: white;
27
- text-shadow: 0 0 4px rgba(0, 0, 0, 1);
28
- z-index: 2;
29
- pointer-events: none;
30
-
31
- background: linear-gradient(180deg, rgba(0,0,0,0.4) 0%, rgba(0,0,0,0.3) 30%, rgba(0,0,0,0.2) 60%, rgba(0,0,0,0.1) 80%, transparent 100%);
32
- transition: opacity 0.1s cubic-bezier(.4,0,1,1);
33
- `
34
-
35
- const loadingInformationStyle = css`
36
- position: absolute;
37
- top: 0;
38
- left: 0;
39
- width: 100%;
40
- height: 100%;
41
- font-size: 2rem;
42
- color: white;
43
- text-shadow: 0 0 4px rgba(0, 0, 0, 1);
44
- z-index: 2;
45
- display: flex;
46
- justify-content: center;
47
- align-items: center;
48
- pointer-events: none;
49
- `
50
-
51
- export const Overlay = ({ loadingInformation }: { loadingInformation?: ReactNode }) => {
52
- const mediaActor = MediaMachineContext.useActorRef()
53
- const duration = MediaMachineContext.useSelector((state) => state.context.media.duration)
54
- const mediaPlayerContext = useContext(MediaPlayerContext)
55
-
56
- const [canvasElement, setCanvasElement] = useState<HTMLCanvasElement | null>()
57
- const refFunction: ClassAttributes<HTMLCanvasElement>['ref'] = useCallback((element: HTMLCanvasElement | null) => {
58
- setCanvasElement(element)
59
- }, [])
60
-
61
- useEffect(() => {
62
- if (!canvasElement) return
63
- mediaActor.send({
64
- type: 'SET_CANVAS_ELEMENT',
65
- canvasElement,
66
- })
67
- }, [canvasElement])
68
-
69
- return (
70
- <>
71
- {
72
- mediaPlayerContext.title
73
- ? (
74
- <div
75
- css={titleStyle}
76
- style={{ ...mediaPlayerContext.hideUI ? { opacity: '0', pointerEvents: 'none' } : {} }}
77
- >
78
- {mediaPlayerContext.title}
79
- </div>
80
- )
81
- : undefined
82
- }
83
- {
84
- duration === undefined && loadingInformation
85
- ? <div css={loadingInformationStyle}>{loadingInformation}</div>
86
- : undefined
87
- }
88
- <canvas ref={refFunction} css={style}/>
89
- </>
90
- )
91
- }
1
+ /// <reference types="@emotion/react/types/css-prop" />
2
+
3
+ import { ClassAttributes, ReactNode, useCallback, useContext, useEffect, useState } from 'react'
4
+ import { css } from '@emotion/react'
5
+
6
+ import { MediaMachineContext } from '../state-machines'
7
+ import { MediaPlayerContext } from '../utils/context'
8
+ import { fonts } from '../utils/fonts'
9
+
10
+ const style = css`
11
+ top: unset !important;
12
+ left: unset !important;
13
+ width: 100%;
14
+ height: 100%;
15
+ margin: auto;
16
+ pointer-events: none;
17
+ `
18
+
19
+ const titleStyle = css`
20
+ position: absolute;
21
+ top: 0;
22
+ left: 0;
23
+ width: 100%;
24
+ padding: 2.4rem;
25
+ ${fonts.headings.small}
26
+ color: white;
27
+ text-shadow: 0 0 4px rgba(0, 0, 0, 1);
28
+ z-index: 2;
29
+ pointer-events: none;
30
+
31
+ background: linear-gradient(180deg, rgba(0,0,0,0.4) 0%, rgba(0,0,0,0.3) 30%, rgba(0,0,0,0.2) 60%, rgba(0,0,0,0.1) 80%, transparent 100%);
32
+ transition: opacity 0.1s cubic-bezier(.4,0,1,1);
33
+ `
34
+
35
+ const loadingInformationStyle = css`
36
+ position: absolute;
37
+ top: 0;
38
+ left: 0;
39
+ width: 100%;
40
+ height: 100%;
41
+ font-size: 2rem;
42
+ color: white;
43
+ text-shadow: 0 0 4px rgba(0, 0, 0, 1);
44
+ z-index: 2;
45
+ display: flex;
46
+ justify-content: center;
47
+ align-items: center;
48
+ pointer-events: none;
49
+ `
50
+
51
+ export const Overlay = ({ loadingInformation }: { loadingInformation?: ReactNode }) => {
52
+ const mediaActor = MediaMachineContext.useActorRef()
53
+ const duration = MediaMachineContext.useSelector((state) => state.context.media.duration)
54
+ const mediaPlayerContext = useContext(MediaPlayerContext)
55
+
56
+ const [canvasElement, setCanvasElement] = useState<HTMLCanvasElement | null>()
57
+ const refFunction: ClassAttributes<HTMLCanvasElement>['ref'] = useCallback((element: HTMLCanvasElement | null) => {
58
+ setCanvasElement(element)
59
+ }, [])
60
+
61
+ useEffect(() => {
62
+ if (!canvasElement) return
63
+ mediaActor.send({
64
+ type: 'SET_CANVAS_ELEMENT',
65
+ canvasElement,
66
+ })
67
+ }, [canvasElement])
68
+
69
+ return (
70
+ <>
71
+ {
72
+ mediaPlayerContext.title
73
+ ? (
74
+ <div
75
+ css={titleStyle}
76
+ style={{ ...mediaPlayerContext.hideUI ? { opacity: '0', pointerEvents: 'none' } : {} }}
77
+ >
78
+ {mediaPlayerContext.title}
79
+ </div>
80
+ )
81
+ : undefined
82
+ }
83
+ {
84
+ duration === undefined && loadingInformation
85
+ ? <div css={loadingInformationStyle}>{loadingInformation}</div>
86
+ : undefined
87
+ }
88
+ <canvas ref={refFunction} css={style}/>
89
+ </>
90
+ )
91
+ }
@@ -0,0 +1,174 @@
1
+ import { useRef, useState, useEffect, useMemo } from 'react'
2
+ import { css } from '@emotion/react'
3
+
4
+ import { MediaMachineContext } from '../state-machines'
5
+
6
+ const style = css`
7
+ display: flex;
8
+ align-items: center;
9
+ justify-content: center;
10
+ flex-direction: column;
11
+ width: 100%;
12
+
13
+ .playback-slider {
14
+ display: flex;
15
+ align-items: center;
16
+
17
+ padding: 8px 4px;
18
+ margin: 0 2px;
19
+
20
+ cursor: pointer;
21
+
22
+ > div {
23
+ position: relative;
24
+
25
+ background: #ccc;
26
+ border-radius: 4px;
27
+
28
+ width: 120px;
29
+ height: 3px;
30
+ @media (min-width: 768px) {
31
+ height: 4px;
32
+ }
33
+ }
34
+
35
+ .playback-handle {
36
+ position: absolute;
37
+
38
+
39
+ background: #ffffff;
40
+ border-radius: 50%;
41
+ transform: translateX(-50%);
42
+ pointer-events: none;
43
+
44
+ width: 10px;
45
+ height: 10px;
46
+ top: -3.5px;
47
+ @media (min-width: 768px) {
48
+ width: 12px;
49
+ height: 12px;
50
+ top: -4px;
51
+ }
52
+ }
53
+ }
54
+ `
55
+
56
+ const PlaybackSlider = () => {
57
+ const playbackRate = MediaMachineContext.useSelector((state) => state.context.media.playbackRate)
58
+ const mediaActor = MediaMachineContext.useActorRef()
59
+ const sliderRef = useRef<HTMLDivElement>(null)
60
+ const [isDragging, setIsDragging] = useState(false)
61
+
62
+ const MIN_RATE = 0.25
63
+ const MAX_RATE = 3.0
64
+ const RANGE = MAX_RATE - MIN_RATE
65
+
66
+ const roundToStep = (value: number, step: number = 0.05): number => {
67
+ return Math.round(value / step) * step
68
+ }
69
+
70
+ const roundedRate = roundToStep(playbackRate || 1)
71
+
72
+ // Calculate position as percentage
73
+ const getHandlePosition = (rate: number): number => {
74
+ return ((rate - MIN_RATE) / RANGE) * 100
75
+ }
76
+
77
+ const getPlaybackRateFromEvent = (clientX: number): number => {
78
+ if (!sliderRef.current) return roundedRate;
79
+
80
+ const rect = sliderRef.current.getBoundingClientRect()
81
+ const xPos = clientX - rect.left
82
+ const percent = Math.max(0, Math.min(xPos, rect.width)) / rect.width
83
+
84
+ const rawRate = MIN_RATE + (percent * RANGE)
85
+
86
+ return roundToStep(rawRate)
87
+ }
88
+
89
+ const handlePointerDown = (e: React.MouseEvent<HTMLDivElement>) => {
90
+ setIsDragging(true)
91
+ const newRate = getPlaybackRateFromEvent(e.clientX)
92
+ mediaActor.send({ type: 'SET_PLAYBACK_RATE', playbackRate: newRate })
93
+ }
94
+
95
+ const handlePointerMove = (e: MouseEvent) => {
96
+ if (!isDragging) return;
97
+ const newRate = getPlaybackRateFromEvent(e.clientX)
98
+ mediaActor.send({ type: 'SET_PLAYBACK_RATE', playbackRate: newRate })
99
+ }
100
+
101
+ const handlePointerUp = () => {
102
+ setIsDragging(false)
103
+ }
104
+
105
+ const handleWheel = (e: WheelEvent) => {
106
+ e.preventDefault()
107
+ const step = 0.05
108
+ let newRate = roundedRate
109
+
110
+ if (e.deltaY < 0) {
111
+ newRate = Math.min(MAX_RATE, newRate + step)
112
+ } else {
113
+ newRate = Math.max(MIN_RATE, newRate - step)
114
+ }
115
+
116
+ mediaActor.send({ type: 'SET_PLAYBACK_RATE', playbackRate: newRate })
117
+ }
118
+
119
+ useEffect(() => {
120
+ window.addEventListener('mousemove', handlePointerMove)
121
+ window.addEventListener('mouseup', handlePointerUp)
122
+ return () => {
123
+ window.removeEventListener('mousemove', handlePointerMove)
124
+ window.removeEventListener('mouseup', handlePointerUp)
125
+ }
126
+ }, [isDragging])
127
+
128
+ useEffect(() => {
129
+ const slider = sliderRef.current
130
+ if (slider) {
131
+ slider.addEventListener('wheel', handleWheel, { passive: false })
132
+ }
133
+ return () => {
134
+ if (slider) {
135
+ slider.removeEventListener('wheel', handleWheel)
136
+ }
137
+ }
138
+ }, [playbackRate])
139
+
140
+ const handlePosition = getHandlePosition(roundedRate)
141
+ const fillColor = '#fff'
142
+ const emptyColor = '#3A3A3A'
143
+
144
+ return (
145
+ <div css={style}>
146
+ <div>
147
+ {roundedRate.toFixed(2)}x
148
+ </div>
149
+ <div
150
+ className="playback-slider"
151
+ ref={sliderRef}
152
+ onMouseDown={handlePointerDown}
153
+ >
154
+ <div
155
+ style={{
156
+ background: `linear-gradient(to right,
157
+ ${fillColor} 0% ${handlePosition}%,
158
+ ${emptyColor} ${handlePosition}% 100%
159
+ )`
160
+ }}
161
+ >
162
+ <div
163
+ className="playback-handle"
164
+ style={{
165
+ left: `${handlePosition}%`
166
+ }}
167
+ />
168
+ </div>
169
+ </div>
170
+ </div>
171
+ )
172
+ }
173
+
174
+ export default PlaybackSlider