@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,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
+ }
@@ -1,174 +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
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