@remotion/promo-pages 4.0.363 → 4.0.365

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 (107) hide show
  1. package/dist/Homepage.js +26 -27
  2. package/dist/Users/jonathanburger/remotion/packages/promo-pages/dist/Homepage.js +26 -27
  3. package/package.json +10 -10
  4. package/.turbo/turbo-lint.log +0 -15
  5. package/.turbo/turbo-make.log +0 -5
  6. package/bundle.ts +0 -54
  7. package/eslint.config.mjs +0 -7
  8. package/server.ts +0 -26
  9. package/src/cn.ts +0 -6
  10. package/src/components/Homepage.tsx +0 -87
  11. package/src/components/TeamPicture.tsx +0 -11
  12. package/src/components/homepage/BackgroundAnimation.tsx +0 -108
  13. package/src/components/homepage/ChooseTemplate.tsx +0 -57
  14. package/src/components/homepage/CommunityStats.tsx +0 -54
  15. package/src/components/homepage/CommunityStatsItems.tsx +0 -304
  16. package/src/components/homepage/Counter.tsx +0 -120
  17. package/src/components/homepage/Demo/Card.tsx +0 -273
  18. package/src/components/homepage/Demo/Cards.tsx +0 -129
  19. package/src/components/homepage/Demo/Comp.tsx +0 -157
  20. package/src/components/homepage/Demo/CurrentCountry.tsx +0 -97
  21. package/src/components/homepage/Demo/DemoError.tsx +0 -18
  22. package/src/components/homepage/Demo/DemoErrorIcon.tsx +0 -32
  23. package/src/components/homepage/Demo/DemoRender.tsx +0 -166
  24. package/src/components/homepage/Demo/DigitWheel.tsx +0 -179
  25. package/src/components/homepage/Demo/DisplayedEmoji.tsx +0 -93
  26. package/src/components/homepage/Demo/DoneCheckmark.tsx +0 -39
  27. package/src/components/homepage/Demo/DownloadNudge.tsx +0 -62
  28. package/src/components/homepage/Demo/DragAndDropNudge.tsx +0 -57
  29. package/src/components/homepage/Demo/EmojiCard.tsx +0 -200
  30. package/src/components/homepage/Demo/Minus.tsx +0 -21
  31. package/src/components/homepage/Demo/PlayPauseButton.tsx +0 -66
  32. package/src/components/homepage/Demo/PlayerControls.tsx +0 -48
  33. package/src/components/homepage/Demo/PlayerSeekBar.tsx +0 -325
  34. package/src/components/homepage/Demo/PlayerVolume.tsx +0 -83
  35. package/src/components/homepage/Demo/Progress.tsx +0 -38
  36. package/src/components/homepage/Demo/Spinner.tsx +0 -60
  37. package/src/components/homepage/Demo/Switcher.tsx +0 -54
  38. package/src/components/homepage/Demo/Temperature.tsx +0 -44
  39. package/src/components/homepage/Demo/TemperatureNumber.tsx +0 -68
  40. package/src/components/homepage/Demo/ThemeNudge.tsx +0 -72
  41. package/src/components/homepage/Demo/TimeDisplay.tsx +0 -43
  42. package/src/components/homepage/Demo/TrendingRepos.tsx +0 -106
  43. package/src/components/homepage/Demo/icons.tsx +0 -114
  44. package/src/components/homepage/Demo/index.tsx +0 -158
  45. package/src/components/homepage/Demo/math.ts +0 -43
  46. package/src/components/homepage/Demo/types.ts +0 -6
  47. package/src/components/homepage/EditorStarterSection.tsx +0 -76
  48. package/src/components/homepage/EvaluateRemotion.tsx +0 -92
  49. package/src/components/homepage/FreePricing.tsx +0 -283
  50. package/src/components/homepage/GetStartedStrip.tsx +0 -77
  51. package/src/components/homepage/GitHubButton.tsx +0 -23
  52. package/src/components/homepage/IconForTemplate.tsx +0 -170
  53. package/src/components/homepage/IfYouKnowReact.tsx +0 -68
  54. package/src/components/homepage/InfoTooltip.tsx +0 -25
  55. package/src/components/homepage/MoreTemplatesButton.tsx +0 -29
  56. package/src/components/homepage/MoreVideoPowerSection.tsx +0 -95
  57. package/src/components/homepage/MuxVideo.tsx +0 -68
  58. package/src/components/homepage/NewsletterButton.tsx +0 -89
  59. package/src/components/homepage/ParameterizeAndEdit.tsx +0 -106
  60. package/src/components/homepage/Pricing.tsx +0 -49
  61. package/src/components/homepage/PricingBulletPoint.tsx +0 -50
  62. package/src/components/homepage/RealMp4Videos.tsx +0 -108
  63. package/src/components/homepage/Spacer.tsx +0 -5
  64. package/src/components/homepage/TemplateIcon.tsx +0 -36
  65. package/src/components/homepage/TextInput.tsx +0 -62
  66. package/src/components/homepage/TrustedByBanner.tsx +0 -145
  67. package/src/components/homepage/VideoApps.tsx +0 -231
  68. package/src/components/homepage/VideoAppsShowcase.tsx +0 -270
  69. package/src/components/homepage/VideoAppsTitle.tsx +0 -11
  70. package/src/components/homepage/VideoPlayerWithControls.tsx +0 -189
  71. package/src/components/homepage/WriteInReact.tsx +0 -34
  72. package/src/components/homepage/YouAreHere.tsx +0 -30
  73. package/src/components/homepage/layout/Button.tsx +0 -93
  74. package/src/components/homepage/layout/colors.ts +0 -17
  75. package/src/components/homepage/layout/use-color-mode.tsx +0 -44
  76. package/src/components/homepage/layout/use-el-size.ts +0 -51
  77. package/src/components/homepage/layout/use-mobile-layout.ts +0 -8
  78. package/src/components/homepage/video-player.css +0 -24
  79. package/src/components/icons/blank.tsx +0 -13
  80. package/src/components/icons/clone.tsx +0 -10
  81. package/src/components/icons/code-hike.tsx +0 -15
  82. package/src/components/icons/cubes.tsx +0 -13
  83. package/src/components/icons/js.tsx +0 -17
  84. package/src/components/icons/music.tsx +0 -9
  85. package/src/components/icons/next.tsx +0 -64
  86. package/src/components/icons/overlay.tsx +0 -24
  87. package/src/components/icons/recorder.tsx +0 -23
  88. package/src/components/icons/remix.tsx +0 -27
  89. package/src/components/icons/skia.tsx +0 -13
  90. package/src/components/icons/stargazer.tsx +0 -13
  91. package/src/components/icons/still.tsx +0 -13
  92. package/src/components/icons/tailwind.tsx +0 -22
  93. package/src/components/icons/tiktok.tsx +0 -13
  94. package/src/components/icons/ts.tsx +0 -18
  95. package/src/components/icons/tts.tsx +0 -13
  96. package/src/components/icons/undo.tsx +0 -11
  97. package/src/components/icons/waveform.tsx +0 -13
  98. package/src/components/team/TeamCards.tsx +0 -167
  99. package/src/components/team/TitleTeamCards.tsx +0 -22
  100. package/src/components/team.css +0 -22
  101. package/src/components/team.tsx +0 -51
  102. package/src/fonts.css +0 -30
  103. package/src/index.css +0 -140
  104. package/src/main.tsx +0 -12
  105. package/src/team.tsx +0 -12
  106. package/tsconfig.json +0 -11
  107. package/vite.config.ts +0 -9
@@ -1,231 +0,0 @@
1
- import React, {useEffect, useState} from 'react';
2
- import {BlueButton} from './layout/Button';
3
- import {useColorMode} from './layout/use-color-mode';
4
- import {Spacer} from './Spacer';
5
- import {YouAreHere} from './YouAreHere';
6
-
7
- const row: React.CSSProperties = {
8
- display: 'flex',
9
- flexDirection: 'row',
10
- };
11
-
12
- const flex: React.CSSProperties = {
13
- flex: 1,
14
- };
15
-
16
- const list: React.CSSProperties = {
17
- listStyleType: 'none',
18
- textAlign: 'center',
19
- paddingLeft: 0,
20
- display: 'flex',
21
- flexDirection: 'column',
22
- alignItems: 'center',
23
- };
24
-
25
- const hr: React.CSSProperties = {
26
- width: 20,
27
- textAlign: 'center',
28
- borderTop: 0,
29
- marginTop: 10,
30
- marginBottom: 10,
31
- };
32
-
33
- const docsButton: React.CSSProperties = {
34
- textDecoration: 'none',
35
- };
36
-
37
- const StepTitle: React.FC<{
38
- readonly children: React.ReactNode;
39
- }> = ({children}) => {
40
- return (
41
- <div className="text-center text-xl font-semibold fontbrand mt-2">
42
- {children}
43
- </div>
44
- );
45
- };
46
-
47
- const Subtitle: React.FC<{
48
- readonly children: React.ReactNode;
49
- }> = ({children}) => {
50
- return (
51
- <div className="text-center text-base fontbrand text-[var(--subtitle)]">
52
- {children}
53
- </div>
54
- );
55
- };
56
-
57
- const Pane: React.FC<{
58
- readonly children: React.ReactNode;
59
- }> = ({children}) => {
60
- return (
61
- <div className="border-effect bg-pane flex-1 p-3 rounded-lg min-h-[500px] flex flex-col text-center">
62
- {children}
63
- </div>
64
- );
65
- };
66
-
67
- export const VideoApps: React.FC<{
68
- readonly active: 'remotion' | 'player' | 'lambda';
69
- }> = ({active}) => {
70
- const {colorMode} = useColorMode();
71
-
72
- const [src, setSrc] = useState('/img/player-example.png');
73
- const [src2, setSrc2] = useState('/img/player-example-dark.png');
74
-
75
- useEffect(() => {
76
- if (colorMode === 'dark') {
77
- setSrc('/img/player-example-dark.png');
78
- } else {
79
- setSrc('/img/player-example.png');
80
- }
81
- }, [colorMode]);
82
- useEffect(() => {
83
- setSrc2(
84
- colorMode === 'dark' ? '/img/cluster-dark.png' : '/img/cluster.png',
85
- );
86
- }, [colorMode]);
87
-
88
- return (
89
- <div className="w-full">
90
- <div className={'flex flex-col lg:flex-row gap-2'}>
91
- <Pane>
92
- {active === 'remotion' ? <YouAreHere /> : null}
93
- <StepTitle>Remotion</StepTitle>
94
- <Subtitle>Make videos programmatically</Subtitle>
95
- <br />
96
- <div
97
- style={{
98
- display: 'flex',
99
- justifyContent: 'center',
100
- alignItems: 'center',
101
- flex: 1,
102
- }}
103
- >
104
- <img className="max-w-[300px]" src="/img/writeinreact.png" />
105
- </div>
106
- <ul style={list}>
107
- <li>Use the Web to create graphics</li>
108
- <hr style={hr} />
109
- <li>Consume user input and APIs</li>
110
- <hr style={hr} />
111
- <li>Render real MP4 videos</li>
112
- </ul>
113
- <div style={row}>
114
- {active === 'remotion' ? null : (
115
- <>
116
- <div style={flex}>
117
- <a style={docsButton} href="/">
118
- <BlueButton loading={false} size="sm">
119
- Learn more
120
- </BlueButton>
121
- </a>
122
- </div>
123
- <Spacer />
124
- <Spacer />
125
- </>
126
- )}
127
- <div style={flex}>
128
- <a style={docsButton} href="/docs">
129
- <BlueButton className="w-full" loading={false} size="sm">
130
- Read docs
131
- </BlueButton>
132
- </a>
133
- </div>
134
- </div>
135
- </Pane>
136
- <Pane>
137
- {active === 'player' ? <YouAreHere /> : null}
138
- <StepTitle>Remotion Player</StepTitle>
139
- <Subtitle>Embeddable interactive videos</Subtitle>
140
- <br />
141
-
142
- <div
143
- style={{
144
- display: 'flex',
145
- justifyContent: 'center',
146
- alignItems: 'center',
147
- flex: 1,
148
- }}
149
- >
150
- <img className="max-w-[300px]" src={src} />
151
- </div>
152
- <ul style={list}>
153
- <li>Preview videos in the browser</li>
154
- <hr style={hr} />
155
- <li>React to user input</li>
156
- <hr style={hr} />
157
- <li>Customize look and behavior</li>
158
- </ul>
159
- <div style={row}>
160
- {active === 'player' ? null : (
161
- <>
162
- <div style={flex}>
163
- <a style={docsButton} href="/player">
164
- <BlueButton className="w-full" loading={false} size="sm">
165
- Learn more
166
- </BlueButton>
167
- </a>
168
- </div>
169
- <Spacer />
170
- <Spacer />
171
- </>
172
- )}
173
- <div style={flex}>
174
- <a style={docsButton} href="/docs/player">
175
- <BlueButton className="w-full" loading={false} size="sm">
176
- Read docs
177
- </BlueButton>
178
- </a>
179
- </div>
180
- </div>
181
- </Pane>
182
- <Pane>
183
- {active === 'lambda' ? <YouAreHere /> : null}
184
- <StepTitle>Remotion Lambda</StepTitle>
185
- <Subtitle>Render at scale</Subtitle>
186
- <br />
187
- <div
188
- style={{
189
- display: 'flex',
190
- justifyContent: 'center',
191
- alignItems: 'center',
192
- flex: 1,
193
- }}
194
- >
195
- <img className="max-w-[300px]" src={src2} />
196
- </div>
197
- <div className="flex-1" />
198
- <ul style={list}>
199
- <li>Render videos in the cloud</li>
200
- <hr style={hr} />
201
- <li>Scale according to your volume</li>
202
- <hr style={hr} />
203
- <li>Fast because distributed</li>
204
- </ul>
205
- <div style={row}>
206
- {active === 'lambda' ? null : (
207
- <>
208
- <div style={flex}>
209
- <a style={docsButton} href="/lambda">
210
- <BlueButton className="w-full" loading={false} size="sm">
211
- Learn more
212
- </BlueButton>
213
- </a>
214
- </div>
215
- <Spacer />
216
- <Spacer />
217
- </>
218
- )}
219
- <div style={flex}>
220
- <a style={docsButton} href="/docs/lambda">
221
- <BlueButton className="w-full" loading={false} size="sm">
222
- Read docs
223
- </BlueButton>
224
- </a>
225
- </div>
226
- </div>
227
- </Pane>
228
- </div>
229
- </div>
230
- );
231
- };
@@ -1,270 +0,0 @@
1
- import React, {useEffect, useRef, useState} from 'react';
2
-
3
- import {IsMutedIcon, NotMutedIcon, PausedIcon, PlayingIcon} from './Demo/icons';
4
- import {MuxVideo} from './MuxVideo';
5
- import {SectionTitle} from './VideoAppsTitle';
6
-
7
- const tabs = [
8
- 'Music visualization',
9
- 'Captions',
10
- 'Screencast',
11
- 'Year in review',
12
- ];
13
-
14
- const videoApps = [
15
- {
16
- title: 'Banger.Show',
17
- description:
18
- 'The all-in-one 3D visual creation tool for ambitious artists. Seamlessly craft visuals that match your sound and propel your brand forward.',
19
- link: 'https://banger.show?ref=remotion',
20
- videoWidth: 1080,
21
- videoHeight: 1080,
22
- muxId: 'Kg02XHfkR6x8400BtO4Ica54XlSPimmmTRpqDHHUaeACk',
23
- buttonText: 'Banger.Show website',
24
- },
25
- {
26
- title: 'Submagic',
27
- description:
28
- 'A video editor for creating short-form content fast. Designed for creators, teams and agencies, it accelerates video editing with AI-powered features such as descriptions, zooms, sound effects and music.',
29
- additionalInfo: '',
30
- link: 'https://www.submagic.co/?ref=remotion',
31
- videoWidth: 540,
32
- videoHeight: 1080,
33
- muxId: 'pxqGEjlBBntnXrEe4v00pYUBw3FPgUPKumfhSym00Vs004',
34
- buttonText: 'Submagic website',
35
- },
36
- {
37
- title: 'Remotion Recorder',
38
- description:
39
- 'The Remotion Recorder is a video production tool built entirely in JavaScript. Create high-quality videos that feel native on each platform while only editing them once.',
40
- link: 'https://www.remotion.dev/recorder',
41
- videoWidth: 1080,
42
- videoHeight: 1080,
43
- muxId: 'pHlwqDZFUH00Aubo9M001ty3gZ6YW8z689XTd9R479ayE',
44
- buttonText: 'More infos',
45
- },
46
- {
47
- title: 'GitHub Unwrapped',
48
- description:
49
- 'Your coding year in review. Get a personalized video of your GitHub activity.',
50
- additionalInfo:
51
- 'Uncover your go-to language, peak productivity hours, and track your GitHub impact – all in one video.',
52
- link: 'https://githubunwrapped.com/',
53
- videoWidth: 1080,
54
- videoHeight: 1080,
55
- muxId: 'OwQFvqomOR00q6yj5SWwaA7DBg01NaCPKcOvczoZqCty00',
56
- buttonText: 'GitHub Unwrapped website',
57
- },
58
- ];
59
-
60
- const icon: React.CSSProperties = {
61
- height: 16,
62
- marginLeft: 10,
63
- };
64
-
65
- const VideoAppsShowcase: React.FC = () => {
66
- const [activeTab, setActiveTab] = useState(0);
67
- const [isMuted, setIsMuted] = useState(true);
68
- const [isPlaying, setIsPlaying] = useState(false);
69
- const videoRef = useRef<HTMLVideoElement>(null);
70
- const containerRef = useRef<HTMLDivElement>(null);
71
-
72
- // Remove the intersection observer autoplay logic
73
- useEffect(() => {
74
- const video = videoRef.current;
75
- if (video) {
76
- video.pause();
77
- setIsPlaying(false);
78
- video.currentTime = 0;
79
- video.load();
80
- }
81
- }, [activeTab]);
82
-
83
- const handlePlayPause = () => {
84
- if (videoRef.current) {
85
- if (videoRef.current.paused) {
86
- const playPromise = videoRef.current.play();
87
-
88
- if (playPromise !== undefined) {
89
- playPromise
90
- .then(() => {
91
- setIsPlaying(true);
92
- })
93
- .catch((error) => {
94
- // eslint-disable-next-line no-console
95
- console.error('Playback error:', error);
96
- setIsPlaying(false);
97
- });
98
- }
99
- } else {
100
- videoRef.current.pause();
101
- setIsPlaying(false);
102
- }
103
- }
104
- };
105
-
106
- const handleMuteToggle = () => {
107
- if (videoRef.current) {
108
- const newMutedState = !videoRef.current.muted;
109
- videoRef.current.muted = newMutedState;
110
- setIsMuted(newMutedState);
111
- }
112
- };
113
-
114
- return (
115
- <div ref={containerRef}>
116
- <SectionTitle>Use Cases</SectionTitle>
117
- <div
118
- className={
119
- 'grid justify-center grid-flow-col grid-rows-1 gap-2.5 justify-self-center mb-4 w-[90vw] md:w-auto -mt-4'
120
- }
121
- >
122
- {tabs.map((tab, index) => (
123
- <button
124
- key={tab}
125
- type="button"
126
- data-active={index === activeTab}
127
- className={`bg-transparent border-none m-0 p-0 lg:mx-3 my-4 cursor-pointer text-base fontbrand font-bold transition-colors text-muted data-[active=true]:text-brand`}
128
- onClick={() => setActiveTab(index)}
129
- >
130
- {tab}
131
- </button>
132
- ))}
133
- </div>
134
- <div className={'card flex p-0 overflow-hidden'}>
135
- <div className={'flex-1 flex flex-col lg:flex-row justify-center'}>
136
- <div
137
- className={
138
- 'w-full max-w-[500px] aspect-square relative overflow-hidden bg-[#eee] cursor-pointer'
139
- }
140
- onClick={handlePlayPause}
141
- >
142
- <MuxVideo
143
- ref={videoRef}
144
- muxId={videoApps[activeTab].muxId}
145
- className={
146
- 'absolute left-0 top-0 w-full h-full object-contain rounded-sm rounded-tr-none rounded-br-none'
147
- }
148
- loop
149
- playsInline
150
- muted={isMuted}
151
- />
152
-
153
- {/* Play/Pause Button - bottom left corner */}
154
- <button
155
- type="button"
156
- className={
157
- 'absolute bottom-2.5 left-2.5 bg-white text-black rounded-full w-8 h-8 flex justify-center items-center text-base cursor-pointer transition-colors border-2 border-black border-solid'
158
- }
159
- onClick={(e) => {
160
- e.stopPropagation();
161
- handlePlayPause();
162
- }}
163
- >
164
- {isPlaying ? (
165
- <PlayingIcon
166
- style={{
167
- width: 12,
168
- height: 20,
169
- marginLeft: '2px',
170
- marginTop: '1px',
171
- }}
172
- />
173
- ) : (
174
- <PausedIcon
175
- style={{
176
- width: 14,
177
- height: 16,
178
- marginLeft: '2px',
179
- marginTop: '0.5px',
180
- }}
181
- />
182
- )}
183
- </button>
184
-
185
- {/* Mute/Unmute Button - bottom right corner */}
186
- <button
187
- type="button"
188
- className={
189
- 'absolute bottom-2.5 right-2.5 bg-white text-black rounded-full w-8 h-8 flex justify-center items-center text-base cursor-pointer transition-colors border-2 border-black border-solid'
190
- }
191
- onClick={(e) => {
192
- e.stopPropagation();
193
- handleMuteToggle();
194
- }}
195
- >
196
- {isMuted ? (
197
- <IsMutedIcon
198
- style={{
199
- width: 16,
200
- height: 16,
201
- marginTop: '1px',
202
- }}
203
- />
204
- ) : (
205
- <NotMutedIcon
206
- style={{
207
- width: 16,
208
- height: 16,
209
- marginTop: '1px',
210
- }}
211
- />
212
- )}
213
- </button>
214
- </div>
215
- <div className={'p-6 flex-1 flex flex-col h-full'}>
216
- <div className="text-4xl font-bold fontbrand mt-0">
217
- {videoApps[activeTab].title}
218
- </div>
219
- <div className="text-muted mt-4 text-base fontbrand">
220
- {videoApps[activeTab].description}
221
- </div>
222
- {videoApps[activeTab].additionalInfo ? (
223
- <div className="text-muted mt-4 text-base fontbrand">
224
- {videoApps[activeTab].additionalInfo}
225
- </div>
226
- ) : null}
227
- <div className="h-5" />
228
- <a
229
- className="no-underline text-brand font-brand font-bold inline-flex flex-row items-center"
230
- href={videoApps[activeTab].link}
231
- >
232
- {videoApps[activeTab].buttonText}
233
- <svg
234
- style={icon}
235
- xmlns="http://www.w3.org/2000/svg"
236
- viewBox="0 0 448 512"
237
- >
238
- <path
239
- fill="currentColor"
240
- d="M438.6 278.6l-160 160C272.4 444.9 264.2 448 256 448s-16.38-3.125-22.62-9.375c-12.5-12.5-12.5-32.75 0-45.25L338.8 288H32C14.33 288 .0016 273.7 .0016 256S14.33 224 32 224h306.8l-105.4-105.4c-12.5-12.5-12.5-32.75 0-45.25s32.75-12.5 45.25 0l160 160C451.1 245.9 451.1 266.1 438.6 278.6z"
241
- />
242
- </svg>
243
- </a>
244
- </div>
245
- </div>
246
- </div>
247
- <div
248
- style={{
249
- marginTop: '1rem',
250
- justifyContent: 'center',
251
- display: 'flex',
252
- }}
253
- >
254
- <div
255
- style={{
256
- fontFamily: 'GTPlanar',
257
- }}
258
- >
259
- For more examples see our{' '}
260
- <a href="/showcase" className="bluelink">
261
- Showcase page
262
- </a>
263
- .
264
- </div>
265
- </div>
266
- </div>
267
- );
268
- };
269
-
270
- export default VideoAppsShowcase;
@@ -1,11 +0,0 @@
1
- import React from 'react';
2
-
3
- export const SectionTitle: React.FC<{readonly children: React.ReactNode}> = ({
4
- children,
5
- }) => {
6
- return (
7
- <div className={'text-center'}>
8
- <h2 className={'fontbrand text-4xl'}>{children}</h2>
9
- </div>
10
- );
11
- };
@@ -1,189 +0,0 @@
1
- /* eslint-disable no-console */
2
- /* eslint-disable react/require-default-props */
3
- import Hls from 'hls.js';
4
- import type Plyr from 'plyr';
5
- // eslint-disable-next-line no-restricted-imports
6
- import 'plyr/dist/plyr.css';
7
- import type {MutableRefObject} from 'react';
8
- import {forwardRef, useCallback, useEffect, useRef, useState} from 'react';
9
- import './video-player.css';
10
-
11
- export interface HTMLVideoElementWithPlyr extends HTMLVideoElement {
12
- plyr: Plyr;
13
- }
14
-
15
- const useCombinedRefs = function (
16
- ...refs: (
17
- | ((instance: HTMLVideoElementWithPlyr | null) => void)
18
- | MutableRefObject<HTMLVideoElementWithPlyr | null>
19
- | null
20
- )[]
21
- ): MutableRefObject<HTMLVideoElementWithPlyr | null> {
22
- const targetRef = useRef(null);
23
-
24
- useEffect(() => {
25
- refs.forEach((ref) => {
26
- if (!ref) return;
27
-
28
- if (typeof ref === 'function') {
29
- ref(targetRef.current);
30
- } else {
31
- ref.current = targetRef.current;
32
- }
33
- });
34
- }, [refs]);
35
-
36
- return targetRef;
37
- };
38
-
39
- /*
40
- * We need to set the width/height of the player depending on what the dimensions of
41
- * the underlying video source is.
42
- *
43
- * On most platforms we know the dimensions on 'loadedmetadata'
44
- * On Desktop Safari we don't know the dimensions until 'canplay'
45
- *
46
- * At first, I tried to get the dimensions of the video from these callbacks, that worked
47
- * great except for on moble Safari. On Mobile Safari none of those callbacks fire until
48
- * there is some user interaction :(
49
- *
50
- * BUT! There is a brilliant hack here. We can create a `display: none` `img` element in the
51
- * DOM, load up the poster image.
52
- *
53
- * Since the poster image will have the same dimensions of the video, now we know if the video
54
- * is vertical and now we can style the proper width/height so the layout doesn't have a sudden
55
- * jump or resize.
56
- *
57
- */
58
-
59
- type Props = {
60
- readonly playbackId: string;
61
- readonly poster: string;
62
- readonly currentTime?: number;
63
- readonly onLoaded: () => void;
64
- readonly onError: (error: ErrorEvent) => void;
65
- readonly onSize: (dim: {width: number; height: number}) => void;
66
- readonly autoPlay?: boolean;
67
- };
68
-
69
- type SizedEvent = {
70
- target: {
71
- width: number;
72
- height: number;
73
- };
74
- };
75
-
76
- export const VideoPlayerWithControls = forwardRef<
77
- HTMLVideoElementWithPlyr,
78
- Props
79
- >(
80
- (
81
- {playbackId, poster, currentTime, onLoaded, onError, onSize, autoPlay},
82
- ref,
83
- ) => {
84
- const videoRef = useRef<HTMLVideoElementWithPlyr>(null);
85
- const metaRef = useCombinedRefs(ref, videoRef);
86
- const playerRef = useRef<Plyr | null>(null);
87
- const [playerInitTime] = useState(Date.now());
88
-
89
- const videoError = useCallback(
90
- (event: ErrorEvent) => onError(event),
91
- [onError],
92
- );
93
-
94
- const onImageLoad = useCallback(
95
- (event: SizedEvent) => {
96
- const [w, h] = [event.target.width, event.target.height];
97
- if (w && h) {
98
- onSize({width: w, height: h});
99
- onLoaded();
100
- } else {
101
- onLoaded();
102
-
103
- console.error('Error getting img dimensions', event);
104
- }
105
- },
106
- [onLoaded, onSize],
107
- );
108
-
109
- /*
110
- * See comment above -- we're loading the poster image just so we can grab the dimensions
111
- * which determines styles for the player
112
- */
113
- useEffect(() => {
114
- const img = new Image();
115
- img.onload = (evt) => onImageLoad(evt as unknown as SizedEvent);
116
- img.src = poster;
117
- }, [onImageLoad, poster]);
118
-
119
- useEffect(() => {
120
- const video = videoRef.current;
121
- const src = `https://stream.mux.com/${playbackId}.m3u8`;
122
- let hls: Hls | null;
123
- hls = null;
124
- if (video) {
125
- video.addEventListener('error', videoError);
126
- const Plyr = require('plyr');
127
- playerRef.current = new Plyr(video, {
128
- previewThumbnails: {
129
- enabled: true,
130
- src: `https://image.mux.com/${playbackId}/storyboard.vtt`,
131
- },
132
- storage: {enabled: false},
133
- fullscreen: {
134
- iosNative: true,
135
- },
136
- captions: {active: true, language: 'auto', update: true},
137
- });
138
-
139
- if (video.canPlayType('application/vnd.apple.mpegurl')) {
140
- // This will run in safari, where HLS is supported natively
141
- video.src = src;
142
- } else if (Hls.isSupported()) {
143
- // This will run in all other modern browsers
144
- hls = new Hls();
145
- hls.loadSource(src);
146
- hls.attachMedia(video);
147
- hls.on(Hls.Events.ERROR, (_event, data) => {
148
- if (data.fatal) {
149
- videoError(new ErrorEvent('HLS.js fatal error'));
150
- }
151
- });
152
- } else {
153
- console.error(
154
- 'This is an old browser that does not support MSE https://developer.mozilla.org/en-US/docs/Web/API/Media_Source_Extensions_API',
155
- );
156
- }
157
- }
158
-
159
- return () => {
160
- if (video) {
161
- video.removeEventListener('error', videoError);
162
- }
163
-
164
- if (hls) {
165
- hls.destroy();
166
- }
167
- };
168
- }, [playbackId, playerInitTime, videoError, videoRef]);
169
-
170
- useEffect(() => {
171
- const video = videoRef.current;
172
- if (currentTime && video) {
173
- video.currentTime = currentTime;
174
- }
175
- }, [currentTime]);
176
-
177
- return (
178
- <div className="video-container">
179
- <video
180
- ref={metaRef}
181
- autoPlay={autoPlay}
182
- poster={poster}
183
- controls
184
- playsInline
185
- />
186
- </div>
187
- );
188
- },
189
- );