diff --git a/docs/es-modules/fluid.html b/docs/es-modules/fluid.html index 464d03183..fbc70435d 100644 --- a/docs/es-modules/fluid.html +++ b/docs/es-modules/fluid.html @@ -39,7 +39,7 @@

Fluid Layouts

diff --git a/docs/es-modules/transformations.html b/docs/es-modules/transformations.html index 8df6abb20..6cda9a071 100644 --- a/docs/es-modules/transformations.html +++ b/docs/es-modules/transformations.html @@ -54,34 +54,36 @@

via data-cld-transformation

diff --git a/docs/profiles.html b/docs/profiles.html index e2b2b88af..ce6a695e4 100644 --- a/docs/profiles.html +++ b/docs/profiles.html @@ -24,35 +24,51 @@ @@ -64,7 +80,13 @@

Cloudinary Video Player

Profiles

-
Player with default profile
+

+ Player profiles provide a mechanism to define and save your video player configuration + to your Cloudinary account and then reference this profile as part of your video player setup. + For details, see the player profiles documentation. +

+ +

Default profile - applies predefined player configuration

-

Example Code:

- -
-    
-
-      <video
-        id="player-default-profile"
-        controls
-        autoplay
-        muted
-        class="cld-video-player"
-        width="500">
-      </video>
-
-      
-      
-        window.addEventListener('load', async function() {
-          const playerWithDefaultProfile = await cloudinary.player('player-default-profile', {
-            cloudName: 'demo',
-            profile: 'cld-default',
-          });
-
-          playerWithDefaultProfile.source('sea_turtle');
-        }, false);
-      
-    
- -
Player with custom profile
+

Custom profile - loads player and source config from profile

-

Example Code:

- -
-    
-
-      <video
-        id="player-custom-profile"
-        controls
-        autoplay
-        muted
-        class="cld-video-player"
-        width="500">
-      </video>
-
-      
-      
-        window.addEventListener('load', async function() {
-          const playerWithCustomProfile = await cloudinary.player('player-custom-profile', {
-            cloudName: 'prod',
-            profile: 'myCustomProfile',
-          });
+  

Override player config - profile config overridden by JS player options

+ - playerWithCustomProfile.source('samples/cld-sample-video'); - }, false); -
-
+

Override source config - profile transformation overridden by JS source options

+ -
Player with custom profile and overrides
+

No profile - fetches asset-specific config from publicId/config.json

-

Example Code:

+

+ Full documentation +

+

Example Code:

     
-
       <video
-        id="player-custom-profile-overrides"
+        id="player-custom-profile"
         controls
         autoplay
         muted
         class="cld-video-player"
         width="500">
       </video>
+    
+    
+      // Load profile configuration with source() method
+      const player = await cloudinary.player('player-default-profile', {
+        cloudName: 'demo',
+        profile: 'cld-default',
+      });
+
+      player.source('samples/sea-turtle');
+
+      // Load profile configuration with publicId
+      await cloudinary.player('player-custom-profile', {
+        cloudName: 'prod',
+        profile: 'demo-profile',
+        publicId: 'samples/sea-turtle',
+      });
+
+      // Override player config (colors) - JS options take precedence
+      await cloudinary.player('player-profile-override', {
+        cloudName: 'prod',
+        profile: 'demo-profile',
+        publicId: 'samples/sea-turtle',
+        colors: {
+          base: '#22004D',
+          accent: '#FA65C7',
+          text: '#FF06D2'
+        },
+      });
+
+      // Override source config (transformation) - JS options take precedence
+      await cloudinary.player('player-source-override', {
+        cloudName: 'prod',
+        profile: 'demo-profile',
+        publicId: 'samples/sea-turtle',
+        transformation: {
+          effect: 'blur:500'
+        },
+      });
 
-      
-      
-        window.addEventListener('load', async function() {
-          const playerWithCustomProfileAndOverrides = await cloudinary.player('player-custom-profile-overrides', {
-            cloudName: 'prod',
-            profile: 'myCustomProfile',
-            colors: {
-              base: "#1532a8"
-            },
-            seekThumbnails: false,
-            aiHighlightsGraph: true,
-          });
-
-          playerWithCustomProfileAndOverrides.source('samples/cld-sample-video');
-        }, false);
-      
-    
- + // No profile - fetches config from asset + await cloudinary.player('player-asset-config', { + cloudName: 'prod', + publicId: 'samples/sea-turtle', + }); + + + diff --git a/src/assets/icon-font/VideoJS.svg b/src/assets/icon-font/VideoJS.svg deleted file mode 100755 index 1b27e37df..000000000 --- a/src/assets/icon-font/VideoJS.svg +++ /dev/null @@ -1,147 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/assets/icon-font/VideoJS.ttf b/src/assets/icon-font/VideoJS.ttf deleted file mode 100755 index 53b10bc14..000000000 Binary files a/src/assets/icon-font/VideoJS.ttf and /dev/null differ diff --git a/src/assets/icon-font/VideoJS.woff b/src/assets/icon-font/VideoJS.woff deleted file mode 100755 index 1b07ea611..000000000 Binary files a/src/assets/icon-font/VideoJS.woff and /dev/null differ diff --git a/src/assets/icons/source_switcher_icon_for_white_bg.svg b/src/assets/icon-font/custom-icons/cld/source-switcher.svg similarity index 96% rename from src/assets/icons/source_switcher_icon_for_white_bg.svg rename to src/assets/icon-font/custom-icons/cld/source-switcher.svg index 6e8e9adeb..ab486f724 100644 --- a/src/assets/icons/source_switcher_icon_for_white_bg.svg +++ b/src/assets/icon-font/custom-icons/cld/source-switcher.svg @@ -1,4 +1,5 @@ - + + diff --git a/src/assets/icon-font/icons.json b/src/assets/icon-font/icons.json index 04f887e06..a7d97f2d4 100644 --- a/src/assets/icon-font/icons.json +++ b/src/assets/icon-font/icons.json @@ -161,5 +161,9 @@ "name": "check", "svg": "check.svg", "root-dir": "./custom-icons/cld/" + }, { + "name": "source-switcher", + "svg": "source-switcher.svg", + "root-dir": "./custom-icons/cld/" }] } diff --git a/src/assets/icons/source_switcher_icon_for_black_bg.svg b/src/assets/icons/source_switcher_icon_for_black_bg.svg deleted file mode 100644 index 76b9ce71c..000000000 --- a/src/assets/icons/source_switcher_icon_for_black_bg.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/assets/styles/_icons.scss b/src/assets/styles/_icons.scss index 488792cb6..77075a8ed 100644 --- a/src/assets/styles/_icons.scss +++ b/src/assets/styles/_icons.scss @@ -13,7 +13,7 @@ $icon-font-family: VideoJS; @font-face { font-family: $icon-font-family; - src: url(data:application/font-woff;charset=utf-8;base64,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) format('woff'); + src: url(data:application/font-woff;charset=utf-8;base64,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) format('woff'); font-weight: normal; font-style: normal; } @@ -65,6 +65,7 @@ $icons: ( audio-description: 'f12b', cart: 'f12c', check: 'f12d', + source-switcher: 'f12e', ); // NOTE: This is as complex as we want to get with SCSS functionality. diff --git a/src/components/source-switcher-button/source-switcher-button.js b/src/components/source-switcher-button/source-switcher-button.js index 4b5187066..ecadcd73e 100644 --- a/src/components/source-switcher-button/source-switcher-button.js +++ b/src/components/source-switcher-button/source-switcher-button.js @@ -1,6 +1,5 @@ import videojs from 'video.js'; import SourceMenuItem from './source-switcher-menu-item'; -import './source-switcher-button.scss'; const MenuButton = videojs.getComponent('MenuButton'); const MenuItem = videojs.getComponent('MenuItem'); @@ -18,6 +17,11 @@ class SourceSwitcherButton extends MenuButton { this._onSelected = typeof options.onSelected === 'function' ? options.onSelected : null; this._setEnabled(this._items.length > 0); + + const placeholder = this.el().querySelector('.vjs-icon-placeholder'); + if (placeholder) { + placeholder.classList.add('vjs-icon-source-switcher'); + } } buildCSSClass() { diff --git a/src/components/source-switcher-button/source-switcher-button.scss b/src/components/source-switcher-button/source-switcher-button.scss deleted file mode 100644 index d6fb8e8b3..000000000 --- a/src/components/source-switcher-button/source-switcher-button.scss +++ /dev/null @@ -1,17 +0,0 @@ -.vjs-control-bar .vjs-menu-button.vjs-source-switcher-button { - background-image: url("../../assets/icons/source_switcher_icon_for_black_bg.svg"); - background-size: 25px; - background-position: center; - background-repeat: no-repeat; - color: inherit; - opacity: 0.9; - - .cld-video-player-skin-light & { - background-image: url("../../assets/icons/source_switcher_icon_for_white_bg.svg"); - } - - &:hover { - cursor: pointer; - opacity: 1; - } -} diff --git a/src/index.all.js b/src/index.all.js index 197791261..cdd83854e 100644 --- a/src/index.all.js +++ b/src/index.all.js @@ -7,17 +7,17 @@ import cloudinary from './index.js'; -export * from './index.js'; -export * from './plugins/adaptive-streaming/adaptive-streaming.js'; -export * from './plugins/chapters/chapters.js'; -export * from './plugins/colors/colors.js'; -export * from './plugins/ima/ima.js'; -export * from './plugins/playlist/playlist.js'; -export * from './plugins/interaction-areas/interaction-areas.service.js'; -export * from './plugins/visual-search/visual-search.js'; -export * from './plugins/text-tracks-manager/index.js'; -export * from './plugins/share/share.js'; -export * from './components/shoppable-bar/shoppable-widget.js'; -export * from './components/recommendations-overlay/recommendations-overlay.js'; +// Import plugin implementations so webpack bundles them in /all.js +import './plugins/adaptive-streaming/adaptive-streaming.js'; +import './plugins/chapters/chapters.js'; +import './plugins/colors/colors.js'; +import './plugins/ima/ima.js'; +import './plugins/playlist/playlist.js'; +import './plugins/interaction-areas/interaction-areas.service.js'; +import './plugins/visual-search/visual-search.js'; +import './plugins/share/share.js'; +import './components/shoppable-bar/shoppable-widget.js'; +import './components/recommendations-overlay/recommendations-overlay.js'; +export * from './index.js'; export default cloudinary; diff --git a/src/index.es.js b/src/index.es.js deleted file mode 100644 index f19be2607..000000000 --- a/src/index.es.js +++ /dev/null @@ -1,19 +0,0 @@ -// This file is bundled as `cld-video-player.js` to be imported as a tree-shaken module. -// It is the default export of the Cloudinary Video Player. - -// Usage: -// import cloudinary from 'cloudinary-video-player'; -// Or: -// import { videoPlayer } from "cloudinary-video-player"; - -// Other modules can be imported like that: -// import dash from 'cloudinary-video-player/dash'; - -import cloudinary from './index.js'; - -export const videoPlayer = cloudinary.videoPlayer; -export const videoPlayers = cloudinary.videoPlayers; - -export const player = cloudinary.player; - -export default cloudinary; diff --git a/src/index.js b/src/index.js index 252c105e6..7e6eb95bf 100644 --- a/src/index.js +++ b/src/index.js @@ -1,45 +1,53 @@ import 'assets/styles/main.scss'; -import pick from 'lodash/pick'; import VideoPlayer from './video-player'; -import createPlayer from './player'; -import { convertKeysToSnakeCase } from './utils/object'; -import { CLOUDINARY_CONFIG_PARAM } from './video-player.const'; +import { getResolveVideoElement, extractOptions } from './video-player.utils'; +import { fetchAndMergeConfig } from './utils/fetch-config'; -const getConfig = (playerOptions = {}, cloudinaryConfig) => { - const snakeCaseCloudinaryConfig = pick(convertKeysToSnakeCase(playerOptions), CLOUDINARY_CONFIG_PARAM); - - // pick cld-configurations and assign them to cloudinaryConfig - return Object.assign(playerOptions, { - cloudinaryConfig: cloudinaryConfig || snakeCaseCloudinaryConfig - }); +const getConfig = (elem, playerOptions = {}) => { + const videoElement = getResolveVideoElement(elem); + const options = extractOptions(videoElement, playerOptions); + + return { videoElement, options }; }; -const getVideoPlayer = config => (id, playerOptions, ready) => - new VideoPlayer(id, getConfig(playerOptions, config), ready); - -const getVideoPlayers = config => (selector, playerOptions, ready) => - VideoPlayer.all(selector, getConfig(playerOptions, config), ready); +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()'); + } + return new VideoPlayer(videoElement, options, ready); +}; -const getPlayer = config => (id, playerOptions, ready) => createPlayer(id, getConfig(playerOptions, config), ready); -const getPlayers = config => (selector, playerOptions, ready) => { +export const videoPlayers = (selector, playerOptions, ready) => { const nodeList = document.querySelectorAll(selector); - const playerConfig = getConfig(playerOptions, config); - return [...nodeList].map((node) => createPlayer(node, playerConfig, ready)); + return [...nodeList].map(node => videoPlayer(node, playerOptions, ready)); }; -export const videoPlayer = getVideoPlayer(); -export const videoPlayers = getVideoPlayers(); +export const player = async (id, playerOptions, ready) => { + const { videoElement, options } = getConfig(id, playerOptions); + + try { + const mergedOptions = await fetchAndMergeConfig(options); + return new VideoPlayer(videoElement, mergedOptions, ready); + } catch (e) { + const videoPlayer = new VideoPlayer(videoElement, options); + videoPlayer.videojs.error('Invalid profile'); + throw e; + } +}; -export const player = getPlayer(); -export const players = getPlayers(); +export const players = async (selector, playerOptions, ready) => { + const nodeList = document.querySelectorAll(selector); + return Promise.all([...nodeList].map(node => player(node, playerOptions, ready))); +}; -const cloudinaryVideoPlayerLegacyConfig = config => { +const cloudinaryVideoPlayerLegacyConfig = () => { console.warn( 'Cloudinary.new() is deprecated and will be removed. Please use cloudinary.videoPlayer() instead.' ); return { - videoPlayer: getVideoPlayer(config), - videoPlayers: getVideoPlayers(config) + videoPlayer, + videoPlayers }; }; diff --git a/src/player.js b/src/player.js deleted file mode 100644 index a8d018bb5..000000000 --- a/src/player.js +++ /dev/null @@ -1,53 +0,0 @@ -import VideoPlayer from './video-player'; -import { defaultProfiles } from 'cloudinary-video-player-profiles'; -import { isRawUrl, getCloudinaryUrlPrefix } from './plugins/cloudinary/common'; - -const isDefaultProfile = (profileName) => !!defaultProfiles.find(({ name }) => profileName === name); -const getDefaultProfileConfig = (profileName) => { - const profile = defaultProfiles.find(({ name }) => profileName === name); - - if (!profile) { - throw new Error(`Default profile with name ${profileName} does not exist`); - } - - return profile.config; -}; - -export const getProfile = async (profile, initOptions) => { - if (isDefaultProfile(profile)) { - return getDefaultProfileConfig(profile); - } - - const urlPrefix = getCloudinaryUrlPrefix(initOptions.cloudinaryConfig); - - const profileUrl = isRawUrl(profile) ? profile : `${urlPrefix}/_applet_/video_service/video_player_profiles/${profile.replaceAll(' ', '+')}.json`; - return fetch(profileUrl, { method: 'GET' }).then(res => res.json()); -}; - -const player = async (elem, initOptions, ready) => { - const { profile, ...otherInitOptions } = initOptions; - try { - const profileOptions = profile ? await getProfile(profile, otherInitOptions) : {}; - const options = Object.assign({}, profileOptions.playerOptions, profileOptions.sourceOptions, otherInitOptions, { - _internalAnalyticsMetadata: { - newPlayerMethod: true, - profile: isDefaultProfile(profile) ? profile : !!profile, - }, - }); - const videoPlayer = new VideoPlayer(elem, options, ready); - - const nativeVideoPlayerSourceMethod = videoPlayer.source; - videoPlayer.source = (id, options) => { - const extendedOptions = Object.assign({}, profileOptions.sourceOptions, options); - return nativeVideoPlayerSourceMethod.call(videoPlayer, id, extendedOptions); - }; - - return videoPlayer; - } catch (e) { - const videoPlayer = new VideoPlayer(elem, otherInitOptions); - videoPlayer.videojs.error('Invalid profile'); - throw e; - } -}; - -export default player; diff --git a/src/utils/fetch-config.js b/src/utils/fetch-config.js new file mode 100644 index 000000000..5929da0df --- /dev/null +++ b/src/utils/fetch-config.js @@ -0,0 +1,69 @@ +import { defaultProfiles } from 'cloudinary-video-player-profiles'; +import { isRawUrl, getCloudinaryUrlPrefix } from '../plugins/cloudinary/common'; + +const isDefaultProfile = (profileName) => !!defaultProfiles.find(({ name }) => profileName === name); + +const getDefaultProfileConfig = (profileName) => { + const profile = defaultProfiles.find(({ name }) => profileName === name); + + if (!profile) { + throw new Error(`Default profile with name ${profileName} does not exist`); + } + + return profile.config; +}; + +const fetchConfig = async (options) => { + const { profile, publicId, cloudinaryConfig, type = 'upload' } = options; + + if (profile && isDefaultProfile(profile)) { + return getDefaultProfileConfig(profile); + } + + const urlPrefix = getCloudinaryUrlPrefix(cloudinaryConfig) + '/_applet_/video_service/video_player_profiles'; + + // TODO: when endpoints are ready + // const urlPrefix = getCloudinaryUrlPrefix(cloudinaryConfig) + '/_applet_/video_service/video_player_config'; + // And: + // `${urlPrefix}/profile/${profile.replaceAll(' ', '+')}.json`; + + let configUrl; + if (profile) { + configUrl = isRawUrl(profile) + ? profile + : `${urlPrefix}/${profile.replaceAll(' ', '+')}.json`; + } else if (publicId) { + configUrl = `${urlPrefix}/video/${type}/${publicId}.json`; + // TODO: remove when endpoints are ready + console.log('This will fetch:', configUrl); + return {}; + } else { + return {}; + } + + return fetch(configUrl, { method: 'GET' }).then(res => { + if (!res.ok) { + // fail silently + return {}; + } + return res.json(); + }); +}; + +export const fetchAndMergeConfig = async (options) => { + const profileOptions = await fetchConfig(options); + const profileAnalytics = { + _internalAnalyticsMetadata: { + newPlayerMethod: true, + profile: options.profile ? (isDefaultProfile(options.profile) ? options.profile : true) : undefined + } + }; + return Object.assign( + {}, + profileOptions.playerOptions || {}, + profileOptions.sourceOptions || {}, + options, + profileAnalytics + ); +}; + diff --git a/src/video-player.const.js b/src/video-player.const.js index ee437dc52..3f00bdd25 100644 --- a/src/video-player.const.js +++ b/src/video-player.const.js @@ -49,6 +49,7 @@ export const PLAYER_PARAMS = SOURCE_PARAMS.concat([ 'playedEventPercents', 'playedEventTimes', 'playlistWidget', + 'profile', 'qualitySelector', 'queryParams', 'seekThumbnails', diff --git a/src/video-player.js b/src/video-player.js index cce8797b6..2d2531733 100644 --- a/src/video-player.js +++ b/src/video-player.js @@ -14,9 +14,8 @@ import Eventable from './mixins/eventable'; import ExtendedEvents from './extended-events'; import VideoSource from './plugins/cloudinary/models/video-source/video-source'; import { - extractOptions, - getResolveVideoElement, - overrideDefaultVideojsComponents + overrideDefaultVideojsComponents, + splitOptions } from './video-player.utils'; import { FLOATING_TO, FLUID_CLASS_NAME } from './video-player.const'; import { isValidPlayerConfig, isValidSourceConfig } from './validators/validators-functions'; @@ -47,13 +46,11 @@ class VideoPlayer extends Utils.mixin(Eventable) { return this.options.playerOptions; } - constructor(elem, initOptions, ready) { + constructor(elem, options, ready) { super(); - this.videoElement = getResolveVideoElement(elem); - - this.options = extractOptions(this.videoElement, initOptions); - + this.videoElement = elem; + this.options = splitOptions(options); this._videojsOptions = this.options.videojsOptions; // Make sure to add 'video-js' class before creating videojs instance diff --git a/src/video-player.utils.js b/src/video-player.utils.js index d1665d748..7a32cb53f 100644 --- a/src/video-player.utils.js +++ b/src/video-player.utils.js @@ -1,4 +1,5 @@ import videojs from 'video.js'; +import pick from 'lodash/pick'; import Utils from './utils'; import defaults from './config/defaults'; import { @@ -9,6 +10,7 @@ import { AUTO_PLAY_MODE } from './video-player.const'; import isString from 'lodash/isString'; +import { convertKeysToSnakeCase } from './utils/object'; /* * Used to escape element identifiers that begin with certain @@ -81,27 +83,40 @@ export const extractOptions = (elem, options) => { if (videojs.dom.hasClass(elem, FLUID_CLASS_NAME) || videojs.dom.hasClass(elem, 'vjs-fluid')) { options.fluid = true; } + + // Extract cloudinaryConfig from playerOptions if not explicitly provided + if (!options.cloudinaryConfig) { + const snakeCaseCloudinaryConfig = pick(convertKeysToSnakeCase(options), CLOUDINARY_CONFIG_PARAM); + if (Object.keys(snakeCaseCloudinaryConfig).length > 0) { + options.cloudinaryConfig = snakeCaseCloudinaryConfig; + } + } + // Default options < Markup options < Player options - options = videojs.obj.merge({}, defaults, elemOptions, options); + return videojs.obj.merge({}, defaults, elemOptions, options); +}; +export const splitOptions = (flatOptions) => { + const options = Object.assign({}, flatOptions); + // In case of 'autoplay on scroll', we need to make sure normal HTML5 autoplay is off normalizeAutoplay(options); - + // VideoPlayer specific options const playerOptions = Utils.sliceAndUnsetProperties(options, ...PLAYER_PARAMS); - + // Cloudinary SDK config (cloud_name, secure, etc.) playerOptions.cloudinary = Utils.sliceAndUnsetProperties(playerOptions, ...CLOUDINARY_CONFIG_PARAM); - + // Merge with cloudinaryConfig from src/index.js (e.g., secureDistribution -> secure_distribution) if (playerOptions.cloudinaryConfig) { Object.assign(playerOptions.cloudinary, playerOptions.cloudinaryConfig); delete playerOptions.cloudinaryConfig; } - + // Source-level config (visualSearch, chapters, etc.) playerOptions.sourceOptions = Utils.sliceAndUnsetProperties(playerOptions, ...SOURCE_PARAMS); - + // Allow explicitly passing options to videojs using the `videojs` namespace, in order // to avoid param name conflicts: // VideoPlayer.new({ controls: true, videojs: { controls: false }) @@ -109,7 +124,7 @@ export const extractOptions = (elem, options) => { Object.assign(options, options.videojs); delete options.videojs; } - + return { playerOptions, videojsOptions: options }; }; diff --git a/test/e2e/specs/ESM/esmProfilesPage.spec.ts b/test/e2e/specs/ESM/esmProfilesPage.spec.ts index 5392d8cf2..a4c006d60 100644 --- a/test/e2e/specs/ESM/esmProfilesPage.spec.ts +++ b/test/e2e/specs/ESM/esmProfilesPage.spec.ts @@ -6,7 +6,7 @@ import { ESM_URL } from '../../testData/esmUrl'; const link = getEsmLinkByName(ExampleLinkName.Profiles); -vpTest(`Test if 3 videos on ESM profiles page are playing as expected`, async ({ page, pomPages }) => { +vpTest(`Test if 5 videos on ESM profiles page are playing as expected`, async ({ page, pomPages }) => { await page.goto(ESM_URL); await testProfilesPageVideoIsPlaying(page, pomPages, link); }); diff --git a/test/e2e/specs/NonESM/profilesPage.spec.ts b/test/e2e/specs/NonESM/profilesPage.spec.ts index 38c78ea4a..34fd18eff 100644 --- a/test/e2e/specs/NonESM/profilesPage.spec.ts +++ b/test/e2e/specs/NonESM/profilesPage.spec.ts @@ -5,6 +5,6 @@ import { testProfilesPageVideoIsPlaying } from '../commonSpecs/profilesPageVideo const link = getLinkByName(ExampleLinkName.Profiles); -vpTest(`Test if 3 videos on profiles page are playing as expected`, async ({ page, pomPages }) => { +vpTest(`Test if 5 videos on profiles page are playing as expected`, async ({ page, pomPages }) => { await testProfilesPageVideoIsPlaying(page, pomPages, link); }); diff --git a/test/e2e/specs/commonSpecs/profilesPageVideoPlaying.ts b/test/e2e/specs/commonSpecs/profilesPageVideoPlaying.ts index 0abb8caf7..29111de58 100644 --- a/test/e2e/specs/commonSpecs/profilesPageVideoPlaying.ts +++ b/test/e2e/specs/commonSpecs/profilesPageVideoPlaying.ts @@ -17,10 +17,22 @@ export async function testProfilesPageVideoIsPlaying(page: Page, pomPages: PageM await test.step('Validating that custom profile video is playing', async () => { await pomPages.profilesPage.profilesCustomProfileVideoComponent.validateVideoIsPlaying(true); }); - await test.step('Scroll until custom profile overrides video element is visible', async () => { - await pomPages.profilesPage.profilesCustomProfileOverridesVideoComponent.locator.scrollIntoViewIfNeeded(); + await test.step('Scroll until profile override video element is visible', async () => { + await pomPages.profilesPage.profilesProfileOverrideVideoComponent.locator.scrollIntoViewIfNeeded(); }); - await test.step('Validating that custom profile overrides video is playing', async () => { - await pomPages.profilesPage.profilesCustomProfileOverridesVideoComponent.validateVideoIsPlaying(true); + await test.step('Validating that profile override video is playing', async () => { + await pomPages.profilesPage.profilesProfileOverrideVideoComponent.validateVideoIsPlaying(true); + }); + await test.step('Scroll until source override video element is visible', async () => { + await pomPages.profilesPage.profilesSourceOverrideVideoComponent.locator.scrollIntoViewIfNeeded(); + }); + await test.step('Validating that source override video is playing', async () => { + await pomPages.profilesPage.profilesSourceOverrideVideoComponent.validateVideoIsPlaying(true); + }); + await test.step('Scroll until asset config video element is visible', async () => { + await pomPages.profilesPage.profilesAssetConfigVideoComponent.locator.scrollIntoViewIfNeeded(); + }); + await test.step('Validating that asset config video is playing', async () => { + await pomPages.profilesPage.profilesAssetConfigVideoComponent.validateVideoIsPlaying(true); }); } diff --git a/test/e2e/src/pom/profilesPage.ts b/test/e2e/src/pom/profilesPage.ts index 1d9e62e86..85e14d9b7 100644 --- a/test/e2e/src/pom/profilesPage.ts +++ b/test/e2e/src/pom/profilesPage.ts @@ -3,7 +3,9 @@ import { VideoComponent } from '../../components/videoComponent'; import { BasePage } from './BasePage'; const PROFILES_PAGE_DEFAULT_PROFILE_VIDEO_SELECTOR = '//*[@id="player-default-profile_html5_api"]'; const PROFILES_PAGE_CUSTOM_PROFILE_VIDEO_SELECTOR = '//*[@id="player-custom-profile_html5_api"]'; -const PROFILES_PAGE_CUSTOM_PROFILE_OVERRIDES_VIDEO_SELECTOR = '//*[@id="player-custom-profile-overrides_html5_api"]'; +const PROFILES_PAGE_PROFILE_OVERRIDE_VIDEO_SELECTOR = '//*[@id="player-profile-override_html5_api"]'; +const PROFILES_PAGE_SOURCE_OVERRIDE_VIDEO_SELECTOR = '//*[@id="player-source-override_html5_api"]'; +const PROFILES_PAGE_ASSET_CONFIG_VIDEO_SELECTOR = '//*[@id="player-asset-config_html5_api"]'; /** * Video player examples profiles page object @@ -11,12 +13,16 @@ const PROFILES_PAGE_CUSTOM_PROFILE_OVERRIDES_VIDEO_SELECTOR = '//*[@id="player-c export class ProfilesPage extends BasePage { public profilesDefaultProfileVideoComponent: VideoComponent; public profilesCustomProfileVideoComponent: VideoComponent; - public profilesCustomProfileOverridesVideoComponent: VideoComponent; + public profilesProfileOverrideVideoComponent: VideoComponent; + public profilesSourceOverrideVideoComponent: VideoComponent; + public profilesAssetConfigVideoComponent: VideoComponent; constructor(page: Page) { super(page); this.profilesDefaultProfileVideoComponent = new VideoComponent(page, PROFILES_PAGE_DEFAULT_PROFILE_VIDEO_SELECTOR); this.profilesCustomProfileVideoComponent = new VideoComponent(page, PROFILES_PAGE_CUSTOM_PROFILE_VIDEO_SELECTOR); - this.profilesCustomProfileOverridesVideoComponent = new VideoComponent(page, PROFILES_PAGE_CUSTOM_PROFILE_OVERRIDES_VIDEO_SELECTOR); + this.profilesProfileOverrideVideoComponent = new VideoComponent(page, PROFILES_PAGE_PROFILE_OVERRIDE_VIDEO_SELECTOR); + this.profilesSourceOverrideVideoComponent = new VideoComponent(page, PROFILES_PAGE_SOURCE_OVERRIDE_VIDEO_SELECTOR); + this.profilesAssetConfigVideoComponent = new VideoComponent(page, PROFILES_PAGE_ASSET_CONFIG_VIDEO_SELECTOR); } } diff --git a/test/unit/cloudinaryConfig.test.js b/test/unit/cloudinaryConfig.test.js index bc4034e09..9c9d6f8d2 100644 --- a/test/unit/cloudinaryConfig.test.js +++ b/test/unit/cloudinaryConfig.test.js @@ -1,18 +1,23 @@ import '../../src/'; import VideoPlayer from '../../src/video-player'; +import { getResolveVideoElement, extractOptions } from '../../src/video-player.utils'; describe('secure true test', () => { it('test force secure true', async () => { jest.useFakeTimers(); document.body.innerHTML = '
'; - const vp = new VideoPlayer('test', { hideContextMenu: true, cloudinaryConfig: { cloud_name: 'demo' } }, false); + const elem = getResolveVideoElement('test'); + const options = extractOptions(elem, { hideContextMenu: true, cloudinaryConfig: { cloud_name: 'demo' } }); + const vp = new VideoPlayer(elem, options, false); const conf = vp.videojs.cloudinary.cloudinaryConfig(); expect(conf.secure).toEqual(true); }); it('test explicit secure false', async () => { jest.useFakeTimers(); document.body.innerHTML = '
'; - const vp = new VideoPlayer('test', { hideContextMenu: true, cloudinaryConfig: { cloud_name: 'demo', secure: false } }, false); + const elem = getResolveVideoElement('test'); + const options = extractOptions(elem, { hideContextMenu: true, cloudinaryConfig: { cloud_name: 'demo', secure: false } }); + const vp = new VideoPlayer(elem, options, false); const conf = vp.videojs.cloudinary.cloudinaryConfig(); expect(conf.secure).toEqual(false); }); diff --git a/test/unit/videoSource.test.js b/test/unit/videoSource.test.js index a078aa3b7..5b79c3ea3 100644 --- a/test/unit/videoSource.test.js +++ b/test/unit/videoSource.test.js @@ -1,7 +1,9 @@ import VideoSource from '../../src/plugins/cloudinary/models/video-source/video-source.js'; import { hasCodec } from '../../src/plugins/cloudinary/models/video-source/video-source.utils'; import { SOURCE_TYPE } from '../../src/utils/consts'; +import { extractOptions, splitOptions } from '../../src/video-player.utils'; import '../../src/'; + const cld = { cloud_name: 'demo' }; describe('video source tests', () => { @@ -558,3 +560,33 @@ describe('test hasCodec method', () => { }); }); }); + +describe('sourceOptions extraction and inheritance', () => { + it('should extract SOURCE_PARAMS into playerOptions.sourceOptions', () => { + let elem = document.createElement('video'); + let options = { + cloudName: 'demo', + transformation: { width: 640, crop: 'scale' }, + allowUsageReport: true + }; + let flatOptions = extractOptions(elem, options); + let { playerOptions } = splitOptions(flatOptions); + + expect(playerOptions.sourceOptions.transformation).toEqual({ width: 640, crop: 'scale' }); + expect(playerOptions.sourceOptions.allowUsageReport).toBe(true); + }); + + it('should separate SOURCE_PARAMS from PLAYER_PARAMS', () => { + let elem = document.createElement('video'); + let options = { + cloudName: 'demo', + transformation: { width: 640 }, + colors: { base: '#FF0000' } + }; + let flatOptions = extractOptions(elem, options); + let { playerOptions } = splitOptions(flatOptions); + + expect(playerOptions.sourceOptions.transformation).toEqual({ width: 640 }); + expect(playerOptions.colors).toEqual({ base: '#FF0000' }); + }); +}); diff --git a/webpack/es6.config.js b/webpack/es6.config.js index dbe253244..e84e1c74b 100644 --- a/webpack/es6.config.js +++ b/webpack/es6.config.js @@ -11,7 +11,7 @@ module.exports = merge(webpackCommon, { mode: 'production', entry: { - 'cld-video-player': './index.es.js', // default + 'cld-video-player': './index.js', // default 'videoPlayer': './index.videoPlayer.js', 'player': './index.player.js', 'all': './index.all.js'