Skip to content

Commit 87e5bd8

Browse files
Merge pull request #191 from cloudinary/SNI-6834-vue-advanced-video
Sni 6834 add vue advanced video
2 parents ca52699 + c72d29d commit 87e5bd8

File tree

4 files changed

+273
-11
lines changed

4 files changed

+273
-11
lines changed

packages/vue/sampleApp/App.vue

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,23 @@
11
<template>
22
<div>
3+
<h1>AdvancedImage</h1>
34
<AdvancedImage :cldImg="cldImg" :plugins="plugins" />
5+
<h1>AdvancedVideo</h1>
6+
<AdvancedVideo :cldVid="cldVid" controls loop width="600" />
47
</div>
58
</template>
69

710
<script lang="ts">
811
import { defineComponent } from "vue";
9-
import { AdvancedImage, responsive } from "../dist";
12+
import { AdvancedImage, AdvancedVideo, responsive } from "../dist";
1013
import { CloudinaryImage } from "@cloudinary/url-gen/assets/CloudinaryImage";
14+
import { CloudinaryVideo } from "@cloudinary/url-gen/assets/CloudinaryVideo";
1115
1216
export default defineComponent({
1317
name: "App",
1418
components: {
1519
AdvancedImage,
20+
AdvancedVideo,
1621
},
1722
setup(props) {
1823
const cldImg = new CloudinaryImage(
@@ -21,10 +26,17 @@ export default defineComponent({
2126
{ analytics: false }
2227
);
2328
29+
const cldVid = new CloudinaryVideo(
30+
"docs/walking_talking",
31+
{ cloudName: "demo" },
32+
{ analytics: false }
33+
);
34+
2435
const plugins = [responsive({ steps: 100 })];
2536
2637
return {
2738
cldImg,
39+
cldVid,
2840
plugins,
2941
};
3042
},
Lines changed: 66 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,72 @@
11
<template>
2-
<video :src="url" />
2+
<video ref="videoRef" />
33
</template>
44

5-
<script lang="ts">
6-
import { defineComponent } from "vue";
5+
<script setup lang="ts">
6+
/**
7+
* @memberOf CloudinaryVueSDK
8+
* @type {Component}
9+
* @description The Cloudinary video component.
10+
* @prop {CloudinaryVideo} cldVid Generated by @cloudinary/url-gen
11+
* @prop {Plugins} plugins Advanced video component plugins accessibility(), responsive(), lazyload(), placeholder()
12+
*/
713
8-
export default defineComponent({
9-
name: "AdvancedVideo",
10-
props: {
11-
url: String,
12-
},
14+
import { ref, onMounted, onUpdated, onUnmounted } from "vue";
15+
import { CloudinaryVideo } from "@cloudinary/url-gen/assets/CloudinaryVideo";
16+
import {
17+
HtmlVideoLayer,
18+
Plugins,
19+
VideoSources,
20+
cancelCurrentlyRunningPlugins,
21+
} from "@cloudinary/html";
22+
23+
interface VideoProps {
24+
cldVid: CloudinaryVideo;
25+
plugins?: Plugins;
26+
sources?: VideoSources;
27+
28+
[x: string]: any;
29+
}
30+
31+
// Disabled linting due to [@vue/compiler-sfc] `defineProps` is a compiler macro and no longer needs to be imported.
32+
// eslint-disable-next-line no-undef
33+
const props = defineProps<VideoProps>();
34+
35+
const videoRef = ref(null);
36+
let htmlLayerInstance;
37+
38+
/**
39+
* On mount, creates a new HTMLLayer instance and initializes with ref to img element,
40+
* user generated cloudinaryVideo and the plugins to be used.
41+
*/
42+
onMounted(() => {
43+
htmlLayerInstance = new HtmlVideoLayer(
44+
videoRef.value,
45+
props.cldVid,
46+
props.sources,
47+
props.plugins
48+
);
49+
});
50+
51+
/**
52+
* On update, we cancel running plugins and update image instance with the state of user
53+
* cloudinaryVideo and the state of plugins.
54+
*/
55+
onUpdated(() => {
56+
cancelCurrentlyRunningPlugins(htmlLayerInstance.htmlPluginState);
57+
// call html layer to update the dom again with plugins and reset toBeCanceled
58+
htmlLayerInstance.update(
59+
props.cldVid,
60+
props.sources,
61+
props.plugins
62+
);
63+
});
64+
65+
/**
66+
* On unmount, we cancel the currently running plugins.
67+
*/
68+
onUnmounted(() => {
69+
// Safely cancel running events on unmount.
70+
cancelCurrentlyRunningPlugins(htmlLayerInstance.htmlPluginState);
1371
});
1472
</script>

packages/vue/src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ import {
99
} from "@cloudinary/html";
1010

1111
import AdvancedImage from "./components/AdvancedImage.vue";
12-
// import AdvancedVideo from "./components/AdvancedVideo.vue";
12+
import AdvancedVideo from "./components/AdvancedVideo.vue";
1313

1414
export {
1515
placeholder,
1616
accessibility,
1717
lazyload,
1818
responsive,
1919
AdvancedImage,
20-
// AdvancedVideo,
20+
AdvancedVideo,
2121
};
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
import { mount } from "@vue/test-utils";
2+
import { CloudinaryVideo } from "@cloudinary/url-gen";
3+
import { auto, vp9 } from "@cloudinary/url-gen/qualifiers/videoCodec";
4+
import { videoCodec } from "@cloudinary/url-gen/actions/transcode";
5+
import { AdvancedVideo } from "../../src";
6+
import { waitTicks } from "../unit/utils";
7+
8+
const cloudinaryVideo = new CloudinaryVideo(
9+
"sample",
10+
{ cloudName: "demo" },
11+
{ analytics: false }
12+
);
13+
14+
const cloudinaryVideoWithAnalytics = new CloudinaryVideo(
15+
"sample",
16+
{ cloudName: "demo" },
17+
{ analytics: true }
18+
);
19+
20+
describe("AdvancedVideo", () => {
21+
it("AdvancedVideo should be truthy", () => {
22+
expect(AdvancedVideo).toBeTruthy();
23+
});
24+
25+
it("should render video with default sources", async function () {
26+
const component = mount(AdvancedVideo, {
27+
props: { cldVid: cloudinaryVideo },
28+
});
29+
// wait because @cloudinary/html takes time to update the img element
30+
await waitTicks(1);
31+
32+
expect(component.html()).toContain(
33+
"<video>\n" +
34+
' <source src="https://res.cloudinary.com/demo/video/upload/sample.webm" type="video/webm">\n' +
35+
' <source src="https://res.cloudinary.com/demo/video/upload/sample.mp4" type="video/mp4">\n' +
36+
' <source src="https://res.cloudinary.com/demo/video/upload/sample.ogv" type="video/ogg">\n</video>'
37+
);
38+
});
39+
40+
it("should generate url sources with correct placement of extension and url analytics", async function () {
41+
const component = mount(AdvancedVideo, {
42+
props: { cldVid: cloudinaryVideoWithAnalytics },
43+
});
44+
await waitTicks(1);
45+
46+
expect(component.html()).toContain(
47+
"https://res.cloudinary.com/demo/video/upload/sample.webm?_a="
48+
);
49+
expect(component.html()).toContain(
50+
"https://res.cloudinary.com/demo/video/upload/sample.ogv?_a="
51+
);
52+
expect(component.html()).toContain(
53+
"https://res.cloudinary.com/demo/video/upload/sample.mp4?_a="
54+
);
55+
});
56+
57+
it("should render video with input sources", async function () {
58+
const sources = [
59+
{
60+
type: "mp4",
61+
codecs: ["vp8", "vorbis"],
62+
transcode: videoCodec(auto()),
63+
},
64+
{
65+
type: "webm",
66+
codecs: ["avc1.4D401E", "mp4a.40.2"],
67+
transcode: videoCodec(vp9()),
68+
},
69+
];
70+
71+
const component = mount(AdvancedVideo, {
72+
props: { cldVid: cloudinaryVideo, sources },
73+
});
74+
await waitTicks(1);
75+
76+
expect(component.html()).toContain(
77+
"<video>\n" +
78+
' <source src="https://res.cloudinary.com/demo/video/upload/vc_auto/sample.mp4" type="video/mp4; codecs=vp8, vorbis">\n' +
79+
' <source src="https://res.cloudinary.com/demo/video/upload/vc_vp9/sample.webm" type="video/webm; codecs=avc1.4D401E, mp4a.40.2">\n</video>'
80+
);
81+
});
82+
83+
it("should pass video attributes", async function () {
84+
const component = mount(AdvancedVideo, {
85+
props: {
86+
cldVid: cloudinaryVideo,
87+
controls: true,
88+
autoplay: true,
89+
playsInline: false,
90+
loop: true,
91+
// muted: true,
92+
},
93+
});
94+
await waitTicks(1);
95+
96+
expect(component.html()).toContain('loop=""');
97+
expect(component.html()).not.toContain("playsinline");
98+
expect(component.html()).toContain('autoplay=""');
99+
expect(component.html()).toContain('controls=""');
100+
// TODO: There are some issues with muted attribute in Vue
101+
// expect(component.html()).toContain('muted=""');
102+
});
103+
104+
it("should contain poster", async function () {
105+
const component = mount(AdvancedVideo, {
106+
props: { cldVid: cloudinaryVideo, poster: "www.example.com" },
107+
});
108+
await waitTicks(1);
109+
110+
expect(component.html()).toContain('poster="www.example.com"');
111+
});
112+
113+
it("should emit play event", async function () {
114+
const mockCallback = jest.fn();
115+
116+
const component = mount(AdvancedVideo, {
117+
props: { cldVid: cloudinaryVideo, play: mockCallback },
118+
});
119+
await waitTicks(1);
120+
121+
component.find("video").trigger("play");
122+
await waitTicks(1);
123+
124+
expect(component.emitted().play).toBeTruthy();
125+
// TODO: This assertion should be enabled
126+
// expect(mockCallback).toHaveBeenCalledTimes(1);
127+
});
128+
129+
it("should emit loadStart event", async function () {
130+
const mockCallback = jest.fn();
131+
132+
const component = mount(AdvancedVideo, {
133+
props: { cldVid: cloudinaryVideo, loadStart: mockCallback },
134+
});
135+
await waitTicks(1);
136+
137+
component.find("video").trigger("loadstart");
138+
await waitTicks(1);
139+
140+
expect(component.emitted().loadstart).toBeTruthy();
141+
// TODO: This assertion should be enabled
142+
// expect(mockCallback).toHaveBeenCalledTimes(1);
143+
});
144+
145+
it("should emit ended event", async function () {
146+
const mockCallback = jest.fn();
147+
148+
const component = mount(AdvancedVideo, {
149+
props: { cldVid: cloudinaryVideo, ended: mockCallback },
150+
});
151+
await waitTicks(1);
152+
153+
component.find("video").trigger("ended");
154+
await waitTicks(1);
155+
156+
expect(component.emitted().ended).toBeTruthy();
157+
// TODO: This assertion should be enabled
158+
// expect(mockCallback).toHaveBeenCalledTimes(1);
159+
});
160+
161+
it("should emit error event", async function () {
162+
const mockCallback = jest.fn();
163+
164+
const component = mount(AdvancedVideo, {
165+
props: { cldVid: cloudinaryVideo, error: mockCallback },
166+
});
167+
await waitTicks(1);
168+
169+
component.find("video").trigger("error");
170+
await waitTicks(1);
171+
172+
expect(component.emitted().error).toBeTruthy();
173+
// TODO: This assertion should be enabled
174+
// expect(mockCallback).toHaveBeenCalledTimes(1);
175+
});
176+
177+
it("should emit playing event", async function () {
178+
const mockCallback = jest.fn();
179+
180+
const component = mount(AdvancedVideo, {
181+
props: { cldVid: cloudinaryVideo, playing: mockCallback },
182+
});
183+
await waitTicks(1);
184+
185+
component.find("video").trigger("playing");
186+
await waitTicks(1);
187+
188+
expect(component.emitted().playing).toBeTruthy();
189+
// TODO: This assertion should be enabled
190+
// expect(mockCallback).toHaveBeenCalledTimes(1);
191+
});
192+
});

0 commit comments

Comments
 (0)