Skip to content

Commit 92c50d0

Browse files
committed
Initial HoC
1 parent fef2637 commit 92c50d0

File tree

5 files changed

+186
-6
lines changed

5 files changed

+186
-6
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ View the [demo](http://mderrick.github.io/react-html5video/).
1616

1717
### Peer Dependencies
1818

19-
- `react@>=0.15.x`
19+
- `react@>=15.0.x`
20+
- `react-dom@>=15.0.x`
2021

2122
## Usage
2223

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@
5353
"testRegex": "(/__tests__/.*|\\.(test|spec))\\.(js|jsx)$"
5454
},
5555
"peerDependencies": {
56-
"react": ">=15.0.0"
56+
"react": ">=15.0.0",
57+
"react-dom": "^15.4.1"
5758
},
5859
"devDependencies": {
5960
"babel": "^6.5.2",
@@ -85,6 +86,7 @@
8586
"react-addons-test-utils": "^15.4.0",
8687
"react-dom": "^15.4.0",
8788
"react-hot-loader": "^3.0.0-beta.6",
89+
"recompose": "^0.20.2",
8890
"style-loader": "^0.13.1",
8991
"url-loader": "^0.5.7",
9092
"webpack": "^1.13.3",

src/Entry.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1 @@
1-
import './assets/video.css';
2-
import {default as Video, Controls, Seek, Play, Mute, Fullscreen, Time, Overlay} from './components/video/Video';
3-
export {Controls, Seek, Play, Mute, Fullscreen, Time, Overlay};
4-
export default Video;
1+
import { default } from './Video/Video';

src/Video/Video.js

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
/**
2+
* This is a HoC that finds a single
3+
* <video> in a component and makes
4+
* all its PROPERTIES and METHODS
5+
* available as props.
6+
*/
7+
import React, { Component } from 'react';
8+
import { findDOMNode } from 'react-dom';
9+
import toClass from 'recompose/toClass';
10+
11+
const EVENTS = [
12+
'onAbort',
13+
'onCanPlay',
14+
'onCanPlayThrough',
15+
'onDurationChange',
16+
'onEmptied',
17+
'onEncrypted',
18+
'onEnded',
19+
'onError',
20+
'onLoadedData',
21+
'onLoadedMetadata',
22+
'onLoadStart',
23+
'onPause',
24+
'onPlay',
25+
'onPlaying',
26+
'onProgress',
27+
'onRateChange',
28+
'onSeeked',
29+
'onSeeking',
30+
'onStalled',
31+
'onSuspend',
32+
'onTimeUpdate',
33+
'onVolumeChange',
34+
'onWaiting'
35+
];
36+
37+
const METHODS = [
38+
'addTextTrack',
39+
'canPlayType',
40+
'load',
41+
'play',
42+
'pause'
43+
];
44+
45+
const PROPERTIES = [
46+
'audioTracks',
47+
'autoPlay',
48+
'buffered',
49+
'controller',
50+
'controls',
51+
'currentSrc',
52+
'currentTime',
53+
'defaultMuted',
54+
'defaultPlaybackRate',
55+
'duration',
56+
'ended',
57+
'error',
58+
'loop',
59+
'mediaGroup',
60+
'muted',
61+
'networkState',
62+
'paused',
63+
'playbackRate',
64+
'played',
65+
'preload',
66+
'readyState',
67+
'seekable',
68+
'seeking',
69+
'src',
70+
'startDate',
71+
'textTracks',
72+
'videoTracks',
73+
'volume'
74+
];
75+
76+
export default (BaseComponent) => {
77+
const BaseComponentClass = toClass(BaseComponent);
78+
79+
class Video extends Component {
80+
constructor(props) {
81+
super(props);
82+
this.getVideoEl = this.getVideoEl.bind(this);
83+
this.updateState = this.updateState.bind(this);
84+
}
85+
86+
getVideoEl() {
87+
return this.videoEl;
88+
}
89+
90+
updateState() {
91+
this.setState(
92+
PROPERTIES.reduce((p, c) => {
93+
p[c] = this.videoEl[c];
94+
return p;
95+
}, {})
96+
);
97+
}
98+
99+
componentDidMount() {
100+
this.videoEl = this.el.getElementsByTagName('video')[0];
101+
EVENTS.forEach(event => {
102+
this.videoEl[event.toLowerCase()] = this.updateState.bind(this);
103+
});
104+
this.methods = METHODS.reduce((p, c) => {
105+
p[c] = (...args) => {
106+
if (this.videoEl[c]) this.videoEl[c].apply(this.videoEl, args);
107+
};
108+
return p;
109+
}, {});
110+
}
111+
112+
render() {
113+
return (
114+
<BaseComponentClass
115+
ref={(el) => this.el = findDOMNode(el)}
116+
video={Object.assign({
117+
// getVideoEl is used to update the video
118+
// using the HTMLMediaElement API.
119+
// e.g getVideoEl().muted = true;
120+
getVideoEl: this.getVideoEl,
121+
// forceUpdateState should never be used
122+
// unless changing a video property that
123+
// does not trigger an EVENT.
124+
// See CaptionMenu component.
125+
forceUpdateState: this.updateState,
126+
}, this.methods, this.state, this.props)}
127+
{...this.props} />
128+
);
129+
}
130+
}
131+
return Video;
132+
}

src/Video/Video.test.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import React from 'react';
2+
import { shallow } from 'enzyme';
3+
import video from './Video';
4+
5+
const TestComponent = () => (
6+
<div>
7+
<video></video>
8+
</div>
9+
);
10+
11+
describe('Video', () => {
12+
let Component;
13+
let component;
14+
15+
beforeAll(() => {
16+
Component = video(TestComponent);
17+
component = shallow(
18+
<Component />
19+
);
20+
});
21+
22+
it('returns a component with `video.forceUpdateState` method', () => {
23+
expect(component.prop('video').forceUpdateState)
24+
.toBe(component.instance().updateState);
25+
});
26+
27+
it('returns a component with `video.getVideoEl` method', () => {
28+
expect(component.prop('video').getVideoEl)
29+
.toBe(component.instance().getVideoEl);
30+
});
31+
32+
it('returns a component with all its state on the `video` prop', () => {
33+
const state = {
34+
html5: '1',
35+
dom: 2,
36+
properties: function() {
37+
return 3;
38+
}
39+
};
40+
component.setState(state);
41+
expect(component.prop('video').html5)
42+
.toEqual(state.html5);
43+
expect(component.prop('video').dom)
44+
.toEqual(state.dom);
45+
expect(component.prop('video').properties)
46+
.toEqual(state.properties);
47+
});
48+
});

0 commit comments

Comments
 (0)