Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/es-modules/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ <h3 class="mt-4">Code examples:</h3>
<li><a href="./profiles.html">Profiles</a></li>
<li><a href="./raw-url.html">Raw URL</a></li>
<li><a href="./recommendations.html">Recommendations</a></li>
<li><a href="./schedule.html">Schedule (weekly time slots)</a></li>
<li><a href="./seek-thumbs.html">Seek Thumbnails</a></li>
<li><a href="./share-plugin.html">Share &amp; Download</a></li>
<li><a href="./shoppable.html">Shoppable Videos</a></li>
Expand Down
125 changes: 125 additions & 0 deletions docs/es-modules/schedule.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Cloudinary Video Player - Schedule (ESM)</title>
<link
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"
rel="icon"
type="image/png"
/>
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
<div class="container p-4 col-12 col-md-9 col-xl-6">
<nav class="nav mb-2">
<a href="/index.html">&#60;&#60; Back to examples index</a>
</nav>
<h1>Cloudinary Video Player</h1>
<h3 class="mb-4">Schedule (Weekly Time Slots) - ESM</h3>

<p class="mb-4">
Video plays only during configured time slots. Outside schedule, a poster image is shown.
Call <code>loadPlayer()</code> on the stub to load the player on demand (e.g. on click).
</p>

<div class="mb-3">
<label class="mr-2">Mode:</label>
<button type="button" id="btn-image" class="btn btn-sm btn-outline-primary mr-1">Show poster (image)</button>
<button type="button" id="btn-player" class="btn btn-sm btn-outline-primary">Show player</button>
</div>

<h4 class="mb-2" id="schedule-label">Schedule</h4>
<div id="player-container">
<video
id="player"
playsinline
controls
muted
class="cld-video-player cld-fluid"
width="500"
></video>
</div>

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

videoPlayer('player', {
cloudName: 'demo',
publicId: 'sea_turtle',
schedule: {
weekly: [
{ day: 'monday', start: '09:00', duration: 8 },
{ day: 'tuesday', start: '09:00', duration: 8 }
]
}
});
</code></pre>
</div>

<script type="module">
import { player } from 'cloudinary-video-player/lazy';
import 'cloudinary-video-player/cld-video-player.min.css';

const DAY_NAMES = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'];
let currentPlayer = null;

function getScheduleForImageMode() {
const now = new Date();
const today = now.getDay();
const slots = [];
for (let d = 0; d < 7; d++) {
if (d !== today) {
slots.push({ day: DAY_NAMES[d], start: '00:00', duration: 24 });
}
}
return slots;
}

function getScheduleForPlayerMode() {
const now = new Date();
const today = now.getDay();
return [{ day: DAY_NAMES[today], start: '00:00', duration: 24 }];
}

function loadPlayerWithMode(showPlayer) {
if (currentPlayer && typeof currentPlayer.dispose === 'function') {
currentPlayer.dispose();
}
const container = document.getElementById('player-container');
container.innerHTML = '<video id="player" playsinline controls muted class="cld-video-player cld-fluid" width="500"></video>';

const scheduleLabel = document.getElementById('schedule-label');
scheduleLabel.textContent = showPlayer
? 'Schedule: Today 00:00-23:59 (player visible)'
: 'Schedule: All days except today (poster visible)';

const schedule = showPlayer ? getScheduleForPlayerMode() : getScheduleForImageMode();
const options = {
cloudName: 'demo',
publicId: 'sea_turtle',
schedule: { weekly: schedule }
};

currentPlayer = player('player', options);
if (currentPlayer && typeof currentPlayer.then === 'function') {
currentPlayer.then((player) => { currentPlayer = player; });
}
}

document.getElementById('btn-image').addEventListener('click', () => loadPlayerWithMode(false));
document.getElementById('btn-player').addEventListener('click', () => loadPlayerWithMode(true));
loadPlayerWithMode(false);
</script>

<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"
/>
</body>
</html>
1 change: 1 addition & 0 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ <h3 class="mt-4">Some code examples:</h3>
<li><a href="./profiles.html">Profiles</a></li>
<li><a href="./raw-url.html">Raw URL</a></li>
<li><a href="./recommendations.html">Recommendations</a></li>
<li><a href="./schedule.html">Schedule (weekly time slots)</a></li>
<li><a href="./seek-thumbs.html">Seek Thumbnails</a></li>
<li><a href="./share-plugin.html">Share &amp; Download</a></li>
<li><a href="./shoppable.html">Shoppable Videos</a></li>
Expand Down
116 changes: 116 additions & 0 deletions docs/schedule.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Cloudinary Video Player - Schedule</title>
<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">

<!-- Bootstrap -->
<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">

<script type="text/javascript" src="./scripts.js"></script>

<script type="text/javascript">
(function () {
var DAY_NAMES = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'];
var currentPlayer = null;

function getScheduleForImageMode() {
var now = new Date();
var today = now.getDay();
var slots = [];
for (var d = 0; d < 7; d++) {
if (d !== today) {
slots.push({ day: DAY_NAMES[d], start: '00:00', duration: 24 });
}
}
return slots;
}

function getScheduleForPlayerMode() {
var now = new Date();
var today = now.getDay();
return [{ day: DAY_NAMES[today], start: '00:00', duration: 24 }];
}

function loadPlayerWithMode(showPlayer) {
if (currentPlayer && typeof currentPlayer.dispose === 'function') {
currentPlayer.dispose();
}
var container = document.getElementById('player-container');
container.innerHTML = '<video id="player" playsinline controls muted class="cld-video-player cld-fluid" width="500"></video>';

var scheduleLabel = document.getElementById('schedule-label');
scheduleLabel.textContent = showPlayer
? 'Schedule: Today 00:00-23:59 (player visible)'
: 'Schedule: All days except today (poster visible)';

var schedule = showPlayer ? getScheduleForPlayerMode() : getScheduleForImageMode();
var options = {
cloudName: 'demo',
publicId: 'sea_turtle',
schedule: { weekly: schedule }
};

currentPlayer = cloudinary.videoPlayer('player', options);
if (currentPlayer && typeof currentPlayer.then === 'function') {
currentPlayer.then(function (player) { currentPlayer = player; });
}
}

window.addEventListener('load', function () {
document.getElementById('btn-image').addEventListener('click', function () { loadPlayerWithMode(false); });
document.getElementById('btn-player').addEventListener('click', function () { loadPlayerWithMode(true); });
loadPlayerWithMode(false);
});
})();
</script>
</head>
<body>
<div class="container p-4 col-12 col-md-9 col-xl-6">
<nav class="nav mb-2">
<a href="./index.html">&#60;&#60; Back to examples index</a>
</nav>
<h1>Cloudinary Video Player</h1>
<h3 class="mb-4">Schedule (Weekly Time Slots)</h3>

<p class="mb-4">
Video plays only during configured time slots. Outside schedule, a poster image is shown.
Call <code>loadPlayer()</code> on the stub to load the player on demand (e.g. on click).
</p>

<div class="mb-3">
<label class="mr-2">Mode:</label>
<button type="button" id="btn-image" class="btn btn-sm btn-outline-primary mr-1">Show poster (image)</button>
<button type="button" id="btn-player" class="btn btn-sm btn-outline-primary">Show player</button>
</div>

<h4 class="mb-2" id="schedule-label">Schedule</h4>
<div id="player-container">
<video
id="player"
playsinline
controls
muted
class="cld-video-player cld-fluid"
width="500">
</video>
</div>

<h3 class="mt-4">Example Code:</h3>
<pre><code class="language-javascript">
cloudinary.videoPlayer('player', {
cloudName: 'demo',
publicId: 'sea_turtle',
schedule: {
weekly: [
{ day: 'monday', start: '09:00', duration: 8 },
{ day: 'tuesday', start: '09:00', duration: 8 }
]
}
});
</code></pre>
</div>
</body>
</html>
19 changes: 19 additions & 0 deletions src/config/configSchema.json
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,25 @@
"type": "boolean",
"default": true
},
"schedule": {
"type": "object",
"properties": {
"weekly": {
"type": "array",
"items": {
"type": "object",
"properties": {
"day": { "type": "string" },
"start": { "type": "string" },
"duration": { "type": "number" }
},
"required": ["day", "start", "duration"]
},
"default": []
}
},
"default": {}
},
"videoSources": {
"type": "array",
"items": {
Expand Down
59 changes: 28 additions & 31 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,48 +1,46 @@
import 'assets/styles/main.scss';
import videojs from 'video.js';
import VideoPlayer from './video-player';
import defaults from './config/defaults';
import { getResolveVideoElement, extractOptions } from './video-player.utils';
import { fetchAndMergeConfig } from './utils/fetch-config';
import { scheduleBootstrap, shouldUseScheduleBootstrap, getElementForSchedule } from './utils/schedule';

const getConfig = (elem, playerOptions = {}) => {
const videoElement = getResolveVideoElement(elem);
const options = extractOptions(videoElement, playerOptions);

return { videoElement, options };
};

const mergeDefaults = (options) => videojs.obj.merge({}, defaults, options);

export const videoPlayer = (id, playerOptions = {}, ready) => {
const { videoElement, options } = getConfig(id, playerOptions);
if (options.profile) {
console.warn('Profile option requires async initialization. Use cloudinary.player() instead of cloudinary.videoPlayer()');
export const videoPlayer = (id, playerOptions = {}, ready) => {
if (shouldUseScheduleBootstrap(playerOptions)) {
return scheduleBootstrap(id, playerOptions);
}
return new VideoPlayer(videoElement, mergeDefaults(options), ready);

return import(/* webpackChunkName: "cld-video-player-core" */ './video-player.js').then(
({ createVideoPlayer }) => createVideoPlayer(id, playerOptions, ready)
);
};

export const videoPlayers = (selector, playerOptions, ready) => {
const nodeList = document.querySelectorAll(selector);
return [...nodeList].map(node => videoPlayer(node, playerOptions, ready));
return [...nodeList].map((node) => videoPlayer(node, playerOptions, ready));
};

export const player = async (id, playerOptions, ready) => {
const { videoElement, options } = getConfig(id, playerOptions);

try {
const videoConfig = await fetchAndMergeConfig(options);
return new VideoPlayer(videoElement, mergeDefaults(videoConfig), ready);
} catch (e) {
const videoPlayer = new VideoPlayer(videoElement, mergeDefaults(options));
videoPlayer.videojs.error('Invalid profile');
throw e;
const { fetchAndMergeConfig } = await import(/* webpackChunkName: "cld-video-player-core" */ './utils/fetch-config');
const mergedOptions = Object.assign({}, playerOptions);
const videoElement = getElementForSchedule(id);

const opts = await (async () => {
try {
const fetched = await fetchAndMergeConfig(mergedOptions);
return Object.assign({}, fetched, mergedOptions);
} catch {
return mergedOptions;
}
})();

if (shouldUseScheduleBootstrap(opts)) {
return scheduleBootstrap(id, opts);
}

const { createPlayerWithConfig } = await import(/* webpackChunkName: "cld-video-player-core" */ './video-player.js');
return createPlayerWithConfig(videoElement, opts, ready);
};

export const players = async (selector, playerOptions, ready) => {
const nodeList = document.querySelectorAll(selector);
return Promise.all([...nodeList].map(node => player(node, playerOptions, ready)));
return Promise.all([...nodeList].map((node) => player(node, playerOptions, ready)));
};

const cloudinaryVideoPlayerLegacyConfig = () => {
Expand All @@ -62,7 +60,6 @@ const cloudinary = {
player,
players,
Cloudinary: {
// Backwards compatibility with SDK v1
new: cloudinaryVideoPlayerLegacyConfig
}
};
Expand Down
1 change: 1 addition & 0 deletions src/utils/get-analytics-player-options.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ export const getAnalyticsFromPlayerOptions = (playerOptions) => filterDefaultsAn
withCredentials: playerOptions.withCredentials,
debug: playerOptions.debug,
type: playerOptions.type,
schedule: hasConfig(playerOptions.schedule?.weekly),

colors: hasConfig(playerOptions.colors),
controlBar: hasConfig(playerOptions.controlBar),
Expand Down
Loading
Loading