You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs(site): rework autoplay snippets per framework idioms
The previous React snippet was a component that called play() in a
useEffect — which throws NO_TARGET synchronously, because the
provider's store.attach() runs after child effects. Verified via test.
React now exposes a useAutoplay() hook that subscribes to the store
and waits for store.target before attempting play. Returns the status
directly so consumers conditionally render their fallback UI.
HTML drops the custom-element wrapper entirely and uses an inline
<script type="module"> that reads the store off the <video-player>
instance. The subscribe-then-retry pattern handles the case where
media hasn't yet attached when the script first runs. Exposes status
via a data-autoplay attribute so CSS can drive the fallback.
Both verified with vitest behavior tests (playing / muted / blocked
status outcomes) using stubbed HTMLMediaElement.prototype.play.
https://claude.ai/code/session_01QLEz6mPy8579e4QXq3h1L8
Copy file name to clipboardExpand all lines: site/src/content/docs/how-to/autoplay.mdx
+77-80Lines changed: 77 additions & 80 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -83,111 +83,108 @@ video.play().catch(() => {
83
83
In v10, route the same logic through the player store so it works for whatever media element you're using — a plain `<video>`, an HLS adapter, or any custom media element.
84
84
85
85
<FrameworkCaseframeworks={["react"]}>
86
-
<DocsLinkslug="reference/use-player">`usePlayer`</DocsLink> with <DocsLinkslug="reference/feature-playback">`selectPlayback`</DocsLink> gives you a `play()` action that returns the same promise, and <DocsLinkslug="reference/feature-volume">`selectVolume`</DocsLink> lets you toggle mute on rejection:
86
+
<DocsLinkslug="reference/use-player">`usePlayer`</DocsLink> with <DocsLinkslug="reference/feature-playback">`selectPlayback`</DocsLink> gives you a `play()` action that returns the same promise, and <DocsLinkslug="reference/feature-volume">`selectVolume`</DocsLink> lets you toggle mute on rejection. Wrap that up as a hook:
Render `<AutoplayHandler />` inside your `<Player.Provider>` and drop the `autoPlay` attribute from `<Video>` when you do — combining the declarative attribute with a manual `play()` call leads to duplicate attempts and inconsistent state.
126
+
Call `useAutoplay()` inside any component nested in `<Player.Provider>`, condition your fallback UI on the returned status, and drop the `autoPlay` attribute from `<Video>` — the hook handles `play()` itself.
127
+
128
+
```tsx title="HeroPlayer.tsx"
129
+
function HeroPlayer() {
130
+
const status =useAutoplay();
131
+
return (
132
+
<Player.Container>
133
+
<Videosrc="hero.mp4"mutedplaysInline />
134
+
{status==='blocked'&& <ClickToPlayOverlay />}
135
+
</Player.Container>
136
+
);
137
+
}
138
+
```
120
139
</FrameworkCase>
121
140
122
141
<FrameworkCaseframeworks={["html"]}>
123
-
<DocsLinkslug="reference/player-controller">`PlayerController`</DocsLink> with <DocsLinkslug="reference/feature-playback">`selectPlayback`</DocsLink> gives you a `play()` action that returns the same promise, and <DocsLinkslug="reference/feature-volume">`selectVolume`</DocsLink> lets you toggle mute on rejection:
124
-
125
-
```ts title="autoplay-handler.ts"
126
-
import {
127
-
MediaElement,
128
-
PlayerController,
129
-
playerContext,
130
-
typePropertyValues,
131
-
selectPlayback,
132
-
selectVolume,
133
-
} from'@videojs/html';
142
+
The provider element exposes its store on the `<video-player>` instance. Read <DocsLinkslug="reference/feature-playback">`selectPlayback`</DocsLink> for the `play()` action, <DocsLinkslug="reference/feature-volume">`selectVolume`</DocsLink> for the muted fallback, and `store.target` to gate the call on media being attached:
Place `<autoplay-handler>` inside `<video-player>` and drop the `autoplay` attribute from your media — combining the declarative attribute with a manual `play()` call leads to duplicate attempts and inconsistent state.
178
+
returntrue;
179
+
};
177
180
178
-
```html title="index.html"
179
-
<scripttype="module">
180
-
import'@videojs/html/video/player';
181
-
import'./autoplay-handler';
181
+
if (!tryPlay()) {
182
+
constunsub=player.store.subscribe(() => { if (tryPlay()) unsub(); });
183
+
}
182
184
</script>
183
-
184
-
<video-player>
185
-
<media-container>
186
-
<videosrc="hero.mp4"mutedplaysinline></video>
187
-
</media-container>
188
-
<autoplay-handler></autoplay-handler>
189
-
</video-player>
190
185
```
186
+
187
+
Drop the `autoplay` attribute from your media when you do this — combining the declarative attribute with a manual `play()` call leads to duplicate attempts and inconsistent state.
191
188
</FrameworkCase>
192
189
193
190
<Asidetype="note">
@@ -203,4 +200,4 @@ When even muted autoplay fails, autoplay isn't going to happen without the viewe
203
200
-**Don't retry on every render.** The browser's decision is based on engagement state that won't change without viewer input. Retrying in a loop wastes work and inflates analytics.
204
201
-**Don't log autoplay rejection as a fatal error.** A blocked autoplay is the browser doing its job. If your analytics treats every `play()` rejection as a playback failure, your error rates will spike on first-visit sessions where the policy hasn't been earned yet. Distinguish a `NotAllowedError` on the initial autoplay attempt from real playback errors.
205
202
206
-
Wire your UI to whichever status the handler above surfaces — a React state, a custom event, or a store slice of your own — and show the fallback overlay only when status reaches `'blocked'`.
203
+
Wire your UI to whichever signal the handler above exposes, and show the fallback overlay only when status reaches `'blocked'`.
0 commit comments