8000 Add Audio component · cloudinary/cloudinary-react@7667968 · GitHub
[go: up one dir, main page]

Skip to content

Commit 7667968

Browse files
author
Nir Maoz
authored
Add Audio component
1 parent 33c0315 commit 7667968

File tree

5 files changed

+238
-3
lines changed

5 files changed

+238
-3
lines changed

src/components/Audio/Audio.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import Video from '../Video';
4+
5+
/**
6+
* A component representing a Cloudinary served audio
7+
* This component extends the Video component and replaces it's render function.
8+
* Audio files are uploaded to Cloudinary as a video asset type.
9+
* An <audio> tag with a video source, will play the audio only.
10+
*/
11+
class Audio extends Video {
12+
mimeType = 'audio';
13+
14+
/**
15+
* Render an audio element
16+
*/
17+
render() {
18+
const {innerRef, fallback, children} = this.props;
19+
20+
const {
21+
tagAttributes, // Attributes of this video element
22+
sources // <source> tags of this video element
23+
} = this.getVideoTagProps();
24+
25+
// We generated video attributes, lets delete the unneeded poster
26+
delete tagAttributes.poster;
27+
28+
return (
29+
<audio
30+
ref={innerRef}
31+
{...tagAttributes}>
32+
{sources}
33+
{fallback}
34+
{children}
35+
</audio>
36+
);
37+
}
38+
}
39+
40+
Audio.propTypes = {publicId: PropTypes.string};
41+
Audio.defaultProps = {sourceTypes: ["aac","mp3","ogg"]};
42+
43+
export default Audio;

src/components/Audio/package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"name": "Audio",
3+
"version": "1.0.0",
4+
"main": "./Audio.js",
5+
"private": true
6+
}

src/components/Video/Video.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import CloudinaryComponent from '../CloudinaryComponent';
77
* A component representing a Cloudinary served video
88
*/
99
class Video extends CloudinaryComponent {
10+
mimeType = 'video';
11+
1012
/**
1113
* Merge context with props
1214
* @return {*}
@@ -46,7 +48,7 @@ class Video extends CloudinaryComponent {
4648
generateSources = (cld, publicId, childTransformations, sourceTransformations, sourceTypes) => (
4749
sourceTypes.map(sourceType => {
4850
const src = this.generateVideoUrl(cld, publicId, childTransformations, sourceTransformations, sourceType);
49-
const mimeType = 'video/' + (sourceType === 'ogv' ? 'ogg' : sourceType);
51+
const mimeType = `${this.mimeType}/${sourceType === 'ogv' ? 'ogg' : sourceType}`;
5052

5153
return <source key={mimeType} src={src} type={mimeType}/>;
5254
})
@@ -62,7 +64,7 @@ class Video extends CloudinaryComponent {
6264
publicId,
6365
fallback,
6466
children,
65-
sourceTypes = Cloudinary.DEFAULT_VIDEO_PARAMS.source_types,
67+
sourceTypes,
6668
sourceTransformation = {},
6769
...options
6870
} = this.getMergedProps();
@@ -114,6 +116,8 @@ class Video extends CloudinaryComponent {
114116
}
115117

116118
Video.propTypes = {publicId: PropTypes.string};
117-
Video.defaultProps = {};
119+
Video.defaultProps = {
120+
sourceTypes: Cloudinary.DEFAULT_VIDEO_PARAMS.source_types
121+
};
118122

119123
export default Video;

stories/index.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { withInfo } from "@storybook/addon-info";
77

88
import Image from "../src/components/Image";
99
import Video from "../src/components/Video";
10+
import Audio from "../src/components/Audio";
1011
import Transformation from "../src/components/Transformation";
1112
import CloudinaryContext from "../src/components/CloudinaryContext";
1213
import cloudinary from "cloudinary-core";
@@ -298,6 +299,55 @@ storiesOf("Video", module)
298299
);
299300
})
300301
);
302+
storiesOf("Audio", module)
303+
.add(
304+
"Simple tag",
305+
withInfo({ text: "Simple tag" })(() => {
306+
return <Audio cloudName="demo" controls publicId="dog" />;
307+
})
308+
)
309+
.add(
310+
"With fallback",
311+
withInfo({ text: "With fallback" })(() => {
312+
return (
313+
<Audio
314+
cloudName="demo"
315+
controls="controls"
316+
publicId="dog"
317+
fallback="Cannot play audio"
318+
/>
319+
);
320+
})
321+
)
322+
.add(
323+
"With inline fallback",
324+
withInfo({ text: "With inline fallback" })(() => {
325+
return (
326+
<Audio cloudName="demo" controls="controls" publicId="dog">
327+
Cannot play <b>audio</b>.
328+
</Audio>
329+
);
330+
})
331+
)
332+
.add(
333+
"With source types",
334+
withInfo({ text: "With source types" })(() => {
335+
return (
336+
<Audio
337+
cloudName="demo"
338+
controls="controls"
339+
publicId="dog"
340+
sourceTypes={["mp3", "wav", "aac"]}
341+
sourceTransformation={{
342+
mp3: { duration: 2 },
343+
wav: { duration: 3 }
344+
}}
345+
>
346+
Cannot play audio.
347+
</Audio>
348+
);
349+
})
350+
);
301351

302352
storiesOf("CloudinaryContext", module)
303353
.add(

test/AudioTest.js

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import React from 'react';
2+
import chai, {expect} from 'chai';
3+
import {shallow, mount} from 'enzyme';
4+
import Audio from '../src/components/Audio';
5+
import Transformation from '../src/components/Transformation';
6+
7+
chai.use(require('chai-string'));
8+
9+
describe('Audio', () => {
10+
it("should include child transformation for a single source type", function () {
11+
let tag = shallow(
12+
<Audio cloudName='demo'
13+
sourceTypes={'wav'}
14+
publicId='dog'
15+
sourceTransformation={{
16+
wav: {duration: 3}
17+
}}>
18+
<Transformation quality='70'/>
19+
</Audio>);
20+
expect(tag.props().src).to.endWith('/q_70/du_3/dog.wav');
21+
});
22+
23+
it('should support startOffset parameter', function () {
24+
let tag = shallow(
25+
<Audio cloudName="demo" sourceTypes={'wav'} publicId="dog">
26+
<Transformation startOffset="auto"/>
27+
</Audio>);
28+
expect(tag.props().src).to.endWith('/so_auto/dog.wav');
29+
tag = shallow(
30+
<Audio cloudName="demo" sourceTypes={'wav'} publicId="dog">
31+
<Transformation startOffset="2"/>
32+
</Audio>);
33+
expect(tag.props().src).to.endWith('/so_2/dog.wav');
34+
tag = shallow(
35+
<Audio cloudName="demo" sourceTypes={'wav'} publicId="dog">
36+
<Transformation startOffset="2.34"/>
37+
</Audio>);
38+
expect(tag.props().src).to.endWith('/so_2.34/dog.wav');
39+
});
40+
41+
it("should include child transformation for multiple source types", function () {
42+
let tag = shallow(
43+
<Audio cloudName='demo'
44+
sourceTypes={['wav', 'mp3']}
45+
publicId='dog'
46+
sourceTransformation={{
47+
wav: {effect: "volume:30"},
48+
mp3: {effect: "volume:45"}
49+
}}>
50+
<Transformation duration="2"/>
51+
</Audio>);
52+
expect(tag.find('[type="audio/wav"]').props().src).to.endWith('/du_2/e_volume:30/dog.wav');
53+
expect(tag.find('[type="audio/mp3"]').props().src).to.endWith('/du_2/e_volume:45/dog.mp3');
54+
});
55+
56+
it('should support inner text', function () {
57+
let tag = shallow(
58+
<Audio cloudName='demo' publicId='dog'>
59+
Your browser does not support the audio tag.
60+
</Audio>
61+
);
62+
expect(tag.type()).to.equal("audio");
63+
});
64+
65+
it('Should support forwarding innerRef to underlying audio element', function () {
66+
let myRef = React.createRef();
67+
68+
let tag = mount(
69+
<Audio
70+
innerRef={myRef}
71+
cloudName='demo'
72+
sourceTypes='ogg'
73+
publicId='dog'
74+
sourceTransformation={{
75+
ogg: {duration: 2}
76+
}}
77+
/>
78+
);
79+
80+
const audio = myRef.current;
81+
82+
expect(tag.find('audio').prop('src')).to.endWith('/du_2/dog.ogg');
83+
expect(audio.src).to.endWith('/du_2/dog.ogg');
84+
['play', 'pause', 'canPlayType', 'addTextTrack'].forEach(func => expect(audio[func]).to.be.a('function'));
85+
});
86+
87+
it('Should not set a poster attribute', function () {
88+
let tag = shallow(
89+
<Audio cloudName='demo' publicId='dog'/>
90+
);
91+
92+
expect(tag.props().poster).to.equal(undefined);
93+
});
94+
95+
it('Should pass camelCase attributes to Audio component', function () {
96+
const tag = shallow(
97+
<Audio playsInline autoPlay cloudName='demo' publicId='dog'/>
98+
);
99+
100+
const {autoPlay, auto_play} = tag.props();
101+
102+
expect(autoPlay).to.equal(true);
103+
104+
expect(auto_play).to.equal(undefined);
105+
});
106+
it('Should pass camelCase attributes to inner audio element', function () {
107+
let tag = mount(
108+
<Audio autoPlay cloudName='demo' publicId='dog'/>
109+
);
110+
111+
const audio = tag.find('audio');
112+
expect(audio.prop('autoPlay')).to.equal(true);
113+
114+
expect(audio.prop('plays_inline')).to.equal(undefined);
115+
expect(audio.prop('auto_play')).to.equal(undefined);
116+
});
117+
it("should generate default source tags", function () {
118+
let tag = shallow(
119+
<Audio cloudName='demo'
120+
publicId='dog'
121+
sourceTransformation={{
122+
aac: {duration: 1},
123+
mp3: {duration: 2},
124+
ogg: {duration: 3},
125+
}}>
126+
<Transformation quality='70'/>
127+
</Audio>);
128+
expect(tag.find('[type="audio/aac"]').props().src).to.endWith('/du_1/dog.aac');
129+
expect(tag.find('[type="audio/mp3"]').props().src).to.endWith('/du_2/dog.mp3');
130+
expect(tag.find('[type="audio/ogg"]').props().src).to.endWith('/du_3/dog.ogg');
131+
});
132+
});

0 commit comments

Comments
 (0)
0