Skip to content

Commit 17246c5

Browse files
authored
feat: lazy-loading player (#1017)
* feat: lazy-loading player * Load player on-click * Load player on-scroll * chore: examples * chore: improve bundle size * chore: e2e * chore: e2e * chore: e2e * fix: getCloudinaryConfigFromOptions * chore: naming * chore: examples * chore: lazy posters
1 parent 23d1cc0 commit 17246c5

32 files changed

+776
-189
lines changed

.config/vitest.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export default defineConfig({
55
test: {
66
globals: true,
77
environment: 'jsdom',
8+
setupFiles: ['test/unit/setup.js'],
89
include: ['test/unit/**/*.test.js'],
910
exclude: [
1011
'test/*.test.js',

docs/es-modules/index.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,14 +64,15 @@ <h3 class="mt-4">Code examples:</h3>
6464
<li><a href="./force-hls-subtitles.html">Force HLS Subtitles</a></li>
6565
<li><a href="./hdr.html">HDR</a></li>
6666
<li><a href="./interaction-area.html">Interaction Area</a></li>
67+
<li><a href="./lazy-player.html">Lazy player</a></li>
6768
<li><a href="./multiple-players.html">Multiple Players</a></li>
6869
<li><a href="./playlist.html">Playlist</a></li>
6970
<li><a href="./playlist-by-tag.html">Playlist by Tag</a></li>
7071
<li><a href="./poster.html">Poster Options</a></li>
7172
<li><a href="./profiles.html">Profiles</a></li>
7273
<li><a href="./raw-url.html">Raw URL</a></li>
7374
<li><a href="./recommendations.html">Recommendations</a></li>
74-
<li><a href="./schedule.html">Schedule (weekly time slots)</a></li>
75+
<li><a href="./schedule.html">Schedule</a></li>
7576
<li><a href="./seek-thumbs.html">Seek Thumbnails</a></li>
7677
<li><a href="./share-plugin.html">Share &amp; Download</a></li>
7778
<li><a href="./shoppable.html">Shoppable Videos</a></li>

docs/es-modules/lazy-player.html

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<title>Cloudinary Video Player - Lazy player (ESM)</title>
6+
<link
7+
href="https://res.cloudinary.com/cloudinary-marketing/image/upload/f_auto,q_auto/c_scale,w_32,e_hue:290/creative_staging/cloudinary_internal/Website/Brand%20Updates/Favicon/cloudinary_web_favicon_192x192.png"
8+
rel="icon"
9+
type="image/png"
10+
/>
11+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
12+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
13+
</head>
14+
<body>
15+
<div class="container p-4 col-12 col-md-9 col-xl-6">
16+
<nav class="nav mb-2">
17+
<a href="/index.html">&#60;&#60; Back to examples index</a>
18+
</nav>
19+
<h1>Cloudinary Video Player</h1>
20+
<h3 class="mb-3">Lazy player (with poster URL)</h3>
21+
<p class="text-muted small mb-2">Uses an explicit <code>poster</code> URL. Custom accent color and logo configured.</p>
22+
23+
<video
24+
id="player"
25+
playsinline
26+
controls
27+
muted
28+
class="cld-video-player cld-fluid"
29+
width="500"
30+
></video>
31+
32+
<button type="button" id="btn-load" class="btn btn-sm btn-outline-primary mt-2 mb-3">Load Player</button>
33+
<p class="text-muted small mb-3">
34+
Click the player or the button above to initialize. The button calls <code>player.loadPlayer()</code>.
35+
</p>
36+
37+
<h3 class="mt-4">Scroll to load (auto-built poster)</h3>
38+
<p class="text-muted small mb-2">No <code>poster</code> URL - built automatically from <code>cloudName</code> and <code>publicId</code>. Custom colors configured.</p>
39+
<p class="text-muted small mb-3">
40+
Scroll down - the player loads automatically when it enters the viewport.
41+
</p>
42+
43+
<div style="height: 80vh; display: flex; color: #aaa; font-size: 2rem;">
44+
&darr; &darr; &darr; &darr; &darr; &darr; &darr;
45+
</div>
46+
47+
<video
48+
id="player-scroll"
49+
playsinline
50+
controls
51+
muted
52+
loop
53+
class="cld-video-player cld-fluid"
54+
width="500"
55+
></video>
56+
57+
<h3 class="mt-4">Example code:</h3>
58+
<pre><code class="language-javascript">
59+
import { player as createPlayer } from 'cloudinary-video-player/lazy';
60+
import 'cloudinary-video-player/cld-video-player.min.css';
61+
62+
// With explicit poster URL
63+
const player = await createPlayer('my-video', {
64+
cloudName: 'demo',
65+
publicId: 'sea_turtle',
66+
poster: 'https://res.cloudinary.com/demo/video/upload/so_0,f_auto,q_auto/sea_turtle.jpg',
67+
lazy: true,
68+
colors: { accent: '#ff4081' }
69+
});
70+
// await player.loadPlayer();
71+
72+
// Auto-built poster from cloudName + publicId
73+
createPlayer('my-video', {
74+
cloudName: 'demo',
75+
publicId: 'sea_turtle',
76+
lazy: { loadOnScroll: true },
77+
colors: { base: '#0d1b2a', accent: '#00b4d8' }
78+
});
79+
</code></pre>
80+
</div>
81+
82+
<script type="module">
83+
import { player as createPlayer } from 'cloudinary-video-player/lazy';
84+
import 'cloudinary-video-player/cld-video-player.min.css';
85+
86+
(async () => {
87+
const player = await createPlayer('player', {
88+
cloudName: 'demo',
89+
publicId: 'sea_turtle',
90+
poster: 'https://res.cloudinary.com/demo/video/upload/so_0,f_auto,q_auto/sea_turtle.jpg',
91+
lazy: true,
92+
colors: { accent: '#ff4081' },
93+
logoImageUrl: 'https://res.cloudinary.com/demo/image/upload/c_scale,w_100/cloudinary_logo.png',
94+
logoOnclickUrl: 'https://cloudinary.com'
95+
});
96+
97+
document.getElementById('btn-load').addEventListener('click', () => {
98+
if (player && typeof player.loadPlayer === 'function') {
99+
player.loadPlayer();
100+
}
101+
});
102+
103+
createPlayer('player-scroll', {
104+
cloudName: 'demo',
105+
publicId: 'sea_turtle',
106+
lazy: { loadOnScroll: true },
107+
colors: { base: '#0d1b2a', accent: '#00b4d8' }
108+
});
109+
})();
110+
</script>
111+
112+
<link
113+
href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
114+
rel="stylesheet"
115+
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"
116+
crossorigin="anonymous"
117+
/>
118+
</body>
119+
</html>

docs/es-modules/schedule.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,10 @@ <h4 class="mb-2" id="schedule-label">Schedule</h4>
4444

4545
<h3 class="mt-4">Example Code (ESM):</h3>
4646
<pre><code class="language-javascript">
47-
import { videoPlayer } from 'cloudinary-video-player';
47+
import { player } from 'cloudinary-video-player';
4848
import 'cloudinary-video-player/cld-video-player.min.css';
4949

50-
videoPlayer('player', {
50+
player('player', {
5151
cloudName: 'demo',
5252
publicId: 'sea_turtle',
5353
schedule: {

docs/index.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ <h3 class="mt-4">Some code examples:</h3>
6868
<li><a href="./force-hls-subtitles-ios.html">Force HLS Subtitles</a></li>
6969
<li><a href="./hdr.html">HDR</a></li>
7070
<li><a href="./interaction-area.html">Interaction Area</a></li>
71+
<li><a href="./lazy-player.html">Lazy player</a></li>
7172
<li><a href="./live-streaming.html">Live Streaming</a></li>
7273
<li><a href="./multiple-players.html">Multiple Players</a></li>
7374
<li><a href="./playlist.html">Playlist</a></li>
@@ -76,7 +77,7 @@ <h3 class="mt-4">Some code examples:</h3>
7677
<li><a href="./profiles.html">Profiles</a></li>
7778
<li><a href="./raw-url.html">Raw URL</a></li>
7879
<li><a href="./recommendations.html">Recommendations</a></li>
79-
<li><a href="./schedule.html">Schedule (weekly time slots)</a></li>
80+
<li><a href="./schedule.html">Schedule</a></li>
8081
<li><a href="./seek-thumbs.html">Seek Thumbnails</a></li>
8182
<li><a href="./share-plugin.html">Share &amp; Download</a></li>
8283
<li><a href="./shoppable.html">Shoppable Videos</a></li>

docs/lazy-player.html

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1" />
6+
<title>Cloudinary Video Player - Lazy player</title>
7+
<link href="https://res.cloudinary.com/cloudinary-marketing/image/upload/f_auto,q_auto/c_scale,w_32/v1597183771/creative_staging/cloudinary_internal/Website/Brand%20Updates/Favicon/cloudinary_web_favicon_192x192.png" rel="icon" type="image/png">
8+
9+
<!-- Bootstrap -->
10+
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
11+
12+
<!-- highlight.js -->
13+
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/solarized-light.min.css">
14+
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js"></script>
15+
<script>hljs.initHighlightingOnLoad();</script>
16+
17+
<script type="text/javascript" src="./scripts.js?lazy"></script>
18+
19+
<script type="text/javascript">
20+
(function () {
21+
var player = null;
22+
23+
async function initLazyPlayer() {
24+
player = await cloudinary.player('player', {
25+
cloudName: 'demo',
26+
publicId: 'sea_turtle',
27+
poster: 'https://res.cloudinary.com/demo/video/upload/so_0,f_auto,q_auto/sea_turtle.jpg',
28+
lazy: true,
29+
colors: { accent: '#ff4081' },
30+
logoImageUrl: 'https://res.cloudinary.com/demo/image/upload/c_scale,w_100/cloudinary_logo.png',
31+
logoOnclickUrl: 'https://cloudinary.com'
32+
});
33+
34+
cloudinary.player('player-scroll', {
35+
cloudName: 'demo',
36+
publicId: 'sea_turtle',
37+
lazy: { loadOnScroll: true },
38+
colors: { accent: '#ff4081' }
39+
});
40+
}
41+
42+
window.addEventListener('load', function () {
43+
initLazyPlayer();
44+
document.getElementById('btn-load').addEventListener('click', function () {
45+
if (player && typeof player.loadPlayer === 'function') {
46+
player.loadPlayer();
47+
}
48+
});
49+
});
50+
})();
51+
</script>
52+
</head>
53+
<body>
54+
<div class="container p-4 col-12 col-md-9 col-xl-6">
55+
<nav class="nav mb-2">
56+
<a href="./index.html">&#60;&#60; Back to examples index</a>
57+
</nav>
58+
<h1>Cloudinary Video Player</h1>
59+
<h3 class="mb-3">Lazy player (with poster URL)</h3>
60+
<p class="text-muted small mb-2">Uses an explicit <code>poster</code> URL. Custom accent color and logo configured.</p>
61+
62+
<video
63+
id="player"
64+
playsinline
65+
controls
66+
muted
67+
class="cld-video-player cld-fluid"
68+
width="500">
69+
</video>
70+
71+
<button type="button" id="btn-load" class="btn btn-sm btn-outline-primary mt-2 mb-3">Load Player</button>
72+
<p class="text-muted small mb-3">
73+
Click the player or the button above to initialize. The button calls <code>player.loadPlayer()</code>.
74+
</p>
75+
76+
<h3 class="mt-4">Scroll to load (auto-built poster)</h3>
77+
<p class="text-muted small mb-2">No <code>poster</code> URL - built automatically from <code>cloudName</code> and <code>publicId</code>. Custom colors configured.</p>
78+
<p class="text-muted small mb-3">
79+
Scroll down - the player loads automatically when it enters the viewport.
80+
</p>
81+
82+
<div style="height: 80vh; display: flex; color: #aaa; font-size: 2rem;">
83+
&darr; &darr; &darr; &darr; &darr; &darr; &darr;
84+
</div>
85+
86+
<video
87+
id="player-scroll"
88+
playsinline
89+
controls
90+
muted
91+
loop
92+
class="cld-video-player cld-fluid"
93+
width="500">
94+
</video>
95+
96+
<h3 class="mt-4">Example code:</h3>
97+
<pre>
98+
<code class="language-javascript">
99+
// With explicit poster URL
100+
const player = await cloudinary.player('my-video', {
101+
cloudName: 'demo',
102+
publicId: 'sea_turtle',
103+
poster: 'https://res.cloudinary.com/demo/video/upload/so_0,f_auto,q_auto/sea_turtle.jpg',
104+
lazy: true,
105+
colors: { accent: '#ff4081' }
106+
});
107+
// await player.loadPlayer();
108+
109+
// Auto-built poster from cloudName + publicId
110+
cloudinary.player('my-video', {
111+
cloudName: 'demo',
112+
publicId: 'sea_turtle',
113+
lazy: { loadOnScroll: true },
114+
colors: { base: '#0d1b2a', accent: '#00b4d8' }
115+
});
116+
</code>
117+
</pre>
118+
</div>
119+
</body>
120+
</html>

docs/schedule.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ <h4 class="mb-2" id="schedule-label">Schedule</h4>
9797

9898
<h3 class="mt-4">Example Code:</h3>
9999
<pre><code class="language-javascript">
100-
cloudinary.videoPlayer('player', {
100+
cloudinary.player('player', {
101101
cloudName: 'demo',
102102
publicId: 'sea_turtle',
103103
schedule: {

src/assets/styles/main.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,11 @@ $icon-font-path: '../icon-font' !default;
7676
text-shadow: none;
7777
}
7878

79+
&.cld-lazy-player {
80+
width: auto;
81+
height: auto;
82+
}
83+
7984
&.cld-fluid {
8085
width: 100%;
8186
max-width: 100%;

src/config/configSchema.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -578,6 +578,22 @@
578578
},
579579
"default": {}
580580
},
581+
"lazy": {
582+
"oneOf": [
583+
{
584+
"type": "boolean",
585+
"default": false
586+
},
587+
{
588+
"type": "object",
589+
"properties": {
590+
"loadOnScroll": { "type": "boolean", "default": false }
591+
},
592+
"additionalProperties": false
593+
}
594+
],
595+
"default": false
596+
},
581597
"videoSources": {
582598
"type": "array",
583599
"items": {
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import pick from 'lodash/pick';
2+
import { CLOUDINARY_CONFIG_PARAM } from '../video-player.const';
3+
import { convertKeysToSnakeCase } from './object';
4+
5+
export const getCloudinaryConfigFromOptions = (options) => {
6+
if (options.cloudinaryConfig) {
7+
return options.cloudinaryConfig;
8+
}
9+
10+
const snakeCaseCloudinaryConfig = pick(convertKeysToSnakeCase(options), CLOUDINARY_CONFIG_PARAM);
11+
return Object.assign({}, snakeCaseCloudinaryConfig);
12+
};

0 commit comments

Comments
 (0)