diff --git a/.github/workflows/e2e_pr.yml b/.github/workflows/e2e_pr.yml index 0ad95782..21fe50ce 100644 --- a/.github/workflows/e2e_pr.yml +++ b/.github/workflows/e2e_pr.yml @@ -26,8 +26,17 @@ jobs: - name: NPM Install run: npm install + - name: Extract PR Number + id: extract_pr_number + run: echo "PR_NUMBER=${{ github.event.pull_request.number }}" >> $GITHUB_ENV + + - name: Set Deploy Preview URL + run: echo "PREVIEW_URL=https://deploy-preview-${{ env.PR_NUMBER }}--cld-vp-esm-pages.netlify.app" >> $GITHUB_ENV + - name: E2E tests run: npm run test:e2e + env: + PREVIEW_URL: ${{ env.PREVIEW_URL }} - name: Upload report to artifact uses: actions/upload-artifact@v4 diff --git a/CHANGELOG-edge.md b/CHANGELOG-edge.md index c29a7a40..1dab850f 100644 --- a/CHANGELOG-edge.md +++ b/CHANGELOG-edge.md @@ -1,5 +1,31 @@ # Changelog +## [2.1.1-edge.2](https://github.com/cloudinary/cloudinary-video-player/compare/v2.1.1-edge.1...v2.1.1-edge.2) (2024-10-27) + + +### Bug Fixes + +* use cld player profiles package for default profiles ([#701](https://github.com/cloudinary/cloudinary-video-player/issues/701)) ([1083b94](https://github.com/cloudinary/cloudinary-video-player/commit/1083b94ac96f4e075d8d820a894703eb644bec7a)) + +## [2.1.1-edge.1](https://github.com/cloudinary/cloudinary-video-player/compare/v2.1.1-edge.0...v2.1.1-edge.1) (2024-10-18) + + +### Features + +* add internal analytics about new method & profiles ([#699](https://github.com/cloudinary/cloudinary-video-player/issues/699)) ([a1c8c1e](https://github.com/cloudinary/cloudinary-video-player/commit/a1c8c1eb4ffd8362ce4cc7ab5ed47276cc5651ee)) + +## [2.1.1-edge.0](https://github.com/cloudinary/cloudinary-video-player/compare/v2.0.6-edge.0...v2.1.1-edge.0) (2024-10-07) + + +### Features + +* add url template for video player profiles ([#696](https://github.com/cloudinary/cloudinary-video-player/issues/696)) ([d60cb4f](https://github.com/cloudinary/cloudinary-video-player/commit/d60cb4f4fc9d8b0ff2a6f0e21621c6c84063c898)) + + +### Bug Fixes + +* default secure option for new method ([#698](https://github.com/cloudinary/cloudinary-video-player/issues/698)) ([0d95b76](https://github.com/cloudinary/cloudinary-video-player/commit/0d95b76381b2130a01755feab87bb2766ead9d59)) + ## [2.0.6-edge.0](https://github.com/cloudinary/cloudinary-video-player/compare/v2.0.5-edge.2...v2.0.6-edge.0) (2024-08-08) diff --git a/docs/es-modules/profiles.html b/docs/es-modules/profiles.html index f2481395..9cebf908 100644 --- a/docs/es-modules/profiles.html +++ b/docs/es-modules/profiles.html @@ -30,9 +30,31 @@
Player with default profile
muted > +
Player with custom profile
+ + +
Player with custom profile and overrides
+ +

Full documentationFull documentation

@@ -44,11 +66,34 @@
Player with default profile
(async () => { const playerWithDefaultProfile = await player('player-default-profile', { cloudName: 'demo', - profile: 'cldDefault' + profile: 'cld-default' }); playerWithDefaultProfile.source('sea_turtle'); })(); + + (async () => { + const playerWithCustomProfile = await player('player-custom-profile', { + cloudName: 'prod', + profile: 'myCustomProfile' + }); + + playerWithCustomProfile.source('samples/cld-sample-video'); + })(); + + (async () => { + 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'); + })(); diff --git a/docs/es-modules/raw-url.html b/docs/es-modules/raw-url.html index 915a9ddb..617fe7e2 100644 --- a/docs/es-modules/raw-url.html +++ b/docs/es-modules/raw-url.html @@ -63,7 +63,7 @@

Video with raw URL - ABR

const player = videoPlayer('player', config); - player.source('https://sample-videos.com/video321/mp4/720/big_buck_bunny_720p_10mb.mp4'); + player.source('https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4'); const abrPlayer = videoPlayer('abrPlayer', config); diff --git a/docs/profiles.html b/docs/profiles.html index 00b09f7c..e2b2b88a 100644 --- a/docs/profiles.html +++ b/docs/profiles.html @@ -26,11 +26,34 @@ window.addEventListener('load', async () => { const playerWithDefaultProfile = await cloudinary.player('player-default-profile', { cloudName: 'demo', - profile: 'cldDefault', + profile: 'cld-default', }); playerWithDefaultProfile.source('sea_turtle'); }, false); + + window.addEventListener('load', async function() { + const playerWithCustomProfile = await cloudinary.player('player-custom-profile', { + cloudName: 'prod', + profile: 'myCustomProfile', + }); + + playerWithCustomProfile.source('samples/cld-sample-video'); + }, false); + + 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); @@ -71,13 +94,94 @@

Example Code:

window.addEventListener('load', async function() { const playerWithDefaultProfile = await cloudinary.player('player-default-profile', { cloudName: 'demo', - profile: 'cldDefault', + profile: 'cld-default', }); playerWithDefaultProfile.source('sea_turtle'); }, false); + +
Player with custom 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',
+          });
+
+          playerWithCustomProfile.source('samples/cld-sample-video');
+        }, false);
+      
+    
+ +
Player with custom profile and overrides
+ + +

Example Code:

+ +
+    
+
+      <video
+        id="player-custom-profile-overrides"
+        controls
+        autoplay
+        muted
+        class="cld-video-player"
+        width="500">
+      </video>
+
+      
+      
+        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);
+      
+    
diff --git a/docs/raw-url.html b/docs/raw-url.html index 43dcaa5d..dcc5cd5c 100644 --- a/docs/raw-url.html +++ b/docs/raw-url.html @@ -35,7 +35,7 @@ player = cloudinary.videoPlayer('player', config); - player.source('https://sample-videos.com/video321/mp4/720/big_buck_bunny_720p_10mb.mp4'); + player.source('https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4'); adpPlayer = cloudinary.videoPlayer('adpPlayer',config); @@ -106,7 +106,7 @@

Example Code:

player = cloudinary.videoPlayer('player', config); - player.source('https://res.cloudinary.com/demo/video/upload/sea_turtle.mp4'); + player.source('https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4'); adpPlayer = cloudinary.videoPlayer('adpPlayer',config); diff --git a/package-lock.json b/package-lock.json index 257234a5..e26978cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "@cloudinary/url-gen": "^1.20.0", "cloudinary-video-analytics": "1.7.1", + "cloudinary-video-player-profiles": "1.1.0", "lodash": "^4.17.21", "uuid": "^10.0.0", "video.js": "^8.17.1", @@ -6538,6 +6539,11 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/cloudinary-video-player-profiles": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/cloudinary-video-player-profiles/-/cloudinary-video-player-profiles-1.1.0.tgz", + "integrity": "sha512-vBpoDmDEq6+iViASIrsbRPNhiykwnnbfW4kdiYyrbdU6FFzogTun+6jmlbw/FR6XG2FEn4s69OOFRlu/qsl01Q==" + }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", diff --git a/package.json b/package.json index 1db08edc..51766b2b 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ }, { "path": "./dist/cld-video-player.light.min.js", - "maxSize": "130kb" + "maxSize": "131kb" }, { "path": "./lib/cld-video-player.js", @@ -80,6 +80,7 @@ "dependencies": { "@cloudinary/url-gen": "^1.20.0", "cloudinary-video-analytics": "1.7.1", + "cloudinary-video-player-profiles": "1.1.0", "lodash": "^4.17.21", "uuid": "^10.0.0", "video.js": "^8.17.1", diff --git a/src/config/profiles/cldAdaptiveStream.json b/src/config/profiles/cldAdaptiveStream.json deleted file mode 100644 index 3f20af1c..00000000 --- a/src/config/profiles/cldAdaptiveStream.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "playerOptions": { - "fluid": true, - "controls": true, - "controlBar": { - "fullscreenToggle": false - }, - "showJumpControls": true, - "hideContextMenu": false, - "floatingWhenNotVisible": "none" - }, - "sourceOptions": { - "chapters": true, - "sourceTypes": ["hls"], - "transformation": [ - { - "effect": ["volume:auto"] - } - ] - } -} diff --git a/src/config/profiles/cldDefault.json b/src/config/profiles/cldDefault.json deleted file mode 100644 index c8c24b97..00000000 --- a/src/config/profiles/cldDefault.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "playerOptions": {}, - "sourceOptions": {} -} diff --git a/src/config/profiles/cldLooping.json b/src/config/profiles/cldLooping.json deleted file mode 100644 index ab33fe5c..00000000 --- a/src/config/profiles/cldLooping.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "playerOptions": { - "fluid": true, - "controls": false, - "muted": true, - "floatingWhenNotVisible": "none", - "hideContextMenu": false, - "autoplay": true, - "loop": true - }, - "sourceOptions": {} -} diff --git a/src/config/profiles/index.js b/src/config/profiles/index.js deleted file mode 100644 index 32df3c8e..00000000 --- a/src/config/profiles/index.js +++ /dev/null @@ -1,9 +0,0 @@ -import cldDefault from './cldDefault.json'; -import cldLooping from './cldLooping.json'; -import cldAdaptiveStream from './cldAdaptiveStream.json'; - -export const defaultProfiles = { - cldDefault, - cldLooping, - cldAdaptiveStream -}; diff --git a/src/index.es.js b/src/index.es.js index da6e2826..f19be260 100644 --- a/src/index.es.js +++ b/src/index.es.js @@ -13,7 +13,6 @@ import cloudinary from './index.js'; export const videoPlayer = cloudinary.videoPlayer; export const videoPlayers = cloudinary.videoPlayers; -export const videoPlayerWithProfile = cloudinary.videoPlayerWithProfile; export const player = cloudinary.player; diff --git a/src/index.js b/src/index.js index 9a0cf729..252c105e 100644 --- a/src/index.js +++ b/src/index.js @@ -29,10 +29,6 @@ const getPlayers = config => (selector, playerOptions, ready) => { export const videoPlayer = getVideoPlayer(); export const videoPlayers = getVideoPlayers(); -export const videoPlayerWithProfile = (id, playerOptions, ready) => { - console.warn('videoPlayerWithProfile method is DEPRECATED and will be removed soon, please use new `player` method instead'); - return getPlayer()(id, playerOptions, ready); -}; export const player = getPlayer(); export const players = getPlayers(); @@ -51,7 +47,6 @@ const cloudinary = { ...(window.cloudinary || {}), videoPlayer, videoPlayers, - videoPlayerWithProfile, player, players, Cloudinary: { diff --git a/src/index.videoPlayerWithProfile.js b/src/index.videoPlayerWithProfile.js deleted file mode 100644 index e739ba95..00000000 --- a/src/index.videoPlayerWithProfile.js +++ /dev/null @@ -1,6 +0,0 @@ -// This file is bundled as `videoPlayerWithProfile.js` to be imported as a tree-shaken module. - -// Usage: -// import videoPlayerWithProfile from 'cloudinary-video-player/videoPlayerWithProfile'; - -export { videoPlayerWithProfile as default } from './index.js'; diff --git a/src/player.js b/src/player.js index 0c6250bb..04d9b6da 100644 --- a/src/player.js +++ b/src/player.js @@ -1,24 +1,49 @@ import VideoPlayer from './video-player'; -import { defaultProfiles } from './config/profiles'; +import { defaultProfiles } from 'cloudinary-video-player-profiles'; import { isRawUrl } from './plugins/cloudinary/common'; +import { unsigned_url_prefix } from '@cloudinary/url-gen/backwards/utils/unsigned_url_prefix'; -export const getProfile = async (cloudName, profile) => { - if (Object.keys(defaultProfiles).includes(profile)) { - return defaultProfiles[profile]; +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`); } - if (isRawUrl(profile)) { - return await fetch(profile, { method: 'GET' }).then(res => res.json()); + return profile.config; +}; + +export const getProfile = async (profile, initOptions) => { + if (isDefaultProfile(profile)) { + return getDefaultProfileConfig(profile); } - throw new Error('Custom profiles will be supported soon, please use one of default profiles: "cldDefault", "cldLooping" or "cldAdaptiveStream"'); + const urlPrefix = unsigned_url_prefix( + null, + initOptions.cloudName ?? initOptions.cloud_name, + initOptions.private_cdn, + initOptions.cdn_subdomain, + initOptions.secure_cdn_subdomain, + initOptions.cname, + initOptions.secure ?? true, + initOptions.secure_distribution, + ); + + 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(otherInitOptions.cloud_name, profile) : {}; - const options = Object.assign({}, profileOptions.playerOptions, otherInitOptions); + const profileOptions = profile ? await getProfile(profile, otherInitOptions) : {}; + const options = Object.assign({}, profileOptions.playerOptions, otherInitOptions, { + _internalAnalyticsMetadata: { + newPlayerMethod: true, + profile: isDefaultProfile(profile) ? profile : !!profile, + }, + }); const videoPlayer = new VideoPlayer(elem, options, ready); const nativeVideoPlayerSourceMethod = videoPlayer.source; diff --git a/src/video-player.const.js b/src/video-player.const.js index afad6d0d..c35c3292 100644 --- a/src/video-player.const.js +++ b/src/video-player.const.js @@ -12,6 +12,7 @@ export const CLOUDINARY_PARAMS = [ ]; export const PLAYER_PARAMS = CLOUDINARY_PARAMS.concat([ + '_internalAnalyticsMetadata', 'debug', 'publicId', 'source', diff --git a/src/video-player.js b/src/video-player.js index f3db2d7a..c1597415 100644 --- a/src/video-player.js +++ b/src/video-player.js @@ -112,6 +112,7 @@ class VideoPlayer extends Utils.mixin(Eventable) { return; } try { + const internalAnalyticsMetadata = options._internalAnalyticsMetadata ?? {}; const analyticsData = getAnalyticsFromPlayerOptions(options); const analyticsParams = new URLSearchParams(analyticsData).toString(); const baseParams = new URLSearchParams({ @@ -120,7 +121,8 @@ class VideoPlayer extends Utils.mixin(Eventable) { // #if (process.env.WEBPACK_BUILD_LIGHT) vpLightBuild: true, // #endif - cloudName: options.cloudinary.cloudinaryConfig.cloud_name + cloudName: options.cloudinary.cloudinaryConfig.cloud_name, + ...internalAnalyticsMetadata, }).toString(); fetch(`${INTERNAL_ANALYTICS_URL}/video_player_source?${analyticsParams}&${baseParams}`); } catch (e) { diff --git a/test/e2e/playwright.config.ts b/test/e2e/playwright.config.ts index c5a555fe..cbb78353 100644 --- a/test/e2e/playwright.config.ts +++ b/test/e2e/playwright.config.ts @@ -6,7 +6,7 @@ import { defineConfig, devices } from '@playwright/test'; export default defineConfig({ testMatch: /test\/e2e\/specs\/.*(\.spec.ts)/, - timeout: 45000, + timeout: 150000, /* Run tests in files in parallel */ fullyParallel: true, /* Fail the build on CI if you accidentally left test.only in the source code. */ diff --git a/test/e2e/specs/linksConsoleErrorsEsmPage.spec.ts b/test/e2e/specs/linksConsoleErrorsEsmPage.spec.ts index d1c7a36c..5652d157 100644 --- a/test/e2e/specs/linksConsoleErrorsEsmPage.spec.ts +++ b/test/e2e/specs/linksConsoleErrorsEsmPage.spec.ts @@ -1,19 +1,26 @@ -import { ConsoleMessage, expect } from '@playwright/test'; +import { ConsoleMessage, expect, Page } from '@playwright/test'; import { vpTest } from '../fixtures/vpTest'; import { ESM_LINKS } from '../testData/esmPageLinksData'; import { waitForPageToLoadWithTimeout } from '../src/helpers/waitForPageToLoadWithTimeout'; import { validatePageErrors } from '../src/helpers/validatePageErrors'; +import { ExampleLinkType } from '../types/exampleLinkType'; + +const EDGE_ESM_URL = 'https://cld-vp-esm-pages.netlify.app/'; +// On PR level it will use the preview deploy URL and locally it will use the latest EDGE. +const ESM_URL = process.env.PREVIEW_URL ?? EDGE_ESM_URL; +// Flag to indicate if the deploy preview URL is ready +let isPreviewUrlLoaded = false; -const ESM_URL = 'https://cld-vp-esm-pages.netlify.app/'; /** * Console error test generated by LINKS object array data. */ for (const link of ESM_LINKS) { vpTest(`Test console errors on link ${link.name}`, async ({ page, consoleErrors, vpExamples }) => { vpTest.skip(link.name === 'Adaptive Streaming', 'Flaky on CI'); - /** - * Navigate to ESM Imports examples page - */ + //Wait for deploy URL to be available if PREVIEW_URL is set, and it is not available yet + if (process.env.PREVIEW_URL && !isPreviewUrlLoaded) { + await waitForDeployPreviewUrl(link, page); + } await page.goto(ESM_URL); await vpExamples.clickLinkByName(link.name); await waitForPageToLoadWithTimeout(page, 5000); @@ -44,9 +51,21 @@ function handleCommonEsmBrowsersErrors(linkName: string, consoleErrors: ConsoleM ); break; case 'VAST & VPAID Support': - validatePageErrors(consoleErrors, [], ["Blocked script execution in 'about:blank' because the document's frame is sandboxed and the 'allow-scripts' permission is not set"]); + validatePageErrors(consoleErrors, [], ["Blocked script execution in 'about:blank' because the document's frame is sandboxed and the 'allow-scripts' permission is not set", 'the server responded with a status of 404']); break; default: - expect(consoleErrors, `The following unexpected console errors were found: ${JSON.stringify(consoleErrors)}`).toHaveLength(0); + validatePageErrors(consoleErrors, [], ['the server responded with a status of 404']); } } + +/** + * Waits for a deploy preview URL to become available by making repeated requests and check that link is visible. + */ +async function waitForDeployPreviewUrl(link: ExampleLinkType, page: Page): Promise { + await expect(async () => { + await page.goto(process.env.PREVIEW_URL); + const linkLocator = page.getByRole('link', { name: link.name, exact: true }); + await expect(linkLocator).toBeVisible({ timeout: 10000 }); + isPreviewUrlLoaded = true; + }).toPass({ intervals: [1_000], timeout: 120000 }); +} diff --git a/webpack/es6.config.js b/webpack/es6.config.js index 1e82ee76..d6194f66 100644 --- a/webpack/es6.config.js +++ b/webpack/es6.config.js @@ -1,7 +1,6 @@ const { merge } = require('webpack-merge'); const webpackCommon = require('./common.config'); const path = require('path'); -const CopyWebpackPlugin = require('copy-webpack-plugin'); delete webpackCommon.output; // overwrite @@ -13,7 +12,6 @@ module.exports = merge(webpackCommon, { entry: { 'cld-video-player': './index.es.js', // default 'videoPlayer': './index.videoPlayer.js', - 'videoPlayerWithProfile': './index.videoPlayerWithProfile.js', 'player': './index.player.js', 'all': './index.all.js' }, @@ -29,15 +27,6 @@ module.exports = merge(webpackCommon, { chunkLoadingGlobal: 'cloudinaryVideoPlayerChunkLoading' }, - plugins: [ - new CopyWebpackPlugin({ - patterns: [{ - from: path.resolve(__dirname, '../src/config/profiles'), - to: `${outputPath}/profiles` - }] - }) - ], - experiments: { outputModule: true }