mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
feat(site): add util reference pipeline (#537)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
c11395ece1
commit
78112fbefd
@@ -0,0 +1,24 @@
|
||||
.react-create-player-basic {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.react-create-player-basic video {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.react-create-player-basic__controls {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
.react-create-player-basic__button {
|
||||
padding-block: 8px;
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
backdrop-filter: blur(10px);
|
||||
color: black;
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
border-radius: 9999px;
|
||||
padding-inline: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { createPlayer, features } from '@videojs/react';
|
||||
import { Video } from '@videojs/react/video';
|
||||
|
||||
import './BasicUsage.css';
|
||||
|
||||
const { Provider, Container, usePlayer } = createPlayer({
|
||||
features: features.video,
|
||||
});
|
||||
|
||||
function Controls() {
|
||||
const store = usePlayer();
|
||||
const paused = usePlayer((s) => s.paused);
|
||||
|
||||
return (
|
||||
<div className="react-create-player-basic__controls">
|
||||
<button
|
||||
type="button"
|
||||
className="react-create-player-basic__button"
|
||||
onClick={() => (paused ? store.play() : store.pause())}
|
||||
>
|
||||
{paused ? 'Play' : 'Pause'}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function BasicUsage() {
|
||||
return (
|
||||
<Provider>
|
||||
<Container className="react-create-player-basic">
|
||||
<Video
|
||||
src="https://stream.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/highest.mp4"
|
||||
autoPlay
|
||||
muted
|
||||
playsInline
|
||||
/>
|
||||
<Controls />
|
||||
</Container>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
import HtmlDemo from '@/components/docs/demos/HtmlDemo.astro';
|
||||
import html from './BasicUsage.html?raw';
|
||||
import './BasicUsage.css';
|
||||
---
|
||||
|
||||
<HtmlDemo html={html} />
|
||||
<script>
|
||||
import './BasicUsage.ts';
|
||||
</script>
|
||||
@@ -0,0 +1,37 @@
|
||||
.html-create-player-basic {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.html-create-player-basic video {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.html-create-player-basic__button {
|
||||
padding-block: 8px;
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
left: 10px;
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
backdrop-filter: blur(10px);
|
||||
color: black;
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
border-radius: 9999px;
|
||||
padding-inline: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.html-create-player-basic__button .show-when-paused {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.html-create-player-basic__button .show-when-playing {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.html-create-player-basic__button[data-paused] .show-when-paused {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.html-create-player-basic__button:not([data-paused]) .show-when-playing {
|
||||
display: inline;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<demo-video-player class="html-create-player-basic">
|
||||
<video
|
||||
src="https://stream.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/highest.mp4"
|
||||
autoplay
|
||||
muted
|
||||
playsinline
|
||||
></video>
|
||||
<demo-play-toggle class="html-create-player-basic__button">
|
||||
<span class="show-when-paused">Play</span>
|
||||
<span class="show-when-playing">Pause</span>
|
||||
</demo-play-toggle>
|
||||
</demo-video-player>
|
||||
@@ -0,0 +1,57 @@
|
||||
import {
|
||||
applyElementProps,
|
||||
applyStateDataAttrs,
|
||||
createButton,
|
||||
createPlayer,
|
||||
features,
|
||||
MediaElement,
|
||||
selectPlayback,
|
||||
} from '@videojs/html';
|
||||
|
||||
const { PlayerElement, PlayerController, context } = createPlayer({
|
||||
features: [...features.video],
|
||||
});
|
||||
|
||||
class VideoPlayer extends PlayerElement {
|
||||
static readonly tagName = 'demo-video-player';
|
||||
}
|
||||
|
||||
class PlayToggle extends MediaElement {
|
||||
static readonly tagName = 'demo-play-toggle';
|
||||
|
||||
readonly #player = new PlayerController(this, context, selectPlayback);
|
||||
|
||||
#disconnect: AbortController | null = null;
|
||||
|
||||
override connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this.#disconnect = new AbortController();
|
||||
|
||||
const buttonProps = createButton({
|
||||
onActivate: () => {
|
||||
const state = this.#player.value;
|
||||
if (!state) return;
|
||||
state.paused ? state.play() : state.pause();
|
||||
},
|
||||
isDisabled: () => !this.#player.value,
|
||||
});
|
||||
|
||||
applyElementProps(this, buttonProps, this.#disconnect.signal);
|
||||
}
|
||||
|
||||
override disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
this.#disconnect?.abort();
|
||||
this.#disconnect = null;
|
||||
}
|
||||
|
||||
protected override update(): void {
|
||||
super.update();
|
||||
const state = this.#player.value;
|
||||
if (!state) return;
|
||||
applyStateDataAttrs(this, state, { paused: 'data-paused', ended: 'data-ended' });
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(VideoPlayer.tagName, VideoPlayer);
|
||||
customElements.define(PlayToggle.tagName, PlayToggle);
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
import HtmlDemo from '@/components/docs/demos/HtmlDemo.astro';
|
||||
import html from './BasicUsage.html?raw';
|
||||
import './BasicUsage.css';
|
||||
---
|
||||
|
||||
<HtmlDemo html={html} />
|
||||
<script>
|
||||
import './BasicUsage.ts';
|
||||
</script>
|
||||
@@ -0,0 +1,36 @@
|
||||
.html-player-controller-basic {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.html-player-controller-basic video {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.html-player-controller-basic__panel {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
padding: 12px;
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.html-player-controller-basic__actions {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.html-player-controller-basic__actions button {
|
||||
padding: 4px 12px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #ccc;
|
||||
background: white;
|
||||
cursor: pointer;
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
.html-player-controller-basic__state {
|
||||
font-size: 0.8125rem;
|
||||
color: #374151;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<demo-ctrl-player class="html-player-controller-basic">
|
||||
<video
|
||||
src="https://stream.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/highest.mp4"
|
||||
autoplay
|
||||
muted
|
||||
playsinline
|
||||
></video>
|
||||
<div class="html-player-controller-basic__panel">
|
||||
<demo-ctrl-actions class="html-player-controller-basic__actions">
|
||||
<button class="action-play">Play</button>
|
||||
<button class="action-pause">Pause</button>
|
||||
<button class="action-volume">50% Volume</button>
|
||||
</demo-ctrl-actions>
|
||||
<demo-ctrl-state class="html-player-controller-basic__state">
|
||||
<span class="state-text">Paused: Yes | Time: 0.0s | Volume: 100%</span>
|
||||
</demo-ctrl-state>
|
||||
</div>
|
||||
</demo-ctrl-player>
|
||||
@@ -0,0 +1,67 @@
|
||||
import { applyElementProps, createButton, createPlayer, features, MediaElement } from '@videojs/html';
|
||||
|
||||
const { PlayerElement, PlayerController, context } = createPlayer({
|
||||
features: [...features.video],
|
||||
});
|
||||
|
||||
class DemoPlayer extends PlayerElement {
|
||||
static readonly tagName = 'demo-ctrl-player';
|
||||
}
|
||||
|
||||
class PlayerActions extends MediaElement {
|
||||
static readonly tagName = 'demo-ctrl-actions';
|
||||
|
||||
readonly #player = new PlayerController(this, context);
|
||||
|
||||
#disconnect: AbortController | null = null;
|
||||
|
||||
override connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this.#disconnect = new AbortController();
|
||||
const signal = this.#disconnect.signal;
|
||||
|
||||
const playBtn = this.querySelector<HTMLButtonElement>('.action-play')!;
|
||||
const pauseBtn = this.querySelector<HTMLButtonElement>('.action-pause')!;
|
||||
const volumeBtn = this.querySelector<HTMLButtonElement>('.action-volume')!;
|
||||
|
||||
const bind = (el: HTMLElement, action: () => void) => {
|
||||
const props = createButton({ onActivate: action, isDisabled: () => !this.#player.value });
|
||||
applyElementProps(el, props, signal);
|
||||
};
|
||||
|
||||
bind(playBtn, () => this.#player.value?.play());
|
||||
bind(pauseBtn, () => this.#player.value?.pause());
|
||||
bind(volumeBtn, () => this.#player.value?.changeVolume(0.5));
|
||||
}
|
||||
|
||||
override disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
this.#disconnect?.abort();
|
||||
this.#disconnect = null;
|
||||
}
|
||||
}
|
||||
|
||||
class PlayerState extends MediaElement {
|
||||
static readonly tagName = 'demo-ctrl-state';
|
||||
|
||||
readonly #state = new PlayerController(this, context, (s) => ({
|
||||
paused: s.paused,
|
||||
currentTime: s.currentTime,
|
||||
volume: s.volume,
|
||||
}));
|
||||
|
||||
protected override update(): void {
|
||||
super.update();
|
||||
const state = this.#state.value;
|
||||
if (!state) return;
|
||||
|
||||
const el = this.querySelector('.state-text');
|
||||
if (el) {
|
||||
el.textContent = `Paused: ${state.paused ? 'Yes' : 'No'} | Time: ${state.currentTime.toFixed(1)}s | Volume: ${Math.round(state.volume * 100)}%`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(DemoPlayer.tagName, DemoPlayer);
|
||||
customElements.define(PlayerActions.tagName, PlayerActions);
|
||||
customElements.define(PlayerState.tagName, PlayerState);
|
||||
@@ -0,0 +1,36 @@
|
||||
.react-render-element-basic {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.react-render-element-basic__toggle {
|
||||
align-self: flex-start;
|
||||
padding: 6px 16px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #ccc;
|
||||
background: #f5f5f5;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.react-render-element-basic__tags {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.react-render-element-basic__tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 6px 12px;
|
||||
border-radius: 9999px;
|
||||
background: #e5e7eb;
|
||||
color: #374151;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.react-render-element-basic__tag--active {
|
||||
background: #3b82f6;
|
||||
color: white;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import { renderElement } from '@videojs/react';
|
||||
import { type ReactNode, useState } from 'react';
|
||||
|
||||
import './BasicUsage.css';
|
||||
|
||||
interface TagState {
|
||||
active: boolean;
|
||||
}
|
||||
|
||||
function Tag({
|
||||
className,
|
||||
style,
|
||||
render,
|
||||
active,
|
||||
children,
|
||||
}: renderElement.ComponentProps<TagState> & { active: boolean; children?: ReactNode }) {
|
||||
const state: TagState = { active };
|
||||
|
||||
return renderElement(
|
||||
'span',
|
||||
{ className, style, render },
|
||||
{
|
||||
state,
|
||||
props: { children },
|
||||
stateAttrMap: { active: 'data-active' },
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export default function BasicUsage() {
|
||||
const [active, setActive] = useState(false);
|
||||
|
||||
const className = (state: TagState) =>
|
||||
`react-render-element-basic__tag${state.active ? ' react-render-element-basic__tag--active' : ''}`;
|
||||
|
||||
const style = (state: TagState) => ({
|
||||
fontSize: state.active ? '1.125rem' : '0.875rem',
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="react-render-element-basic">
|
||||
<button type="button" className="react-render-element-basic__toggle" onClick={() => setActive((prev) => !prev)}>
|
||||
{active ? 'Deactivate' : 'Activate'}
|
||||
</button>
|
||||
|
||||
<div className="react-render-element-basic__tags">
|
||||
<Tag active={active} className={className} style={style}>
|
||||
Default <span>
|
||||
</Tag>
|
||||
|
||||
<Tag active={active} className={className} style={style} render={<strong />}>
|
||||
Element <strong>
|
||||
</Tag>
|
||||
|
||||
<Tag
|
||||
active={active}
|
||||
className={className}
|
||||
style={style}
|
||||
render={(props, state) => <em {...props}>{state.active ? 'Active!' : 'Inactive'}</em>}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
.react-use-button-basic {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.react-use-button-basic__button {
|
||||
align-self: flex-start;
|
||||
padding: 8px 20px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #ccc;
|
||||
background: #f5f5f5;
|
||||
cursor: pointer;
|
||||
font-variant-numeric: tabular-nums;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.react-use-button-basic__button[disabled] {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.react-use-button-basic__label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 0.875rem;
|
||||
color: #6b7280;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { useButton } from '@videojs/react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import './BasicUsage.css';
|
||||
|
||||
export default function BasicUsage() {
|
||||
const [count, setCount] = useState(0);
|
||||
const [disabled, setDisabled] = useState(false);
|
||||
|
||||
const { getButtonProps, buttonRef } = useButton({
|
||||
displayName: 'ActivateButton',
|
||||
onActivate: () => setCount((c) => c + 1),
|
||||
isDisabled: () => disabled,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="react-use-button-basic">
|
||||
<button ref={buttonRef} {...getButtonProps()} className="react-use-button-basic__button" disabled={disabled}>
|
||||
Activated {count} times
|
||||
</button>
|
||||
<label className="react-use-button-basic__label">
|
||||
<input type="checkbox" checked={disabled} onChange={(e) => setDisabled(e.target.checked)} />
|
||||
Disabled
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
.react-use-media-basic {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.react-use-media-basic video {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.react-use-media-basic__info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
margin: 0;
|
||||
padding: 12px;
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
.react-use-media-basic__info div {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.react-use-media-basic__info dt {
|
||||
color: #6b7280;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.react-use-media-basic__info dd {
|
||||
margin: 0;
|
||||
font-variant-numeric: tabular-nums;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { createPlayer, features, useMedia } from '@videojs/react';
|
||||
import { Video } from '@videojs/react/video';
|
||||
|
||||
import './BasicUsage.css';
|
||||
|
||||
const { Provider, Container } = createPlayer({
|
||||
features: features.video,
|
||||
});
|
||||
|
||||
function MediaInfo() {
|
||||
const media = useMedia();
|
||||
|
||||
if (!media) return null;
|
||||
|
||||
return (
|
||||
<dl className="react-use-media-basic__info">
|
||||
<div>
|
||||
<dt>tagName</dt>
|
||||
<dd>{media.tagName.toLowerCase()}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>src</dt>
|
||||
<dd>{media.currentSrc || '—'}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>videoWidth</dt>
|
||||
<dd>{media.videoWidth}px</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>videoHeight</dt>
|
||||
<dd>{media.videoHeight}px</dd>
|
||||
</div>
|
||||
</dl>
|
||||
);
|
||||
}
|
||||
|
||||
export default function BasicUsage() {
|
||||
return (
|
||||
<Provider>
|
||||
<Container className="react-use-media-basic">
|
||||
<Video
|
||||
src="https://stream.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/highest.mp4"
|
||||
autoPlay
|
||||
muted
|
||||
playsInline
|
||||
loop
|
||||
/>
|
||||
<MediaInfo />
|
||||
</Container>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
.react-use-player-selector {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.react-use-player-selector video {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.react-use-player-selector__state {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
padding: 12px;
|
||||
margin: 0;
|
||||
font-size: 0.8125rem;
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.react-use-player-selector__state div {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.react-use-player-selector__state dt {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.react-use-player-selector__state dd {
|
||||
margin: 0;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { createPlayer, features, usePlayer } from '@videojs/react';
|
||||
import { Video } from '@videojs/react/video';
|
||||
|
||||
import './Selector.css';
|
||||
|
||||
const { Provider, Container } = createPlayer({
|
||||
features: features.video,
|
||||
});
|
||||
|
||||
function StateDisplay() {
|
||||
const state = usePlayer((s) => ({
|
||||
paused: s.paused,
|
||||
currentTime: s.currentTime,
|
||||
duration: s.duration,
|
||||
}));
|
||||
|
||||
return (
|
||||
<dl className="react-use-player-selector__state">
|
||||
<div>
|
||||
<dt>Paused</dt>
|
||||
<dd>{String(state.paused)}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Time</dt>
|
||||
<dd>
|
||||
{state.currentTime.toFixed(1)}s / {state.duration.toFixed(1)}s
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Selector() {
|
||||
return (
|
||||
<Provider>
|
||||
<Container className="react-use-player-selector">
|
||||
<Video
|
||||
src="https://stream.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/highest.mp4"
|
||||
autoPlay
|
||||
muted
|
||||
playsInline
|
||||
loop
|
||||
/>
|
||||
<StateDisplay />
|
||||
</Container>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
.react-use-player-store {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.react-use-player-store video {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.react-use-player-store__controls {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
padding: 12px;
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.react-use-player-store__controls button {
|
||||
padding: 4px 12px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #ccc;
|
||||
background: white;
|
||||
cursor: pointer;
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { createPlayer, features, usePlayer } from '@videojs/react';
|
||||
import { Video } from '@videojs/react/video';
|
||||
|
||||
import './StoreAccess.css';
|
||||
|
||||
const { Provider, Container } = createPlayer({
|
||||
features: features.video,
|
||||
});
|
||||
|
||||
function Controls() {
|
||||
const store = usePlayer();
|
||||
|
||||
return (
|
||||
<div className="react-use-player-store__controls">
|
||||
<button type="button" onClick={() => store.play()}>
|
||||
Play
|
||||
</button>
|
||||
<button type="button" onClick={() => store.pause()}>
|
||||
Pause
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function StoreAccess() {
|
||||
return (
|
||||
<Provider>
|
||||
<Container className="react-use-player-store">
|
||||
<Video
|
||||
src="https://stream.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/highest.mp4"
|
||||
autoPlay
|
||||
muted
|
||||
playsInline
|
||||
loop
|
||||
/>
|
||||
<Controls />
|
||||
</Container>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
.react-use-store-selector {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.react-use-store-selector video {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.react-use-store-selector__state {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
padding: 12px;
|
||||
margin: 0;
|
||||
font-size: 0.8125rem;
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.react-use-store-selector__state div {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.react-use-store-selector__state dt {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.react-use-store-selector__state dd {
|
||||
margin: 0;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { createPlayer, features, usePlayer, useStore } from '@videojs/react';
|
||||
import { Video } from '@videojs/react/video';
|
||||
|
||||
import './Selector.css';
|
||||
|
||||
const { Provider, Container } = createPlayer({
|
||||
features: features.video,
|
||||
});
|
||||
|
||||
function DerivedState() {
|
||||
const store = usePlayer();
|
||||
const derived = useStore(store, (s) => ({
|
||||
remaining: s.duration - s.currentTime,
|
||||
progress: s.duration > 0 ? (s.currentTime / s.duration) * 100 : 0,
|
||||
}));
|
||||
|
||||
return (
|
||||
<dl className="react-use-store-selector__state">
|
||||
<div>
|
||||
<dt>Remaining</dt>
|
||||
<dd>{derived.remaining.toFixed(1)}s</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Progress</dt>
|
||||
<dd>{derived.progress.toFixed(1)}%</dd>
|
||||
</div>
|
||||
</dl>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Selector() {
|
||||
return (
|
||||
<Provider>
|
||||
<Container className="react-use-store-selector">
|
||||
<Video
|
||||
src="https://stream.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/highest.mp4"
|
||||
autoPlay
|
||||
muted
|
||||
playsInline
|
||||
loop
|
||||
/>
|
||||
<DerivedState />
|
||||
</Container>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
.react-use-store-access {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.react-use-store-access video {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.react-use-store-access__controls {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
padding: 12px;
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.react-use-store-access__controls button {
|
||||
padding: 4px 12px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #ccc;
|
||||
background: white;
|
||||
cursor: pointer;
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { createPlayer, features, usePlayer, useStore } from '@videojs/react';
|
||||
import { Video } from '@videojs/react/video';
|
||||
|
||||
import './StoreAccess.css';
|
||||
|
||||
const { Provider, Container } = createPlayer({
|
||||
features: features.video,
|
||||
});
|
||||
|
||||
function SeekControls() {
|
||||
const store = usePlayer();
|
||||
const s = useStore(store);
|
||||
|
||||
return (
|
||||
<div className="react-use-store-access__controls">
|
||||
<button type="button" onClick={() => s.seek(0)}>
|
||||
Go to start
|
||||
</button>
|
||||
<button type="button" onClick={() => s.seek(s.state.duration / 2)}>
|
||||
Go to middle
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function StoreAccess() {
|
||||
return (
|
||||
<Provider>
|
||||
<Container className="react-use-store-access">
|
||||
<Video
|
||||
src="https://stream.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/highest.mp4"
|
||||
autoPlay
|
||||
muted
|
||||
playsInline
|
||||
loop
|
||||
/>
|
||||
<SeekControls />
|
||||
</Container>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user