Skip to content

Commit 6769344

Browse files
authored
feat: fetch auto title & description (#882)
* chore: info title & description - promote to root config * feat: auto-fetch video details * feat: auto-fetch video details * feat: auto-fetch video details * feat: auto-fetch video details * test: auto-fetch video details * feat: auto-fetch video details * feat: auto-fetch video details
1 parent ad7a808 commit 6769344

21 files changed

+436
-34
lines changed

docs/es-modules/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ <h3 class="mt-4">Code examples:</h3>
7373
<li><a href="./subtitles-and-captions.html">Subtitles & Captions</a></li>
7474
<li><a href="./transformations.html">Video Transformations</a></li>
7575
<li><a href="./vast-vpaid.html">VAST & VPAID Support</a></li>
76+
<li><a href="./video-details.html">Video Details</a></li>
7677
<li><a href="./visual-search.html">Visual Search</a></li>
7778
<li><a href="./360.html">VR/360 Videos</a></li>
7879
<hr>

docs/es-modules/ui-config.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ <h3 class="mb-4">Display Configurations</h3>
5353
const player = videoPlayer('player', {
5454
cloudName: 'demo',
5555
publicId: 'snow_horses',
56-
info: { title: 'Snow Horses', subtitle: 'A movie about horses' },
56+
title: 'Snow Horses',
57+
description: 'A movie about horses',
5758
hideContextMenu: true,
5859
logoImageUrl: 'https://upload.wikimedia.org/wikipedia/commons/3/38/Facebook_Like_React.png',
5960
logoOnclickUrl: 'https://google.com',

docs/es-modules/video-details.html

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<title>Cloudinary Video Player</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-4">Video Details</h3>
21+
22+
<h4>Auto-fetch Both Title & Description</h4>
23+
<video
24+
id="player1"
25+
class="cld-video-player cld-fluid"
26+
crossorigin="anonymous"
27+
controls
28+
muted
29+
playsinline
30+
autoplay
31+
></video>
32+
33+
<h4 class="mt-4">Auto-fetch Title, Custom Description</h4>
34+
<video
35+
id="player2"
36+
class="cld-video-player cld-fluid"
37+
crossorigin="anonymous"
38+
controls
39+
muted
40+
playsinline
41+
autoplay
42+
></video>
43+
44+
<h4 class="mt-4">Mixed with Legacy Format</h4>
45+
<video
46+
id="player3"
47+
class="cld-video-player cld-fluid"
48+
crossorigin="anonymous"
49+
controls
50+
muted
51+
playsinline
52+
autoplay
53+
></video>
54+
55+
<p class="mt-4">
56+
<a href="https://cloudinary.com/documentation/cloudinary_video_player"
57+
>Full documentation</a
58+
>
59+
</p>
60+
</div>
61+
62+
<script type="module">
63+
import { videoPlayer } from 'cloudinary-video-player';
64+
import 'cloudinary-video-player/cld-video-player.min.css';
65+
66+
// Example 1: Auto-fetch both title and description
67+
const player1 = videoPlayer('player1', {
68+
cloudName: 'demo'
69+
});
70+
71+
player1.source({
72+
publicId: 'snow_horses',
73+
title: true,
74+
description: true
75+
});
76+
77+
// Example 2: Auto-fetch title only, custom description
78+
const player2 = videoPlayer('player2', {
79+
cloudName: 'demo'
80+
});
81+
82+
player2.source({
83+
publicId: 'snow_horses',
84+
title: true,
85+
description: 'Hard-coded description for snow horses video'
86+
});
87+
88+
// Example 3: Mixed with legacy format
89+
const player3 = videoPlayer('player3', {
90+
cloudName: 'demo'
91+
});
92+
93+
player3.source({
94+
publicId: 'snow_horses',
95+
title: true,
96+
info: { subtitle: 'Fallback description from legacy format' }
97+
});
98+
</script>
99+
100+
<!-- Bootstrap -->
101+
<link
102+
href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
103+
rel="stylesheet"
104+
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"
105+
crossorigin="anonymous"
106+
/>
107+
</body>
108+
</html>

docs/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ <h3 class="mt-4">Some code examples:</h3>
7878
<li><a href="./subtitles-and-captions.html">Subtitles & Captions</a></li>
7979
<li><a href="./transformations.html">Video Transformations</a></li>
8080
<li><a href="./vast-vpaid.html">VAST & VPAID Support</a></li>
81+
<li><a href="./video-details.html">Video Details</a></li>
8182
<li><a href="./visual-search.html">Visual Search</a></li>
8283
<li><a href="./360.html">VR/360 Videos</a></li>
8384
<hr>

docs/ui-config.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@
3030
player = cloudinary.videoPlayer('player', {
3131
cloud_name: 'demo',
3232
publicId: 'snow_horses',
33-
info: { title: 'Snow Horses', subtitle: 'A movie about horses' },
33+
title: 'Snow Horses',
34+
description: 'A movie about horses',
3435
hideContextMenu: true,
3536
logoImageUrl: 'https://upload.wikimedia.org/wikipedia/commons/3/38/Facebook_Like_React.png',
3637
logoOnclickUrl: 'https://google.com',

docs/video-details.html

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<title>Cloudinary Video Player</title>
6+
<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">
7+
8+
<!-- Bootstrap -->
9+
<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">
10+
11+
<!-- highlight.js -->
12+
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/solarized-light.min.css">
13+
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js"></script>
14+
<script>hljs.initHighlightingOnLoad();</script>
15+
16+
<script type="text/javascript" src="./scripts.js"></script>
17+
18+
<script type="text/javascript">
19+
window.addEventListener('load', function(){
20+
21+
// Example 1: Auto-fetch both title and description
22+
const player1 = cloudinary.videoPlayer('player1', {
23+
cloudName: 'demo'
24+
});
25+
26+
player1.source({
27+
publicId: 'snow_horses',
28+
title: true,
29+
description: true
30+
});
31+
32+
// Example 2: Auto-fetch title only, custom description
33+
const player2 = cloudinary.videoPlayer('player2', {
34+
cloudName: 'demo'
35+
});
36+
37+
player2.source({
38+
publicId: 'snow_horses',
39+
title: true,
40+
description: 'Hard-coded description for snow horses video'
41+
});
42+
43+
// Example 3: Mixed with legacy format
44+
const player3 = cloudinary.videoPlayer('player3', {
45+
cloudName: 'demo'
46+
});
47+
48+
player3.source({
49+
publicId: 'snow_horses',
50+
title: true,
51+
info: { subtitle: 'Fallback description from legacy format' }
52+
});
53+
54+
}, false);
55+
</script>
56+
57+
</head>
58+
<body>
59+
<div class="container p-4 col-12 col-md-9 col-xl-6">
60+
<nav class="nav mb-2">
61+
<a href="./index.html">&#60;&#60; Back to examples index</a>
62+
</nav>
63+
<h1>Cloudinary Video Player</h1>
64+
<h3 class="mb-4">Video Details</h3>
65+
66+
<h4>Auto-fetch Both Title & Description</h4>
67+
<video
68+
id="player1"
69+
playsinline
70+
controls
71+
muted
72+
autoplay
73+
class="cld-video-player"
74+
width="500"
75+
></video>
76+
77+
<h4 class="mt-4">Auto-fetch Title, Custom Description</h4>
78+
<video
79+
id="player2"
80+
playsinline
81+
controls
82+
muted
83+
autoplay
84+
class="cld-video-player"
85+
width="500"
86+
></video>
87+
88+
<h4 class="mt-4">Mixed with Legacy Format</h4>
89+
<video
90+
id="player3"
91+
playsinline
92+
controls
93+
muted
94+
autoplay
95+
class="cld-video-player"
96+
width="500"
97+
></video>
98+
99+
<p class="mt-4">
100+
<a href="https://cloudinary.com/documentation/cloudinary_video_player">Full documentation</a>
101+
</p>
102+
103+
<h3 class="mt-4">Example Code:</h3>
104+
<pre>
105+
<code class="language-html">
106+
&lt;video
107+
id="player"
108+
controls
109+
muted
110+
autoplay
111+
class="cld-video-player"
112+
width="500"
113+
&gt;&lt;/video&gt;
114+
</code>
115+
<code class="language-javascript">
116+
// Auto-fetch both title and description
117+
const player = cloudinary.videoPlayer('player', { cloudName: 'demo' });
118+
119+
player.source({
120+
publicId: 'my-video',
121+
title: true, // Auto-fetch from metadata endpoint
122+
description: true // Auto-fetch from metadata endpoint
123+
});
124+
125+
// Mixed approach: auto-fetch title, custom description
126+
player.source({
127+
publicId: 'my-video',
128+
title: true,
129+
description: 'Custom description text'
130+
});
131+
132+
// Backward compatible with legacy format
133+
player.source({
134+
publicId: 'my-video',
135+
title: true,
136+
info: { subtitle: 'Fallback if auto-fetch fails' }
137+
});
138+
</code>
139+
</pre>
140+
</div>
141+
142+
</body>
143+
</html>

src/components/title-bar/title-bar.js

Lines changed: 52 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import videojs from 'video.js';
22
import 'assets/styles/components/title-bar.scss';
33
import componentUtils from '../component-utils';
4+
import { getCloudinaryUrlPrefix } from 'plugins/cloudinary/common';
45

56
// support VJS5 & VJS6 at the same time
67
const dom = videojs.dom || videojs;
@@ -18,34 +19,72 @@ class TitleBar extends Component {
1819
setItem(source) {
1920
if (!source) {
2021
this.setTitle('');
21-
this.setSubtitle('');
22-
22+
this.setDescription('');
2323
return;
2424
}
2525

2626
const info = source.info();
27-
2827
this.setTitle(info.title);
29-
this.setSubtitle(info.subtitle);
28+
this.setDescription(info.subtitle);
29+
30+
// auto-fetch title/description if `true`
31+
const shouldFetchTitle = source.title && source.title() === true;
32+
const shouldFetchDescription = source.description && source.description() === true;
33+
34+
if (shouldFetchTitle || shouldFetchDescription) {
35+
this.fetchAutoMetadata(source, shouldFetchTitle, shouldFetchDescription);
36+
}
37+
}
38+
39+
fetchAutoMetadata(source, fetchTitle, fetchDescription) {
40+
if (source.isRawUrl) return;
41+
42+
const config = source.cloudinaryConfig();
43+
const publicId = source.publicId();
44+
45+
if (!config?.cloud_name || !publicId) return;
46+
47+
const urlPrefix = getCloudinaryUrlPrefix(config);
48+
const deliveryType = source.getInitOptions().type || 'upload';
49+
const metadataUrl = `${urlPrefix}/_applet_/video_service/video_metadata/${deliveryType}/${publicId}.json`;
50+
51+
fetch(metadataUrl)
52+
.then(response => {
53+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
54+
return response.json();
55+
})
56+
.then(metadata => {
57+
if (fetchTitle && metadata.title) {
58+
this.setTitle(metadata.title);
59+
}
60+
if (fetchDescription && metadata.description) {
61+
this.setDescription(metadata.description);
62+
}
63+
})
64+
.catch(error => {
65+
console.warn(`Failed to fetch metadata for ${publicId}:`, error);
66+
});
3067
}
3168

3269
setTitle(text) {
33-
componentUtils.setText(this.titleEl, text);
70+
const displayText = typeof text === 'string' ? text : '';
71+
componentUtils.setText(this.titleEl, displayText);
3472
this.refresh();
35-
return text;
73+
return displayText;
3674
}
3775

38-
setSubtitle(text) {
39-
componentUtils.setText(this.subtitleEl, text);
76+
setDescription(text) {
77+
const displayText = typeof text === 'string' ? text : '';
78+
componentUtils.setText(this.descriptionEl, displayText);
4079
this.refresh();
41-
return text;
80+
return displayText;
4281
}
4382

4483
refresh() {
4584
const titleValue = () => this.titleEl.innerText;
46-
const subtitleValue = () => this.subtitleEl.innerText;
85+
const descriptionValue = () => this.descriptionEl.innerText;
4786

48-
if (!titleValue() && !subtitleValue()) {
87+
if (!titleValue() && !descriptionValue()) {
4988
this.hide();
5089
return;
5190
}
@@ -58,7 +97,7 @@ class TitleBar extends Component {
5897
className: 'vjs-title-bar-title'
5998
});
6099

61-
this.subtitleEl = dom.createEl('div', {
100+
this.descriptionEl = dom.createEl('div', {
62101
className: 'vjs-title-bar-subtitle'
63102
});
64103

@@ -68,7 +107,7 @@ class TitleBar extends Component {
68107
});
69108

70109
el.appendChild(this.titleEl);
71-
el.appendChild(this.subtitleEl);
110+
el.appendChild(this.descriptionEl);
72111

73112
return el;
74113
}

0 commit comments

Comments
 (0)