Skip to content

Commit f641aa0

Browse files
committed
feat: add <Video> component
1 parent 20c9ee6 commit f641aa0

File tree

2 files changed

+229
-1
lines changed

2 files changed

+229
-1
lines changed

src/Audio/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ export class Audio extends Component<IAudioProps, IAudioState> {
170170

171171
render () {
172172
const {props, event} = this;
173-
const {children, src, autoPlay, loop, muted, preload, volume, noJs = noop as any} = props;
173+
const {children, src, autoPlay, loop, muted, preload, volume, noJs} = props;
174174

175175

176176
const audio = h('audio', {

src/Video/index.ts

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
import {Component, cloneElement, Children} from 'react';
2+
import {h, noop} from '../util';
3+
import renderProp from '../util/renderProp';
4+
5+
export type TVideoEvent = (event, Audio, IAudioState) => void;
6+
export type TVideoRenderProp = (video: Video, state: IVideoState) => React.ReactElement<any>;
7+
8+
export interface IVideo {
9+
play();
10+
pause();
11+
seek(time: number);
12+
volume(volume: number);
13+
mute();
14+
unmute();
15+
}
16+
17+
export interface IVideoProps {
18+
src: string;
19+
children?: TVideoRenderProp;
20+
render?: TVideoRenderProp;
21+
autoPlay?: boolean;
22+
loop?: boolean;
23+
muted?: boolean;
24+
preload?: 'none' | 'metadata' | 'auto';
25+
volume?: number;
26+
noJs?: React.ReactElement<any>;
27+
28+
onAbort?: TVideoEvent,
29+
onCanPlay?: TVideoEvent,
30+
onCanPlayThrough?: TVideoEvent,
31+
onDurationChange?: TVideoEvent,
32+
onEmptied?: TVideoEvent,
33+
onEncrypted?: TVideoEvent,
34+
onEnded?: TVideoEvent,
35+
onError?: TVideoEvent,
36+
onLoadedData?: TVideoEvent,
37+
onLoadedMetadata?: TVideoEvent,
38+
onLoadStart?: TVideoEvent,
39+
onPause?: TVideoEvent,
40+
onPlay?: TVideoEvent,
41+
onPlaying?: TVideoEvent,
42+
onProgress?: TVideoEvent,
43+
onRateChange?: TVideoEvent,
44+
onSeeked?: TVideoEvent,
45+
onSeeking?: TVideoEvent,
46+
onStalled?: TVideoEvent,
47+
onSuspend?: TVideoEvent,
48+
onTimeUpdate?: TVideoEvent,
49+
onVolumeChange?: TVideoEvent,
50+
onWaiting?: TVideoEvent
51+
}
52+
53+
export interface IVideoState {
54+
time?: number;
55+
duration?: number;
56+
isPlaying?: boolean;
57+
muted?: boolean;
58+
volume?: number;
59+
}
60+
61+
export class Video extends Component<IVideoProps, IVideoState> implements IVideo {
62+
el: HTMLVideoElement = null;
63+
video: React.ReactElement<any> = null;
64+
65+
state: IVideoState = {
66+
time: 0,
67+
duration: 0,
68+
isPlaying: false,
69+
muted: false,
70+
volume: 1
71+
};
72+
73+
ref = (el) => {
74+
this.el = el;
75+
};
76+
77+
componentDidMount () {
78+
if (this.props.autoPlay && this.el.paused) {
79+
this.play();
80+
}
81+
82+
this.setState({
83+
volume: this.el.volume
84+
});
85+
}
86+
87+
componentWillUnmount () {
88+
this.el = null;
89+
}
90+
91+
play = () => {
92+
if (this.el) {
93+
// TODO: In some browsers `.play()` method returns a `Promise`, where you
94+
// TODO: cannot call `pauer()` or `play()` again before that promise resolves.
95+
const promise = this.el.play();
96+
}
97+
};
98+
99+
pause = () => {
100+
if (this.el) {
101+
this.el.pause();
102+
}
103+
};
104+
105+
seek = (time: number) => {
106+
if (this.el) {
107+
time = Math.min(this.state.duration, Math.max(0, time));
108+
this.el.currentTime = time;
109+
}
110+
};
111+
112+
volume = (volume) => {
113+
if (this.el) {
114+
volume = Math.min(1, Math.max(0, volume));
115+
116+
this.el.volume = volume;
117+
this.setState({
118+
volume
119+
});
120+
}
121+
};
122+
123+
mute = () => {
124+
if (this.el) {
125+
this.el.muted = true;
126+
}
127+
};
128+
129+
unmute = () => {
130+
if (this.el) {
131+
this.el.muted = false;
132+
}
133+
};
134+
135+
event = (name: string) => (event) => {
136+
const handler = this.props[name];
137+
138+
if (handler) {
139+
handler(event, this, this.state);
140+
}
141+
};
142+
143+
onPlay = (event) => {
144+
this.setState({
145+
isPlaying: true
146+
});
147+
148+
this.event('onPlay')(event);
149+
};
150+
151+
onPause = (event) => {
152+
this.setState({
153+
isPlaying: false
154+
});
155+
156+
this.event('onPause')(event);
157+
};
158+
159+
onVolumeChange = (event) => {
160+
const {muted, volume} = this.el;
161+
162+
this.setState({
163+
muted,
164+
volume
165+
});
166+
167+
this.event('onVolumeChange')(event);
168+
};
169+
170+
onDurationChange = (event) => {
171+
this.setState({
172+
duration: this.el.duration
173+
});
174+
175+
this.event('onDurationChange')(event);
176+
};
177+
178+
onTimeUpdate = (event) => {
179+
this.setState({
180+
time: this.el.currentTime
181+
});
182+
183+
this.event('onTimeUpdate')(event);
184+
};
185+
186+
render () {
187+
const {props, event} = this;
188+
const {children, src, autoPlay, loop, muted, preload, volume, noJs} = props;
189+
190+
this.video = h('video', {
191+
ref: this.ref,
192+
controls: false,
193+
src,
194+
autoPlay,
195+
loop,
196+
muted,
197+
preload,
198+
volume,
199+
onAbort: event('onAbort'),
200+
onCanPlay: event('onCanPlay'),
201+
onCanPlayThrough: event('onCanPlayThrough'),
202+
onDurationChange: this.onDurationChange,
203+
onEmptied: event('onEmptied'),
204+
onEncrypted: event('onEncrypted'),
205+
onEnded: event('onEnded'),
206+
onError: event('onError'),
207+
onLoadedData: event('onLoadedData'),
208+
onLoadedMetadata: event('onLoadedMetadata'),
209+
onLoadStart: event('onLoadStart'),
210+
onPause: this.onPause,
211+
onPlay: this.onPlay,
212+
onPlaying: event('onPlaying'),
213+
onProgress: event('onProgress'),
214+
onRateChange: event('onRateChange'),
215+
onSeeked: event('onSeeked'),
216+
onSeeking: event('onSeeking'),
217+
onStalled: event('onStalled'),
218+
onSuspend: event('onSuspend'),
219+
onTimeUpdate: this.onTimeUpdate,
220+
onVolumeChange: this.onVolumeChange,
221+
onWaiting: event('onWaiting')
222+
},
223+
noJs
224+
);
225+
226+
return renderProp(this.props, this, this.state);
227+
}
228+
}

0 commit comments

Comments
 (0)